Профили пользователей: Представления и формы

⏱ 30 мин · 🧩 заданий: 5

Страница профиля

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

Вы не вошли — ответы проверяются, но прогресс не сохранится. Войти или зарегистрироваться.
Задание 1 Написать код

Напишите ProfileDetailView (DetailView): модель Profile, шаблон system/profile_detail.html, имя в контексте profile; queryset оптимизируйте select_related("user").

Задание 2 Написать код

В ProfileUpdateView переопределите get_object, чтобы пользователь всегда редактировал свой профиль: верните self.request.user.profile.

Задание 3 Один вариант ответа

Зачем сохранять две формы (User и Profile) внутри transaction.atomic()?

Задание 4 Написать код

Напишите форму UserUpdateForm (ModelForm для User) с полями username, email, first_name, last_name и методом clean_email, который запрещает email, уже занятый другим пользователем (User.objects.filter(email=email).exclude(username=username).exists()).

Задание 5 Несколько вариантов

Что нужно, чтобы форма загрузки аватара заработала в UpdateView?

Войдите, чтобы отмечать пройденные уроки.