В прошлом посте, мы доработали профиль пользователя, превратив его в кабинет автора. Для этого мы написали свой декоратор и расширили шаблон страницы.
В этом посте мы разберёмся, что такое миксины (mixins), для чего они нужны, как применяются и как написать свои.
Изменим имеющееся представление страницы добавления поста, превратив его в базовый класс, от которого унаследуем два представления: добавления и изменения поста.
Также добавим представление и страницу удаления поста.
Начнём с небольшой теории.
Что такое миксины (mixins)?
Миксины (mixins) представляют собой специальные классы, которые содержат методы и функциональность для повторного использования в других классах. Они используются для организации кода, обеспечивая переиспользование функциональности между различными классами представлений, моделей или других компонентов Django.
Миксины позволяют создавать небольшие модули функциональности, которые могут быть добавлены к классам через множественное наследование. Это позволяет разделить код на более мелкие и переиспользуемые компоненты, что улучшает читаемость, поддержку и уменьшает дублирование кода.
Как работают миксины в Django:
Определение миксина:
Миксин - это обычно класс Python, содержащий методы и атрибуты, предназначенные для использования в других классах. Например:
class MyMixin:
def some_method(self):
# Логика метода
pass
Использование миксина:
Для применения миксина к классу необходимо добавить его в список наследования:
from django.views import View
class MyView(MyMixin, View):
def get(self, request):
# Логика представления
return HttpResponse('Hello, World!')
Здесь MyView
наследуется от MyMixin
и View
. Теперь методы из MyMixin
доступны в MyView
.
Применение миксинов в Django:
- Представления (Views): Миксины могут использоваться для добавления функциональности к представлениям Django, например, для добавления проверок доступа, кэширования или другой логики.
- Модели (Models): Миксины также могут содержать методы для работы с моделями Django, например, для добавления общих методов или свойств к нескольким моделям.
- Формы (Forms): Миксины можно использовать для повторного использования функциональности в формах Django, такой как валидация данных или обработка форм.
- Другие компоненты: Кроме того, миксины можно использовать практически в любом компоненте Django, где требуется повторное использование функциональности.
Важно помнить, что использование миксинов должно быть осторожным, так как слишком много миксинов или их неправильное использование может привести к сложному коду и трудностям в понимании работы приложения.
Собственные миксины.
От теории перейдём к практике и напишем два миксина:
- Миксин проверки принадлежности пользователя к указанной группе.
- Миксин проверки того, что пользователь и автор поста - один человек.
В директории приложения user_app
создадим файл mixins.py
. В нём будем писать наши миксины.
Миксин проверки принадлежности к группе.
Для того, что бы проверять входит ли пользователь в необходимую группу, в данном случае в группу Автор
, можно в каждом представлении прописывать метод dispatch()
с проверкой. Чтобы избежать дублирования кода, мы напишем миксин с проверкой.
Создадим класс PermissionGroupRequiredMixin
.
В нём пропишем поле group_required
и присвоим ему значение None
. Это поле с названием необходимой группы будем прописывать в представлении.
Затем создадим метод dispatch()
. В методе проверяем, входит ли пользователь в указанную группу, то мы вызываем dispatch()
из класса представления. Иначе поднимаем ошибку 403 HttpResponseForbidden
с сообщением об отсутствии доступа.
Код миксина:
from django.http import HttpResponseForbidden
class PermissionGroupRequiredMixin:
group_required = None
def dispatch(self, request, *args, **kwargs):
if request.user.groups.filter(name=self.group_required).exists():
return super().dispatch(request, *args, **kwargs)
else:
raise HttpResponseForbidden("Вы не имеете доступа к этой странице.")

Как видим, достаточно просто.
Миксин проверки автора поста.
Для представлений редактирования и удаления поста, необходима проверка того, что автор поста и текущий пользователь - один человек.
Создадим класс PermissionSameAuthorMixin
.
Код будет такой же, как и предыдущий.
Полей у миксина не будет, а в методе dispatch()
изменим проверку.
Код миксина:
from django.http import HttpResponseForbidden
class PermissionSameAuthorMixin:
def dispatch(self, request, *args, **kwargs):
if request.user self.get_object().author:
return super().dispatch(request, *args, **kwargs)
else:
raise HttpResponseForbidden("Вы не имеете доступа к этой странице.")

Представления.
В директории приложения user_app
откроем файл views.py
.
В посте Django 36. Добавление постов пользователем мы написали следующее представление для страницы добавления поста:
class AddPostByAuthorView(UserPassesTestMixin, CreateView):
template_name = 'user_app/add_post.html'
form_class = forms.AddPostByAuthorForm
model = PostModel
def test_func(self):
return self.request.user.groups.filter(name='Автор').exists()
def get_success_url(self):
return reverse('user_app:user_profile', kwargs={'username': self.request.user.username})
def form_valid(self, form):
form.instance.slug = slugify(form.instance.title)
form.instance.author = self.request.user
return super().form_valid(form)
Мы его изменим, уберём лишнее и создадим два наследуемых класса.
Базовое представление.
Первым делом изменим имя класса на PostByAuthor
, убрав все наследования.
К имеющимся полям добавим новое поле group_required
со значением необходимой для доступа к представлениям группы. В нашем случае группы Автор
.
Удалим метод test_func
, так как он нам больше не нужен.
Всё остальное оставляем как есть. Если IDE будет ругаться на некоторые методы, игнорируем. У нас в классе их нет, но они будут в наших классах наследниках.
Код класса:
from django.urls import reverse_lazy
from pytils.translit import slugify
from blog.models import PostModel
from user_app import forms
class PostByAuthor:
template_name = 'user_app/add_post.html'
group_required = 'Автор'
form_class = forms.AddPostByAuthorForm
model = PostModel
def get_success_url(self):
return reverse('user_app:user_profile', kwargs={'username': self.request.user.username})
def form_valid(self, form):
form.instance.slug = slugify(form.instance.title)[:50]
form.instance.author = self.request.user
return super().form_valid(form)

Представления страниц добавления поста и редактирования поста.
Как вы могли заметить в коде выше, среди полей основного класса мы оставили поле с именем шаблона страницы. Это связано с тем, что для добавления и для редактирования поста будем использовать один и тот же шаблон. Нам нет необходимости создавать второй шаблон, т.к. по содержанию они будут идентичны. Разница лишь в том, что при добавлении поста, поля будут пустыми, а при редактировании они будут заполнены.
Создаём два класса:
AddPostByAuthorView
, унаследованный отPostByAuthor, PermissionGroupRequiredMixin, CreateView
.EditPostByAuthorView
, унаследованный отPostByAuthor, PermissionSameAuthorMixin, UpdateView
.
В первом классе мы наследуемся от нашего основного класса, миксина проверки принадлежности к группе и встроенного класса CreateView
.
Во втором классе мы также наследуемся от основного класса, миксина, проверяющего идентичность автора поста и пользователя, и от стандартного UpdateView
.
В обоих классах добавляем единственное поле extra_context
, в котором прописываем заголовок страницы. Это всё, что будет в двух представлениях.
Код представлений:
from django.views.generic import CreateView, UpdateView
from user_app.mixins import PermissionSameAuthorMixin, PermissionGroupRequiredMixin
class AddPostByAuthorView(PostByAuthor, PermissionGroupRequiredMixin, CreateView):
extra_context = {'title': 'Добавление поста'}
class EditPostByAuthorView(PostByAuthor, PermissionSameAuthorMixin, UpdateView):
extra_context = {'title': 'Изменить пост'}

Представление страницы удаления поста.
Для страницы удаления поста мы напишем отдельное представление.
Создадим класс DeletePostByAuthorView
, унаследованный от PermissionSameAuthorMixin
и DeleteView
.
Прописываем четыре поля:
template_name
- шаблон страницы.model
- модель поста.group_required
- требуемая группа для доступа к странице.extra_context
- дополнительные данные передаваемые в шаблон. В нашем случае заголовок страницы.
Также переопределяем метод get_success_url()
, возвращая на страницу профиля. Он такой же как и в представлениях выше.
Код представления:
from django.views.generic import DeleteView
from django.urls import reverse_lazy
from blog.models import PostModel
from user_app.mixins import PermissionSameAuthorMixin
class DeletePostByAuthorView(PermissionSameAuthorMixin, DeleteView):
template_name = 'user_app/delete_post_confirm.html'
model = PostModel
group_required = 'Автор'
extra_context = {'title': 'Удалить пост?'}
def get_success_url(self):
return reverse_lazy('user_app:user_profile', kwargs={'username': self.request.user.username})

Шаблон страницы удаления поста.
Создадим файл delete_post_confirm.html
в директории с шаблонами.
В шаблоне прописываем форму с двумя кнопками: "подтвердить удаление поста" и "вернуться в профиль".
Код шаблона:
{% extends 'blog/base.html' %}
{% block title %}{{ title }}{% endblock %}
{% block content %}
<div class="form-section container mt-3">
<h2>Удаление поста {{ object }}</h2>
<p>Вы действительно хотите удалить этот пост?</p>
<form method="POST">
{% csrf_token %}
<button class="btn btn-secondary my-btn-danger me-2" type="submit">Да, удалить</button>
<a href="{% url 'user_app:user_profile' username=request.user.username %}" class="btn btn-primary my-btn-success">Отмена</a>
</form> </div>{% endblock %}

URL-паттерны.
Осталось добавить URL-паттерны
представлений.
Кнопки в кабинете автора мы добавили в прошлом посте.
Откроем файл urls.py
в директории приложения user_app
и добавим следующие строки в список urlpatterns
:
path('post/add_post/', views.AddPostByAuthorView.as_view(), name='add_post'),
path('post/edit_post/<int:pk>', views.EditPostByAuthorView.as_view(), name='edit_post'),
path('post/delete_post/<int:pk>', views.DeletePostByAuthorView.as_view(), name='delete_post'),

Заключение.
В этом посте мы разобрали, что такое миксины. В Django есть ряд встроенных миксинов, таких как LoginRequiredMixin
, проверяющий зарегистрирован пользователь или нет. Написание собственных миксинов поможет расширить функционал представлений, без дублирования кода.
Кроме того, мы ещё раз попробовали создать базовый класс и сделать на его основе представления. Мы сделали один большой класс и два буквально двустрочных
. Если бы мы не сделали базовый класс, у нас вместо трёх, было бы два представления, однако оба были бы идентичными.



Комментарии
Оставить комментарийВойдите, чтобы оставить комментарий.
Я пытаюсь изменить пост, но вместо этого создается еще одна его копия.
в шаблоне изменил на
<form method="post" enctype="multipart/form-data" action="{% if form.instance.pk %}{% url 'user_app:edit_post' form.instance.pk %}{% else %}{% url 'user_app:add_post' %}{% endif %}">
и все заработало