• Какой приоритет операций в данной ситуации с None, is и !=?

    werevolff
    @werevolff
    (1 != (None is None))

    Прежде, чем сравнить 1 и второе выражение, python вычислит выражение. то-есть:
    1. (None is None) = True
    2. 1 != True
    https://docs.python.org/release/3.0.1/reference/da...
    The Boolean type is a subtype of the integer type, and Boolean values behave like the values 0 and 1, respectively, in almost all contexts, the exception being that when converted to a string, the strings "False" or "True" are returned, respectively.


    В python3 type boolean является подтипом integer и, соответственно, значения 0 и 1 равны (но не идентичны) False и True соответственно

    (1 != None) is None

    Сначала, python должен вычислить все значения в операции сравнения (не совсем сравнения, но об этом позже). 1 != None - это верное выражение (True). Соответственно, на втором шаге:
    True is None
    is - оператор идентичности. То есть, он сравнивает не только значение, но и тип данных. Например, предыдущая задача:
    1 is True
    Out: False
    соответственно, True: boolean, None: None Типы разные, значит идентичности нет. False

    (1 != None is None)

    В этом примере используется цепочка сравнений (chaining). Запись эквивалентна
    1 != None and None is None
    Out: True

    если необходимо выполнить пошаговое сравнение, то стоит записать
    (1 != None) is None

    Приоритет у != и is должен быть одинаковый, просто в Python есть много магии, и при указанном выше выражении срабатывает преобразование, указанное выше.
    Ответ написан
  • Как развернуть сайт на django?

    werevolff
    @werevolff
    django запускается как отдельный процесс или демон. Обычно, для запуска используется gunicorn или wsgi. Для запуска gunicorn лучше всего создать systemd конфиг для Debian/Ubuntu систем. Gunicorn работает на IP адресе и открывает порт для IO операций. Например, 127.0.0.1:8000. Далее, в Nginx прописывается:

    server {
        server_name my.site.com;
        listen 80;
        server_tokens off;
        location / {
            include proxy_params;
            proxy_pass http://127.0.0.1:8000/;
        }
    }


    То есть, nginx является лишь прокси-сервером для переадресации запросов на ваш django-wsgi процесс, который непрерывно запущен в системе.

    Второй вариант - поднять систему в докере. Докер предоставляет то же самое: он занимает какой-то порт на localhost, а nginx переадресует запросы извне (от компьютеров клиентов) на docker контейнер.

    Есть и другие варианты запуска, но для начала это то, что вы должны знать.

    Итак, для запуска вам понадобится:
    1. Доступ к systemd конфигам хостинга, либо, возможность запустить docker контейнер с Django
    2. Доступ к конфигу Nginx/Apache2

    Если этих доступов нет, то говорить особо не о чем.

    https://gunicorn.org/
    https://www.docker.com/
    https://docs.docker.com/compose/
    https://habr.com/ru/company/cloverr/blog/247629/
    https://habr.com/ru/post/272811/ - моя очень старая статья. Может не запуститься и имеет не совсем продовский конфиг.
    Ответ написан
  • Как правильно сделать чат?

    werevolff
    @werevolff
    Воспользуйтесь для чата любым реактивным фрэймворком. Angular.js, React.js, Vue.js или его фрэймворк второго уровня Nuxt.js.

    1. Сообщения должны отдаваться постранично.
    2. Паджинация осуществляется путём скроллинга.
    3. Новые сообщения должны прилетать через WebSocket. Например, готовый вэбсокет - https://github.com/centrifugal/centrifugo
    4. Через фрэймворк поддерживается постоянная длина массива с сообщениями. Например, 100 сообщений. Допустим, на вэбсокет приходит очередное сообщение. Оно добавляется в конец массива, а первый элемент удаляется. Если пользователь скроллит сообщения до первого показанного, скрипт отправляет на сайт запрос: отдай мне страницу с 50 сообщениями старше вот этого последнего. Сервер отдаёт страницу, она вставляется в начало массива, после чего, из него удаляются последние 50 элементов. Аналогично должен работать скроллинг к более новым сообщениям. Поскольку фрэймворки реактивные, ничего в HTML вставлять не надо: само отрендерится.
    Ответ написан
  • Как проверить текст на reply_message?

    werevolff
    @werevolff
    # Проверяет в keys(). Вызовет ошибку, если event.raw['object']['fwd_messages'][0] - не словарь
    if 'text' in event.raw['object']['fwd_messages'][0].keys():
        pass
    
    # Аналогичная проверка, но, допустим, если event.raw['object']['fwd_messages'][0] - это строка или iterable, не вызовет ошибки, а попытается найти 'text'
    if 'text' in event.raw['object']['fwd_messages'][0]:
        pass
    
    # Пытается получить из  event.raw['object']['fwd_messages'][0] value по ключу "text". Если такого ключа нет в словаре, вернёт default (None)
    text = event.raw['object']['fwd_messages'][0].get("text", None)
    Ответ написан
  • Как найти удаленную работу Junior React разработчику без опыта?

    werevolff
    @werevolff
    Есть ещё хабр-карьера и хабр-фриланс. Сайт fl.ru. За 5 лет работы в аутсорсе двух постоянных заказчиков я нашёл именно на fl.ru и хабр-фрилансе (тогда это был просто freelansim.ru). И отработал с каждым достаточно долгое время. Но поиск может быть долгим. Особенно, сейчас. Поэтому, стоит поработать в офисе. Даже за копейки. А параллельно искать нормальный контракт. В офисе нужно работать, как минимум, на ставке js-разработчика. Опыт работы идёт в резюме.
    Ответ написан
  • Как описать хранение рабочих часов в бд?

    werevolff
    @werevolff
    1. Рабочие часы - это тип данных str а не datetime! Почему? Потому, что Datetime в любом случае возвращает конкретную дату. Как надо себе это представлять:

    start_time: str = '12:20'
    end_time: str = '16:00'


    Есть ещё TimeField, но тогда надо будет в поле объявлять format=

    2. Есть несколько вариантов записи этих данных в модель. Самый простой - через Choices:

    class WorkDay(models.Model):
        DAYS_OF_WEEK = (
            (1, _('Monday')),
            (2, _('Tuesday')),
            (3, _('Wednesday')),
            (4, _('Thursday')),
            (5, _('Friday')),
            (6, _('Saturday')),
            (7, _('Sunday')),
        )
        DAY_TYPES = (
            ('weekday', _('weekday')),
            ('holiday', _('holiday')),
        )
        day_of_the_week = models.PositiveIntegerField(
            verbose_name=_('day of the week'),
            choices=DAYS_OF_WEEK
        )
        start_time = models.CharField(
            verbose_name=_('start time'),
            max_length=5
        )
        end_time = models.CharField(
            verbose_name=_('end time'),
            max_length=5
        )
        day_type = models.CharField(
            verbose_name=_('day type'),
            max_length=255,
            choices=DAY_TYPES
        )


    3. Нам может потребоваться записать в рабочий день несколько пар рабочих часов. В этом случае, наиболее верным способом я счёл бы использование ArrayField или ListField

    from django.contrib.postgres.fields import ArrayField
    
    class WorkDay(models.Model):
        DAYS_OF_WEEK = (
            (1, _('Monday')),
            (2, _('Tuesday')),
            (3, _('Wednesday')),
            (4, _('Thursday')),
            (5, _('Friday')),
            (6, _('Saturday')),
            (7, _('Sunday')),
        )
        DAY_TYPES = (
            ('weekday', _('weekday')),
            ('holiday', _('holiday')),
        )
        day_of_the_week = models.PositiveIntegerField(
            verbose_name=_('day of the week'),
            choices=DAYS_OF_WEEK
        )
        working_hours = ArrayField(
            ArrayField(
                models.CharField(
                    max_length=5
                ),
                size=2
            ),
            verbose_name=_('working hours')
        )
        day_type = models.CharField(
            verbose_name=_('day type'),
            max_length=255,
            choices=DAY_TYPES
        )


    4. Также, мы можем использовать JSONField (MySQL или PostgreSQL )

    from django.contrib.postgres.fields import JSONField
    
    class WorkGraph(models.Model):
        working_graph = JSONField(
            verbose_name=_('working graph'),
            default={
                'monday': {
                    'hours': ['12:20', '16:00'],
                    'day_type': 'weekday'
                }
            }
        )


    Я бы предпочёл вариант с ArrayField.

    ArrayField как и JSONField поддерживаются DRF и представляют данные в виде массивов Python, которые можно отработать через validate.

    При таком подходе фронт берёт на себя построение UI, при котором пользователь просто выбирает часы и минуты в форме и добавляет их. JS строит на основе формы массив и передаёт в DRF.

    И бонусом, используем валидацию для полей Django и DRF:

    import re
    from django.core.exceptions import ValidationError
    from typing import NoReturn
    
    def validate_working_hours(
            value: str
    ) -> NoReturn:
        m = re.match(
            r'^(?P<hours>\d{2}):(?P<minutes>\d{2})$',
            value
        )
        if m is None:
            raise ValidationError('please, use format HH:mm')
        else:
            hours = int(
                m.group('hours')
            )
            if hours > 23:
                raise ValidationError('hours is a value from 0 to 23')
            minutes = int(
                m.group('minutes')
            )
            if minutes > 59:
                raise ValidationError('minutes is a value from 0 to 59')
    Ответ написан
  • Насколько frontend часть django медленнее чем работа с чистым frontend, общаясь с джангой с помощью API?

    werevolff
    @werevolff
    Замерьте скорость рендеринга с помощью Инструментов Разработчика Google Chrome. Это очень относительное утверждение.

    Скорость рендеренинга страницы - не совсем верный термин. Вы замеряете время:

    1. За которое получаете ответ от сервера на ваш запрос
    2. За которое браузер скачивает все файлы для построения страницы
    3. За которое браузер показывает первые полезные данные (не loader)
    4. За которое браузер полностью рендерит страницу

    В случае с Django:

    1. Запрос проходит через Nginx - на wsgi
    2. Wsgi ищет правильную привязку URL
    3. Wsgi запускает все Middleware
    4. Wsgi запускает view
    5. После того, как отработал view, HTML код возвращается через все Middleware
    6. Браузер получает код целой страницы со всеми ссылками на файлы и изображения. Допустим, 20 килобайт страница и ещё около сотни ссылок на ней.

    В случае с Vue.js:

    1. Запрос приходит в nginx
    2. Nginx ищет нужный html файл и отдаёт его.
    3. В браузер приходит небольшой файл с одним js. Там ничего нет по сути, кроме скрипта vue.js и пустого html файла.

    Что это всё значит?

    1. Django всегда запускает свои Middleware чтобы получить HTML код. Плюс, сама вьюха
    2. Django возвращает уже готовый HTML страницы. Это много кода и много ссылок в коде
    3. Современные браузеры загружают файлы по ссылкам в коде параллельно. То есть, загрузка и рендеринг 100 файлов параллельно будут происходить быстрее, чем загрузка и рендеринг одного файла.

    Здесь, правда, стоит поспорить: Загрузка 100 файлов для рендеринга страницы происходит быстрее, чем, скажем, 5 файлов потому, что эти файлы сразу начинают отображаться после загрузки. Часть страницы можно вообще не рендерить и подгрузить позже. В случае с классическим рендерингом страницы средствами браузера, эти 5 файлов будут качаться сразу, и мы будем ждать каждый из этих файлов.

    Разумеется, если брать копирование данных или тот же FTP, загрузка 100 мелких файлов никогда не будет быстрее загрузки одного файла, поскольку каждая загрузка будет создавать запрос к серверу и ждать ответ.

    Итак, в браузер пользователя пришёл HTML код с одной ссылкой на Vue.js. Пришёл быстрее, чем код с Джанги. Теперь Vue.js начинает грузить дополнительные данные с сервера (многопоточно) и рендерить их. Рендеринг происходит на стороне клиента, и именно от скорости клиентской машины зависит окончательное время рендеринга. В любом случае, Vue.js уже получил преимущество: он быстрее скачал данные для отображения первичного контента, он может качать страницу частями параллельно.

    Можно посоветовать:

    1. Сделать оптимизацию своего кода Django. Используй кеширование страниц, где это возможно. Используй быстрый Redis Cache.

    from django.views.decorators.cache import cache_page
    
    pages_urls = [
        re_path(
            r'^account/',
            cache_page(
                60 * 60 * 24 * 30
            )(
                TemplateView.as_view(
                    template_name='base.html'
                )
            ),
            name='account'
        ),
    ]


    2. С этим подходом можно включить gzip сжатие страниц перед кешированием, чтобы снизить их вес.
    3. Уменьши число Middleware

    Правда, твой товарищ может сделать то же самое для Vue.js, и у него будет более значительный прирост в скорости рендеринга страницы.

    Выводы:

    Vue.js не то чтобы быстрее Django. Просто сама архитектура позволяет ему за одинаковое с Django время грузить страницы бОльшего объёма. Vue.js готова показать первый контент на странице раньше чем, Django сделает это. Vue.js грузит части страниц и контент параллельно в несколько потоков. Vue.js при получении статических HTML, JS, CSS файлов не ждёт, пока сервер выполнит python или php код, работая напрямую с nginx. Так что, при равных условиях это будет быстрее, чем получать всю страницу от Django.

    Также, следует помнить, что браузер будет ждать от Django полной загрузки основного HTML контента перед тем, как его отрендерить. За это время Vue.js уже покажет первый контент. После чего, Vue.js спокойно догрузит оставшиеся файлы. Если сжать все js в один файл и все css в один файл, то браузер будет ждать их загрузки. Vue.js может сначала отрендерить страницу, а потом уже догрузить недостающий код.
    Ответ написан
  • Django ORM: Как отсортировать по prefetched_set?

    werevolff
    @werevolff
    А то, что ты в prefetch получаешь, нельзя как-нибудь прогнать через annotation? А потом уже с аннотации сортировать?

    А вообще, может использовать аргумент to_attr, чтобы переименовать атрибут?

    class Prefetch(lookup, queryset=None, to_attr=None)
    The to_attr argument sets the result of the prefetch operation to a custom attribute.

    Я так полагаю, numbers_set может ссылаться на существующее поле объекта.

    Решение найдено через FilteredRelation(). Mekalure объяснение: Собственно, Prefetch() метод не создаёт атрибут, который доступен в order_by. Я прогнал у себя: этот атрибут не доступен и в annotate. Только в результате выборки. Поэтому, отсортировать по нему не выйдет. FilteredRelation() объявляется внутри annotate и создаёт QuerySet, доступный в order_by и последующей фильтрации. Но, увы, этот QuerySet не будет доступен в результатах выборки. Подозреваю, что Джанга умеет связывать два QuerySet только через prefetch_related, но не через annotate. В любом случае, эта функция делает нужные данные доступными при сортировке.

    Получается:

    qs1 = Model1.objects.filter(**kwargs)
    qs2 = Model2.objects.annotate(order=FilteredRelation('lookup', condition=Q(**kwarg))).prefetch_related(Prefetch('lookup', queryset=qs1)).order_by('order')
    Ответ написан
  • Для чего применяются парсеры в Django Rest Framework?

    werevolff
    @werevolff
    Открой код парсеров и посмотри:

    class JSONParser(BaseParser):
        """
        Parses JSON-serialized data.
        """
        media_type = 'application/json'
        renderer_class = renderers.JSONRenderer
        strict = api_settings.STRICT_JSON
    
        def parse(self, stream, media_type=None, parser_context=None):
            """
            Parses the incoming bytestream as JSON and returns the resulting data.
            """
            parser_context = parser_context or {}
            encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET)
    
            try:
                decoded_stream = codecs.getreader(encoding)(stream)
                parse_constant = json.strict_constant if self.strict else None
                return json.load(decoded_stream, parse_constant=parse_constant)
            except ValueError as exc:
                raise ParseError('JSON parse error - %s' % str(exc))
    
    
    class FormParser(BaseParser):
        """
        Parser for form data.
        """
        media_type = 'application/x-www-form-urlencoded'
    
        def parse(self, stream, media_type=None, parser_context=None):
            """
            Parses the incoming bytestream as a URL encoded form,
            and returns the resulting QueryDict.
            """
            parser_context = parser_context or {}
            encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET)
            return QueryDict(stream.read(), encoding=encoding)


    Парсер получает некий контент из HTTPRequest и преобразует его до словаря. Например, попробуй передать данные в твой API в формате YAML. Ты получишь ошибку формата.

    Конкретный юзкейс был у меня недавно: платёжная система присылала сообщения о событиях на API проекта. К сожалению, формат данных был с ошибкой, и мне потребовалось наследоваться от парсера JSON и обрабатывать ошибку.
    Ответ написан
  • Django. Как отловить изменение в поле модели?

    werevolff
    @werevolff
    Здесь есть два популярных решения проблемы. Но, сперва, разберёмся в архитектуре.

    1. У нас работает некий таймер, который с некоторой периодичностью запускает python функцию.
    2. Эта функция фильтрует Card по статусу активности. Например:
    qs = Card.objects.filter(
        expired_data__gt = datetime.now()
    )


    3. Для инстансов в Queryset применяются выбранные действия.

    for instance in qs:
        do_something(instance)


    Итак, наша задача - найти этот таймер. Наиболее популярное решение - celery beat с periodic tasks

    Как это выглядит:

    1. Ставим на проект Celery, убедимся, что task.delay() работает для тестового таска.
    2. Создаём my_package/tasks.py.
    3. Создадим внутри таск, который фильтрует наши неактивные карточки и выполняет на них операцию с выставлением счёта, или что там у тебя.
    4. Заносим этот таск в settings.CELERY_BEAT_SCHEDULE

    CELERY_BEAT_SCHEDULE = {
        'my_task_name': {
            'task': 'my_package.tasks.my_task_name',
            'schedule': 24 * 60 * 60,  # 24 hours
            'args': ()
        }
    }


    5. Убедимся, что celery запущена в режиме beat.

    Что если мы не хотим ставить Celery? Есть решение в виде django-crontab.

    1. Ставим django-crontab.
    2. Создаём функцию, которая фильтрует и обрабатывает не активные карточки.
    3. Заносим нашу функцию в settings.CRONJOBS

    CRONJOBS = [
        ('*/5 * * * *', 'myapp.cron.my_scheduled_job')
    ]


    4. Выполняем python manage.py crontab add

    Дополнительно нам может потребоваться сохранять данные о том, какие карты игнорировать. Это можно решить элементарным добавлением поля ignore в модель и изменением правила фильтрации карточки.

    Для тестирования: берём таск или функцию для крона и выполняем её в тестах. Убедимся, что операция выполнена только для не активных карточек. Не забываем использовать CELERY_ALWAYS_EAGER=True в override_settings для теста.
    Ответ написан
  • Какой выбрать ssh клиент?

    werevolff
    @werevolff
    Я ставил wsl и настраивал терминалы на ConEmu. Лучше линуксового ssh не видел никаких решений. Это и ssh, и scp. Словом, полный фарш. А под cmd ставил git - у него в коробке есть стандартный ssh. Хотя, сейчас, вроде, в 10-ке он встроенный. Если нет, то стоит поставить git for windows. Но я предпочитаю работать через wsl.
    Ответ написан
  • Какова суть фреймворков и библиотек?

    werevolff
    @werevolff
    1. Фрэймворк - это каркас приложения. Библиотека - это готовый код. Фрэймворк предоставляет разработчику архитектуру, на которой выстраивается приложение. Библиотека не предоставляет архитектуры. Ты вызываешь её, чтобы получить результат в отдельной части своего приложения.
    2. Можно использовать несколько фрэймворков. Каждый фрэймворк отвечает за своё приложение или свою часть проекта. Например, можно сделать админку сайта на Angular.js а клиентскую часть - на React. Можно одну страницу сделать на React.js, вторую - на vue.js. одну и ту же часть проекта на разных фрэймворках делать не получится. Какой выбрать - вопрос опыта. Пока не начнёшь писать - не поймёшь.
    3. Некоторые библиотеки могут заменить фрэймворки, имея полный набор абстракций доя решения задачи. Можно ли сравнивать библиотеки и фрэймворки? Это зависит от параметров сравнения. Если можешь обосновать что ты сравниваешь, то не вижу проблем. Но, для обоснования надо иметь опыт работы и решения прикладных задач.
    4. Ничего не могу посоветовать: выбор библиотеки или фрэймворка - это ответственность разработчика. Если разработчик с этим вопросом идёт на тостер, то дешевле посоветовать другого разработчика.
    P.S. Не стоит зацикливаться на ванильном JS. Стоит выбрать фрэймворк и работать с ним. Понимание придёт с опытом.
    Ответ написан
  • CustomUser model auth не работает?

    werevolff
    @werevolff
    def login(request):
    
        defaults = {
            'authentication_form': CustomAuthForm,
            'template_name': 'accounts/login.html',
            }
        if request.user.is_anonymous():
            return auth_login(request, **defaults)
        elif request.user.is_owner():
            return redirect('/asdasdasd')
    
        return auth_login(request, **defaults)


    Читаем код:

    1. Функция логина
    2. Если пользователь анонимный - залогинить.
    3. Если пользователь не анонимен и метод is_owner() возвращает True - редиректнуть.
    4. Во всех остальных случаях - залогинить.

    Соответственно, проверка is_owner() происходит только в том случае, если пользователь авторизован и пытается зайти на страницу login. Теперь об ошибках:

    1. Непонятно зачем дёргается стандартная ф-ия аутентификации.
    2. Не думаю, что смысл в проверке is_owner имеет смысл вообще.
    3. Её можно упростить до вида:

    def login(request):
        defaults = {
            'authentication_form': CustomAuthForm,
            'template_name': 'accounts/login.html',
            }
        if not request.user.is_authenticated() and request.user.is_owner():
            return redirect('/asdasdasd')
        return auth_login(request, **defaults)


    Теперь по поводу вопроса:

    Пользователь зашёл и ввёл логин/пароль. Его перекинуло на стандартную View авторизации джанги (иными словами, эта вьюха вообще не нужна: всё что она делает - редиректит ), вот он и вошёл безо всяких проверок на owner.

    Для решения задачи необходимо написать собственное представление для входа в систему.

    P.S. Какие странные пользователи появились у моей любимой Django в последнее время. Аж дух захватывает.
    Ответ написан
  • Как реализовать древовидные комментарии на django с использование tinymce/ckeditor?

    werevolff
    @werevolff
    Django не является CMS. Это не джумла, и не вордпресс. Здесь нет отговорок типа "мне лень это писать". Ставим treebeard или mptt, реализуем модель комментариев. При необходимости, делаем REST интерфейс для вывода дерева, в JS добавляем возможность при клике на "ответить" выставлять id комментария в переменную. Открываем tinymce/ckeditor, при сабмите отправляем id родителя и текст сообщения.

    django-threadedcomments реализует только модель и кучу templatetags. Абсолютно не django way. Как вы собрались решать проблему, если вам банально лень открыть анализатор запросов и посмотреть что отправляется на сервер? Почему не уходит parent_id?
    Ответ написан
  • Python не ставится библиотека?

    werevolff
    @werevolff
    Вообще, есть готовые билды некоторых пакетов python под windows: www.lfd.uci.edu/~gohlke/pythonlibs

    Однако, лично я не верю в то, что можно делать серьёзные сетевые приложения на Python под Windows. Я год парился с такой конфигурацией. Кроме массового просера сроков ничего не получил. Ещё пол года работал на Arch Linux. В принципе, система хороша для Python, но когда начинаешь работать со связками языков, арч упирается рогом. Поэтому, самый правильный вариант на Windows - поставить VirtualBox с Ubuntu 16.04. А если нет желания настраивать порты и айпишники для проекта, следует использовать Vagrant.
    Ответ написан
  • Добавить месенджер к rest api?

    werevolff
    @werevolff
    Не знаю, поможет или нет, но я бы начинал поиск с готовых решений:

    Для бэкенда
    Для фронтэнда

    В качестве библиотеки, реализующей вэб-сокеты используется Ratchet

    Полагаю, что для PHP надо будет ещё посношаться с настройкой деплоймента, хотя не факт. Получается, что у нас будет два типа сообщений:

    1. Состояние списка пользователей.
    2. Сообщения пользователей.

    Если бы речь шла об ангуляре, то можно было бы отслеживать фокус на input, либо изменения модели, привязанной к input. Либо, можно было бы усложнить процесс и изучать кол-во изменений модели за единицу времени. Есть ещё возможность отлавливать keypress. В общем, когда мы устанавливаем, что current client печатает, мы отправляем на сервер сообщение одного типа. Если пользователь производит submit - сообщение другого типа.

    Так мы можем использовать Redis / RabbitMQ сервер для сохранения статусов пользователей открытых бесед (и удалять instance, если беседа закрыта). Получается следующая схема:

    В ОЗУ хранится {'dialogIDqweqweqwe': {'users': {'vasya': {'state': 'online', 'activity': 'printing'}, 'petya': {'state': 'online', 'activity': null'}} ]}}
    При изменении статуса от Пети, его клиент отправляет {'action': 'userActivity', 'value':'printing'}. Насколько я понял доку по рэтчету, когда приходит сообщение от одного из клиентов, срабатывает хук onMessage. В нём мы проверяем action ответа и, либо сохраняем данные и делаем $client->send (если что-то изменилось в сравнении с предыдущим состоянием), либо игнорируем изменения.

    Механизм web-сокетов вы понимаете правильно. На бэкенде поднимается server - приложение, которое реализует протокол WebSocket. Общее представление о работе даёт его название. Server представляет собой сокет, к которому подключается N клиентов и слушают изменения (ждут событий).
    Ответ написан
  • Как области изображения сделать прозрачными при помощи jQuery или подобного?

    werevolff
    @werevolff
    Не совсем понятен вопрос:

    1. Что означает "делать область прозрачной"? Вот, я навожу мышку на изображение и пиксели под курсором исчезают. Это называется - сделать прозрачным. Или я навожу мышку, и область под мышкой становится полупрозрачной. Это другое.
    2. Для чего оно надо? Это визуальный эффект или изображение после такого "вытирания" надо сохранить?
    3. О какой области идёт речь? Это небольшой круг под курсором? Это многоугольник на изображении?
    4. Какая стоит задача? Если необходимо просто навесить свистоперделку на курсор, то не факт что что-то надо делать прозрачным.
    Ответ написан
  • Как правильно восстановить ubuntu?

    werevolff
    @werevolff
    Настройки оформления Unity хранятся в домашней папке пользователя. Если слетела прорисовка, то:

    1. Ищем ответ в сети. Ситуация типовая, и перезагрузка оконного менеджера решает вопрос.
    2. Если не помогло, можно удалить папки в Homedir, отвечающие за оформление. Какие точно - не вспомню, но в сети можно найти ответ на этот вопрос. При следующем входе в систему настройки Unity будут сброшены до дефолтных.
    3. Если совсем ничего не помогает, нужно создать нового пользователя, переключиться на него, у старого забэкапить личные файлы, потом удалить его и его домашнюю директорию. А потом создать снова. Этот метод можно считать крайним, а не переустановку системы. Линукс может годами работать с критическими ошибками, вызванными установкой проприетарных драйверов или сменой оборудования. Эксперименты с Unity точно не должны быть причиной переустановки системы.
    Ответ написан