← Назад к вопросам

Что такое кастомный менеджер модели в Django?

2.0 Middle🔥 71 комментариев
#Python Core#Soft Skills

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Кастомный менеджер модели в 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 для работы с БД.

Что такое кастомный менеджер модели в Django? | PrepBro