Django База [2023]: Профиль пользователя - представление, формы #17
Django

Django База [2023]: Профиль пользователя - представление, формы #17

Razilator

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

Первая часть урока - создание модели профиля, сигналов.

Напоминаю, что профиль мы создавали по второму методу с помощью связи один-к-одному.

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

Первым делом нам необходимо создать формы для профиля и для встроенной пользовательской модели Django.

Для этого в нашем приложении system создадим файл forms.py

В файле forms.py мы должны создать форму для пользователя и его профиля:

modules/system/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.

Необходимый фрагмент кода, основанный на представления на основе классов:

modules/system/views.py
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 и добавим следующий код:

modules/system/urls.py
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/urls.py
"""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:

templates/system/profile_detail.html
{% 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 %}

И шаблон для редактирования профиля:

templates/system/profile_edit.html
{% 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 запросы

Ещё бы мне хотелось оптимизировать SQL запросы, в одном из уроков я уже это показывал, как делать, тут покажу способ не создавая менеджер модели, а оптимизирую SQL во views.py

modules/system/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

Проверяем в деле:

Отлично, SQL стало на один меньше!
Отлично, SQL стало на один меньше!

На этом урок с профилем Django закончен, можно двигаться дальше.

;