Использование related_name и Manager в view-функции

У меня есть модели в Django. Объявлен новый атрибут published у модели Post и присвоен PostManager.

from core.models import PublishedModel
from django.contrib.auth import get_user_model
from django.db import models
from django.utils import timezone

User = get_user_model()


class Category(PublishedModel):
    title = models.CharField(
        'Заголовок',
        max_length=256,
        blank=False
    )
    description = models.TextField(
        verbose_name='Описание',
        blank=False
    )
    slug = models.SlugField(
        'Идентификатор',
        max_length=64,
        unique=True,
        help_text='Идентификатор страницы для URL; '
                  'разрешены символы латиницы, '
                  'цифры, дефис и подчёркивание.'
    )

    class Meta:
        verbose_name = 'категория'
        verbose_name_plural = 'Категории'

    def __str__(self):
        return self.title


class Location(PublishedModel):
    name = models.CharField(
        'Название места',
        max_length=256,
        blank=False
    )

    class Meta:
        verbose_name = 'местоположение'
        verbose_name_plural = 'Местоположения'

    def __str__(self):
        return self.name21


class PostQuerySet(models.QuerySet):
    def published(self):
        return self.filter(
            is_published=True,
            pub_date__date__lte=timezone.now(),
            category__is_published=True
        )


class PublishedPostManager(models.Manager):

    def published(self):
        return PostQuerySet(self.model, using=self._db).published()


class Post(PublishedModel):
    objects = models.Manager()
    published = PublishedPostManager()
    title = models.CharField(
        'Заголовок',
        max_length=256,
        blank=False
    )
    text = models.TextField('Текст', blank=False)
    pub_date = models.DateTimeField(
        'Дата и время публикации',
        auto_now_add=False,
        help_text='Если установить дату и '
                  'время в будущем — можно делать '
                  'отложенные публикации.'
    )

    author = models.ForeignKey(
        User,
        verbose_name='Автор публикации',
        on_delete=models.CASCADE,
        blank=False
    )

    category = models.ForeignKey(
        Category,
        on_delete=models.SET_NULL,
        related_name='posts',
        verbose_name='Категория',
        null=True,
        blank=False
    )
    location = models.ForeignKey(
        Location,
        on_delete=models.SET_NULL,
        related_name='posts',
        verbose_name='Местоположение',
        null=True
    )

    class Meta:
        verbose_name = 'публикация'
        verbose_name_plural = 'Публикации'

    def __str__(self):
        return self.title

views.py

from django.conf import settings
from django.shortcuts import get_object_or_404, render
from django.utils import timezone

from .models import Category, Post


def index(request):
    template = 'blog/index.html'
    post_list = (
        Post.published.published()
        .order_by('-pub_date')
        [:settings.POSTS_ON_PAGE]
    )
    context = {'post_list': post_list}
    return render(request, template, context)


def post_detail(request, id):
    template = 'blog/detail.html'
    post = get_object_or_404(
        Post.published.published()
        | Post.objects
        .filter(author_id=request.user.id),
        id=id
    )
    context = {'post': post}
    return render(request, template, context)


def category_posts(request, category_slug):
    template = 'blog/category.html'
    category = get_object_or_404(
        Category,
        slug=category_slug,
        is_published=True
    )
    posts = category.posts.filter(
        is_published=True,
        pub_date__lte=timezone.now()
    )
    context = {'category': category, 'post_list': posts}
    return render(request, template, context)

Код работает, но есть комментарии ревьюера:

  1. post_list = (Post.published.published() Нам не нужно звать тут метод published(), его уже вызывает менеджер.
  2. posts = category.posts.filter( Когда мы используем related_name, мы можем указать менеджер, который хотим использовать, это позволит нам переиспользовать методы, это позволит избежать сортировок руками. category.posts(manager='')

При замене:

post_list = (
        Post.published
        .order_by('-pub_date')
        [:settings.POSTS_ON_PAGE]
    )
...
post = get_object_or_404(
        Post.published
        | Post.objects
        .filter(author_id=request.user.id),
        id=id
    )
...
posts = category.posts(manager='published')

Условия из метода published() не выполняются. Не могу разобраться, как правильно работать с related_name и Manager в view-функции. Чтение документации ситуацию не прояснило.

  1. Переопределен метод published.
class PostQueryset(models.QuerySet):
    def published(self):
        return self.filter(
            is_published=True,
            pub_date__lt=timezone.now(),
            category__is_published=True
        ).select_related('author', 'category', 'location')


class PublishedPostManager(models.Manager):
    def get_queryset(self):
        return PostQueryset(self.model, using=self._db).published()


class Post(PublishedModel):
    objects = PostQueryset.as_manager()
    published = PublishedPostManager()
  1. Вызов метода:
def index(request):
    template_name = 'blog/index.html'
    post_list = (
        Post
        .published
        .order_by('-pub_date')
        [:settings.POSTS_ON_PAGE]
    )
    context = {
        'post_list': post_list
    }
    return render(request, template_name, context)


def post_detail(request, id):
    template_name = 'blog/detail.html'
    post = get_object_or_404(
        Post.published, pk=id
    )
    context = {
        'post': post,
    }
    return render(request, template_name, context)


def category_posts(request, category_slug):
    template = 'blog/category.html'
    category = get_object_or_404(
        Category,
        slug=category_slug,
        is_published=True
    )
    posts = category.posts(manager='published').all()
    context = {'category': category, 'post_list': posts}
    return render(request, template, context)

Главная проблема была в определении метода published.

Добавлен метод select_related() - который позволяет получать связанные объекты из базы данных вместе с основным объектом.

Переопределяется метод objects = PostQueryset.as_manager() QuerySet.as_manager() может использоваться для создания экземпляра Manager с копией пользовательских методов QuerySet:

class Post(PublishedModel):
    objects = PostQueryset.as_manager()

Экземпляр Manager, созданный QuerySet.as_manager(), будет практически идентичен PublishedPostManager.

  1. related_name - создаваемое Django имя для обратной связи от модели Category к Post. related_name используется для создания более осмысленного и удобного имени для обратной связи между моделями.

category.posts(manager='<метод>') posts = category.posts(manager='published').all()

https://docs.djangoproject.com/en/5.0/topics/db/managers/

https://django.fun/docs/django/5.0/topics/db/managers/

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