Django 35.2. Расширенный профиль пользователя - форма и шаблон
В этом посте продолжим писать расширенный профиль пользователя, а именно сделаем формы, отредактируем представление и перепишем шаблоны.
Дополнительные материалы
Для скачивания материалов необходимо войти или зарегистрироваться
Файлы также можно получить в Telegram-боте по коду: 206476
Реклама
Мы написали модель и сделали сигнал о регистрации.
Теперь займёмся расширенной формой и доработкой шаблона.
Форма модели профиля.
В форме пропишем следующие поля: gender
, dob
, site_link
, telegram_link
, user_avatar
, show_last_name
, show_email
, show_telegram
.
В целом класс формы не отличается от других ранее созданных нами форм.
Описываем поле, указываем его тип, прописываем название поля и прописываем CSS-класс, также прописываем, что все поля необязательны к вводу.
Опишу только новые или отличающиеся поля:
gender
- поле выбораChoiceField
. В аргументах указываем название поля, доступные выборы параметромchoices
и виджетSelect
.dob
- поле датыDateField
. Указываем название поля и виджетDateInput
. В аргументах к виджету передаём параметрformat
. С этим связана забавная особенность. В БД дата хранится в видеГГГГ-ММ-ДД
, однако в нашей локали принята записьДД.ММ.ГГГГ
. В шаблон оно передаётся в нашем виде, но HTML-форма даты принимает только в форматеГГГГ-ММ-ДД
, при этом отображает в формате пользовательской локали. Сложно и не понятно. Для решения этой ситуации указываем параметрformat='%Y-%m-%d'
. Также в параметрattrs
, помимо класса стиля, передаём тип формы'type': 'date'
, т.к. по умолчаниюDateInput
это просто текстовое поле.user_avatar
- поле загрузки файловFileField
. В атрибутах название поля и виджетFileInput
. В параметреattrs
виджета, кроме класса, указываем тип поля'type': 'file'
.show_last_name
,show_email
,show_telegram
- логические поляBooleanField
. Виджет поляCheckboxInput
.
Ниже класс Meta
с указанием модели ProfileModel
и перечислением полей.
Код формы:
class ProfileForm(forms.ModelForm):
gender = forms.ChoiceField(
label='Пол',
choices=models.ProfileModel.Genders.choices,
required=False,
widget=forms.Select(
attrs={
'class': 'form-select',
}
)
)
dob = forms.DateField(
label='Дата рождения',
required=False,
widget=forms.DateInput(
format='%Y-%m-%d',
attrs={
'type': 'date',
'class': 'form-control',
}
)
)
site_link = forms.CharField(
label='Личный сайт',
required=False,
widget=forms.TextInput(
attrs={
'class': 'form-control',
'placeholder': 'https://...'
}
)
)
telegram_link = forms.CharField(
label='Профиль в Telegram',
required=False,
widget=forms.TextInput(
attrs={
'class': 'form-control',
'placeholder': 'https://t.me/...'
}
)
)
user_avatar = forms.FileField(
label='Аватарка',
required=False,
widget=forms.FileInput(
attrs={
'type': 'file',
'class': 'form-control',
}
)
)
show_last_name = forms.BooleanField(
required=False,
widget=forms.CheckboxInput(
attrs={
'class': 'form-check-input mt-0',
}
)
)
show_email = forms.BooleanField(
required=False,
widget=forms.CheckboxInput(
attrs={
'class': 'form-check-input mt-0',
}
)
)
show_telegram = forms.BooleanField(
required=False,
widget=forms.CheckboxInput(
attrs={
'class': 'form-check-input mt-0'
}
)
)
class Meta:
model = models.ProfileModel
fields = ['gender', 'dob', 'site_link', 'telegram_link', 'user_avatar', 'show_last_name', 'show_email',
'show_telegram']
Изменение представления страницы настроек профиля.
Для добавления новой формы нам надо добавить несколько строк.
В метод get_context_data
добавляем строку с передачей новой формы в шаблон, передавая в качестве аргумента объект профиля:
context['user_profile_form'] = forms.ProfileForm(instance=self.request.user.profile)
В методе post
, в первом условии if 'user_info_form' in request.POST:
, добавляем новую переменную формы, передавая в неё данные из POST-запроса, файл из POST-запроса и объект профиля.
user_profile_form = forms.ProfileForm(request.POST, request.FILES, instance=self.request.user.profile)
Далее в проверку на валидность, добавляем проверку и второй формы:
if user_info_form.is_valid() and user_profile_form.is_valid():
После чего, если обе формы валидны, сохраняем объекты с новыми данными:
user_info_form.save()
user_profile_form.save()
Если одна или обе формы не валидны, возвращаем формы с введёнными данными:
context['user_info_form'] = user_info_form
context['user_profile_form'] = user_profile_form
Код изменённых методов:
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['user_info_form'] = forms.UserInfoForm(instance=self.request.user)
context['user_profile_form'] = forms.ProfileForm(instance=self.request.user.profile)
context['user_password_form'] = forms.UserPasswordForm(self.request.user)
context['title'] = f'Настройки профиля {self.request.user}'
return context
def post(self, request, *args, **kwargs):
if 'user_info_form' in request.POST:
user_info_form = forms.UserInfoForm(request.POST, instance=request.user)
user_profile_form = forms.ProfileForm(request.POST, request.FILES, instance=self.request.user.profile)
if user_info_form.is_valid() and user_profile_form.is_valid():
user_info_form.save()
user_profile_form.save()
messages.success(request, 'Данные успешно изменены.')
return redirect('user_app:user_profile_settings', user_info_form.cleaned_data.get('username'))
else:
context = self.get_context_data(**kwargs)
context['user_info_form'] = user_info_form
context['user_profile_form'] = user_profile_form
return render(request, self.template_name, context)
elif 'user_password_form' in request.POST:
form = forms.UserPasswordForm(request.user, request.POST)
if form.is_valid():
form.save()
messages.success(request, 'Пароль успешно изменён.')
return self.get(request, *args, **kwargs)
else:
context = self.get_context_data(**kwargs)
context['user_password_form'] = form
return render(request, self.template_name, context)
else:
return self.get(request, *args, **kwargs)
Шаблон страницы настроек:
Тут мы объединяем обе формы в одном теге form
. Дополнительным аргументом указываем enctype="multipart/form-data"
, сообщая, что эта форма может принимать файлы.
Далее, можно по стандарту вывести обе формы:
{% csrf_token %}
{{ user_info_form.as_p }}
{{ user_profile_form.as_p }}
Но я для лучшего отображения применил классы Bootstrap. Для этого пришлось каждое поле выводить по отдельности.
Код шаблона:
{% extends 'blog/base.html' %}
{% block title %}{{ title }}{% endblock %}
{% block content %}
<div class="container mt-3">
<div class="row d-flex justify-content-between align-items-center">
<div class="col-lg-10 col-sm-12">
<h2>Настройки профиля {{ user }}</h2>
</div>
<div class="col-lg-2 col-sm-12 text-right">
<a class="btn btn-primary my-btn h-100"
href="{% url 'user_app:user_profile' username=user.username %}">Вернуться в профиль</a>
</div>
</div>
<hr>
<div class="row d-flex justify-content-evenly">
<div class="row text-center">
{% if messages %}
<p class="alert alert-success">
{% for message in messages %}
{{ message }}<br>
{% endfor %}
</p>
{% endif %}
</div>
<div class="col-lg-8 col-sm-12">
<form method="post" enctype="multipart/form-data">
<div class="row">
<div class="col-lg-6 col-sm-12">
<h3 class="mb-3 mt-3">Основная информация:</h3>
{% csrf_token %}
{% for error in user_info_form.errors.values %}
{{ error }}
{% endfor %}
{{ user_info_form.username.label }}
{{ user_info_form.username }}
<br>
{{ user_info_form.email.label }}
<div class="input-group">
<div class="input-group-text">
{{ user_profile_form.show_email }}
<span class="ms-2">Отображать?</span>
</div>
{{ user_info_form.email }}
</div>
<br>
{{ user_info_form.first_name.label }}
{{ user_info_form.first_name }}
<br>
{{ user_info_form.last_name.label }}
<div class="input-group">
<div class="input-group-text">
{{ user_profile_form.show_last_name }}
<span class="ms-2">Отображать?</span>
</div>
{{ user_info_form.last_name }}
</div>
</div>
<div class="col-lg-6 col-sm-12">
<h3 class="mb-3 mt-3">Дополнительная информация:</h3>
{% for error in user_profile_form.errors.values %}
{{ error }}
{% endfor %}
{{ user_profile_form.gender.label }}
{{ user_profile_form.gender }}
<br>
{{ user_profile_form.dob.label }}
{{ user_profile_form.dob }}
<br>
{{ user_profile_form.site_link.label }}
{{ user_profile_form.site_link }}
<br>
{{ user_profile_form.telegram_link.label }}
<div class="input-group">
<div class="input-group-text">
{{ user_profile_form.show_telegram }}
<span class="ms-2">Отображать?</span>
</div>
{{ user_profile_form.telegram_link }}
</div>
<br>
{{ user_profile_form.user_avatar.label }}
{{ user_profile_form.user_avatar }}<br>
</div>
</div>
<div class="row">
<div class="col-12 text-center">
<button class="btn btn-primary my-btn text-center mt-1" type="submit" name="user_info_form">
Отправить
</button>
</div>
</div>
</form>
</div>
<div class="col-lg-4 col-sm-12">
<h3 class="mb-3 mt-3">Изменить пароль:</h3>
<form method="post">
{% csrf_token %}
{{ user_password_form.as_p }}
<div class="row">
<div class="col-12 text-center">
<button class="btn btn-primary my-btn mt-3" type="submit" name="user_password_form">
Отправить
</button>
</div>
</div>
</form>
</div>
</div>
</div>
{% endblock %}
Шаблон страницы профиля.
Тут почти стандартно, отображаем нужные поля профиля с проверкой на наличие данных в них:
<div class="col-xl-4 col-sm-12 border border-start border-0Z">
<h3 class="mb-3 text-center">Информация:</h3>
<div class="col-12 text-center">
<img src="{{ user_profile.profile.user_avatar.url }}" alt="{{ user_profile }}" width="300"
height="300" class="img-fluid"><br>
{% for group in user_profile.groups.all %}
<span class="badge border mt-3 lead">{{ group.name }}</span>
{% endfor %}
</div>
<div class="col-12 ms-3 mt-4">
<b>Имя пользователя:</b> {{ user_profile }}
<hr class="mb-0">
{% if user_profile.first_name %}
<br>
<b>Имя:</b> {{ user_profile.first_name }}
{% endif %}
{% if user_profile.last_name and user_profile.profile.show_last_name %}
<br>
<b>Фамилия:</b> {{ user_profile.last_name }}
{% endif %}
<br>
<b>Пол:</b> {{ user_profile.profile.get_gender_display }}
{% if user_profile.profile.dob %}
<br>
<b>Возраст:</b> {{ user_profile.profile.get_age }}
{% endif %}
<br>
<b>На сайте:</b> {{ user_profile.profile.get_on_site }}
<hr class="mb-0">
{% if user_profile.profile.show_email %}
<br>
<b>Email:</b> {{ user_profile.email }}
{% endif %}
{% if user_profile.profile.show_telegram %}
<br>
<b>Ссылка на Telegram:</b> <a
href="{{ user_profile.profile.telegram_link }}">{{ user_profile.profile.get_telegram_username }}</a>
{% endif %}
{% if user_profile.profile.site_link %}
<br>
<b>Личный сайт:</b> {{ user_profile.profile.site_link }}
{% endif %}
</div>
<div class="col-12 text-center mt-5">
{% if user_profile user %}
<a class="btn btn-primary my-btn"
href="{% url 'user_app:user_profile_settings' username=user_profile.username %}">Настройки
профиля</a>
{% endif %}
</div>
</div>
На этом всё. У нас теперь есть расширенный профиль пользователя с необходимыми полями. Вероятно, в будущем он будет изменяться или расширяться. Следующим шагом будет создание своего расширенного пользователя, но этим мы займёмся в одном из следующих постов.
Все статьи