Django 5 для начинающих

Прогресс по курсу:  9/1004

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

В этом шаге мы создадим и отобразим форму, в которой пользователи смогут обновлять свой профиль.

Для создания форм мы будем использовать ModelForm. Она позволяет нам создавать формы, которые взаимодействуют с определенной моделью внутри базы данных.

Для этого в нашем приложении accounts создадим файл forms.py. И добавим в него следующий код:

from django import forms
from django.contrib.auth.models import User

from .models import Profile


class UserUpdateForm(forms.ModelForm):
    """
    Форма обновления данных пользователя
    """
    username = forms.CharField(max_length=100,
                               widget=forms.TextInput(
                                   attrs={"class": "form-control mb-1"}))
    email = forms.EmailField(widget=forms.TextInput(attrs={"class": "form-control mb-1"}))
    first_name = forms.CharField(max_length=100,
                                 widget=forms.TextInput(attrs={"class": "form-control mb-1"}))
    last_name = forms.CharField(max_length=100,
                                widget=forms.TextInput(attrs={"class": "form-control mb-1"}))

    class Meta:
        model = User
        fields = ('username', 'email', 'first_name', 'last_name')

    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):
    """
    Форма обновления данных профиля пользователя
    """
    slug = forms.CharField(max_length=100,
                           widget=forms.TextInput(
                               attrs={"class": "form-control mb-1"}))
    birth_date = forms.DateField(
        widget=forms.TextInput(attrs={"class": "form-control mb-1"}))
    bio = forms.CharField(max_length=500,
                          widget=forms.Textarea(attrs={'rows': 5, "class": "form-control mb-1"}))

    avatar = forms.ImageField(widget=forms.FileInput(attrs={"class": "form-control mb-1"}))

    class Meta:
        model = Profile
        fields = ('slug', 'birth_date', 'bio', 'avatar')

UserUpdateForm взаимодействует с моделью пользователя, позволяя пользователям обновлять свое имя пользователя и адрес электронной почты, а также имя и фамилию.

ProfileUpdateForm взаимодействует с моделью профиля, позволяя пользователям обновлять свой профиль.

В классе Meta мы указываем model - модель для обработки, а также fields - наши поля.

В методе clean_email() мы сделали возможность проверки email на уникальность присутствия в базе данных, если такой email уже используется, то мы запрещаем пользователю его устанавливать.

 

Создание представлений для просмотра профиля, редактирования профиля

Теперь перейдем к созданию необходимых представлений в файле views.py нашего приложения accounts.

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 = 'accounts/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 = 'accounts/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 для использования переменных в шаблоне.
  • Добавили контекст для заголовка страницы f'Профиль пользователя: {self.object.user.username}'.


Для второго представления мы используем класс UpdateView для редактирования профиля. Мы импортируем и используем созданные формы из файла forms.py.

  • В методе get_object() мы передаем текущего пользователя, чтобы не редактировать чужие профили.
  • В контексте мы добавляем форму пользователя, где ссылаемся на текущего пользователя.
  • В методе form_valid() мы используем transaction.atomic, для корректного сохранения данных двух форм в нашей БД.
  • Проверяем обе формы на правильность, и сохраняем их.
  • В методе get_success_url() мы ссылаемся на наш профиль, т.е после сохранения мы переходим на страницу нашего профиля.

Далее нам необходимо указать обработку представлений в файле urls.py, для этого мы его создадим в приложении accounts и добавим следующий код:

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 обработку ссылок из приложения accounts

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('apps.blog.urls')),
    path('', include('apps.accounts.urls')),

]

if settings.DEBUG:
    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
    urlpatterns += [path('__debug__/', include('debug_toolbar.urls'))]

  • Комментария
Будьте вежливы и соблюдайте наши принципы сообщества. Пожалуйста, не оставляйте решения и подсказки в комментариях, для этого есть отдельный форум.
Оставить комментарий

Наткнулся в документации на интересную фишку, предлагаю рефакторинг форм:

from django import forms
from django.contrib.auth.models import User

from .models import Profile

UPDATE_FORM_WIDGET = forms.TextInput(attrs={'class': 'form-control mb-1'})


class UserUpdateForm(forms.ModelForm):
    class Meta:
        model = User
        fields = ('username', 'email', 'first_name', 'last_name')
        widgets = {
            'username': UPDATE_FORM_WIDGET,
            'email': UPDATE_FORM_WIDGET,
            'first_name': UPDATE_FORM_WIDGET,
            'last_name': UPDATE_FORM_WIDGET
        }

    def clean_email(self): ...


class ProfileUpdateForm(forms.ModelForm):
    class Meta:
        model = Profile
        fields = ('slug', 'birth_date', 'bio', 'avatar')
        widgets = {
            'slug': UPDATE_FORM_WIDGET,
            'birth_date': UPDATE_FORM_WIDGET,
            'bio': forms.Textarea(
                attrs={'rows': 5, "class": "form-control mb-1"}
            ),
            'avatar': forms.FileInput(attrs={"class": "form-control mb-1"})
        }

1) context['title'] = f'Страница пользователя: {self.object.user.username}'

2) Добавили контекст для заголовка <title>Профиль пользователя: admin</title>

@Aleksandr_Gurov, спасибо, исправил.