Профили пользователей: Модели и сигналы
Приложение system
Всё, что связано с пользователями, вынесем в отдельное приложение:
python manage.py startapp system modules/system
modules/system/apps.py:
class SystemConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "modules.system"
verbose_name = "Пользователи"
Добавьте "modules.system.apps.SystemConfig" в INSTALLED_APPS.
Модель профиля
modules/system/models.py:
from datetime import date
from django.contrib.auth import get_user_model
from django.core.validators import FileExtensionValidator
from django.db import models
from django.urls import reverse
from modules.services.utils import unique_slugify
User = get_user_model()
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="profile")
slug = models.SlugField(verbose_name="URL", max_length=255, blank=True, unique=True)
avatar = models.ImageField(
verbose_name="Аватар",
upload_to="images/avatars/%Y/%m/%d/",
default="images/avatars/default.png",
blank=True,
validators=[FileExtensionValidator(allowed_extensions=("png", "jpg", "jpeg"))],
)
bio = models.TextField(max_length=500, blank=True, verbose_name="Информация о себе")
birth_date = models.DateField(null=True, blank=True, verbose_name="Дата рождения")
class Meta:
ordering = ("user",)
verbose_name = "Профиль"
verbose_name_plural = "Профили"
def __str__(self):
return self.user.username
def save(self, *args, **kwargs):
if not self.slug:
self.slug = unique_slugify(self, self.user.username)
super().save(*args, **kwargs)
def get_absolute_url(self):
return reverse("profile_detail", kwargs={"slug": self.slug})
@property
def age(self):
if not self.birth_date:
return None
today = date.today()
b = self.birth_date
return today.year - b.year - ((today.month, today.day) < (b.month, b.day))
default="images/avatars/default.png"— аватар по умолчанию (положите картинку вmedia/images/avatars/);FileExtensionValidatorразрешает только картинки нужных форматов;- слаг из имени пользователя — для адресов вида
/user/anna/; - свойство
ageвычисляет возраст.
Сигналы
Сигнал — уведомление о событии, на которое можно подписать функцию-обработчик. Отправитель не знает о получателях — это ослабляет связанность кода.
Встроенные сигналы моделей: pre_save, post_save, pre_delete, post_delete, m2m_changed, pre_init, post_init. Есть и другие: request_started, user_logged_in, user_logged_out…
Создаём профиль при создании пользователя — modules/system/signals.py:
from django.contrib.auth import get_user_model
from django.db.models.signals import post_save
from django.dispatch import receiver
from .models import Profile
User = get_user_model()
@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()
Аргументы обработчика post_save:
| Аргумент | Значение |
|---|---|
sender |
Класс модели (User) |
instance |
Сохранённый объект |
created |
True, если объект только что создан |
**kwargs |
Прочее (raw, using, update_fields) — обязательно принимайте |
Подключение сигналов
Модуль signals.py сам по себе не импортируется — обработчики не зарегистрируются. Импортируем его при запуске приложения:
class SystemConfig(AppConfig):
...
def ready(self):
import modules.system.signals # noqa: F401
Админка
@admin.register(Profile)
class ProfileAdmin(admin.ModelAdmin):
list_display = ("user", "birth_date", "slug")
list_display_links = ("user", "slug")
Создайте нового пользователя в админке — профиль появится автоматически. Для уже существующих пользователей создайте профили в shell:
for user in User.objects.filter(profile__isnull=True):
Profile.objects.create(user=user)
Не злоупотребляйте сигналами
Сигналы удобны, но делают поток выполнения неочевидным: глядя на user.save(), не видно, что создаётся профиль. Используйте их для слабосвязанных реакций на события, а основную логику держите явной — в моделях, формах и сервисных функциях.
Итог
- Профиль —
OneToOneFieldна пользователя, со слагом, аватаром по умолчанию и валидатором расширений. - Сигнал
post_saveсcreated=Trueсоздаёт профиль при любом способе регистрации. - Обработчики подключаются импортом в
AppConfig.ready().
Практика решено 0 из 6
Что такое сигналы в Django?
Напишите обработчик сигнала create_user_profile, который при создании пользователя (created=True)
создаёт для него Profile. Используйте декоратор @receiver(post_save, sender=User).
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver
from .models import Profile
@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
Подключите сигналы приложения: в классе SystemConfig переопределите метод ready,
импортировав внутри него модуль modules.system.signals.
from django.apps import AppConfig
class SystemConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "modules.system"
def ready(self):
import modules.system.signals # noqa: F401
Какие встроенные сигналы моделей существуют?
Почему профиль удобнее создавать сигналом, а не во view регистрации?
Добавьте в модель Profile метод get_absolute_url, возвращающий адрес маршрута profile_detail с параметром slug=self.slug.
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
slug = models.SlugField(max_length=255, blank=True, unique=True)
def get_absolute_url(self):
return reverse("profile_detail", kwargs={"slug": self.slug})
Войдите, чтобы отмечать пройденные уроки.