Создание сайта на Django: Урок 17, свой кастомный менеджер QuerySet
avatar
7 | (offline)
❤️‍🔥Notehunter Developer
Добавлено:
Категория: Руководства «Django»
Комментариев: 0

Создание своего кастомного менеджера для модели в Django

Для начала я создам папку managers с __init__.py файлом в нашем приложении blog

Напоминаю, что в моем проекте структура декомпозирована. Декомпозиция выглядит следующим образом, как и в многих CMS

  • modules
    • blog
      • migrations
      • managers
        • __init__.py
        • articles.py
      • models
        • __init__.py
        • categories.py
        • articles.py
      • views
        • articles.py

Примечание: Вы можете не следовать моему примеру, а делать все в папке models, либо в файле managers.py внутри папки blog.

В файле managers/articles.py добавляем следующее содержимое:

modules/blog/managers/articles.py

from django.db import models


class ArticleManager(models.Manager):
    """
    Кастомный менеджер для модели статей.

    """

    def all(self):
        """
        Список статей (SQL запрос с фильтрацией для страницы списка статей)
        """
        return self.get_queryset().filter(is_published=True)

Пояснения:

  • Наследуемся от models.Manager (импортируя from django.db import models)
  • Метод all() возвращается полученный qs с фильтром флага is_published (опубликовано)

В одном из уроков я добавлю и оптимизацию к нашему менеджеру.

Если вы используете структуру файлов как у меня в проекте, то в файле __init__.py мы должны сделать следующее:

from modules.blog.managers.articles import *

__all__ = '__all__'

Пояснения:

  • Делаю я это для того, чтоб можно было импортировать все с managers, а не с managers.articles

Подключение менеджера к модели Django

Редактируем нашу модель статей, добавим ей наш менеджер

modules/blog/models/articles.py

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

from modules.blog.managers import ArticleManager
from modules.system.services.utils import ImageDirectorySave, unique_slugify


class Article(models.Model):
    title = models.CharField(verbose_name='Заголовок', max_length=255)
    slug = models.SlugField(verbose_name='URL', max_length=255, blank=True)
    category = TreeForeignKey('Category', on_delete=models.PROTECT, related_name='articles', verbose_name='Категория')
    short_description = models.TextField(max_length=300, verbose_name='Краткое описание')
    full_description = models.TextField(verbose_name='Описание')
    author = models.ForeignKey(
        User, verbose_name='Автор материала',
        on_delete=models.PROTECT,
        related_name='article_author'
    )
    updated_by = models.ForeignKey(User,
                                   verbose_name='Автор обновления',
                                   on_delete=models.PROTECT,
                                   related_name='article_updated_by',
                                   blank=True,
                                   null=True
    )
    created_at = models.DateTimeField(verbose_name='Дата добавления', auto_now_add=True, db_index=True)
    updated_at = models.DateTimeField(verbose_name='Дата обновления', auto_now=True, db_index=True)
    reason = models.CharField(verbose_name='Причина обновления', blank=True, max_length=100)
    is_fixed = models.BooleanField(verbose_name='Зафиксировано', default=False, db_index=True)
    is_published = models.BooleanField(verbose_name='Опубликовано', default=True)
    thumbnail = models.ImageField(
        verbose_name='Превью поста',
        blank=True,
        upload_to= ImageDirectorySave('images/thumbnails/'),
        validators=[FileExtensionValidator(
            allowed_extensions=('png', 'jpg', 'webp', 'jpeg', 'gif'))
        ]
    )

    objects = models.Manager()
    custom = ArticleManager()

    class Meta:
        """
        Сортировка, название модели в админ панели, таблица в данными
        """
        ordering = ('-is_fixed', '-created_at')
        verbose_name = 'Статья'
        verbose_name_plural = 'Статьи'
        db_table = 'app_articles'

    def save(self, *args, **kwargs):
        """
        Сохранение полей модели при их отсутствии заполнения
        """
        if not self.slug:
            self.slug = unique_slugify(self, self.title)
        super().save(*args, **kwargs)

    def __str__(self):
        """
        Возвращение строки в виде заголовка статьи
        """
        return self.title

    @property
    def get_thumbnail(self):
        """
        Получение аватара при отсутствии загруженного
        """
        if not self.thumbnail:
            return '/media/images/placeholder.png'
        return self.thumbnail.url

    def get_absolute_url(self):
        """
        Ссылка на статью
        """
        return reverse('article-detail', kwargs={'slug': self.slug})

Пояснения:

  • Для примера я не переопределял стандартный менеджер, а добавил custom. Но можно и переопределить стандартный менеджер objects = ArticleManager(). На деплое notehunter.net переопределен стандартный менеджер)
  • Обязательно укажите стандартный менеджер, если вы его не хотите переопределять: objects = models.Manager(), иначе Article.objects.all() не будет работать.

Как работать с новым менеджером?

Чтобы вызвать список статей, мы можем воспользоваться методом из нашего кастомного менеджера.

articles_list = Article.custom.all()

Но и файле views/articles.py для списка нам нужно подкорректировать под кастомный менеджер вывод статей:

class ArticleListView(ListView):
    model = Article
    template_name = 'modules/blog/articles/article-list.html'
    context_object_name = 'articles'
    paginate_by = 1
    queryset = Article.custom.all()

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['title'] = 'Главная страница'
        return context

Примечание: если мы просто переопределить objects = ArticleManager(), то в views/articles.py не нужно добавлять queryset = Article.custom.all()

Таким образом у нас выведутся все фильтрованные статьи. Для примера я добавлю 3 статью на сайт и сделаю ее непубличной.

Скриншоты ниже мои последовательные действия:

Я добавил статью, но непублично 

Проверяю на сайте: (как видите, ее нет)

Делаю статью публичной:

На сайте она доступна:

Таким образом, через менеджеры мы можем задавать различные фильтры для узконаправленных методов показа на странице. 

Конечно, мы можем сделать тоже самое во views/articles.py в параметре queryset обычного менеджера objects:

class ArticleListView(ListView):
    model = Article
    template_name = 'modules/blog/articles/article-list.html'
    context_object_name = 'articles'
    paginate_by = 1
    queryset = Article.objects.all().filter(is_published=True)

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['title'] = 'Главная страница'
        return context

Но, лучше все таки делать это через менеджеры.

Комментарии к статье 0
Комментариев нет
Форма добавления комментария (необходима регистрация)