Evgenii Legotckoi
2 сентября 2019 г. 17:31

Django - Урок 048. Как добавить статус онлайн на сайте

Содержание

Я долго не добавлял такой функционал на сайт по той простой причине, что не знал, с какой стороны к этому подойти, ведь хотелось бы обрабатывать эту информацию в одном-единственном месте, а не в каждом View.

Со временем я начал модифицировать Backend классы для сайта и решение пришло само собой. Необходимо было только изменить каждый сервер аутентификации, который используется на сайте. И переписать метод get_user, который будет хранить информацию о последнем обращении пользователя к сайту. И статус онлайн можно сохранить за последние 15 минут, тогда статус будет офлайн.

А дату последнего запроса можно сохранить либо в профиле пользователя, который будет моделью OneToOne для пользователя, либо переопределить модель пользователя. Я выбрал вариант переопределения пользовательской модели.


my_auth

Давайте создадим собственный модуль аутентификации my_auth, если вы его еще не создали

  1. python manage.py startapp my_auth

models.py

Затем переопределите пользовательскую модель

  1. # -*- coding: utf-8 -*-
  2.  
  3. from django.contrib.auth.models import AbstractUser
  4. from django.contrib.humanize.templatetags.humanize import naturaltime
  5. from django.db import models
  6. from django.utils import timezone
  7. from django.utils.translation import ugettext_lazy as _
  8.  
  9.  
  10. class User(AbstractUser):
  11.  
  12. last_online = models.DateTimeField(blank=True, null=True)
  13.  
  14. # In this method, check that the date of the last visit is not older than 15 minutes
  15. def is_online(self):
  16. if self.last_online:
  17. return (timezone.now() - self.last_online) < timezone.timedelta(minutes=15)
  18. return False
  19.  
  20. # If the user visited the site no more than 15 minutes ago,
  21. def get_online_info(self):
  22. if self.is_online():
  23. # then we return information that he is online
  24. return _('Online')
  25. if self.last_online:
  26. # otherwise we write a message about the last visit
  27. return _('Last visit {}').format(naturaltime(self.last_online))
  28. # If you have only recently added information about a user visiting the site
  29.             # then for some users there may not be any information about the visit, we will return information that the last visit is unknown
  30. return _('Unknown')
  31.  

backends.py

Далее мы пишем собственный бэкенд аутентификации, который заменит бэкенд Django.

  1. # -*- coding: utf-8 -*-
  2.  
  3. from django.contrib.auth import get_user_model
  4. from django.utils import timezone
  5.  
  6.  
  7. class MyBackend:
  8.  
  9. def get_user(self, user_id):
  10. try:
  11. user = get_user_model().objects.get(pk=user_id)
  12. user.last_online = timezone.now() # At the request of the user, we will update the date and time of the last visit
  13. user.save(update_fields=['last_online'])
  14. return user
  15. except get_user_model().DoesNotExist:
  16. return None
  17.  

settings.py

Не забудьте зарегистрировать наше приложение...

  1. INSTALLED_APPS = [
  2. 'my_auth.apps.MyAuthConfig',
  3. ...
  4. ]

... и добавить бэкенд аутентификации

  1. AUTHENTICATION_BACKENDS = (
  2. 'my_auth.backends.MyBackend',
  3. 'django.contrib.auth.backends.ModelBackend',
  4. )

Вывод

Теперь при каждом обращении пользователя на сайт вашему пользователю будет обновляться информация о его последнем посещении.

И вы можете сделать в шаблоне с пользователями контент, независимо от того, онлайн он или нет. Например, чтобы добавить значок «В сети» на аватар

  1. {% if user.is_online %}
  2. <div class="online"></div>
  3. {% endif %}

или так отображать информацию о его онлайн-статусе.

  1. <span class="text-muted">{{ user_profile.get_online_info }}</span>

Возможный минус этого способа в том, что пользователь может открыть страницу и целый час ничего на ней не делать.
Но в моем случае это не проблема, потому что я каждую минуту вызываю информацию о статусе аутентифицированного пользователя, чтобы пока страница пользователя оставалась открытой, его онлайн-статус обновлялся каждую минуту.

Вам это нравится? Поделитесь в социальных сетях!

Владислав Меленчук
  • 3 июня 2020 г. 15:43

Доброго дня. А можно реализовать с помощью этой фичи вывод блок "Пользователи онлайн" и окрашивать взависимости от группы юзера? Как в скрине ниже.
Screenshot_36.png Screenshot_36.png

Добрый день.
Конечно, нужно будет только шаблонный тег написать, который будет забирать всех пользователей онлайн, проверяя группу текущего пользователя.
У меня пользователи онлайн на отдельной странице в разделе пользователей.

Anton
  • 21 июля 2020 г. 17:47
  • (ред.)


Здравствуйте. как внедрить эту систему если у меня уже есть авторизация и всё с ней связанное? что бы это не перекрывало весь мой код?

Evgenii Legotckoi
  • 22 июля 2020 г. 1:49

Добрый день. Вам нужно скопировать в класс AdvUser содержимое класса User из статьи. Полностью весь класс копировать не требуется.
Полагаю, что в файле settings.py у вас уже переопределён класс пользователя на ваш в переменной AUTH_USER_MODEL

Остальной код написан относительно универсально, достаточно лишь добавить файлы с содержимым как в статье в ваше существующее app аутентификации.

Также не забудьте создать миграцию и применить её, чтобы добавилось новое поле в таблице пользователя.

Anton
  • 22 июля 2020 г. 11:21

Большое спасибо!

Anton
  • 22 июля 2020 г. 13:50

И так , не все получилось. Сделал как вы говорили. И у меня как и раньше перебивает мою авторизацию. При входе в admin Вот эта ошибка

после чего я удалил из settings поле 'manager_school.backends.MyBackend'. Меня пустило. Но дело в том что(понятно что оно без этой строчки работать не будет) если я дату указываю в поле админки то мне выдаёт что человек онлайн.И даже если я выйду он всё-равно online. без этой строчки не отдаботает

И я понимаю что дело в 'manager_school.backends.MyBackend' этой строчке . но Как же мне сделать что бы он не заменял собой все настройки авторизации...?

Evgenii Legotckoi
  • 22 июля 2020 г. 14:01

Длительность сохранения статуса онлайн от последнего запроса страницы составляет 15 минут.

  1. def is_online(self):
  2. if self.last_online:
  3. return (timezone.now() - self.last_online) < timezone.timedelta(minutes=15) # здесь выполняется эта проверка на 15 минутный интервал
  4. return False

Можете уменьшить на одну минуту и посмотреть результат. Вполне возможно, что у вас всё работает.

Данная система статуса онлайн не совершенна, поскольку требует периодического обращения к серверу. Чтобы обновлять время последнего статуса онлайн. У меня эта проблема решается за счёт обращения к серверу раз в одну минуту для проверки уведомлений через JavaScript. Остальные разработчики решают эту часть своими способоами, как у них это выходит.

Evgenii Legotckoi
  • 22 июля 2020 г. 14:15

Да ещё.

Покажите код ваших бэкендов аутентификации, а также как скопировали код класса MyBackend

Помимо прочего, какую версию Джанго используете?

P/S/ Используйте диалог вставки программного кода, чтобы был код с разметкой. Не надо вставлять изображения кода, на любьом форуме это считается плохим тоном.

P/P/S/ И я просил на форуме задать вопрос, а не в комментариях к статье ))

Anton
  • 22 июля 2020 г. 14:18

Спасибо за ответ. уменьшил время до 14 минут и до 10 минут , ошибка осталась та же 'MyBackend' object has no attribute 'authenticate'

Anton
  • 22 июля 2020 г. 14:26
  • (ред.)

MyBackend

  1. from django.contrib.auth import get_user_model
  2. from django.utils import timezone
  3.  
  4.  
  5. class MyBackend:
  6. def get_user(self, user_id):
  7. try:
  8. user = get_user_model().objects.get(pk=user_id)
  9. user.last_online = timezone.now() # При запросе пользователя выполним обновлении даты и времени последнего посещения
  10. user.save(update_fields=['last_online'])
  11. return user
  12. except get_user_model().DoesNotExist:
  13. return None

Версия Django 3.0.8
бэк аутентификации

  1. AUTHENTICATION_BACKENDS = (
  2. 'manager_school.backends.MyBackend',
  3. 'django.contrib.auth.backends.ModelBackend',
  4. )
Evgenii Legotckoi
  • 22 июля 2020 г. 14:28
  • (ред.)

Извиняюсь. Время здесь влияние не оказывает, это я ответил по поводу сохранения статуса онлайн.

Evgenii Legotckoi
  • 22 июля 2020 г. 14:29

Надо подумать. Я писал этот статус онлайн для Django 2. Вполне возможно, что для третьей версии там используется несколько иной механизм бэкендов.
Смогу посмотреть это только после работы уже.

Anton
  • 22 июля 2020 г. 17:11

Я нашел ответ на ошибку

  1. from django.contrib.auth.backends import BaseBackend
  2.  
  3. class MyBackend(BaseBackend):
  4. ....
Evgenii Legotckoi
  • 22 июля 2020 г. 17:18

Ясно, значит для Django 3 обязательно наследование от BaseBackend при создание бэкенда аутентификации.
Скорее всего отсутствовал метод authenticate, который присутсвует при наследовании от BaseBackend

NSProject
  • 18 февраля 2022 г. 22:33
  • (ред.)

Хорошая статья для создания собственной батарейки для Django. Правда под каждую версию фреймворка приходится допиливать код бэкенда. Так как для версии Django 4 если наследовать бэкенд от BaseBackend то обновление статуса не происходит. Он просто проходит мимо метода get_user. По этому наследование лучше делать от ModelBackend и всё начинает работать.
Остальной весь код рабочий! Проверено на Django 4.

V
  • 2 апреля 2022 г. 23:09

Сделал всё как описано но при создании миграций возникает ошибка:
SystemCheckError: System check identified some issues:

ERRORS:
auth.User.groups: (fields.E304) Reverse accessor for 'auth.User.groups' clashes with reverse accessor for 'users.User.groups'.
HINT: Add or change a related_name argument to the definition for 'auth.User.groups' or 'users.User.groups'.
auth.User.user_permissions: (fields.E304) Reverse accessor for 'auth.User.user_permissions' clashes with reverse accessor for 'users.User.user_permissions'.
HINT: Add or change a related_name argument to the definition for 'auth.User.user_permissions' or 'users.User.user_permissions'.
users.User.groups: (fields.E304) Reverse accessor for 'users.User.groups' clashes with reverse accessor for 'auth.User.groups'.
HINT: Add or change a related_name argument to the definition for 'users.User.groups' or 'auth.User.groups'.
users.User.user_permissions: (fields.E304) Reverse accessor for 'users.User.user_permissions' clashes with reverse accessor for 'auth.User.user_permissions'.
HINT: Add or change a related_name argument to the definition for 'users.User.user_permissions' or 'auth.User.user_permissions'.

NSProject
  • 3 апреля 2022 г. 0:03
  • (ред.)

А вы создали в settings.py?

  1. AUTH_USER_MODEL = 'you_app.User'

А так же во всех моделях где есть внешний ключ на юзера нужно ставить

  1. settings.AUTH_USER_MODEL
NSProject
  • 26 августа 2022 г. 20:40

Евгений у меня вопрос. В общем и целом оно всё работает. Но есть маленький затык. Это так сказать дело в том что при переключении языка "Last visit" переводится а вот то что отдаёт функция "naturaltime" нет. Есть какая то возможность это исправить? Или нужно переписать код этой самой функции?

Evgenii Legotckoi
  • 2 сентября 2022 г. 15:12

Я обычно использую localtime и всё работает.

  1. {% load tz %}
  2.  
  3. {% localtime on %}
  4. {{ value }}
  5. {% endlocaltime %}
  6.  
  7. {% localtime off %}
  8. {{ value }}
  9. {% endlocaltime %}

Комментарии

Только авторизованные пользователи могут публиковать комментарии.
Пожалуйста, авторизуйтесь или зарегистрируйтесь