Создание middleware для статуса онлайн
В модуле system я добавлю папку middleware с файлом __init__.py, а в ней создам файл activeuser.py
modules/system/middleware/activeuser.py
from django.contrib.auth.models import User
from django.core.cache import cache
from django.utils import timezone
from django.utils.deprecation import MiddlewareMixin
class ActiveUserMiddleware(MiddlewareMixin):
def process_request(self, request):
if request.session.session_key:
cache_key = f'last-seen-{request.session.session_key}'
last_login = cache.get(cache_key)
if not last_login and request.user.is_authenticated:
User.objects.filter(id=request.user.id).update(last_login=timezone.now())
visit_time = 300
cache.set(cache_key, 1, visit_time)
Пояснение:
- Мы наследуемся от миксина MiddlewareMixin, где используя метод, мы получаем ключ сессии, сохраняем его в кеше, и обновляем дату последнего посещения на сегодняшнюю. Сессия онлайна длится 300 секунд и обновляется при обновлении страницы.
Добавим в __init__.py вызов нашего класса (если вы используете ту же структуру, как у меня)
modules/system/middleware/__init__.py
from modules.system.middleware.activauser import ActiveUserMiddleware
__all__ = ('ActiveUserMiddleware', )
Этот способ использует кэширование. Крайне рекомендую Redis кэширование.
Но сейчас мы воспользуемся просто файловым кэшированием.
Поэтому в корне проекта я создам папку cache.

Далее нам необходимо настроить кэш в settings.py, а также добавить наш новый middleware
backend/settings.py
Добавление middleware
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'debug_toolbar.middleware.DebugToolbarMiddleware',
'modules.system.middleware.ActiveUserMiddleware',
]
Добавление настроек кэша где-нибудь в файле settings.py:
# Cache settings
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
'LOCATION': (BASE_DIR / 'cache'),
}
}
Также добавим метод для получение статуса онлайна в модель профиля пользователя:
modules/system/models/profiles.py
from datetime import date, timedelta
from django.core.validators import FileExtensionValidator
from django.db import models
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.urls import reverse
from django.utils import timezone
from modules.system.services.utils import ImageDirectorySave, unique_slugify
class Profile(models.Model):
user = models.OneToOneField(User, verbose_name='Профиль пользователя', on_delete=models.CASCADE)
slug = models.SlugField(verbose_name='Персональная ссылка', max_length=255, blank=True, unique=True)
bio = models.TextField(max_length=500, verbose_name='Информация о себе', blank=True)
avatar = models.ImageField(
verbose_name='Аватар профиля',
blank=True,
upload_to=ImageDirectorySave('images/avatars/'),
validators=[FileExtensionValidator(
allowed_extensions=('png', 'jpg', 'webp', 'jpeg'))
]
)
date_birthday = models.DateField(verbose_name='Дата рождения', blank=True, null=True)
following = models.ManyToManyField('self', verbose_name='Подписки', related_name='followers', symmetrical=False, blank=True)
class Meta:
"""
Сортировка, название модели в админ панели, таблица в данными
"""
ordering = ('user',)
verbose_name = 'Профиль'
verbose_name_plural = 'Профили пользователей'
db_table = 'app_profiles'
def save(self, *args, **kwargs):
"""
Сохранение параметров модели при их отсутствии заполнения
"""
if not self.slug:
self.slug = unique_slugify(self, self.user.username)
if self.slug:
self.slug = self.slug.lower()
super().save(*args, **kwargs)
def __str__(self):
"""
Возвращение имени пользователя
"""
return self.user.username
@property
def get_avatar(self):
"""
Получение аватара при отсутствии загруженного
"""
if not self.avatar:
return f'https://ui-avatars.com/api/?size=128&background=random&name={self.user.username}'
return self.avatar.url
@property
def get_age(self):
"""
Вычисление возраста пользователя
"""
if self.date_birthday:
return (date.today() - self.date_birthday) // timedelta(days=365.2425)
return 'не указан'
def is_online(self):
"""
Показывает данные об онлайне
"""
if self.user.last_login:
now = timezone.now()
if now > self.user.last_login + timezone.timedelta(seconds=300):
return False
return True
else:
return False
def get_absolute_url(self):
"""
Ссылка на профиль
"""
return reverse('profile', kwargs={'slug': self.slug})
@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
"""
Сигнал создания профиля пользователя
"""
if created:
Profile.objects.create(user=instance)
@receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
"""
Сигнал пересохранения профиля пользователя
"""
instance.profile.save()
Пояснение:
- Я добавил метод
is_online
, остальной код - лишь напоминание о нашей проделанной работы в уроках. - В данном методе мы сравниваем дату последнего посещения с текущим временем и возвращаем соответственный результат.
Теперь добавим в шаблон наш статус:
templates/modules/system/profiles/profile-detail.html
{% extends 'main.html' %}
{% load static %}
{% block content %}
<div class="card border-0 mb-2">
<div class="card-body">
<div class="row">
<div class="col-md-3">
<figure>
<img src="{{ profile.get_avatar }}" class="img-fluid rounded-0" alt="{{ profile }}">
</figure>
</div>
<div class="col-md-9">
<h5 class="card-title">
{{ profile }}
</h5>
<div class="card-text">
<ul>
<li>Никнейм: {{ profile.user.username }}</li>
<li>Заходил: {{ profile.user.last_login }}</li>
<li>Статус: {% if profile.is_online %}<span class="text-success">Online</span>{% else %}<span class="text-danger">Offline</span>{% endif %}</li>
<li>Возраст: {{ profile.get_age }}</li>
<li>О себе: {{ profile.bio }}</li>
</ul>
{% if request.user != profile.user and request.user.is_authenticated %}
{% if request.user.profile in profile.followers.all %}
<button class="btn btn-sm btn-danger btn-following" data-slug="{{ profile.slug }}">
Отписаться от {{ profile }}
</button>
{% else %}
<button class="btn btn-sm btn-primary btn-following" data-slug="{{ profile.slug }}">
Подписаться на {{ profile }}
</button>
{% endif %}
{% endif %}
</div>
</div>
</div>
</div>
</div>
<div class="card border-0">
<div class="card-body">
<div class="row">
<div class="col-md-6">
<h6 class="card-title">
Подписки
</h6>
<div class="card-text">
<div class="row">
{% for following in profile.following.all %}
<div class="col-md-2">
<a href="{{ following.get_absolute_url }}">
<img src="{{ following.get_avatar }}" class="img-fluid rounded-1" alt="{{ following }}"/>
</a>
</div>
{% endfor %}
</div>
</div>
</div>
<div class="col-md-6">
<h6 class="card-title">
Подписчики
</h6>
<div class="card-text">
<div class="row" id="followersBox">
{% for follower in profile.followers.all %}
<div class="col-md-2" id="user-slug-{{ follower.slug }}">
<a href="{{ follower.get_absolute_url }}">
<img src="{{ follower.get_avatar }}" class="img-fluid rounded-1" alt="{{ follower }}"/>
</a>
</div>
{% endfor %}
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block script %}
<script src="{% static 'custom/js/profile.js' %}"></script>
{% endblock %}
Добавил:
<li>Статус: {% if profile.is_online %}<span class="text-success">Online</span>{% else %}<span class="text-danger">Offline</span>{% endif %}</li>
Теперь можем смотреть результат на самом сайте:

Зайдем к тому, кого нет сейчас на сайте:

У нас все получилось. Этот статус можно вывести везде, где вам только это необходимо.