Django Channels: уведомления в реальном времени

Всякий раз, когда вы слышите слово «в реальном времени», первое, что приходит вам в голову (если вы разработчик), - это подключение к веб-сокету. Django не поддерживает веб-сокеты, и здесь на помощь приходят Django Channels.

Ниже приведены материалы для Django-каналов версии 2.x и Python версии 3.x.

Что я реализовал с помощью Channels?

  • Задача - формировать уведомления и отправлять их в реальном времени с помощью сокетов.
  • Чтобы упростить задачу, мы рассмотрим сценарий для одного пользователя. Предположим, что конкретное уведомление должно быть отправлено пользователю X, основная задача состоит в том, чтобы определить количество мест, из которых пользователь X вошел в систему, а затем отправить это уведомление всем им.
  • Для этого я использую концепцию под названием Группа в Django Channels, и я могу отправлять уведомление во все места, фактически не зная количество мест, откуда X входит в систему. Давайте посмотрим, что такое группа в Django Channels.

Что такое группы в Django Channels?

  • Группа - это набор соединений сокетов.
  • Каждой группе присваивается уникальное имя.
  • Channels идентифицируют соединение сокета, предоставляя ему уникальный ключ.
  • Поэтому, когда вы добавляете соединение в группу, ключ этого соединения сохраняется в группе.
  • Следовательно, Группа содержит все ключи различных соединений сокетов.
  • Одно соединение сокета можно добавить в несколько групп.
  • Преимущество группы состоит в том, что вы можете транслировать сообщение сразу всем соединениям группы.
  • Чтобы добавить соединение в группу, используйте метод group_add уровня channel_layer, а для удаления соединения из группы используйте метод group_discard уровня канала.
  • Вы можете транслировать сообщение на каналы в группе, используя метод group_send канального уровня.
  • Если вы хотите узнать больше о том, что такое канальный слой, проверьте здесь.

Как я проектировал группы для индивидуальных пользователей?

  • Каждый раз, когда пользователь входит в мою платформу, я использую Reconnecting WebSocket, чтобы открыть новое соединение сокета с Django-каналами.
  • Я передаю токен аутентификации пользователя при подключении к сокету, а в бэкэнде использую этот токен для идентификации пользователя.
  • Я также добавил свойство в свою модель User, чтобы сгенерировать имя группы для каждого пользователя. Ниже приведен код для него
    class User(AbstractBaseUser, PermissionsMixin):
        """
            Определение пользователя и его атрибутов
        """
    
        @property
        def group_name(self):
            """
            Возвращает имя группы на основе идентификатора пользователя,
            которое будет использоваться Django Channels.
            Пример использования:
            user = User.objects.get(pk=1)
            group_name = user.group_name
            """
            return "user_%s" % self.id
  • Теперь, когда новое соединение с сокетом выполняется с помощью ReconnectingWebSocket, Channels вызывают метод подключения моего потребителя. Узнать больше о потребителях.
  • В этом методе подключения я определяю пользователя, получаю имя группы для этого пользователя и, наконец, добавляю новое подключение к этой группе с помощью метода group_add. Код смотрите в конце.
  • Теперь, вспоминая сценарий, если я должен отправить уведомление конкретному пользователю X и X вошел в систему с 5 вкладок, мне просто нужно передать его в группе, и Django Channels сделают необходимое, чтобы отправить его на все вкладки.
  • Вам может быть интересно, как удалить канал из группы, когда пользователь закрывает вкладку или выходит из системы. Поэтому всякий раз, когда пользователь закрывает вкладку, сокет автоматически отключается для этой вкладки, и если пользователь выходит из системы, я явно написал код, который отключает активное соединение сокета.
  • Каждый раз, когда соединение закрывается, Django Channels вызывают метод отключения потребителя.
    В этом методе отключения я использую метод group_discard для удаления соединения из группы. Смотрите фрагмент кода ниже.
class NotificationConsumer(AsyncJsonWebsocketConsumer):
    """
    Этот потребитель уведомлений обрабатывает подключения к веб-сокетам для клиентов.

    Он использует AsyncJsonWebsocketConsumer, что означает, что все функции обработки 
    должны быть асинхронными, а любая работа по синхронизации (например, доступ ORM)
    должна выполняться в database_sync_to_async или sync_to_async.
    """
    # Обработчики событий WebSocket

    async def connect(self):
        """
        Вызывается, когда веб-сокет подтверждает связь при первоначальном подключении.
        """
        try:
            # Передайте токен аутентификации как часть URL-адреса.
            token = self.scope.get('url_route', {}).get(
                'kwargs', {}).get('token', False)
            # Если токен не указан, закройте соединение
            if not token:
                logger.error('No token supplied')
                await self.close()
            # Попробуйте аутентифицировать токен из модели токена DRF
            try:
                token = Token.objects.select_related('user').get(key=token)
            except Token.DoesNotExist:
                logger.error("Token doesn't exist")
                await self.close()

            if not token.user.is_active:
                logger.error('User not active')
                await self.close()
            user = token.user

            # Получите группу, на которую нужно подписаться.
            group_name = user.group_name

            # Добавьте этот канал в группу.
            await self.channel_layer.group_add(
                group_name,
                self.channel_name,
            )
            await self.accept()
        except Exception as e:
            logger.error(e)
            await self.close()

    async def disconnect(self, code):
        """
        Вызывается при закрытии веб-сокета по любой причине.
        Оставьте все комнаты, в которых мы еще находимся.
        """
        try:
            # Получить токен аутентификации по URL-адресу.
            token = self.scope.get('url_route', {}).get(
                'kwargs', {}).get('token', False)
            try:
                token = Token.objects.select_related('user').get(key=token)
            except Token.DoesNotExist:
                logger.error(
                    "Token doesn't exist while closing the connection")
            user = token.user

            # Получить группу, из которой нужно исключить пользователя.
            group_name = user.group_name

            # выкинуть этот канал из группы.
            await self.channel_layer.group_discard(group_name, self.channel_name)
        except Exception as e:
            logger.error(e)

Итак, используя Django Channels, я могу выполнить свою задачу по отправке уведомлений в режиме реального времени. Я также использовал каналы для создания приложения для обмена сообщениями в реальном времени, и оно работает без проблем.

Вернуться на верх