Django База [2023]: Регистрация и авторизация пользователя #20
Django

Django База [2023]: Регистрация и авторизация пользователя #20

Razilator

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

Создаем форму регистрации

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

Первый фрагмент кода - форма регистрации.

modules/system/forms.py
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User


class UserRegisterForm(UserCreationForm):
    """
    Переопределенная форма регистрации пользователей
    """

    class Meta(UserCreationForm.Meta):
        fields = UserCreationForm.Meta.fields + ('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

    def __init__(self, *args, **kwargs):
        """
        Обновление стилей формы регистрации
        """
        super().__init__(*args, **kwargs)
        for field in self.fields:
            self.fields['username'].widget.attrs.update({"placeholder": 'Придумайте свой логин'})
            self.fields['email'].widget.attrs.update({"placeholder": 'Введите свой email'})
            self.fields['first_name'].widget.attrs.update({"placeholder": 'Ваше имя'})
            self.fields["last_name"].widget.attrs.update({"placeholder": 'Ваша фамилия'})
            self.fields['password1'].widget.attrs.update({"placeholder": 'Придумайте свой пароль'})
            self.fields['password2'].widget.attrs.update({"placeholder": 'Повторите придуманный пароль'})
            self.fields[field].widget.attrs.update({"class": "form-control", "autocomplete": "off"})

Так как встроенная система авторизации и регистрации нас устраивает, то можно воспользоваться переопределением форм. К переопределенной форме мы добавили метод проверки clean_email на уникальность email адреса, также добавили поля для ввода при регистрации: email, first_name, last_name. Стили используем для bootstrap.

Создаем форму авторизации

Отлично, теперь ниже создадим форму для авторизации. Код по-отдельности, поэтому после этих двух форм я скину полный код нашего forms.py, который мы создавали на основе предыдущих уроков.

modules/system/forms.py
from django.contrib.auth.forms import AuthenticationForm

class UserLoginForm(AuthenticationForm):
    """
    Форма авторизации на сайте
    """

    def __init__(self, *args, **kwargs):
        """
        Обновление стилей формы регистрации
        """
        super().__init__(*args, **kwargs)
        for field in self.fields:
            self.fields['username'].widget.attrs['placeholder'] = 'Логин пользователя'
            self.fields['password'].widget.attrs['placeholder'] = 'Пароль пользователя'
            self.fields['username'].label = 'Логин'
            self.fields[field].widget.attrs.update({
                'class': 'form-control',
                'autocomplete': 'off'
            })

В примере выше мы наследуемся от существующей формы авторизации, добавив к ней стили от boostrap, и некоторые placeholder.

Полный файл forms.py

modules/system/forms.py
from django import forms
from django.contrib.auth.models import User
from django.contrib.auth.forms import UserCreationForm, AuthenticationForm

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'
            })


class UserRegisterForm(UserCreationForm):
    """
    Переопределенная форма регистрации пользователей
    """

    class Meta(UserCreationForm.Meta):
        fields = UserCreationForm.Meta.fields + ('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

    def __init__(self, *args, **kwargs):
        """
        Обновление стилей формы регистрации
        """
        super().__init__(*args, **kwargs)
        for field in self.fields:
            self.fields['username'].widget.attrs.update({"placeholder": 'Придумайте свой логин'})
            self.fields['email'].widget.attrs.update({"placeholder": 'Введите свой email'})
            self.fields['first_name'].widget.attrs.update({"placeholder": 'Ваше имя'})
            self.fields["last_name"].widget.attrs.update({"placeholder": 'Ваша фамилия'})
            self.fields['password1'].widget.attrs.update({"placeholder": 'Придумайте свой пароль'})
            self.fields['password2'].widget.attrs.update({"placeholder": 'Повторите придуманный пароль'})
            self.fields[field].widget.attrs.update({"class": "form-control", "autocomplete": "off"})


class UserLoginForm(AuthenticationForm):
    """
    Форма авторизации на сайте
    """

    def __init__(self, *args, **kwargs):
        """
        Обновление стилей формы регистрации
        """
        super().__init__(*args, **kwargs)
        for field in self.fields:
            self.fields['username'].widget.attrs['placeholder'] = 'Логин пользователя'
            self.fields['password'].widget.attrs['placeholder'] = 'Пароль пользователя'
            self.fields['username'].label = 'Логин'
            self.fields[field].widget.attrs.update({
                'class': 'form-control',
                'autocomplete': 'off'
            })

Представление для регистрации пользователя

Переходим к представлениям, в файле views.py нашего приложения system добавим следующий код для представления регистрации пользователя:

modules/system/views.py
from django.views.generic import CreateView
from django.urls import reverse_lazy
from django.contrib.messages.views import SuccessMessageMixin

from .forms import UserRegisterForm


class UserRegisterView(SuccessMessageMixin, CreateView):
    """
    Представление регистрации на сайте с формой регистрации
    """
    form_class = UserRegisterForm
    success_url = reverse_lazy('home')
    template_name = 'system/user_register.html'
    success_message = 'Вы успешно зарегистрировались. Можете войти на сайт!'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['title'] = 'Регистрация на сайте'
        return context

В примере выше мы используем миксин (урок с миксинами ) и представление CreateView (урок с CRUD запросами), также импортируем нашу созданную форму из файла forms.py.

Представление для авторизации пользователя

Теперь создаем представление для авторизации, также во views.py:

modules/system/views.py
from django.contrib.messages.views import SuccessMessageMixin
from django.contrib.auth.views import LoginView

from .forms UserLoginForm


class UserLoginView(SuccessMessageMixin, LoginView):
    """
    Авторизация на сайте
    """
    form_class = UserLoginForm
    template_name = 'system/user_login.html'
    next_page = 'home'
    success_message = 'Добро пожаловать на сайт!'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['title'] = 'Авторизация на сайте'
        return context

В данном примере мы также используем миксин, но наследуемся уже от готового класса представления LoginView.

Представление для деавторизации пользователя

Это самая мелочь, но необходимая часть каждого проекта с системой авторизации. Также в файл views.py после других представлений добавим следующий фрагмент кода:

system/views.py
from django.contrib.auth.views import LogoutView
    
class UserLogoutView(LogoutView):
    """
    Выход с сайта
    """
    next_page = 'home'

В примере выше мы наследуемся от встроенного представлений LogoutView и переправляем пользователя на домашнюю страницу.

Полный файл views.py на основе всех предыдущих уроков

И весь файл views.py основанный на предыдущих уроках, включаяя изменения из этого урока:

system/views.py
from django.views.generic import DetailView, UpdateView, CreateView
from django.db import transaction
from django.urls import reverse_lazy
from django.contrib.messages.views import SuccessMessageMixin
from django.contrib.auth.views import LoginView, LogoutView

from .models import Profile
from .forms import UserUpdateForm, ProfileUpdateForm, UserRegisterForm, UserLoginForm


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


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})
    


class UserRegisterView(SuccessMessageMixin, CreateView):
    """
    Представление регистрации на сайте с формой регистрации
    """
    form_class = UserRegisterForm
    success_url = reverse_lazy('home')
    template_name = 'system/user_register.html'
    success_message = 'Вы успешно зарегистрировались. Можете войти на сайт!'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['title'] = 'Регистрация на сайте'
        return context
    

class UserLoginView(SuccessMessageMixin, LoginView):
    """
    Авторизация на сайте
    """
    form_class = UserLoginForm
    template_name = 'system/user_login.html'
    next_page = 'home'
    success_message = 'Добро пожаловать на сайт!'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['title'] = 'Авторизация на сайте'
        return context
        

class UserLogoutView(LogoutView):
    """
    Выход с сайта
    """
    next_page = 'home'

Настройка urls.py для представлений

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

system/urls.py
from django.urls import path

from .views import ProfileUpdateView, ProfileDetailView, UserRegisterView, UserLoginView, UserLogoutView

urlpatterns = [
    path('user/edit/', ProfileUpdateView.as_view(), name='profile_edit'),
    path('user/<str:slug>/', ProfileDetailView.as_view(), name='profile_detail'),
    path('register/', UserRegisterView.as_view(), name='register'),
    path('login/', UserLoginView.as_view(), name='login'),
    path('logout/', UserLogoutView.as_view(), name='logout'),
]

Осталось лишь одно, создать шаблоны и проверить в деле.

Создание шаблонов для регистрации и авторизации

В папке templates/system создаем два файла: user_register.html и user_login.html

В файл user_register.html добавляем следующую HTML разметку:

templates/system/user_register.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" action="{% url 'register' %}" enctype="multipart/form-data">
                {% csrf_token %}
                {{ form.as_p }}
                <button type="submit" class="btn btn-dark mt-2">Зарегистрироваться</button>
            </form>
        </div>
    </div>
{% endblock %}

А в файл user_login.html следующий фрагмент:

templates/system/user_login.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" action="{% url 'login' %}">
                {% csrf_token %}
                {{ 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 %}

Для деавторизации мы не создаем никаких шаблонов.

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

Теперь проверим, как все у нас работает. Для этого я разлогинюсь из аккаунта и попробую зарегистрировать новый аккаунт и авторизоваться с помощью него.

Регистрируемся
Регистрируемся
Успешная регистрация
Успешная регистрация
Авторизация
Авторизация
Успешная авторизация
Успешная авторизация
Наш профиль, созданный при регистрации
Наш профиль, созданный при регистрации

Отлично, у нас получилось все, как нужно. Надеюсь и у вас все получилось!

;