Создание своего кастомного менеджера для модели в Django
Для начала я создам папку managers с __init__.py файлом в нашем приложении blog.
Напоминаю, что в моем проекте структура декомпозирована. Декомпозиция выглядит следующим образом, как и в многих CMS.
- modules
- blog
- migrations
- managers
- __init__.py
- articles.py
- models
- __init__.py
- categories.py
- articles.py
- views
- articles.py
- blog
Примечание: Вы можете не следовать моему примеру, а делать все в папке 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
Но, лучше все таки делать это через менеджеры.