Django
3 February 2012

Немного подробностей про Class Based Views, ч.3

Приветствую вас вновь, дорогие читатели! Публикую очередную статью из цикла немного про Class Based Views (далее CBV) в Django. На этот раз я, как и планировал, хотел бы рассмотреть ListView (отвечает за отображение списка объектов) и DetailView (отвечает за отображение информации об отдельном объекте).

Часть 1, часть 2, часть 3, часть 4

Также, как и ранее, я буду рад обратной связи с читателями — если вы обнаружите ошибку или неточность в статье, то прошу сообщить, сделаем статью лучше вместе.

Ссылки для быстрого поиска методов

get_paginate_by
get_allow_empty
get_context_object_name
get_object
get_slug_field

Ссылки для быстрого поиска атрибутов

paginate_by
paginator_class
allow_empty
context_object_name
object
slug_field

Отображение списка объектов

Несомненно эта операция требуется в абсолютном большинстве проектов и она, к счастью, предусмотрена и разработчиками Django в фреймворке. Методы и атрибуты, описанные в предыдущей части, я думаю не нуждаются в дополнительном описании, поэтому буду учитывать лишь новые встретившиеся.

В случаях, когда необходимо отобразить огромное число объектов, крайне нежелательно выводить их все одновременно. В этом случае требуется механизм для постраничного вывода данных (пагинация). Класс DetailView наследует примесь MultipleObjectMixin, которая реализует требуемый нам функционал. Для определения количества объектов на страницу используется метод get_paginate_by, который по умолчанию возвращает значение атрибута paginate_by. С помощью атрибута мы можем без особых хлопот указать количество выводимых на 1 страницу объектов.
Иногда возникает необходимость реализовать постраничный вывод своим способом, для этого мы можем передать наш класс пагинации атрибуту paginator_class. По умолчанию этот атрибут содержит ссылку на стандартный класс Paginator, который реализован в модуле django.core.paginator. Дважды подумайте, если все же решите его изменить и уточните нет ли требуемой вам функциональности изначально.
Атрибут allow_empty определяет как обработать ситуацию, когда нет ни одного объекта в списке. Если мы установим значение данного атрибута в True (по умолчанию), то будет возвращаться пустой список объектов. В случае значения False будет возвращаться ошибка 404. Значение данного атрибута возвращает метод get_allow_empty. Его же можно использовать, если требуется некоторая дополнительная проверка или изменение логики.
В шаблоне наш список объектов будет доступен по имени, которое задано с помощью атрибута context_object_name (или возвращается методом get_context_object_name, рассматривался в предыдущей статье). Объекты с текущей страницы находятся в переменной с именем object_list. Значение переменной is_paginated (булево значение) определяет разбит ли наш список объектов на страницы. Более подробно про систему пагинации вы можете узнать в документации.
Атрибут object_list хранит список наших объектов. Необходимо помнить о том, что мы должны не забыть передать текущую страницу нашему отображению для корректной работы. Наиболее простой способ — использование именованных групп в нашем файле urls.py

url(r'^page/(?P<page>\d+)/$', PrivatePostList.as_view()),


Разбавим описание небольшим примером использования:
# coding: utf-8

from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from django.views.generic.list import ListView
from content.models import Post

class PrivatePostList(ListView):
    model = Post
    paginate_by = 10
    context_object_name = 'posts'
    template_name = 'private_posts.html'

    @method_decorator(login_required)
    def dispatch(self, request, *args, **kwargs):
        """
        Декорируем диспетчер функцией login_required, чтобы запретить просмотр отображения неавторизованными
        пользователями
        """
        return super(PrivatePostList, self).dispatch(request, *args, **kwargs)

    def get_queryset(self):
        """
        Список наших объектов будет состоять лишь из приватных и не удаленных статей
        """
        return Post.objects.filter(is_private=True, is_deleted=False)

А вот, примерно, так это может выглядеть в шаблоне:

{% extends 'base.html' %}

{% block title %}Список приватных статей{% endblock %}

{% block content %}
    {% for post in object_list %}
        <div class="post">
            <div class="post_name">{{ post.name }}</div>
            <div class="post_text">{{ post.text|safe }}</div>
            <div class="post_info">{{ post.author.username }}, {{ post.created_at|date:"d.m.Y в H:i" }}</div>
        </div>
    {% empty %}
        <p>Статей не обнаружено :(</p>
    {% endfor %}

    {% if paginator.num_pages > 1 %}
        {% if page_obj.has_previous %}
            <a href="/page{{ page_obj.previous_page_number }}/">&larr;</a>
        {% endif %}
        <span class="current_page">{{ paginator.number }}</span>
        {% if page_obj.has_next %}
            <a href="/page{{ page_obj.next_page_number }}">&rarr;</a>
        {% endif %}
    {% endif %}

{% endblock %}


Думаю на этом этапе можно закончить рассмотрение функциональности DetailView, если я все же пропустил что-то важное, то прошу сообщить и я дополню статью.

Просмотр информации об отдельном объекте

Теперь настала пора рассмотреть функциональность класса DetailView, который отвечает за просмотр отдельного объекта.
Чтобы получить единичный объект, нам необходимо его идентифицировать по какому-нибудь параметру. Обычно для этого используется так называемый уникальный первичный ключ (pk, id). Django также позволяет идентифицировать объект по полю slug, которое может быть любым уникальным словом. Разумеется для SEO иногда удобнее использовать именно slug, в случае если объектами выступают статьи или список пользователей (как, например здесь, на Хабре). Однако в других случаях использовать для идентификации slug нет смысла (например просмотр комментария или личного сообщения). В таких случаях используется первичный ключ.
После того, как мы выбрали способ идентификации объекта, мы должны сообщить Django о своем выборе, передав переменную с соответствующим именем с помощью маршрута файла urls.py.
url(r'^post/(?P<pk>\d+)/$', PostDetail.as_view()),

Какой бы способ идентификации объекта мы не приняли, наш объект будет доступен с помощью метода get_object. Этот метод поочередно пытается найти в маршруте переменные pk и slug, в данном случае переменная с именем pk будет обладать большим приоритетом. С помощью метода get_slug_field мы можем переопределить имя поля slug нашей модели. По умолчанию данный метод возвращает значение атрибута slug_field. Наш объект хранится в атрибуте object.

from django.http import Http404
from django.views.generic.detail import DetailView
from content.models import Post

class PostView(DetailView):
    model = Post
    context_object_name = 'post'
    template_name = 'private_post_detal.html'

    def get_object(self):
        """
        Для неавторизованного пользователя возвращает 404 ошибку
        Конечно мы можем как и в предыдущем примере использовать декоратор login_required
        """
        object = super(PostView, self).get_object()
        if not self.request.user.is_authenticated():
            raise Http404
        return object


На данном этапе я думаю мы можем закончить рассмотрение классов ListView и DetailView. В следующей статье я постараюсь написать про FormView и работу с формами. Спасибо, что уделили время на прочтение. Как обычно я открыт для критики и сообщений о недочетах. Желаю всем удачных выходных дней!)

+8
23.6k 75
Comments 13
Top of the day