Django 35.1. Расширенный профиль пользователя - модель и сигналы
В этом посте создадим модель расширяющую профиль пользователя, а также добавим сигналы для обработки новых зарегистрированных пользователей.
Дополнительные материалы
Для скачивания материалов необходимо войти или зарегистрироваться
Файлы также можно получить в Telegram-боте по коду: 585887
Реклама
Продолжаем тему профиля пользователя, начатую в прошлом посте.
В этом и следующем посте мы расширим его дополнительными полями, создав модель профиля, форму, написав сигнал для создания профиля при регистрации и изменим наши шаблоны.
Я решил расширить профиль следующими полями:
- Пол
- Дата рождения
- Личный сайт - ссылка на сайт пользователя.
- Профиль в Telegram - ссылка на Telegram-профиль.
- Аватарка - изображения по умолчанию у меня не будет, да и скучно это, когда много пользователей с одинаковыми изображениями. Вместо этого, для каждого пользователя по умолчанию будет генерироваться аватар в сервисе https://robohash.org/.
- Три переключателя, определяющих отображать в профиле или нет следующие поля: Фамилия, Email, Telegram-профиль.
В профиле это будет отображаться несколько иначе:
- Вместо даты рождения, будет отображаться возраст пользователя, если указана дата.
- Ссылка на профиль в Telegram, будет отображать только ник.
- Также будет поле для отображения информации о том, как давно зарегистрировался пользователь.
Библиотека Pillow.
Для работы с изображениями необходима библиотека Pillow
, если она у вас не установлена, то установить её можно командой:
pip install Pillow
Не забудьте добавить библиотеку в файл requirements.txt
:
Pillow~=10.1.0
Модель профиля.
Откроем файл models.py
в директории user_app
и создадим класс ProfileModel
, унаследованный от models.Model
.
В самом начале пропишем внутренний класс Genders
, это будет перечисление с вариантами выбора пола пользователя. Подобный класс перечисления мы писали в посте "Django 20. Модель поста".
Далее будет девять полей:
user
- поле для связи с пользователем через связь "Один к Одному" (OneToOne). В аргументах передаём модель, название, а так же имя связи, по которому мы сможем обратиться к объекту модели из объекта пользователя.gender
- текстовое поле для хранения выбранного в перечислении значения пола пользователя. В аргументах указываем максимальную длину поля, указываем класс с перечислениями, указываем стандартное значение, а также имя поля.dob
- поле с датой рождения пользователя. В аргументах указываемnull
иblank
равноеTrue
, что делает поле необязательным к вводу, а также имя поля.site_link
,telegram_link
- поля ссылок (URLField
) . Стандартные аргументы: имя поля,null
иblank
, максимальная длина. Дляtelegram_link
добавляем атрибутunuque=True
, что бы ссылки на профили не повторялись.user_avatar
- поле изображения (ImageField
). В аргументах передаём путь в директорииmedia
, куда будут загружаться изображения, также имя поля иnull
иblank
. Также, можно указать изображение по умолчанию.show_email
,show_telegram
,show_last_name
- логические поля (BooleanField
). В аргументах передаём имя поля и стандартное значениеFalse
.
Прописываем класс Meta
, указывая имя модели.
Затем у нас будет несколько методов:
Переопределённый метод save
.
В этом методе, сперва проверяем, установлена ли у пользователя аватарка.
Если не установлена, то делаем запрос на сайт и сохраняем полученное изображение.
В противном случаем, мы проверяем, что загруженное пользователем изображение не больше 300х300 и если оно больше, сжимаем до нужного размера.
Метод get_on_site
, высчитывающий из текущей даты и даты регистрации то, сколько дней/месяцев/лет назад зарегистрировался пользователь.
Метод get_telegram_username
, отделяющий от ссылки на профиль имя пользователя в Telegram.
Метод get_age
, высчитывающий возраст пользователя.
Dunder-метод __str__
, возвращающий имя пользователя при обращении к объекту.
Код модели:
from datetime import datetime
from urllib import request
from PIL import Image
from django.contrib.auth.models import User
from django.db import models
class ProfileModel(models.Model):
class Genders(models.TextChoices):
UNDEFINED = 'U', 'не выбран'
MALE = 'M', 'мужской'
FEMALE = 'F', 'женский'
user = models.OneToOneField(User,
on_delete=models.CASCADE,
related_name='profile',
verbose_name='Пользователь')
gender = models.CharField(max_length=1,
choices=Genders.choices,
default=Genders.UNDEFINED,
verbose_name='Пол')
dob = models.DateField(blank=True,
null=True,
verbose_name='Дата рождения')
site_link = models.URLField(max_length=200,
blank=True,
null=True,
verbose_name='Ссылка на сайт')
telegram_link = models.URLField(max_length=200,
blank=True,
null=True,
verbose_name='Ссылка на Telegram',
unique=True)
user_avatar = models.ImageField(upload_to='user/avatars/',
blank=True,
null=True,
verbose_name='Аватар пользователя')
show_email = models.BooleanField(default=False,
verbose_name='Отображать Email?')
show_telegram = models.BooleanField(default=False,
verbose_name='Отображать Telegram?')
show_last_name = models.BooleanField(default=False,
verbose_name='Отображать фамилию?')
class Meta:
verbose_name = 'Профиль'
verbose_name_plural = 'Профили'
def save(self, *args, **kwargs):
if not self.user_avatar:
encoded_username = quote(self.user.username, safe='')
image_url = f'https://robohash.org/{encoded_username}'
response = request.urlopen(image_url)
self.user_avatar.save(f'{self.user.username}.png', response, save=False)
super().save(*args, **kwargs)
img = Image.open(self.user_avatar.path)
if img.height > 300 or img.width > 300:
output_size = (300, 300)
img.thumbnail(output_size)
img.save(self.user_avatar.path)
def get_on_site(self):
delta = datetime.now() - self.user.date_joined.replace(tzinfo=None)
years = delta.days // 365
months = (delta.days % 365) // 30
days = (delta.days % 365) % 30
if delta.days 0:
return 'меньше суток'
else:
on_site_string = ""
if years > 0:
on_site_string += f"{years} лет, " if months > 0:
on_site_string += f"{months} месяца, " if days > 0:
on_site_string += f"{days} дней"
return on_site_string
def get_telegram_username(self):
return self.telegram_link.split('/')[-1]
def get_age(self):
if self.dob:
today = datetime.today()
age = today.year - self.dob.year
if today.month < self.dob.month:
age -= 1
elif today.month self.dob.month and today.day < self.dob.day:
age -= 1
return age
def __str__(self):
return self.user.username
Регистрация в панели администратора.
Откроем файл admin.py
в директории user_app
.
Я не вижу смысла тут прописывать поля для отображения, по крайней мере пока, по этому просто зарегистрирую модель:
from django.contrib import admin
from user_app import models
admin.site.register(models.ProfileModel)
Сигнал о регистрации пользователя.
Когда пользователь регистрируется на сайте, необходимо, что бы создавалась модель профиля, с этим нам помогут сигналы (signals) в Django.
Сигналы (signals) - это механизм, который позволяет отправлять сообщения о событиях в приложении. Сигналы используются для оповещения других частей приложения о том, что произошло определенное событие, например, создание или обновление объекта модели.
В директории приложения user_app
создадим новый файл signals.py
и напишем следующий код:
from django.contrib.auth.models import User
from .models import ProfileModel
from django.db.models.signals import post_save
from django.dispatch import receiver
@receiver(post_save, sender=User)
def create_profile(sender, instance, created, **kwargs):
if created:
ProfileModel.objects.create(user=instance)
create_profile
- это функция-обработчик сигнала post_save
, который вызывается каждый раз, когда сохраняется экземпляр модели User
.
В нашем случае он будет отрабатывать только при создании нового объекта, т.е. регистрации пользователя. Для этого в теле функции есть условие if created
.
Если условие верное, то будет создан экземпляр модели ProfileModel
и связан с экземпляром модели User
, который был передан в функцию обработчиком сигнала.
Подключение сигнала.
Откроем файл apps.py
и внутри класса добавим метод ready
, который будет срабатывать при запуске приложения:
def ready(self):
import user_app.signals
Внимание! Если у вас уже есть зарегистрированные пользователи, а как минимум один (суперпользователь) у вас есть, вам необходимо для них в админке создать профили самостоятельно!
Все статьи