ListView, DetailView, CreateView, UpdateView, DeleteView, FormView
Generic views (CBV) — это готовые классы для типовых операций CRUD (Create, Read, Update, Delete). Они значительно сокращают количество кода для стандартных задач.
💡 Правило: Используйте generic views для стандартных операций. Переопределяйте методы только когда нужна кастомная логика.
| Категория | Views | Назначение |
|---|---|---|
| Base | View, TemplateView, RedirectView | Базовые view |
| List/Detail | ListView, DetailView | Чтение данных |
| Edit | CreateView, UpdateView, DeleteView | CRUD операции |
| Form | FormView, CreateView, UpdateView | Работа с формами |
| Date | ArchiveIndexView, YearArchiveView, MonthArchiveView, WeekArchiveView, DayArchiveView, TodayArchiveView, DateDetailView | Архив по датам |
# views.py
from django.views.generic import ListView
from blog.models import Post
class PostListView(ListView):
model = Post
template_name = 'blog/post_list.html' # По умолчанию: blog/post_list.html
context_object_name = 'posts' # По умолчанию: object_list
paginate_by = 10 # Пагинация по 10 объектов
ordering = ['-created_at'] # Сортировка<!-- template -->
{% for post in posts %}
<h2>{{ post.title }}</h2>
{% empty %}
<p>No posts yet.</p>
{% endfor %}
{% if is_paginated %}
<div class="pagination">
{% if page_obj.has_previous %}
<a href="?page={{ page_obj.previous_page_number }}">Previous</a>
{% endif %}
<span>Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}</span>
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}">Next</a>
{% endif %}
</div>
{% endif %}class PublishedPostListView(ListView):
model = Post
template_name = 'blog/post_list.html'
context_object_name = 'posts'
def get_queryset(self):
# Фильтрация опубликованных постов
return super().get_queryset().filter(
is_published=True,
status='published'
)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# Добавление дополнительных переменных
context['categories'] = Category.objects.all()
context['tags'] = Tag.objects.all()[:10]
return contextclass AuthorPostListView(ListView):
model = Post
template_name = 'blog/author_posts.html'
def get_queryset(self):
# Фильтрация по автору из URL
self.author = get_object_or_404(User, username=self.kwargs['username'])
return super().get_queryset().filter(author=self.author, is_published=True)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['author'] = self.author
return contextfrom django.views.generic import DetailView
from blog.models import Post
class PostDetailView(DetailView):
model = Post
template_name = 'blog/post_detail.html'
context_object_name = 'post' # По умолчанию: object
# Получение по pk
# URL: path('posts/<int:pk>/', PostDetailView.as_view())
# Или получение по slug
# slug_field = 'slug'
# slug_url_kwarg = 'slug'
# URL: path('posts/<slug:slug>/', PostDetailView.as_view())class PublishedPostDetailView(DetailView):
model = Post
def get_queryset(self):
# Только опубликованные посты
return super().get_queryset().filter(
is_published=True,
status='published'
)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# Похожие посты
post = self.object
context['related_posts'] = Post.objects.filter(
category=post.category,
is_published=True
).exclude(pk=post.pk)[:5]
return contextfrom django.views.generic import CreateView
from django.urls import reverse_lazy
from blog.models import Post
from blog.forms import PostForm
class PostCreateView(CreateView):
model = Post
form_class = PostForm # Или fields = ['title', 'content', 'category']
template_name = 'blog/post_form.html'
success_url = reverse_lazy('blog:post_list')
# Автоматически создаёт ModelForm из model и fields
# fields = ['title', 'content', 'category', 'tags']from django.contrib.auth.mixins import LoginRequiredMixin
class PostCreateView(LoginRequiredMixin, CreateView):
model = Post
fields = ['title', 'content', 'category']
template_name = 'blog/post_form.html'
def form_valid(self, form):
# Установка автора перед сохранением
form.instance.author = self.request.user
return super().form_valid(form)
def get_success_url(self):
# Динамический URL после создания
return reverse_lazy('blog:post_detail', kwargs={'pk': self.object.pk})class PostWithTagsCreateView(CreateView):
model = Post
fields = ['title', 'content', 'category']
template_name = 'blog/post_form.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['tag_form'] = TagForm()
return context
def post(self, request, *args, **kwargs):
form = self.get_form()
tag_form = TagForm(request.POST)
if form.is_valid() and tag_form.is_valid():
return self.form_valid(form, tag_form)
else:
return self.form_invalid(form)
def form_valid(self, form, tag_form):
response = super().form_valid(form)
# Обработка тегов
tags = tag_form.cleaned_data.get('tags', '').split(',')
for tag_name in tags:
tag, _ = Tag.objects.get_or_create(name=tag_name.strip())
self.object.tags.add(tag)
return responsefrom django.views.generic import UpdateView
class PostUpdateView(UpdateView):
model = Post
form_class = PostForm
template_name = 'blog/post_form.html'
success_url = reverse_lazy('blog:post_list')
# URL: path('posts/<int:pk>/edit/', PostUpdateView.as_view())from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
class PostUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
model = Post
fields = ['title', 'content', 'category']
template_name = 'blog/post_form.html'
def test_func(self):
# Только автор или админ может редактировать
post = self.get_object()
return post.author == self.request.user or self.request.user.is_staff
def form_valid(self, form):
# Логирование изменений
old_post = Post.objects.get(pk=self.object.pk)
if old_post.title != form.instance.title:
PostRevision.objects.create(
post=old_post,
old_title=old_post.title,
new_title=form.instance.title,
changed_by=self.request.user
)
return super().form_valid(form)from django.views.generic import DeleteView
class PostDeleteView(DeleteView):
model = Post
template_name = 'blog/post_confirm_delete.html'
success_url = reverse_lazy('blog:post_list')
# URL: path('posts/<int:pk>/delete/', PostDeleteView.as_view())
# GET: показывает страницу подтверждения
# POST: удаляет объект и редиректит<!-- post_confirm_delete.html -->
<form method="post">
{% csrf_token %}
<p>Are you sure you want to delete "{{ object.title }}"?</p>
<button type="submit">Confirm</button>
<a href="{{ object.get_absolute_url }}">Cancel</a>
</form>class PostDeleteView(DeleteView):
model = Post
template_name = 'blog/post_confirm_delete.html'
def delete(self, request, *args, **kwargs):
# Вместо физического удаления — мягкое
self.object = self.get_object()
self.object.is_deleted = True
self.object.deleted_at = timezone.now()
self.object.save()
return HttpResponseRedirect(self.get_success_url())
def get_success_url(self):
return reverse_lazy('blog:post_list')from django.views.generic import FormView
from blog.forms import ContactForm
class ContactFormView(FormView):
template_name = 'blog/contact.html'
form_class = ContactForm
success_url = reverse_lazy('blog:contact_success')
def form_valid(self, form):
# Отправка email
send_email(
to='admin@example.com',
subject=form.cleaned_data['subject'],
message=form.cleaned_data['message']
)
return super().form_valid(form)class RegistrationView(FormView):
template_name = 'accounts/register.html'
form_class = UserRegistrationForm
success_url = reverse_lazy('accounts:register_success')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
if 'profile_form' not in context:
context['profile_form'] = ProfileForm()
return context
def post(self, request, *args, **kwargs):
form = self.get_form()
profile_form = ProfileForm(request.POST, request.FILES)
if form.is_valid() and profile_form.is_valid():
return self.form_valid(form, profile_form)
else:
return self.form_invalid(form)
def form_valid(self, form, profile_form):
user = form.save()
profile = profile_form.save(commit=False)
profile.user = user
profile.save()
return super().form_valid(form)from django.views.generic.dates import (
YearArchiveView,
MonthArchiveView,
DayArchiveView,
TodayArchiveView,
)
class PostYearArchiveView(YearArchiveView):
queryset = Post.objects.filter(is_published=True)
date_field = 'created_at'
make_object_list = True # Показывать объекты для года
allow_future = False
template_name = 'blog/archive_year.html'
class PostMonthArchiveView(MonthArchiveView):
queryset = Post.objects.filter(is_published=True)
date_field = 'created_at'
month_format = '%m' # '01'-'12'
allow_future = False
template_name = 'blog/archive_month.html'
class PostDayArchiveView(DayArchiveView):
queryset = Post.objects.filter(is_published=True)
date_field = 'created_at'
day_format = '%d'
allow_future = False
template_name = 'blog/archive_day.html'# blog/views.py
from django.views.generic import (
ListView, DetailView, CreateView, UpdateView, DeleteView
)
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.urls import reverse_lazy
from .models import Post, Category
from .forms import PostForm
class PostListView(ListView):
"""Список всех опубликованных постов."""
model = Post
template_name = 'blog/post_list.html'
context_object_name = 'posts'
paginate_by = 10
def get_queryset(self):
return Post.objects.filter(
is_published=True,
status='published'
).select_related('author', 'category').prefetch_related('tags')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['categories'] = Category.objects.all()
return context
class PostDetailView(DetailView):
"""Детальный просмотр поста."""
model = Post
template_name = 'blog/post_detail.html'
context_object_name = 'post'
def get_queryset(self):
return Post.objects.filter(
is_published=True,
status='published'
).select_related('author')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
post = self.object
context['related_posts'] = Post.objects.filter(
category=post.category,
is_published=True
).exclude(pk=post.pk)[:3]
return context
class PostCreateView(LoginRequiredMixin, CreateView):
"""Создание нового поста."""
model = Post
form_class = PostForm
template_name = 'blog/post_form.html'
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
def get_success_url(self):
return reverse_lazy('blog:post_detail', kwargs={'pk': self.object.pk})
class PostUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
"""Редактирование поста."""
model = Post
form_class = PostForm
template_name = 'blog/post_form.html'
def test_func(self):
post = self.get_object()
return post.author == self.request.user or self.request.user.is_staff
def form_valid(self, form):
# Сохранение истории изменений
old_post = Post.objects.get(pk=self.object.pk)
if old_post.content != form.instance.content:
PostRevision.objects.create(
post=old_post,
old_content=old_post.content,
new_content=form.instance.content,
changed_by=self.request.user
)
return super().form_valid(form)
def get_success_url(self):
return reverse_lazy('blog:post_detail', kwargs={'pk': self.object.pk})
class PostDeleteView(LoginRequiredMixin, UserPassesTestMixin, DeleteView):
"""Удаление поста."""
model = Post
template_name = 'blog/post_confirm_delete.html'
success_url = reverse_lazy('blog:post_list')
def test_func(self):
post = self.get_object()
return post.author == self.request.user or self.request.user.is_staffВопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.