Cat

Django 35.1. Расширенный профиль пользователя - модель и сигналы

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

Все статьи

Icon Link

Дополнительные материалы

Icon Link

Реклама

Icon Link
Сайт на Django proDream 29 Октябрь 2023 Просмотров: 983

Продолжаем тему профиля пользователя, начатую в прошлом посте.

В этом и следующем посте мы расширим его дополнительными полями, создав модель профиля, форму, написав сигнал для создания профиля при регистрации и изменим наши шаблоны.

Я решил расширить профиль следующими полями:

  • Пол
  • Дата рождения
  • Личный сайт - ссылка на сайт пользователя.
  • Профиль в 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

 

Внимание! Если у вас уже есть зарегистрированные пользователи, а как минимум один (суперпользователь) у вас есть, вам необходимо для них в админке создать профили самостоятельно!

 

Автор

    Нет комментариев

    Реклама