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, я могу выполнить свою задачу по отправке уведомлений в режиме реального времени. Я также использовал каналы для создания приложения для обмена сообщениями в реальном времени, и оно работает без проблем.
Вернуться на верх