Что такое кастомный менеджер модели в Django?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Кастомный менеджер модели в Django: Расширенный QuerySet
Кастомный менеджер модели — это объект (Manager), который определяет методы для выполнения запросов к БД для конкретной модели. По умолчанию Django создает менеджер objects, но можно создать свой менеджер с кастомными методами для более удобной работы с БД.
Зачем нужен кастомный менеджер
# БЕЗ кастомного менеджера — много повторяющегося кода
active_users = User.objects.filter(is_active=True)
active_admins = User.objects.filter(is_active=True, is_staff=True)
recent_users = User.objects.filter(is_active=True).order_by('-created_at')[:10]
# С кастомным менеджером — чистый и понятный код
active_users = User.objects.active() # ✓ Лучше!
admins = User.objects.admins() # ✓ Лучше!
recent = User.objects.active().recent(limit=10) # ✓ Лучше!
Кастомный менеджер инкапсулирует логику запросов и делает их переиспользуемыми.
Базовая структура
from django.db import models
class CustomManager(models.Manager):
"""Кастомный менеджер для модели"""
def get_queryset(self):
"""Переопределяем базовый QuerySet"""
# Может применять фильтры по умолчанию
return super().get_queryset().filter(is_deleted=False)
def custom_method(self):
"""Добавляем кастомный метод"""
return self.get_queryset().filter(is_active=True)
class MyModel(models.Model):
name = models.CharField(max_length=100)
is_active = models.BooleanField(default=True)
is_deleted = models.BooleanField(default=False)
objects = CustomManager() # Применяем менеджер
# Использование
active_items = MyModel.objects.custom_method()
Пример 1: Менеджер для "мягкого" удаления (soft delete)
from django.db import models
from django.utils import timezone
class SoftDeleteManager(models.Manager):
"""Менеджер для работы с мягким удалением"""
def get_queryset(self):
# По умолчанию показываем только не удаленные
return super().get_queryset().filter(deleted_at__isnull=True)
def deleted(self):
# Показываем только удаленные
return super().get_queryset().filter(deleted_at__isnull=False)
def all_with_deleted(self):
# Показываем все, включая удаленные
return super().get_queryset()
class User(models.Model):
username = models.CharField(max_length=100)
email = models.EmailField()
deleted_at = models.DateTimeField(null=True, blank=True)
objects = SoftDeleteManager()
def soft_delete(self):
"""Мягкое удаление"""
self.deleted_at = timezone.now()
self.save()
def restore(self):
"""Восстановление"""
self.deleted_at = None
self.save()
# Использование
active_users = User.objects.all() # Только активные
deleted_users = User.objects.deleted() # Только удаленные
all_users = User.objects.all_with_deleted() # Все
Пример 2: QuerySet расширение (очень популярно)
Чтобы добавить методы к QuerySet, создаём кастомный QuerySet класс:
from django.db import models
class UserQuerySet(models.QuerySet):
"""Кастомный QuerySet для User"""
def active(self):
"""Только активные пользователи"""
return self.filter(is_active=True)
def admins(self):
"""Только администраторы"""
return self.filter(is_staff=True)
def recently_created(self, days=7):
"""Созданные в последние N дней"""
from datetime import timedelta
from django.utils import timezone
cutoff = timezone.now() - timedelta(days=days)
return self.filter(created_at__gte=cutoff)
def with_posts_count(self):
"""Добавляет количество постов (отсчет)"""
from django.db.models import Count
return self.annotate(posts_count=Count('posts'))
def ordered_by_activity(self):
"""Сортируем по последней активности"""
return self.order_by('-last_login')
class UserManager(models.Manager):
"""Менеджер, который возвращает кастомный QuerySet"""
def get_queryset(self):
return UserQuerySet(self.model, using=self._db)
# Теперь все методы QuerySet доступны прямо на менеджере
def active(self):
return self.get_queryset().active()
def admins(self):
return self.get_queryset().admins()
def recently_created(self, days=7):
return self.get_queryset().recently_created(days=days)
def with_posts_count(self):
return self.get_queryset().with_posts_count()
def ordered_by_activity(self):
return self.get_queryset().ordered_by_activity()
class User(models.Model):
username = models.CharField(max_length=100)
email = models.EmailField()
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
last_login = models.DateTimeField(null=True)
objects = UserManager()
# Использование
active_admins = User.objects.active().admins() # Цепирование!
recent_with_count = User.objects.recently_created(days=30).with_posts_count()
ordered = User.objects.ordered_by_activity()
Пример 3: Сложная логика в менеджере
from django.db import models
from django.db.models import Q, Count, Avg
from datetime import timedelta
from django.utils import timezone
class OrderQuerySet(models.QuerySet):
def pending(self):
return self.filter(status='pending')
def completed(self):
return self.filter(status='completed')
def recent(self, hours=24):
cutoff = timezone.now() - timedelta(hours=hours)
return self.filter(created_at__gte=cutoff)
def expensive(self, min_price=1000):
"""Дорогие заказы"""
return self.filter(total_price__gte=min_price)
def high_value_customers(self):
"""Клиенты с суммой заказов > $5000"""
return self.values('customer').annotate(
total=Sum('total_price')
).filter(total__gt=5000)
def with_stats(self):
"""Добавляет статистику"""
return self.annotate(
items_count=Count('items'),
avg_item_price=Avg('items__price')
)
def search(self, query):
"""Полнотекстовый поиск"""
return self.filter(
Q(order_id__icontains=query) |
Q(customer__name__icontains=query) |
Q(customer__email__icontains=query)
)
class OrderManager(models.Manager):
def get_queryset(self):
return OrderQuerySet(self.model, using=self._db)
def pending(self):
return self.get_queryset().pending()
def completed(self):
return self.get_queryset().completed()
def recent(self, hours=24):
return self.get_queryset().recent(hours=hours)
def expensive(self, min_price=1000):
return self.get_queryset().expensive(min_price=min_price)
def high_value_customers(self):
return self.get_queryset().high_value_customers()
def with_stats(self):
return self.get_queryset().with_stats()
def search(self, query):
return self.get_queryset().search(query)
class Order(models.Model):
order_id = models.CharField(max_length=50)
customer = models.ForeignKey('Customer', on_delete=models.CASCADE)
total_price = models.DecimalField(max_digits=10, decimal_places=2)
status = models.CharField(max_length=20, choices=[
('pending', 'Pending'),
('completed', 'Completed')
])
created_at = models.DateTimeField(auto_now_add=True)
objects = OrderManager()
# Использование
pending_expensive = Order.objects.pending().expensive(min_price=500)
recent_completed = Order.objects.recent(hours=48).completed()
results_with_stats = Order.objects.with_stats()
high_value = Order.objects.high_value_customers()
search_results = Order.objects.search("John")
Пример 4: Несколько менеджеров на одной модели
from django.db import models
class ArticleQuerySet(models.QuerySet):
def published(self):
return self.filter(status='published')
def drafts(self):
return self.filter(status='draft')
class ArticleManager(models.Manager):
def get_queryset(self):
return ArticleQuerySet(self.model, using=self._db)
def published(self):
return self.get_queryset().published()
class PublishedOnlyManager(models.Manager):
"""Менеджер только для опубликованных статей"""
def get_queryset(self):
return super().get_queryset().filter(status='published')
class Article(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
status = models.CharField(max_length=20, choices=[
('draft', 'Draft'),
('published', 'Published')
])
# Несколько менеджеров
objects = ArticleManager() # По умолчанию, все статьи
published_objects = PublishedOnlyManager() # Только опубликованные
# Использование
all_articles = Article.objects.all() # Все
published = Article.published_objects.all() # Только опубликованные
drafts = Article.objects.drafts() # Только черновики
Лучшие практики
# ✓ ХОРОШО: Менеджер инкапсулирует логику
class BlogQuerySet(models.QuerySet):
def recent(self):
return self.order_by('-published_date')[:10]
def by_author(self, author):
return self.filter(author=author)
class BlogManager(models.Manager):
def get_queryset(self):
return BlogQuerySet(self.model, using=self._db)
def recent(self):
return self.get_queryset().recent()
Posts.objects.recent() # Чисто
# ✗ ПЛОХО: Логика в представлении
# posts = Post.objects.filter(...).order_by(...)
# # Снова тот же фильтр в другом месте?
# ✓ ХОРОШО: Для фильтрации используй QuerySet методы
recent_posts = Post.objects.recent().by_author(user)
# ✗ ПЛОХО: Логика в Python (неэффективно)
posts = list(Post.objects.all())
recent = [p for p in posts if (timezone.now() - p.created).days < 7]
# ✓ ХОРОШО: Используй select_related и prefetch_related
class CommentQuerySet(models.QuerySet):
def with_author_posts(self):
return self.select_related('author__user').prefetch_related('post__comments')
comments = Comment.objects.with_author_posts() # Оптимизирует N+1 query
Пример: Интеграция с Django Admin
from django.contrib import admin
@admin.register(User)
class UserAdmin(admin.ModelAdmin):
list_display = ('username', 'email', 'is_active', 'created_at')
def get_queryset(self, request):
# Используем кастомный менеджер в админке
qs = super().get_queryset(request)
# Оптимизируем запросы
return qs.select_related('profile').prefetch_related('posts')
Резюме
Кастомный менеджер модели в Django — это класс, расширяющий models.Manager, который позволяет добавлять свои методы для запросов к БД. Это делает код чище, переиспользуемым и более оптимальным. Обычно создают кастомный QuerySet с методами фильтрации, затем обвязывают его в Manager для доступа через Model.objects. Это стандартная практика в Django для работы с БД.