Function-based vs Class-based views, mixins, decorators
Выбор между Function-Based Views (FBV) и Class-Based Views (CBV) — одно из ключевых решений при разработке на Django. Этот туториал поможет понять различия и выбрать правильный подход.
💡 Правило: Используйте FBV для простой логики и декораторов. Используйте CBV для переиспользования кода и generic views.
from django.shortcuts import render, get_object_or_404
from django.http import HttpResponse, HttpResponseRedirect
from django.views.decorators.http import require_http_methods
def hello_world(request):
"""Простое FBV представление."""
return HttpResponse("Hello, World!")
def user_detail(request, user_id):
"""Представление с параметрами."""
user = get_object_or_404(User, pk=user_id)
return render(request, 'users/detail.html', {'user': user})
@require_http_methods(["GET", "POST"])
def create_post(request):
"""Обработка POST запроса."""
if request.method == 'POST':
title = request.POST.get('title')
content = request.POST.get('content')
# Обработка данных
return HttpResponseRedirect('/posts/')
return render(request, 'posts/create.html')| Преимущество | Описание |
|---|---|
| Простота | Легко читать и понимать |
| Явность | Вся логика видна в функции |
| Декораторы | Простое применение декораторов |
| Гибкость | Нет ограничений структуры класса |
| Тестирование | Легко тестировать изолированно |
| Недостаток | Описание |
|---|---|
| Переиспользование | Сложнее переиспользовать код |
| Наследование | Нет возможности наследования |
| Состояние | Сложнее управлять состоянием |
| Generic логика | Дублирование кода для CRUD операций |
from django.views import View
from django.shortcuts import render, get_object_or_404
from django.http import HttpResponse, HttpResponseRedirect
class HelloView(View):
"""Простое CBV представление."""
def get(self, request):
return HttpResponse("Hello, World!")
class UserDetailView(View):
"""Представление с параметрами."""
def get(self, request, user_id):
user = get_object_or_404(User, pk=user_id)
return render(request, 'users/detail.html', {'user': user})
class CreatePostView(View):
"""Обработка POST запроса."""
def get(self, request):
return render(request, 'posts/create.html')
def post(self, request):
title = request.POST.get('title')
content = request.POST.get('content')
# Обработка данных
return HttpResponseRedirect('/posts/')class ArticleView(View):
"""Все HTTP методы."""
def get(self, request, pk):
"""Получение статьи."""
article = get_object_or_404(Article, pk=pk)
return render(request, 'articles/detail.html', {'article': article})
def post(self, request, pk):
"""Создание комментария."""
content = request.POST.get('content')
# Обработка
return HttpResponseRedirect(f'/articles/{pk}/')
def put(self, request, pk):
"""Обновление статьи."""
# Обработка PUT
return HttpResponse('Updated')
def delete(self, request, pk):
"""Удаление статьи."""
article = get_object_or_404(Article, pk=pk)
article.delete()
return HttpResponse('Deleted')
def patch(self, request, pk):
"""Частичное обновление."""
# Обработка PATCH
return HttpResponse('Patched')
def head(self, request, pk):
"""Заголовки без тела."""
response = HttpResponse()
response['Content-Length'] = 100
return responseclass MyView(View):
def dispatch(self, request, *args, **kwargs):
# Выполняется ДО get/post/put/etc
print(f"Request method: {request.method}")
print(f"User: {request.user}")
# Можно вернуть ответ сразу
if not request.user.is_authenticated:
from django.shortcuts import redirect
return redirect('login')
# Продолжить обработку
return super().dispatch(request, *args, **kwargs)
def get(self, request):
return HttpResponse("GET response")
def post(self, request):
return HttpResponse("POST response")class AdminRequiredMixin:
"""Mixin для проверки прав администратора."""
def dispatch(self, request, *args, **kwargs):
if not request.user.is_staff:
from django.http import HttpResponseForbidden
return HttpResponseForbidden()
return super().dispatch(request, *args, **kwargs)
class LogRequestMixin:
"""Mixin для логирования запросов."""
def dispatch(self, request, *args, **kwargs):
import logging
logger = logging.getLogger(__name__)
logger.info(f"{request.method} {request.path} by {request.user}")
return super().dispatch(request, *args, **kwargs)
class JSONResponseMixin:
"""Mixin для JSON ответов."""
def render_to_json_response(self, data, **kwargs):
import json
from django.http import HttpResponse
return HttpResponse(
json.dumps(data),
content_type='application/json',
**kwargs
)
# Использование нескольких mixins
class AdminArticleView(AdminRequiredMixin, LogRequestMixin, View):
def get(self, request, pk):
article = get_object_or_404(Article, pk=pk)
return self.render_to_json_response({'title': article.title})from django.contrib.auth.mixins import (
LoginRequiredMixin,
PermissionRequiredMixin,
UserPassesTestMixin,
)
from django.views.generic.base import TemplateResponseMixin
class ProtectedView(LoginRequiredMixin, View):
"""Только для авторизованных."""
login_url = '/login/' # Переопределение URL входа
redirect_field_name = 'next' # Имя параметра для redirect
def get(self, request):
return HttpResponse("Protected content")
class AdminOnlyView(UserPassesTestMixin, View):
"""Только для админов."""
def test_func(self):
return self.request.user.is_staff
def get(self, request):
return HttpResponse("Admin content")
class EditArticleView(PermissionRequiredMixin, View):
"""Требуется разрешение."""
permission_required = 'blog.change_article'
def get(self, request, pk):
article = get_object_or_404(Article, pk=pk)
return render(request, 'articles/edit.html', {'article': article})from django.utils.decorators import method_decorator
from django.contrib.auth.decorators import login_required
from django.views.decorators.cache import cache_page
# Вариант 1: Декорирование dispatch
@method_decorator(login_required, name='dispatch')
class ProtectedView(View):
def get(self, request):
return HttpResponse("Protected")
# Вариант 2: Декорирование в классе
class ProtectedView2(View):
@method_decorator(login_required)
@method_decorator(cache_page(60 * 15))
def get(self, request):
return HttpResponse("Protected and cached")
# Вариант 3: Несколько декораторов
@method_decorator([login_required, cache_page(60 * 15)], name='dispatch')
class ProtectedView3(View):
def get(self, request):
return HttpResponse("Protected and cached")# ✅ Простая логика
def health_check(request):
return HttpResponse("OK")
# ✅ Декораторы
@cache_page(60 * 15)
@require_GET
def cached_view(request):
return HttpResponse("Cached")
# ✅ Нестандартная логика
def complex_workflow(request):
# Сложная логика, не вписывающаяся в CBV
if condition1:
# ...
elif condition2:
# ...
else:
# ...
return response# ✅ Generic CRUD
from django.views.generic import ListView, DetailView, CreateView, UpdateView, DeleteView
class PostListView(ListView):
model = Post
template_name = 'posts/list.html'
context_object_name = 'posts'
paginate_by = 10
def get_queryset(self):
return super().get_queryset().filter(is_published=True)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['categories'] = Category.objects.all()
return context
# ✅ Переиспользование через mixins
class AdminOnlyView(LoginRequiredMixin, UserPassesTestMixin, View):
def test_func(self):
return self.request.user.is_staff
def get(self, request):
return HttpResponse("Admin only")
# ✅ API с разными HTTP методами
class ArticleAPI(View):
def get(self, request, pk=None):
if pk:
article = get_object_or_404(Article, pk=pk)
return JsonResponse({'title': article.title})
articles = Article.objects.all()
return JsonResponse({'articles': list(articles.values('title'))})
def post(self, request):
# Создание
pass
def put(self, request, pk):
# Обновление
pass
def delete(self, request, pk):
# Удаление
pass# articles/views.py
from django.views import View
from django.views.generic import ListView, DetailView, CreateView, UpdateView
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.shortcuts import render, get_object_or_404, redirect
from django.http import JsonResponse
from django.utils.decorators import method_decorator
from django.views.decorators.cache import cache_page
from .models import Article
from .forms import ArticleForm
from .mixins import AdminRequiredMixin, LogRequestMixin
# FBV для простого запроса
def article_search(request):
"""Поиск статей — простое FBV."""
query = request.GET.get('q', '')
articles = Article.objects.filter(
is_published=True,
title__icontains=query
)[:10]
return render(request, 'articles/search.html', {'articles': articles, 'query': query})
# CBV для списка
class ArticleListView(ListView):
"""Список статей с пагинацией."""
model = Article
template_name = 'articles/list.html'
context_object_name = 'articles'
paginate_by = 10
def get_queryset(self):
queryset = super().get_queryset()
category = self.request.GET.get('category')
if category:
queryset = queryset.filter(category__slug=category)
return queryset.filter(is_published=True).order_by('-published_at')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['categories'] = Category.objects.all()
return context
# CBV для детали
@method_decorator(cache_page(60 * 15), name='dispatch')
class ArticleDetailView(DetailView):
"""Детальный просмотр с кэшированием."""
model = Article
template_name = 'articles/detail.html'
context_object_name = 'article'
def get_queryset(self):
return super().get_queryset().filter(is_published=True)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['related_articles'] = self.object.related_articles()[:5]
return context
# CBV для создания
class ArticleCreateView(LoginRequiredMixin, CreateView):
"""Создание статьи."""
model = Article
form_class = ArticleForm
template_name = 'articles/form.html'
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
# CBV для редактирования
class ArticleUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
"""Редактирование статьи."""
model = Article
form_class = ArticleForm
template_name = 'articles/form.html'
def test_func(self):
article = self.get_object()
return article.author == self.request.user or self.request.user.is_staff
def get_success_url(self):
return f'/articles/{self.object.pk}/'
# CBV для API
class ArticleAPI(View):
"""API для статей."""
def get(self, request, pk=None):
if pk:
article = get_object_or_404(Article, pk=pk)
return JsonResponse({
'id': article.id,
'title': article.title,
'content': article.content,
})
articles = Article.objects.filter(is_published=True)[:10]
return JsonResponse({
'articles': [
{'id': a.id, 'title': a.title}
for a in articles
]
})
def post(self, request):
if not request.user.is_staff:
return JsonResponse({'error': 'Unauthorized'}, status=403)
# Создание через API
passВопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.