Django База [2023]: Права доступа с помощью миксинов в представлениях #19
Django

Django База [2023]: Права доступа с помощью миксинов в представлениях #19

Razilator

В этой статье мы рассмотрим использование миксинов в работе с представлениями Django. Мы реализуем возможность редактировать статью только для авторов статьи и админов сайта.

Стандартные миксины Django

Для безопасности проекта мы можем использовать встроенные миксины, которые запрещают неавторизованным пользователям добавлять материал. На этот случай в Django существует миксин LoginRequiredMixin, который дает возможность добавлять материалы только после авторизации пользователя на сайте.

В нашем проекте в одном из уроков мы реализовали добавление статьи с помощью представления CreateView. Давайте наше представление дополним, добавив в него миксин LoginRequiredMixin.

blog/views.py
from django.views.generic import CreateView
from django.contrib.auth.mixins import LoginRequiredMixin

from .models import Article, Category


class ArticleCreateView(LoginRequiredMixin, CreateView):
    """
    Представление: создание материалов на сайте
    """
    model = Article
    template_name = 'blog/articles_create.html'
    form_class = ArticleCreateForm
    login_url = 'home'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['title'] = 'Добавление статьи на сайт'
        return context

    def form_valid(self, form):
        form.instance.author = self.request.user
        form.save()
        return super().form_valid(form)

По-сути, мы подмешали миксин к классу CreateView, дополнив код представления как под капотом, так и добавили свойство для ссылки авторизации пользователя, так как у нас пока нет авторизации, мы будем перенаправлять пользователя на страницу со статьями.

Давайте проверим в деле, как это работает. Я деавторизуюсь на сайте и попробую добавить статью, перейдя по следующему адресу: http://127.0.0.1:8000/articles/create/. Автоматически, меня перебрасывает на страницу всех статей со статусом HTTP 302.

Нельзя добавить статью без авторизации
Нельзя добавить статью без авторизации

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

blog/views.py
from django.views.generic import UpdateView
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.messages.views import SuccessMessageMixin

from .models import Article


class ArticleUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
    """
    Представление: обновления материала на сайте
    """
    model = Article
    template_name = 'blog/articles_update.html'
    context_object_name = 'article'
    form_class = ArticleUpdateForm
    login_url = 'home'
    success_message = 'Материал был успешно обновлен'

    def get_context_data(self, *, object_list=None, **kwargs):
        context = super().get_context_data(**kwargs)
        context['title'] = f'Обновление статьи: {self.object.title}'
        return context
    
    def form_valid(self, form):
        # form.instance.updater = self.request.user
        form.save()
        return super().form_valid(form)

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

Нам необходимо настроить шаблон уведомления, для этого в папке templates я создам папку includes, где размещу HTML файл messages.html со следующим фрагментом кода:

templates/includes/messages.html
{% if messages %}
{% for message in messages %}
    <div class="alert alert-{% if message.tags %}{{ message.tags }}{% endif %} alert-dismissible fade show" role="alert">
        <i class="fas fa-info-circle"></i> {{message}}
        <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
    </div>
{% endfor %}
{% endif %}

Шаблон мы используем из Bootstrap, а сами теги из документации по django.contrib.messages

Далее нам необходимо подлкючить компонент messages в main.html, поэтому обновляем содержимое:

templates/main.html
<!DOCTYPE html>
<html lang="ru">
<head>
    {% load static %}
    <meta charset="UTF-8">
    <title>{{ title }}</title>
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <!-- INCLUDE CSS -->
    <link href="{% static 'bootstrap/css/bootstrap.min.css' %}" type="text/css" rel="stylesheet">
</head>
<body>
    <div class="container">
        {% include 'header.html' %}
        <div class="row">
            <div class="col-8">
            {% include 'includes/messages.html' %}
            {% block content %}

            {% endblock %}
            {% include 'pagination.html' %}
            </div>
            <div class="col-4">
                {% include 'sidebar.html' %}
            </div>
        </div>
    </div>
<script src="{% static 'bootstrap/js/bootstrap.bundle.min.js' %}"></script>
</body>
</html>

Отлично, теперь давайте обновим одну из наших статей и посмотрим как работает миксин уведомления.

После обновления статьи получаем успешное сообщение об обновлении
После обновления статьи получаем успешное сообщение об обновлении

Отлично, все работает.

Пример кастомного миксина

Теперь давайте в нашей папке services, которую мы создали в уроке по обработке кириллицы создадим файл mixins.py, и добавим следующий код:

services/mixins.py
from django.contrib.auth.mixins import AccessMixin
from django.contrib import messages
from django.shortcuts import redirect


class AuthorRequiredMixin(AccessMixin):

    def dispatch(self, request, *args, **kwargs):
        if not request.user.is_authenticated:
            return self.handle_no_permission()
        if request.user.is_authenticated:
            if request.user != self.get_object().author or request.user.is_staff:
                messages.info(request, 'Изменение и удаление статьи доступно только автору')
                return redirect('home')
        return super().dispatch(request, *args, **kwargs)

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

blog/views.py
from django.views.generic import UpdateView, DeleteView
from django.contrib.messages.views import SuccessMessageMixin

from .models import Article
from ..services.mixins import AuthorRequiredMixin


class ArticleUpdateView(AuthorRequiredMixin, SuccessMessageMixin, UpdateView):
    """
    Представление: обновления материала на сайте
    """
    model = Article
    template_name = 'blog/articles_update.html'
    context_object_name = 'article'
    form_class = ArticleUpdateForm
    login_url = 'home'
    success_message = 'Материал был успешно обновлен'

    def get_context_data(self, *, object_list=None, **kwargs):
        context = super().get_context_data(**kwargs)
        context['title'] = f'Обновление статьи: {self.object.title}'
        return context
    
    def form_valid(self, form):
        # form.instance.updater = self.request.user
        form.save()
        return super().form_valid(form)


class ArticleDeleteView(AuthorRequiredMixin, DeleteView):
    """
    Представление: удаления материала
    """
    model = Article
    success_url = reverse_lazy('home')
    context_object_name = 'article'
    template_name = 'blog/articles_delete.html'

    def get_context_data(self, *, object_list=None, **kwargs):
        context = super().get_context_data(**kwargs)
        context['title'] = f'Удаление статьи: {self.object.title}'
        return context

В этом коде я убрал LoginRequiredMixin, так как данная логика уже добавлена в кастомный миксин AuthorRequiredMixin. Осталось проверить на сайте, для этого я зайду как с гостя, так и с другого аккаунта:

Редактирование с гостя:

Недоступно
Недоступно

Редактирование с другого пользователя:

Недоступно
Недоступно

Редактирование с автора статьи:

Доступно
Доступно

Отлично, миксин работает. Вы можете добавлять, изменять миксины так, как вам хочется.

Более подробная информация о миксинах есть на официальном сайте Django

;