Профили пользователей: Представления и формы
Страница профиля
modules/system/views.py:
from django.views.generic import DetailView
from .models import Profile
class ProfileDetailView(DetailView):
model = Profile
context_object_name = "profile"
template_name = "system/profile_detail.html"
queryset = Profile.objects.select_related("user")
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["title"] = f"Профиль пользователя: {self.object.user.username}"
return context
# modules/system/urls.py
urlpatterns = [
path("user/edit/", ProfileUpdateView.as_view(), name="profile_edit"),
path("user/<str:slug>/", ProfileDetailView.as_view(), name="profile_detail"),
]
Маршрут user/edit/ стоит выше user/<str:slug>/ — иначе слово «edit» воспримется как слаг.
templates/system/profile_detail.html:
{% extends "main.html" %}
{% block content %}
<div class="card mb-3">
<div class="row g-0">
<div class="col-md-3">
<img src="{{ profile.avatar.url }}" class="img-fluid rounded-start" alt="{{ profile.user.username }}">
</div>
<div class="col-md-9">
<div class="card-body">
<h5 class="card-title">{{ profile.user.get_full_name|default:profile.user.username }}</h5>
{% if profile.age %}<p>Возраст: {{ profile.age }}</p>{% endif %}
<p class="card-text">{{ profile.bio|linebreaksbr }}</p>
{% if request.user == profile.user %}
<a href="{% url 'profile_edit' %}" class="btn btn-sm btn-primary">Редактировать</a>
{% endif %}
</div>
</div>
</div>
</div>
{% endblock %}
Формы
modules/system/forms.py:
from django import forms
from django.contrib.auth import get_user_model
from .models import Profile
User = get_user_model()
class UserUpdateForm(forms.ModelForm):
class Meta:
model = User
fields = ("username", "email", "first_name", "last_name")
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"})
def clean_email(self):
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")
widgets = {"birth_date": forms.DateInput(attrs={"type": "date"}, format="%Y-%m-%d")}
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"})
Циклом в __init__ добавляем класс Bootstrap form-control всем полям.
Редактирование: UpdateView с двумя формами
from django.contrib.auth.mixins import LoginRequiredMixin
from django.db import transaction
from django.urls import reverse_lazy
from django.views.generic import UpdateView
from .forms import ProfileUpdateForm, UserUpdateForm
class ProfileUpdateView(LoginRequiredMixin, 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().form_valid(form)
def get_success_url(self):
return reverse_lazy("profile_detail", kwargs={"slug": self.object.slug})
Разбор:
get_object()возвращает профиль текущего пользователя — чужой профиль отредактировать невозможно, адрес не содержит слага.- Основная форма (
form_class) — профиль; вторую форму (пользователь) создаём вget_context_data. UpdateViewсам передаёт в основную формуrequest.FILES— аватар загрузится.transaction.atomic()— обе формы сохраняются вместе или не сохраняются вовсе.get_success_url()— куда перейти после сохранения.
templates/system/profile_edit.html:
{% extends "main.html" %}
{% block content %}
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ user_form.as_p }}
{{ form.as_p }}
<button type="submit" class="btn btn-primary">Сохранить</button>
</form>
{% endblock %}
Итог
DetailViewдля страницы профиля по слагу,select_related("user").UpdateView+get_object()→ пользователь редактирует только себя.- Две формы в одной view: вторая — в
get_context_data, сохранение вform_validвнутриtransaction.atomic(). - Стили Bootstrap для всех полей — в
__init__формы.
Практика решено 0 из 5
Напишите ProfileDetailView (DetailView): модель Profile, шаблон system/profile_detail.html,
имя в контексте profile; queryset оптимизируйте select_related("user").
class ProfileDetailView(DetailView):
model = Profile
context_object_name = "profile"
template_name = "system/profile_detail.html"
queryset = Profile.objects.select_related("user")
В ProfileUpdateView переопределите get_object, чтобы пользователь всегда редактировал свой профиль:
верните self.request.user.profile.
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
Зачем сохранять две формы (User и Profile) внутри transaction.atomic()?
Напишите форму UserUpdateForm (ModelForm для User) с полями username, email, first_name, last_name
и методом clean_email, который запрещает email, уже занятый другим пользователем
(User.objects.filter(email=email).exclude(username=username).exists()).
class UserUpdateForm(forms.ModelForm):
class Meta:
model = User
fields = ("username", "email", "first_name", "last_name")
def clean_email(self):
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
Что нужно, чтобы форма загрузки аватара заработала в UpdateView?
Войдите, чтобы отмечать пройденные уроки.