Профили пользователей: Модели и сигналы

⏱ 25 мин · 🧩 заданий: 6

Приложение 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

Вы не вошли — ответы проверяются, но прогресс не сохранится. Войти или зарегистрироваться.
Задание 1 Один вариант ответа

Что такое сигналы в Django?

Задание 2 Написать код

Напишите обработчик сигнала create_user_profile, который при создании пользователя (created=True) создаёт для него Profile. Используйте декоратор @receiver(post_save, sender=User).

Задание 3 Написать код

Подключите сигналы приложения: в классе SystemConfig переопределите метод ready, импортировав внутри него модуль modules.system.signals.

Задание 4 Несколько вариантов

Какие встроенные сигналы моделей существуют?

Задание 5 Один вариант ответа

Почему профиль удобнее создавать сигналом, а не во view регистрации?

Задание 6 Написать код

Добавьте в модель Profile метод get_absolute_url, возвращающий адрес маршрута profile_detail с параметром slug=self.slug.

Войдите, чтобы отмечать пройденные уроки.