Django База [2023]: Профиль пользователя - модель, сигналы #16
Django

Django База [2023]: Профиль пользователя - модель, сигналы #16

Razilator

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

В приложении blog мы не будем создавать профиль, а создадим новое приложение system, где будут храниться различные модели, функции для работы с логической частью сайта. Там же я и создам модель профиля.

Другие методы работы с пользовательской аутентификацией Django, если вы не хотите создавать профиль пользователя.

Создание приложения system

Для этого в терминале пишем следующую команду: py manage.py startapp system После создания приложения system перемещаем его в папку modules, как мы это делали в первом уроке

Приложения blog и system, а также функции services
Приложения blog и system, а также функции services

Теперь необходимо отредактировать внутри приложения файл apps.py, добавив, что приложение у нас находится в папке modules, с этого кода:

modules/system/apps.py
from django.apps import AppConfig


class SystemConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'system'

На следующий:

modules/system/apps.py
from django.apps import AppConfig


class SystemConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'modules.system'
    verbose_name = 'Система'

Далее добавим наше приложение в конфигурационный файл settings.py для того, чтобы Django мог работать с ним.

backend/settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'modules.blog.apps.BlogConfig',
    'modules.system.apps.SystemConfig',
    'mptt',
    'debug_toolbar',
]

Отлично. Перейдем к созданию модели.

Создание модели профиля

Внутри system открываем файл models.py и добавляем модель профиля:

modules/system/models.py
from django.db import models
from django.contrib.auth import get_user_model
from django.core.validators import FileExtensionValidator
from django.urls import reverse
from datetime import date, timedelta

from modules.services.utils import unique_slugify

User = get_user_model()


class Profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    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.jpg',
        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:
        """
        Сортировка, название таблицы в базе данных
        """
        db_table = 'app_profiles'
        ordering = ('user',)
        verbose_name = 'Профиль'
        verbose_name_plural = 'Профили'

    def save(self, *args, **kwargs):
        """
        Сохранение полей модели при их отсутствии заполнения
        """
        if not self.slug:
            self.slug = unique_slugify(self, self.user.username)
        super().save(*args, **kwargs)
    
    def __str__(self):
        """
        Возвращение строки
        """
        return self.user.username
    
    def get_absolute_url(self):
        """
        Ссылка на профиль
        """
        return reverse('profile_detail', kwargs={'slug': self.slug})

В примере выше мы использовали поля slug, avatar, bio, birth_date для данных профиля, а также мы используем связь один-к-одному с встроенной пользовательской моделью Django.

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

Ещё мы добавили метод сохранения, чтобы у пользователя был свой уникальный url, по которому мы сможем заходить в профиль.

Также нам нужно установить изображение по умолчанию для аватарки, я буду использовать такое изображение:

default.jpg
default.jpg

И помещу его в папку media/images/avatars

Поместили заглушку
Поместили заглушку

Дополнительная возможность: мы можем и не указывать дефолтное значение для аватарки, а вместо этого создать функцию для модели и воспользоваться сайтом плейсхолдером, например мы можем использовать такой метод для модели:

modules/system/models.py
from django.db import models
from django.core.validators import FileExtensionValidator
from django.contrib.auth import get_user_model

User = get_user_model()


class Profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    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/', 
        blank=True,  
        validators=[FileExtensionValidator(allowed_extensions=('png', 'jpg', 'jpeg'))])
        
    # Поля модели
        
    # Другие методы... 

    @property
    def get_avatar(self):
        if self.avatar:
            return self.avatar.url
        return f'https://ui-avatars.com/api/?size=150&background=random&name={self.slug}'

В шаблоне профиля вызывать аватарку через {{ profile.get_avatar }}, вместо {{ profile.avatar.url }}.

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

Добавление сигналов для создания профиля

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

modules/system/models.py
from django.db import models
from django.contrib.auth.models import User
from django.core.validators import FileExtensionValidator
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.urls import reverse

from modules.services.utils import unique_slugify


class Profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    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.jpg',
        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:
        db_table = 'app_profiles'
        ordering = ('user',)
        verbose_name = 'Профиль'
        verbose_name_plural = 'Профили'

    def save(self, *args, **kwargs):
        """
        Сохранение полей модели при их отсутствии заполнения
        """
        if not self.slug:
            self.slug = unique_slugify(self, self.user.username)
        super().save(*args, **kwargs)

    def __str__(self):
        """
        Возвращение строки
        """
        return self.user.username
    
    def get_absolute_url(self):
        """
        Ссылка на профиль
        """
        return reverse('profile_detail', 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()

Модель мы создали, давайте ее зарегистрируем в админ-панели, для этого перейдем в файл admin.py и добавим следующий код:

modules/system/admin.py
from django.contrib import admin

from .models import Profile

@admin.register(Profile)
class ProfileAdmin(admin.ModelAdmin):
    """
    Админ-панель модели профиля
    """
    list_display = ('user', 'birth_date', 'slug')
    list_display_links = ('user', 'slug')

Отлично, давайте создадим и проведем миграции в базу данных с помощью команд: py manage.py makemigrations и py manage.py migrate

Результат выполнения:

Shell
(venv) PS C:\Users\Razilator\Desktop\Base\backend> py manage.py makemigrations
Migrations for 'system':
  modules\system\migrations\0001_initial.py
    - Create model Profile
(venv) PS C:\Users\Razilator\Desktop\Base\backend> py manage.py migrate        
Operations to perform:
  Apply all migrations: admin, auth, blog, contenttypes, sessions, system
Running migrations:
  Applying system.0001_initial... OK

Отлично, перейдем в админ-панель для проверки, работает ли все так, как нам необходимо:

Для первого пользователя профиль создаем вручную
Для первого пользователя профиль создаем вручную

Создадим профиль нашего супер-пользователя, добавим туда определенные данные:

Заполняем свои данные
Заполняем свои данные

Сохраним, и посмотрим на профиль:

Сохраненный профиль
Сохраненный профиль

Отлично, профиль работает, тажке добавляется slug из нашей кастомной функции, которую мы сделали в одном из уроков по автоматической генерации slug и обработке кириллицы

Метод save отработал для slug
Метод save отработал для slug

Теперь проверим работу сигналов, создав нового пользователя в админ-панеле:

Создаю тестового пользователя
Создаю тестового пользователя

Пользователь сохранен

Сохраненный новый пользователь
Сохраненный новый пользователь

Теперь проверим, создался ли профиль для этого пользователя:

Профиль создался автоматически с помощью сигналов
Профиль создался автоматически с помощью сигналов

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

Обновление статьи: 28.01.2023 - добавил метод __str__ к модели Profile, а также verbose_name к приложению в apps.py, чтобы вместо system показывалось - система в админ-панеле.

;