Django База [2023]: Профиль пользователя - представление, формы #17
Этот урок является второй частью работы с профилем пользователя в Django, в нем мы рассмотрим создание представления для профиля, создание форм, и возможность редактирования профиля.
Если вы хотите выразить благодарность автору сайта, статей и курса по Django, вы можете сделать это по ссылке ниже:
Первая часть урока - создание модели профиля, сигналов.
Напоминаю, что профиль мы создавали по второму методу с помощью связи один-к-одному.
Создание форм для редактирования профиля
Первым делом нам необходимо создать формы для профиля и для встроенной пользовательской модели Django.
Для этого в нашем приложении system создадим файл forms.py
В файле forms.py мы должны создать форму для пользователя и его профиля:
from django import forms
from django.contrib.auth.models import User
from .models import Profile
class UserUpdateForm(forms.ModelForm):
"""
Форма обновления данных пользователя
"""
class Meta:
model = User
fields = ('username', 'email', 'first_name', 'last_name')
def __init__(self, *args, **kwargs):
"""
Обновление стилей формы под bootstrap
"""
super().__init__(*args, **kwargs)
for field in self.fields:
self.fields[field].widget.attrs.update({
'class': 'form-control',
'autocomplete': 'off'
})
def clean_email(self):
"""
Проверка email на уникальность
"""
email = self.cleaned_data.get('email')
username = self.cleaned_data.get('username')
if email and User.objects.filter(email=email).exclude(username=username).exists():
raise forms.ValidationError('Email адрес должен быть уникальным')
return email
class ProfileUpdateForm(forms.ModelForm):
"""
Форма обновления данных профиля пользователя
"""
class Meta:
model = Profile
fields = ('slug', 'birth_date', 'bio', 'avatar')
def __init__(self, *args, **kwargs):
"""
Обновление стилей формы обновления
"""
super().__init__(*args, **kwargs)
for field in self.fields:
self.fields[field].widget.attrs.update({
'class': 'form-control',
'autocomplete': 'off'
})
В примере выше, мы создали формы от наших моделей User и Profile.
В классе Meta мы указываем model
- модель для обработки, а также fields
- наши поля.
В методе init мы переопределяем стили инпутов формы под Bootstrap. Конечно, есть ещё и другие способы добавления стилей в форму, но другие способы будут рассмотрены в отдельной статье.
В методе clean_email()
мы сделали возможность проверки email на уникальность присутствия в базе данных, если такой email уже используется, то мы запрещаем пользователю его устанавливать.
Создание представлений для просмотра профиля, редактирования профиля
Теперь перейдем к созданию необходимых представлений в файле views.py нашего приложения system.
Необходимый фрагмент кода, основанный на представления на основе классов:
from django.views.generic import DetailView, UpdateView
from django.db import transaction
from django.urls import reverse_lazy
from .models import Profile
from .forms import UserUpdateForm, ProfileUpdateForm
class ProfileDetailView(DetailView):
"""
Представление для просмотра профиля
"""
model = Profile
context_object_name = 'profile'
template_name = 'system/profile_detail.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['title'] = f'Страница пользователя: {self.object.user.username}'
return context
class ProfileUpdateView(UpdateView):
"""
Представление для редактирования профиля
"""
model = Profile
form_class = ProfileUpdateForm
template_name = 'system/profile_edit.html'
def get_object(self, queryset=None):
return self.request.user.profile
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['title'] = f'Редактирование профиля пользователя: {self.request.user.username}'
if self.request.POST:
context['user_form'] = UserUpdateForm(self.request.POST, instance=self.request.user)
else:
context['user_form'] = UserUpdateForm(instance=self.request.user)
return context
def form_valid(self, form):
context = self.get_context_data()
user_form = context['user_form']
with transaction.atomic():
if all([form.is_valid(), user_form.is_valid()]):
user_form.save()
form.save()
else:
context.update({'user_form': user_form})
return self.render_to_response(context)
return super(ProfileUpdateView, self).form_valid(form)
def get_success_url(self):
return reverse_lazy('profile_detail', kwargs={'slug': self.object.slug})
Для первого представления на основе классов мы использовали класс DetailView, для получения одного объекта.
- Задали свойство
context_object_name
, какprofile
для использования переменных в шаблоне. - Добавили контекст для заголовка
<title>Профиль пользователя: Razilator</title>
Для второго представления мы используем класс UpdateView для редактирования профиля. Мы импортируем и используем созданные формы из файла forms.py.
- В методе
get_object()
мы передаем текущего пользователя, чтобы не редактировать чужие профили. - В контексте мы добавляем форму пользователя, где ссылаемся на текущего пользователя.
- В методе
form_valid()
мы используемtransaction.atomic
, для корректного сохранения данных двух форм в нашей БД. - Проверяем обе формы на правильность, и сохраняем их.
- В методе
get_success_url()
мы ссылаемся на наш профиль, т.е после сохранения мы переходим на страницу нашего профиля.
Далее нам необходимо указать обработку представлений в файле urls.py, для этого мы его создадим в приложении system и добавим следующий код:
from django.urls import path
from .views import ProfileUpdateView, ProfileDetailView
urlpatterns = [
path('user/edit/', ProfileUpdateView.as_view(), name='profile_edit'),
path('user/<str:slug>/', ProfileDetailView.as_view(), name='profile_detail'),
]
И подключим в основном файле urls.py обработку ссылок из приложения system.
"""backend URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/4.1/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path, include
from django.conf.urls.static import static
from django.conf import settings
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('modules.blog.urls')),
path('', include('modules.system.urls')),
]
if settings.DEBUG:
urlpatterns = [path('__debug__/', include('debug_toolbar.urls'))] + urlpatterns
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
Отлично. Перейдем к созданию шаблонов.
Создание шаблонов profile_detail.html и profile_edit.html
Для этого создадим в папке templates папку system, а в ней два файла profile_detail.html и profile_edit.html.
Заполняем profile_detail.html следующей HTML разметкой как пример, на Bootstrap:
{% extends 'main.html' %}
{% block content %}
<div class="card border-0">
<div class="card-body">
<div class="row">
<div class="col-md-3">
<figure>
<img src="{{ profile.avatar.url }}" class="img-fluid rounded-0" alt="{{ profile }}">
</figure>
</div>
<div class="col-md-9">
<h5 class="card-title">
{{ profile }}
</h5>
<div class="card-text">
<ul>
<li>Никнейм: {{ profile.user.username }}</li>
{% if profile.user.get_full_name %} <li>Имя и фамилия: {{ profile.user.get_full_name }}</li> {% endif %}
<li>Заходил: {{ profile.user.last_login }}</li>
<li>Дата рождения: {{ profile.birth_date }}</li>
<li>О себе: {{ profile.bio }}</li>
</ul>
<a href="{% url 'profile_edit' %}" class="btn btn-sm btn-primary">Редактировать профиль</a>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
И шаблон для редактирования профиля:
{% extends 'main.html' %}
{% block content %}
<div class="card mb-3 border-0 nth-shadow">
<div class="card-body">
<div class="card-title nth-card-title">
<h4>Изменение профиля</h4>
</div>
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ user_form.as_p }}
{{ form.as_p }}
<div class="d-grid gap-2 d-md-block mt-2">
<button type="submit" class="btn btn-dark">Подтвердить изменение профиля</button>
</div>
</form>
</div>
</div>
{% endblock %}
Осталось лишь проверить проделанную работу. Переходим на страницу своего пользователя, в моем случае это выглядит следующим образом: http://127.0.0.1:8000/user/razilator/
Отлично, профиль отображается. Давайте отредактируем его, нажав на нашу кнопку редактировать профиль:
Давайте изменим аватарку пользователя, загружу свою произвольную картинку, а также добавлю имя и фамилию:
Отлично, профиль сохраняется без каких либо проблем, имя фамилия добавились и обновился аватар пользователя.
Оптимизируем SQL запросы (дополнительно)
Ещё бы мне хотелось оптимизировать SQL запросы, в одном из уроков я уже это показывал, как делать, тут покажу способ не создавая менеджер модели, а оптимизирую SQL во views.py
class ProfileDetailView(DetailView):
"""
Представление для просмотра профиля
"""
model = Profile
context_object_name = 'profile'
template_name = 'system/profile_detail.html'
queryset = model.objects.all().select_related('user')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['title'] = f'Страница пользователя: {self.object.user.username}'
return context
Проверяем в деле:
На этом урок с профилем Django закончен, можно двигаться дальше.