Django 5 для начинающих

Прогресс по курсу:  9/1004

10.4 Работа с DetailView, форматирование и обработка кириллицы в Slug
2 из 4 шагов пройдено
0 из 8 баллов  получено

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


Если мы решим написать представление на View, то оно будет выглядеть примерно таким:

class Product(View):
    def get(self, request, *args, **kwargs):
        product = get_object_or_404(Product, pk=kwargs['pk'])
        context = {'product': product}
        return render(request, 'base/products_detail.html', context)


Как и в прошлом разделе, мы должны написать метод get в нашем классе, в котором мы должны указать запрос и в какой шаблон мы будем выводить результат.

Но проще всего будет работать через класс DetailView, который отвечает за просмотр отдельного объекта, рассмотрим его структуру:

DetailView наследует непосредственно от 1 представления и 1 миксина, но содержит в себе в общей сложности 2 представления и 4 миксина, которые дают этому представлению все атрибуты и методы, которые он имеет.

DetailView может идентифицировать объект(используется метод filter()), как по полю первичного ключа - pk, id, так и по полю слага - slug, приняв значения из параметров pk или slug передаваемых в URL-адресе.

Важными атрибутами класса DetailView являются slug_url_kwarg, pk_url_kwarg и slug_field.

  • slug_url_kwarg: задаёт имя параметра, передаваемого в URL-адресе, для идентификации по полю слага, значение по умолчанию 'slug'.
  • pk_url_kwarg: задаёт имя параметра, передаваемого в URL-адресе, для идентификации по полю первичного ключа, значение по умолчанию 'pk'.
  • slug_field: задаёт поле слага, значение по умолчанию 'slug'.
  • query_pk_and_slug: для идентификации по полям слага и первичного ключа одновременно, значение по умолчанию False.

Полный список атрибутов и методов мы можем посмотреть по этой ссылке.


Приступим к практике, перейдем в файл views.py приложения blog и создадим наше первое представление для вывода списка статей:

from django.views.generic import ListView, DetailView
from .models import Post


# код класса PostListView


class PostDetailView(DetailView):
    model = Post
    template_name = 'blog/post_detail.html'
    context_object_name = 'post'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['title'] = self.object.title
        return context
  • model - название нашей модели, Post.
  • template_name - По умолчанию DetailView ищет шаблон с префиксом имени модели и суффиксом _detail.html, если не установлено иное.
  • context_object_name - переопределим имя Queryset по умолчанию.
  • context['title] - наш заголовок передаваемый в шаблон, а self.object.title - это наш объект, т.е наша статья, у которой мы получаем заголовок.


Нам необходимо добавить представление на основе классов PostDetailView в маршруты, добавим в файл urls.py приложения blog следующий код:

from django.urls import path
from .views import PostListView, PostDetailView

urlpatterns = [
    path('', PostListView.as_view(), name='home'),
    path('post/<str:slug>/', PostDetailView.as_view(), name='post_detail'),

]

В этом коде наше представление PostDetailView получает значение из параметра slug, передаваемого в URL-адресе.


Следующим шагом нам нужна функция, которая будет возвращать адрес у постов, для этого в модели Post добавим функцию:

from django.db import models
from django.core.validators import FileExtensionValidator
from django.contrib.auth.models import User
from django.urls import reverse
from mptt.models import MPTTModel, TreeForeignKey

class Post(models.Model):
    """
    Модель постов для нашего блога
    """

   # поля модели

    class Meta:
        db_table = 'blog_post'
        ordering = ['-fixed', '-create']
        indexes = [models.Index(fields=['-fixed', '-create', 'status'])]
        verbose_name = 'Статья'
        verbose_name_plural = 'Статьи'

    def __str__(self):
        return self.title

    def get_absolute_url(self):
        """
        Получаем прямую ссылку на статью
        """
        return reverse('post_detail', kwargs={'slug': self.slug}) 

Данный метод позволяет получать прямую ссылку на статью, без вызова {% url '' %} Также мы импортировали reverse для формирования правильной ссылки.

Также изменим шаблон вывода списка статей, для этого откроем templates/blog/post_list.html и изменим код на следующий:

{% extends 'main.html' %}

{% block content %}
    {% for post in posts %}
        <img src={{ post.thumbnail.url }} alt={{ post.title }} width="150"/>
        <strong><a href="{{ post.get_absolute_url }}">{{ post.title }}</a></strong>
        <p>{{ post.description }}</p>
        <small>{{ post.create }}</small>
        <hr>
        <span>{{ post.category.title }}</span>
    {% endfor %}
{% endblock %}


Как мы видим, мы добавили ссылку на пост в имени записи. Теперь создадим шаблон полной статьи: templates/blog/post_detail.html:

{% extends 'main.html' %}

{% block content %}
        <img src={{ post.thumbnail.url }} alt={{ post.title }} width="250"/>
        <strong>{{ post.title }}</strong>
        <p>{{ post.description }}</p>
        <p>{{ post.text }}</p>
        <small>{{ post.create }}</small>
        <hr>
        <span>{{ post.category.title }}</span>
{% endblock %}

При работе через класс DetailView нам не нужен цикл, поэтому напрямую обращаемся к post, это переменная, которую мы задали в context_object_name = 'post'.

Запустим сервер и перейдем на главную страницу проекта:


Мы видим что название нашей записи стало ссылкой, нажмем на нее:


Как мы видим, вывод записи работает прекрасно. В следующем шаге мы настроим наши slug, чтобы они создавались автоматически и могли обрабатывать кириллицу.


  • Комментария
Будьте вежливы и соблюдайте наши принципы сообщества. Пожалуйста, не оставляйте решения и подсказки в комментариях, для этого есть отдельный форум.
Оставить комментарий

class Product(View):

    def get(self, request, *args, **kwargs):

        products = get_object_or_404(Product, pk=kwargs['pk'])

        context = {'product': product}

        return render(request, 'base/products_detail.html', context)

А так точно будет работать?)

@Aleksandr_Gurov, Спасибо, поправил опечатку)

Так как ListView изначально унаследован от ContextMixin, можно несколько упростить присвоение заголовка, и вместо

class PostDetailView(DetailView):
    model = Post
    template_name = 'blog/post_detail.html'
    context_object_name = 'post'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['title'] = self.object.title
        return context

написать

class PostListView(ListView):
    model = Post
    template_name = 'blog/post_list.html'
    context_object_name = 'posts'
    extra_context = {'title': 'Главная страница'}

Если атрибут extra_context задан, то его содержимое автоматом добавляется в контекст миксином.
Но это "прокатит" только для статических строк, никакие специфические данные объектов из БД так использовать не получится

Изменен ilya kutaev

Из-за чего может быть такая ошибка? Как будто он не может достать абсолютный урл из модели, но вроде все копировал в точности (видимо сейчас окажется что нет))

@Георгий_Тимофеев, ошибка была (внезапно) в отсутствии слага в моем единственном посте)) привык что он автоматически прописывается..

@Георгий_Тимофеев, мы как раз в следующем шаге это сделаем)