Django DetailView - основы использования

Django позволяет создавать приложения очень легко. Если приложение должно быть выпущено быстро и является относительно общим, то эта среда Python идеально подходит для этого. В течение нескольких лет я профессионально работал в этой среде и часто рылся внутри, поэтому знаю почти всё, и сегодня я представлю вам все, что нужно знать, чтобы эффективно использовать универсальный DetailView в Django.

Классовые методы имеют своих сторонников и противников. Люди, которые любят объектно-ориентированные программы, естественно, выбирают общие представления, потому что они более дружелюбны к ним. С другой стороны, люди, которые предпочитают функцию-представление на основе классов, используют ее, только если она приносит разумное упрощение. Я думаю, что стоит придерживаться одного соглашения, поэтому, если вы хотите иметь несколько представлений в своем приложении, лучше, если они все классовые или функциональные. И поскольку классные комнаты предоставляют гораздо больше возможностей, конечно, мой выбор падает на общие взгляды.

Давайте вернемся к первому из них и познакомимся с общим DetailView.

Базовая модель

Представление отображает данные, но вам нужно подготовить их. Давайте подготовим модель данных, которую мы хотим отобразить. Пусть это будет модель Post с базовыми данными, такими как заголовок, слаг, некоторый контент и тизер.

from django.db import models


class Post(models.Model):
    active = models.BooleanField(verbose_name='активный', default=False)
    title = models.CharField(max_length=255, verbose_name='заголовок')
    slug = models.SlugField(unique=True)
    body = models.TextField(verbose_name='текст')
    lead = models.TextField(verbose_name='кратко')

    def __str__(self):
        return self.title

Представление

У нас есть модель, поэтому мы собираемся написать простое представление. Представление очень простое, и наиболее важными вопросами являются импорт класса, от которого мы унаследуем, и модели, которую мы регистрируем.

from django.views.generic.detail import DetailView

from .models import Post


class PostDetailView(DetailView):
    model = Post

Регистрация представления в urls.py

Поскольку у нас есть представление, мы должны теперь присоединить его, все адреса указаны в файле urls.py. В целях одного представления мы не будем извлекать это в файл для приложения. Если запись окажется ценной и из нее будет создана серия, то в резюме мы проведем рефакторинг.

from django.urls import path
from .views import PostDetailView

urlpatterns = [
    ... 
    path('blog/<slug:slug>/', PostDetailView.as_view(), name='post-detail'),
    ...
]

Регистрация представления по полю pk

Представление может быть просто зарегистрировано по полю pk, которое видно ниже.

from django.urls import path
from .views import PostDetailView

urlpatterns = [
    ... 
    path('blog/<int:pk>/', PostDetailView.as_view(), name='post-detail'),
    ...
]

Общий вид DetailView построен настолько умно, что по умолчанию обеспечивает вызов как по pk, так и по slug.

Пользовательский slug_field

Бывает, что вам нужно добавить отображение к существующей модели. Если в этой модели есть поле SlugField, но оно не называется slug, и вы хотите создать адрес по этому полю, вам не нужно паниковать или комбинировать. Просто переопределите атрибут slug_field.

class PostDetailView(DetailView):
    model = Post
    slug_field = 'custom_slug_field'

Шаблон

По умолчанию Django ищет шаблон в каталоге <application_name>/<model_name>_detail.html, т.е. blog/post_detail.html. Давайте создадим этот упрощенный шаблон.

<h1>{{ object.title }}</h1>
<span class='lead'>{{ object.lead }} </span>
<div class='body'>{{ object.body|safe }} </div>

По сути, эти 3 простых шага позволят вам создать простой сайт. В следующей части поста я представлю дополнительные возможности, предлагаемые общими представлениями, которые стоит знать и могут быть полезны более одного раза.

Загрузка пользовательских объектов

Иногда вам нужно загрузить пользовательский объект, например, из внешнего API. Это можно сделать очень просто, переписав метод get_object. Давайте рассмотрим простой пример и вручную загрузим объект по полю slug. (В среднем это имеет смысл, потому что такая поддержка встроена, но я не хочу усложнять ее)

from .exceptions import PostDoesNotExist


class PostDetailView(DetailView):

    def get_object(self, queryset=None):
        slug = self.kwargs.get(self.slug_url_kwarg, None)
        try:
            return queryset.get(slug=slug)
        except PostDoesNotExist:
            raise Http404('Ох, нет объекта;)')

Использование пользовательских запросов

Объект отображается из общего множества объектов в базе данных, иногда мы хотим иметь собственные механизмы для фильтрации, например, объектов, которые еще не активны, или добавить select_related, если мы используем ссылки на другие классы. Это также очень просто и может быть сделано, например, с помощью атрибута queryset.

Давайте посмотрим пример того, как отображать только активные записи. Предположим, у этого объекта есть ссылка на фото.

class PostDetailView(DetailView):
    model = Post
    queryset = Post.objects.filter(active=True).select_related('photo')

Метод get_queryset

Если вы не хотите использовать атрибут по разным причинам, это также возможно. В представлении есть метод get_queryset, который по умолчанию возвращает значение из атрибута, описанного ранее, но вы можете легко перезаписать его и вернуть свой собственный листинг. Это полезно, если вы хотите загружать объекты не из базы данных, а, например, после некоторого API. Ниже приведен пример простого переопределения такого метода.

from .api import PostApi


class PostDetailView(DetailView):
    model = Post
    
    def get_queryset(self):
        api = PostApi()
        return api.get_posts()

Конечно, здесь есть много упрощений, и я не дал реализацию Api, потому что она здесь неактуальна. Важно, что вы можете свободно переопределить этот метод. Вы также можете сделать своего рода гибрид, который набор запросов будет загружать из базы данных и уже детали после API, или наоборот. Попробуйте сами и дайте мне знать, если вы нашли какое-либо интересное приложение.

Практическое переопределение метода get_queryset

Я долго задавался вопросом, каким может быть практическое применение переопределения метода get_queryset. Если у нас есть немного более сложная структура и мы хотим, чтобы URL категории был видимым, мы можем достичь этого очень просто. Попробуем добавить такой сервис в URL.

urlpatterns = [
    ... 
    path('blog/<slug:category_slug>/<slug:slug>/', PostDetailView.as_view(), name='post-detail'),
    ...
]

Это все? Ну, не совсем. Таким образом, мы никоим образом не защищаем адрес и не проверяем, появляется ли категория в адресе. Если мы не справимся с этим, существует риск, что один и тот же контент будет отображаться на многих адресах, что не является лучшим решением для Google.

Здесь я вижу два решения. Первый - перенаправление на правильную страницу. В нашем случае мы не хотим такого перенаправления, и достаточно бросить 404, которое, в свою очередь, будет брошено, если нет объекта. Давайте перезапишем метод, чтобы он фильтровал записи по категориям по адресу.

class PostDetailView(DetailView):
    model = Post
    
    def get_queryset(self):
        category = self.kwargs.get('category_slug', '')
        q = super().get_queryset()
        return q.filter(category__slug=category).select_related('category')

Конечно, мы помним о select_related все время. А так просто добавили интересный патч, который легко забыть.

Наименование объекта в шаблоне

Возможно, вы заметили, что объект в шаблоне называется object. Иногда это может сбивать с толку, и вам может понадобиться изменить его. Ничего сложного, просто переопределите атрибут context_object_name. Отныне в шаблоне вы можете использовать новое имя вашего объекта.

class PostDetailView(DetailView):
    model = Post
    context_object_name = 'post'

После такого изменения мы можем улучшить шаблон

<h1>{{ post.title }}</h1>
<span class='lead'>{{ post.lead }} </span>
<div class='body'>{{ post.body|safe }} </div>

Альтернативой также является использование метода get_context_object_name, но, на мой взгляд, здесь нет необходимости, поскольку описанного атрибута достаточно.

Альтернативный шаблон

Что, если я хочу иметь два разных представления, например, потому что они поддерживают разную логику, но способ отображения данных идентичен? Я боролся с этим много раз и, поверьте мне, со старыми версиями django, такими как 1.2 (хоть и давно это было),  было много путанницы. Однако сегодня просто переопределите атрибут template_name для любого шаблона.

class PostDetailView(DetailView):
    model = Post
    template_name = 'my_template.html'

Декораторы на представлениях-классах

Джанго имеет широкий спектр различных декораторов. От того, который может отключить кеш на определенных страницах, до того, который заставляет вас войти в систему. Чтобы закрепить такой декоратор, поместите его в метод отправки в представлении или в urls.py. Две возможности ниже

from django.views.decorators.cache import never_cache


class PostDetailView(DetailView):
    model = Post

    @never_cache
    def dispatch(self, *args, **kwargs):
        return super(PostDetailView, self).dispatch(*args, **kwargs)     

Если вы не хотите перезаписывать какой-либо метод, давайте сделаем это прямо в файле urls.py.

from django.views.decorators.cache import never_cache

urlpatterns = [
    ... 
    path('blog/<slug:slug>/', never_cache(PostDetailView.as_view()), name='post-detail'),
    ...
]

Дополнительные данные в контексте

Не раз у вас возникнет ситуация, когда вам придется перенести дополнительные данные в шаблон. Есть много возможностей, и все зависит от того, что вы хотите передать.

Это можно сделать, например, с помощью промежуточного программного обеспечения (middleware) или тегов шаблонов. Другой вариант - воспользоваться тем фактом, что представление передает конкретный контекст в шаблон, а это всего лишь словарь. В общем виде передача дополнительного значения в шаблон так же проста, как и предыдущие операции, и вы, вероятно, догадываетесь, что все, что вам нужно сделать, это переписать соответствующий атрибут. Давайте попробуем добавить три поста в контекст.

from django.views.decorators.cache import never_cache


class PostDetailView(DetailView):
    model = Post
    extra_context = {'latest': Post.objects.all()[:3]}

Давайте напишем последние записи в шаблоне:

<h1>{{ post.title }}</h1>-
<span class='lead'>{{ post.lead }} </span>
<div class='body'>{{ post.body|safe }} </div>

<ul class='latest'>
{% for post in latest %} <li>{{ post.pk}}. {{ post.title }}</li>{% endfor %}
</ul>

Бонус - загрузка объектов без slug и pk

Чаще всего для извлечения объекта вам нужен ключ, то есть какое-то значение, после которого мы должны определить объект. Но это не всегда так, о чем свидетельствует загрузка чего-либо из сессии или из самого запроса. Хорошим примером является загрузка пользователя, например. Посмотрим на пример:

urlpatterns = [
    ...
    path('blog/', UserDetailView.as_view(), name='user-detail'),
    ...
]

Небольшое обновление в представлении:

from django.views.generic.detail import DetailView
from django.shortcuts import get_object_or_404                                  


class UserDetailView(DetailView):
    
    def get_object(self):
        return get_object_or_404(User, id=self.request.user.id)
        # return get_object_or_404(User, id=self.request.session.get('user'))

Это представление имеет средний смысл, поскольку пользователь в запросе является встроенным, но если мы не используем имя входа по умолчанию и поддерживаем его сами, записывая в сессии, этот закомментированный код будет иметь больше смысла в этом смысле. Этот случай можно практически использовать, например, на странице редактирования учетной записи, но мы обнаружим наибольший потенциал этого подхода в ListView.

Итоги

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

Перевод статьи https://ddeby.pl/blog/django-detailview-podstawowy-widok-generyczny

Вернуться на верх