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

    Доброго времени суток, уважаемые читатели! Не так давно мной была опубликована первая часть статьи на данную тему. Я хочу еще раз поблагодарить всех пользователей, высказавших конструктивную критику, благодаря которой примеры в статье удалось привести к почти идеальному виду. В то же время я понял, что данный формат подачи материала является неэффективным: мы рассмотрели всего пару методов, реализованных в Class Based View (далее по тексту CBV). Во второй части я решил переработать подачу и далее постараюсь описать максимально возможное количество методов, представленных в API. Постараюсь, также, охватить те методы, которые были упущены в первой части. Очень надеюсь на конструктивную поддержку читателей и надеюсь, что и в дальнейшем у нас получится продуктивный диалог, в результате которого статья станет еще более информативной.


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

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

    dispatch
    get_context_data
    get_template_names
    get_context_object_name
    get
    post
    put
    delete
    head
    options
    trace
    render_to_response
    get_queryset

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

    object
    template_name
    template_name_suffix
    context_object_name
    http_method_names
    queryset
    model

    Общая информация

    Для начала определимся, что такое вообще эти CBV, нужны ли они и где их стоит использовать. Наверное стоит подчеркнуть, что особого выбора нам разработчики Django не предоставляют. Скорее всего уже с 1.4 версии (судя по транку) generic views будут объявлены как deprecated, а в одной из следующих версий могут вообще отказаться от их использования. Поэтому я решил, как это часто бывает, не откладывать изучение в долгий ящик, а начать работу с CBV прямо сейчас. Собственно, за неимением подробной информации, данная статья и будет написана на личных наблюдениях.
    Итак, CBV позволяет нам использовать особенности объектно-ориентированного программирования при разработке наших отображений. Теперь мы можем реализовывать базовые классы, несущие определенную функциональность и использовать их как примеси (mixins) для наших отображений.

    Все отображения имеют точку входа, который можно считать и конструктором, это метод dispatch. Данный метод принимает аргумент request, являющийся экземпляром объекта HttpRequest, в качестве первого аргумента. В этом его основная схожесть с function based views. Также данный метод принимает все переданные отображению переменные в качестве именованных и неименованных позиционных аргументов (*args/**kwargs). Рассмотрим пару примеров работы с данным методом.
    Метод dispatch является наилучшим местом, для использования декораторов. Для их указания необходимо использовать реализованный в django декоратор method_decorator. Допустим нам необходимо предоставить доступ к определенному списку объектов лишь для авторизованных пользователей:

    from django.contrib.auth.decorators import login_required
    from django.utils.decorators import method_decorator
    from django.views.generic import ListView
    
    class PostList(ListView):
    
        model = Post
    
        @method_decorator(login_required())
        def dispatch(self, request, *args, **kwargs):
            return super(PostList, self).dispatch(request, *args, **kwargs)


    Имейте ввиду, что метод dispatch для корректной работы должен возвращать ссылку на родительский диспетчер. В противном случае не произойдет корректной передачи управления.

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

    from django.views.generic.detail import DetailView
    from models import Post, Comment
    
    class PostDetail(DetailView):
        
        model = Post
    
        def get_context_data(self, **kwargs):
            context = super(PostDetail, self).get_context_data(**kwargs)
            context['comments'] = Comment.objects.filter(post=self.object, is_delete=False).order_by('-created_at')
            return context
    


    Несколько забегая вперед уточню, что получить доступ к текущему объекту при реализации DetailView можно через атрибут object.

    Если мы хотим узнать (или задать) имя для шаблона, который будет использовать наше отображение, то нам нужно обратить внимание на метод get_template_names. Данный метод возвращает список имен шаблонов, которые Django будет пытаться использовать в порядке их указания. По умолчанию наивысшим приоритетом будет обладать шаблон, указанный в атрибуте template_name нашего объекта. Если данный атрибут не указан, то Django будет пытаться использовать шаблон «название_приложения/имя_объекта_префикс». Например для приложения content и модели post данный путь будет иметь вид «content/post_префикс.html». Префикс задается с помощью атрибута template_name_suffix, либо используется префикс по умолчанию для данного типа отображения. Например для ListView префикс носит имя "_list", для DetailView "_detail". Если мы просто хотим, чтобы наше отображение использовало шаблон, который мы хотим, то нам достаточно лишь указать его с помощью атрибута template_name. Переопределение метода get_template_names может потребоваться, если нам необходимо собирать имя шаблона по своим правилам.

    Часто бывает необходимо изменить имя переменной, под которой наш объект (или список объектов) доступен в шаблоне. Метод, отвечающий за данную функциональность носит имя get_context_object_name. Данный метод принимает в качестве аргумента объект (или список объектов). В случае с отдельным объектом имя переменной в шаблоне по умолчанию будет именем самого объекта. В случае со списком объектов это будет имя объекта с суффиксом _list. Например для объекта Post иимена переменных будут post и post_list для отдельного объекта и списка объектов соответственно. Мы можем явно указать имя переменной, если присвоим ее значение атрибуту context_object_name.

    Представим, что перед нами стоит задача по реализации RESTful приложения, CBV предоставляет исчерпывающий набор методов для их реализации. Чтобы реализовать работу приложения через определенный метод, нам нужно лишь переопределить одноименный метод в нашем отображении. Список доступных методов: get, post, put, delete, head, options, trace. Работа с формами упрощается в разы, ведь мы можем использовать метод get для отображения нашей формы, а ее обработку перенести в метод post. Требуется добавить какой-либо дополнительный специфический http метод? Нет проблем, достаточно лишь дополнить атрибут http_method_names, содержащий список имен доступных методов, а затем определить одноименный метод у себя в отображении. Все http методы должны возвращать объект HttpResponse. Если нам необходимо отрендерить шаблон нашего отображения, то мы можем для этого вызвать в вышеуказанных http методах метод render_to_response. Данный метод имеет не только такое же имя, как и его функциональный собрат, но и схожую функциональность.

    class PostDetail(DetailView):
    
        model = Post
        
        def get(self, request, **kwargs):
            return self.render_to_response(self.get_context_data(), **kwargs)


    Метод принимает в качестве аргумента требуемый контекст.

    Прежде чем вывести информацию в шаблон нам необходимо ее получить. В этом нам поможет метод get_queryset, задача которого вернуть объект QuerySet. По умолчанию данный метод возвращает атрибут queryset если он определен, либо список всех объектов модели, которая указана в атрибуте model. Мы можем переопределить данный метод, чтобы он удовлетворял нашим задачам.

    class PostList(ListView):
        
        model = Post
    
        def get_queryset(self):
            qs = Post.objects.filter(is_delete=False).order_by('-created_at')
            if not self.request.user.is_authenticated():
                return qs.exclude(is_private=True)
            return qs


    В данной части я постарался охватить по возможности все базовые (имеющиеся во всех типах CBV) методы. Если я что-то пропустил, то прошу сообщить и я дополню статью. Следующие части будут включать в себя описание особенностей отдельных типов CBV. Начну, пожалуй, с описания методов ListView и DetailView. Буду очень рад, если мои статьи помогут людям в поиске информации. Спасибо, что прочитали данную статью :)
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 9

      +2
      Но ведь никто не заставит переписывать свои view на CBV? :)
        +2
        Никто не заставляет, но некоторые вещи можно будет упростить.
        Я, например, переписал на CBV вьюхи в которых есть и GET и POST
        +1
        def get() и def post() напомнило о webpy…
          +2
          Спасибо большое за статьи, информации о class based views действительно немного. Да, переписывать уже имеющиеся вьюхи, скорее всего, не буду, но в новых проектах обязательно постараюсь использовать CBV. Раньше очень раздражала необходимость повторять одни и те же кусочки кода, чтобы создать интерфейс добавления/редактирования/удаления экземпляра — в итоге написал свой небольшой класс-прослойку, которым достаточно успешно пользуюсь — github
            +1
            Не то что бы прям канонически правильный, но всё же неплохой пример того, как CBV может облегчить жизнь: github.com/lig/django-supergeneric/
              0
              Хм… очень странно, но у меня ваш пример

              class PostDetail(DetailView):

              model = Post

              def get(self, request, **kwargs):
              return self.render_to_response(self.get_context_data(), **kwargs)

              Вызывает странную ошибку «object has no attribute 'object'»

              /usr/local/lib/python2.7/dist-packages/django/views/generic/detail.py in get_context_data
              context_object_name = self.get_context_object_name(self.object)

              Ни как не могу понять почему в этом случае не вызывается метод get_object, специально его определил, указал модель, но ошибку так и не победил. =(
                0
                Разобрался
                1. В методах get, post,… etc.
                надо перед self.get_context_data() всегда делать вот такую ерунду
                self.object = self.get_object() # мне это с самого начала показалось идиотизмом, но тут
                /usr/local/lib/python2.7/dist-packages/django/views/generic/detail.py (BaseDetailView-->get) я нашёл подтверждение

                2. Вместо
                return self.render_to_response(self.get_context_data(), **kwargs)
                должно быть
                return self.render_to_response(self.get_context_data())
                0
                Для меня за кулисам остались две вещи:

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

                class CategoriesList(ListView):
                
                    model = Post
                
                    def get_context_data(self, **kwargs):
                        context = super(CategoriesList, self).get_context_data(**kwargs)
                        select_post = Category.objects.get(id=1)
                        context['select_all_posts'] = select_post.tags.all()
                        return context
                


                Как получить id текущей категории вместо id=1?

                2. Получение прямой ссылки на элементы контекста. Пример:

                class PostDetailView(DetailView):
                
                    ''' Show single news '''
                    model = Post
                    template_name = 'news/one_new.html'
                    context_object_name = 'post'
                
                def get_next(self):
                .......
                
                def get_prev(self):
                .......
                
                def get_context_data(self, **kwargs):
                        context = super(PostDetailView, self).get_context_data(**kwargs)
                        context['next'] = self.get_next()
                        context['prev'] = self.get_prev()
                        return context
                


                В шаблоне выводим <a href="{{ next }} >Next</a>, в результате чего адрес текущей ссылки лишь дополняется именем следующего поста. Может, с помощью {% url %} как-то можно получить полный путь к next/prev?
                  0
                  некропостинг, конечно… но вдруг кому-то потом пригодится
                  1. Вообще не понятно, как Вы пытались выбранную категорию передать… и почему для класса CategoriesList в качестве модели Вы даёте Post
                  вариант а) нужно вывести список категорий и рядом с каждой — несколько постов
                  model=Category
                  

                  контекст не переопределяем и в шаблоне делаем перебор всего в category_list, добираясь в тч до постов через .post_set.all
                  вариант б) нужно вывести список постов в той же категории, что и текущий
                  class PostDetailView(DetailView):
                      model=Post
                  

                  контекст опять же не переопределяем, а в шаблоне можно сходить в post.category.post_set.all
                  хотя можно это же сделать и переопределив контекст, сперва добравшись до нужных данных через self.get_object()
                  вариант в) нужно вывести список постов заданной категории
                  class CategoryDetailView(DetailView):
                      model=Category
                  

                  ну и далее в шаблоне также будет всё необходимое

                  2. Что-то вроде
                  {% url "post_detail_view" next.slug %}

                Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                Самое читаемое