Создание сайта на Django: Урок 23, миксины для представлений на основе классов в Django
avatar
7 | (offline)
❤️‍🔥Notehunter Developer
Добавлено:
Категория: Руководства «Django»
Комментариев: 0

Миксины в Django 4.1

Мы рассмотрим миксины на примере наших представлений. 

Если мы хотим обезопасить наш проект, то мы должны запретить добавление, удаление, и обновление статей гостям. В Django существует ряд стандартных миксинов. Например, LoginRequiredMixin, который дает доступ только авторизированным пользователям к какому-либо действию в представлении.

Давайте дополним ArticleCreateView

modules/blog/views/articles.py

from django.contrib.auth.mixins import LoginRequiredMixin

class ArticleCreateView(LoginRequiredMixin, CreateView):
    """
    Представление: создание материалов на сайте
    """
    model = Article
    template_name = 'modules/blog/articles/article-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)

Пояснения:

  • Мы добавили в наше представление миксин с дополнительным функционалом, который не позволяет гостям добавлять материалы. 
  • Также у этого миксина есть свойство login_url которое перенаправляет неавторизованных пользователей на заданную страницу, так как мы ещё не сделали страничку авторизации, мы добавим главную страницу сайта.

Изменим urls.py, чтоб все выглядело более правильно:

modules/blog/ulrs.py

from django.urls import path, include

from modules.blog.views import ArticleListView, ArticleDetailView, ArticleByCategoryListView, ArticleCreateView, \
    ArticleUpdateView, ArticleDeleteView

urlpatterns = [
    path('', ArticleListView.as_view(), name='home'),
    path('cat/<int:pk>/<str:slug>/', ArticleByCategoryListView.as_view(), name='article-by-cat'),
    path('articles/', include([
        path('', ArticleListView.as_view(), name='article-list'),
        path('create/', ArticleCreateView.as_view(), name='article-create-view'),
        path('<str:slug>/', ArticleDetailView.as_view(), name='article-detail'),
        path('<str:slug>/update/', ArticleUpdateView.as_view(), name='article-update-view'),
        path('<str:slug>/delete/', ArticleDeleteView.as_view(), name='article-delete-view'),
    ])),
]

Пояснения:

  • Все что касается статей, их детальная информация, создание, обновление и удаление начинается с '/articles/'
  • Также я добавил ссылку 'home', с тем же представлением, что и у списка статей.

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

При переходе на страницу неавторизованным пользователем по адресу http://127.0.0.1:8000/articles/create/ нас перебрасывает на главную, как мы и задали в login_url = 'home'

Давайте сделаем какой-нибудь вывод информации например для успешного обновления статьи... Для этого мы можем использовать миксины уведомлений!

from django.contrib.messages.views import SuccessMessageMixin

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

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

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

Пояснения:

  • Мы добавили миксин на вывод сообщения об успешном обновлении статьи. Давайте выведем сообщение в html.

В templates я создам дополнительную папку, назвав ее includes. Эта папка будет использована для незначительных шаблонных подключений. А в ней я создам 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 %}

И в 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 'plugins/bootstrap-5.2.0-dist/css/bootstrap.min.css' %}" type="text/css" rel="stylesheet">
</head>
<body>
<div class="container">
    <div class="row">
        <div class="col-md-8">
            {% include 'includes/messages.html' %}
            {% block content %}

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

Проверим на деле! Пробуем обновить статью авторизованным пользователем и хотим увидеть сообщение об успехе:

функционал SuccessMessageMixin сработал! Отлично. 

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

Для этого, я создам папку services с файлом mixins.py в модуле Blog. А теперь создадим такой миксин:

modules/blog/services/mixins.py

from django.contrib.auth.mixins import AccessMixin

class AuthorRequiredMixin(AccessMixin):
    """Verify that the current user is authenticated."""

    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:
                self.permission_denied_message = 'Редактировать можно лишь автору данной статьи'
                return self.handle_no_permission()
        return super().dispatch(request, *args, **kwargs)

И если вы используете структуру папок как у меня, то не забываем про __init__.py

from modules.blog.services.mixins import AuthorRequiredMixin

__all__ = 'AuthorRequiredMixin'

И наконец добавляем наш модернизированный миксин

modules/blog/views/articles.py

from modules.blog.services import AuthorRequiredMixin

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

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

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

Для теста я зарегистрирую другой аккаунт и попробую перейти на страничку обновления статьи с другого аккаунта:

При переходе, я получаю исключение в консоле:

[02/Sep/2022 22:04:58] "GET /articles/testovaya-statya-3-nepublichnaya/ HTTP/1.1" 200 12204
Forbidden (Permission denied): /articles/testovaya-statya-3-nepublichnaya/update/
Traceback (most recent call last):
  File "C:\Users\Razilator\Desktop\Courses\App\venv\lib\site-packages\django\core\handlers\exception.py", line 55, in inner
    response = get_response(request)
  File "C:\Users\Razilator\Desktop\Courses\App\venv\lib\site-packages\django\core\handlers\base.py", line 197, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "C:\Users\Razilator\Desktop\Courses\App\venv\lib\site-packages\django\views\generic\base.py", line 103, in view
    return self.dispatch(request, *args, **kwargs)
  File "C:\Users\Razilator\Desktop\Courses\App\backend\modules\blog\services\mixins.py", line 14, in dispatch
    return self.handle_no_permission()
  File "C:\Users\Razilator\Desktop\Courses\App\venv\lib\site-packages\django\contrib\auth\mixins.py", line 48, in handle_no_permission
    raise PermissionDenied(self.get_permission_denied_message())
django.core.exceptions.PermissionDenied: Редактировать можно лишь автору данной статьи
[02/Sep/2022 22:05:01] "GET /articles/testovaya-statya-3-nepublichnaya/update/ HTTP/1.1" 403 135

И вот такую надпись: доступ запрещен

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

Обновляем наш миксин:

class AuthorRequiredMixin(AccessMixin):
    """Verify that the current user is authenticated."""

    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:
                messages.info(request, 'Редактирование и удаление доступно только автору статьи')
                return redirect('home')
        return super().dispatch(request, *args, **kwargs)

Проверяем:

Отлично. По такому примеру вы можете создавать любые миксины для ваших классов. Подробнее вы можете ознакомиться уже из официальной документации по Using mixins with class-based views | Django documentation | Django 

Комментарии к статье 0
Комментариев нет
Форма добавления комментария (необходима регистрация)