Создание профиля в Django с аватаром, возрастом, и системой подписок
Разместим это все в нашем модуле "system", поэтому давайте создадим в папке models файл profiles.py.
modules/system/models/profiles.py
from datetime import date, timedelta
from django.core.validators import FileExtensionValidator
from django.db import models
from django.contrib.auth.models import User
from django.urls import reverse
from modules.system.services.utils import ImageDirectorySave, unique_slugify
class Profile(models.Model):
user = models.OneToOneField(User, verbose_name='Профиль пользователя', on_delete=models.CASCADE)
slug = models.SlugField(verbose_name='Персональная ссылка', max_length=255, blank=True, unique=True)
bio = models.TextField(max_length=500, verbose_name='Информация о себе', blank=True)
avatar = models.ImageField(
verbose_name='Аватар профиля',
blank=True,
upload_to=ImageDirectorySave('images/avatars/'),
validators=[FileExtensionValidator(
allowed_extensions=('png', 'jpg', 'webp', 'jpeg'))
]
)
date_birthday = models.DateField(verbose_name='Дата рождения', blank=True, null=True)
following = models.ManyToManyField('self', verbose_name='Подписки', related_name='followers', symmetrical=False, blank=True)
class Meta:
"""
Сортировка, название модели в админ панели, таблица в данными
"""
ordering = ('user',)
verbose_name = 'Профиль'
verbose_name_plural = 'Профили пользователей'
db_table = 'app_profiles'
Пояснения:
- Модель профиля ссылается на лишь одного пользователя, не более! И если пользователь будет удален, то и профиль тоже удалится.
- Добавим slug, bio.
- Добавим возможность ставить аватар, путь к ним будет images/avatars/г/м/д
- Добавим возможность ставить возраст
- Сделаем систему подписок, с symmetrical=False, чтобы ManyToMany не было двухсторонним!
- Сортировать будем по пользователю.
Давайте добавим метод для получения возраста, и чтоб с аватарками не было проблем:
@property
def get_avatar(self):
"""
Получение аватара при отсутствии загруженного
"""
if not self.avatar:
return f'https://ui-avatars.com/api/?size=128&background=random&name={self.user.username}'
return self.avatar.url
Пояснения:
- Сервис ui-avatars.com вернет картинку svg формата, где будут две заглавные буквы имени пользователя.
Теперь метод получения возраста:
@property
def get_age(self):
"""
Вычисление возраста пользователя
"""
return (date.today() - self.date_birthday) // timedelta(days=365.2425)
Пояснения:
- Из текущей даты вычитаем указанную дату рождения, и считаем от нее по году. Таким образом получаем возраст.
Добавим метод get_absolute_url для будущего создания представления профиля
def get_absolute_url(self):
"""
Ссылка на профиль
"""
return reverse('profile', kwargs={'slug': self.slug})
И конечно, строку и метод сохранения для слагов.
def save(self, *args, **kwargs):
"""
Сохранение параметров модели при их отсутствии заполнения
"""
if not self.slug:
self.slug = unique_slugify(self, self.user.username)
if self.slug:
self.slug = self.slug.lower()
super().save(*args, **kwargs)
def __str__(self):
"""
Возвращение имени пользователя
"""
return self.user.username
Теперь мы должны добавить сигналы, чтобы при регистрации пользователя к нему создавался профиль.
modules/system/models/profiles.py
from django.db.models.signals import post_save
from django.dispatch import receiver
@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()
Должно получиться вот так:
from datetime import date, timedelta
from django.core.validators import FileExtensionValidator
from django.db import models
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.urls import reverse
from modules.system.services.utils import ImageDirectorySave, unique_slugify
class Profile(models.Model):
user = models.OneToOneField(User, verbose_name='Профиль пользователя', on_delete=models.CASCADE)
slug = models.SlugField(verbose_name='Персональная ссылка', max_length=255, blank=True, unique=True)
bio = models.TextField(max_length=500, verbose_name='Информация о себе', blank=True)
avatar = models.ImageField(
verbose_name='Аватар профиля',
blank=True,
upload_to=ImageDirectorySave('images/avatars/'),
validators=[FileExtensionValidator(
allowed_extensions=('png', 'jpg', 'webp', 'jpeg'))
]
)
date_birthday = models.DateField(verbose_name='Дата рождения', blank=True, null=True)
following = models.ManyToManyField('self', verbose_name='Подписки', related_name='followers', symmetrical=False, blank=True)
class Meta:
"""
Сортировка, название модели в админ панели, таблица в данными
"""
ordering = ('user',)
verbose_name = 'Профиль'
verbose_name_plural = 'Профили пользователей'
db_table = 'app_profiles'
def save(self, *args, **kwargs):
"""
Сохранение параметров модели при их отсутствии заполнения
"""
if not self.slug:
self.slug = unique_slugify(self, self.user.username)
if self.slug:
self.slug = self.slug.lower()
super().save(*args, **kwargs)
def __str__(self):
"""
Возвращение имени пользователя
"""
return self.user.username
@property
def get_avatar(self):
"""
Получение аватара при отсутствии загруженного
"""
if not self.avatar:
return f'https://ui-avatars.com/api/?size=128&background=random&name={self.user.username}'
return self.avatar.url
@property
def get_age(self):
"""
Вычисление возраста пользователя
"""
return (date.today() - self.date_birthday) // timedelta(days=365.2425)
def get_absolute_url(self):
"""
Ссылка на профиль
"""
return reverse('profile', 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()
Идем в __init__.py, если вы используете структуру файлов как и у меня в уроках и туда импортируем модель:
from modules.system.models.abstract import AbstractBaseMeta
from modules.system.models.profiles import Profile
__all__ = (
'AbstractBaseMeta',
'Profile',
)
Создадим и применим миграции:
(venv) PS C:\Users\Razilator\Desktop\Courses\App\backend> python manage.py makemigrations
Migrations for 'system':
modules\system\migrations\0001_initial.py
- Create model Profile
(venv) PS C:\Users\Razilator\Desktop\Courses\App\backend> python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, blog, contenttypes, sessions, system
Running migrations:
Applying system.0001_initial... OK
Подключим профиль в админ панель:
modules/system/admin.py
from django.contrib import admin
from modules.system.models import Profile
admin.site.register(Profile)
Давайте создадим нашему суперпользователю профиль:
Отлично, профиль мы создали. Для последующих регистрирующихся пользователей профиль будет создаваться автоматически!
На этом урок я заканчиваю, в следующем уроке мы займемся выводом информации в шаблон, подключим urls.py и конечно создадим представление для профиля пользователя.