Теория
В данном посте рассмотрим возможность обернуть методы вида в классы, иначе говоря в Class Based Views и воспользуемся миксинами, чтобы осуществить принцип DRY на уровне кода Python для Django.
В данном посте не будем добавлять в приложение новых фитч, а будем усовершенстововать код, который используется в файле blog/views.py.
Это усовершенствование преследует 2 цели:
- расширить возможности функций-обработчиков видов заменив их классами;
- уменьшить повторяемость.
Для расширения возможности видов в Django есть специальный класс Views в модуле django.views.generic, а уменьшение повторяемости можно достичь используя миксин в виде отдельного универсального класса для однотипных задач, которые имеют всего лишь разные значения.
Примечание. Mixin — это особый вид наследования в Python (и других объектно-ориентированных языках), и он начинает получать большую популярность в разработке Django / Web Application. Вы можете использовать Mixin, чтобы позволить классам на Python делиться методами между любым другим классом, который наследует от этого Mixin.
Использование наследуемых видов виде классов в дальнейшем нам потребуется, чтобы выполнять операции добавления и сохранения через формы в базу данных через POST — метод, учитывая, что за чтение данных из БД отвечает GET -метод. Без CBW мы использовали просто функции с методом GET.
В дополнении ко всему рассмотрим, как на уровне вида выдать ошибку 404, вместо стандартного Traceback Django.
Практика
Процесс перехода будем выполнять в несколько этапов, чтобы не пропустить промежуточные итоги усовершенствования кода.
Этап 1. Переход к CBV
На этом этапе заменим функции post_detail() и tag_detail() в blog/view.py на аналогичные по функциональности классы и поправим код в blog/urls.py.
Открываем файл blog/view.py и правим код. Сохраним все предыдущее состояние кода,для наглядности того, что поменяли
from django.shortcuts import render from django.http import HttpResponse from .models import Post, Tag from django.views.generic import View # Create your views here. def posts_list(request): posts = Post.objects.all() return render(request, 'blog/index.html', context = {'posts' : posts}) #return HttpResponse("<h1>Hello! Hello!</h1>") class PostDetail(View): def get(self, request, slug): post = Post.objects.get(slug__iexact=slug) return render(request, 'blog/post_detail.html', context={"post" : post}) # def post_detail(request, slug): # post = Post.objects.get(slug__iexact = slug) # return render(request, 'blog/post_detail.html', context = {'post' : post}) def tags_list(request): tags = Tag.objects.all() return render(request, 'blog/tags_list.html', context={'tags': tags}) class TagDetail(View): def get(self, request, slug): tag = Tag.objects.get(slug__iexact = slug) return render(request, 'blog/tag_detail.html', context={'tag': tag}) # def tag_detail(request, slug): # tag = Tag.objects.get(slug__iexact = slug) # return render(request, 'blog/tag_detail.html', context={'tag': tag})
Что делает данный код? Мы подключаем модуль django.views.generic и импортируем оттуда класс View, далее создаем 2 класса, которых наследуем от класса View, для переопределения функции get() в них. Этот метод соответствует методу запроса GET, поэтому он должен возвращать рендер шаблона вида с значениям постов и тегов, чтобы показать в браузере пользователя, отправившего этот запрос.
Теперь открываем файл blog/urls.py и правим метод передачи обработки по шаблону запроса маршрутизации с post_detail и tag_detail на PostDetail.as_view() и TagDetail.as_view() соответственно
from django.urls import path from .views import * urlpatterns = [ path('', posts_list, name='posts_list_url'), #path('post/<str:slug>', post_detail, name='post_detail_url'), path('post/<str:slug>', PostDetail.as_view(), name='post_detail_url'), path('tags/', tags_list, name='tags_list_url'), #path('tag/<str:slug>', tag_detail, name='tag_detail_url'), path('tag/<str:slug>', TagDetail.as_view(), name='tag_detail_url'), ]
Перезапускаем и убеждаемся, что все запускается ровно также, как и при старом коде.
Этап 2. Генерация страницы 404
Открываем файл blog/views.py и меняем содержимое на такое
from django.shortcuts import render, get_object_or_404 from django.http import HttpResponse from .models import Post, Tag from django.views.generic import View # Create your views here. def posts_list(request): posts = Post.objects.all() return render(request, 'blog/index.html', context = {'posts' : posts}) #return HttpResponse("<h1>Hello! Hello!</h1>") class PostDetail(View): def get(self, request, slug): #post = Post.objects.get(slug__iexact=slug) post = get_object_or_404(Post, slug__iexact=slug) #return render(request, 'blog/post_detail.html', context={"post" : post}) return render(request, 'blog/post_detail.html', context={Post.__name__.lower(): post}) # def post_detail(request, slug): # post = Post.objects.get(slug__iexact = slug) # return render(request, 'blog/post_detail.html', context = {'post' : post}) def tags_list(request): tags = Tag.objects.all() return render(request, 'blog/tags_list.html', context={'tags': tags}) class TagDetail(View): def get(self, request, slug): #tag = Tag.objects.get(slug__iexact = slug) tag = get_object_or_404(Tag, slug__iexact=slug) #return render(request, 'blog/tag_detail.html', context={'tag': tag}) return render(request, 'blog/tag_detail.html', context={Tag.__name__.lower(): tag}) # def tag_detail(request, slug): # tag = Tag.objects.get(slug__iexact = slug) # return render(request, 'blog/tag_detail.html', context={'tag': tag})
В общем, в данном файле мы все запросы к БД выполняем через функцию get_object-or_404(…), который внутри себя реализует запрос к БД и если ничего не найдено, то выдает страницу 404 вместо Traceback, который не понятен бывалому пользователю сайта
Конструкция Tag.__name__.lower() возвращает название класса(в данном случае название модели) с заглавной буквы, но нам нужны только строчные буквы и поэтому используем функцию lower() и далее передаем его в виде названием параметра словаря контекста. Это нам пригодится, что на следующем этапе определить для этих классов общий миксин.
Этап 3. Создание Миксина
Теперь создадим еще один файл blog/utils.py, в котором определим наш миксин в виде универсального класса для уменьшения кода, который будет унаследован классами с переопределенными свойствами
from django.shortcuts import render, get_object_or_404 from django.views.generic import View from .models import * class ObjectDetailMixin: model = None template = None def get(self, request, slug): obj = get_object_or_404(self.model, slug__iexact=slug) return render(request, self.template, context={self.model.__name__.lower(): obj})
В данном классе мы определили 2 свойства:
- model — класс модели;
- template — шаблон модели.
Как мы видим, данный класс-миксин не содержит в себе каких-то индивидуальных параметров, которые бы его ограничивали в использовании. Он универсален для вывода и поста и для тега и это для нас может разыграть отличное условие, чтобы соответствовать принципу DRY в коде Python/Django.
Теперь открываем файл blog/views.py перезаписываем вьюшки с наследованием нашего миксина. Старый код закоментирован, чтобы лучше можно было понять шаги и изменение кода
from django.shortcuts import render, get_object_or_404 from django.http import HttpResponse from .models import Post, Tag from django.views.generic import View from .utils import ObjectDetailMixin # Create your views here. def posts_list(request): posts = Post.objects.all() return render(request, 'blog/index.html', context = {'posts' : posts}) #return HttpResponse("<h1>Hello! Hello!</h1>") class PostDetail(ObjectDetailMixin, View): model = Post template = 'blog/post_detail.html' ''' class PostDetail(View): def get(self, request, slug): #post = Post.objects.get(slug__iexact=slug) post = get_object_or_404(Post, slug__iexact=slug) #return render(request, 'blog/post_detail.html', context={"post" : post}) return render(request, 'blog/post_detail.html', context={Post.__name__.lower(): post}) ''' # def post_detail(request, slug): # post = Post.objects.get(slug__iexact = slug) # return render(request, 'blog/post_detail.html', context = {'post' : post}) def tags_list(request): tags = Tag.objects.all() return render(request, 'blog/tags_list.html', context={'tags': tags}) class TagDetail(ObjectDetailMixin, View): model = Tag template = 'blog/tag_detail.html' ''' class TagDetail(View): def get(self, request, slug): #tag = Tag.objects.get(slug__iexact = slug) tag = get_object_or_404(Tag, slug__iexact=slug) #return render(request, 'blog/tag_detail.html', context={'tag': tag}) return render(request, 'blog/tag_detail.html', context={Tag.__name__.lower(): tag}) ''' # def tag_detail(request, slug): # tag = Tag.objects.get(slug__iexact = slug) # return render(request, 'blog/tag_detail.html', context={'tag': tag})
Перезапускаем сервер и убеждаемся, что все работает также, как и прежде.