Виды в виде классов(Class Based Views) и использование миксинов

👁 59 просмотров
1 Звезда2 Звезды3 Звезды4 Звезды5 Звезд (Пока оценок нет)
Загрузка...

Теория

В данном посте рассмотрим возможность обернуть методы вида в классы, иначе говоря в 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, который не понятен бывалому пользователю сайта

Генерация ошибки 404 для несуществующей страницы Django
Генерация ошибки 404 для несуществующей страницы Django

Конструкция 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})

Перезапускаем сервер и убеждаемся, что все работает также, как и прежде.