Skip to content

Commit 58b1675

Browse files
committed
improve l23
1 parent 1886325 commit 58b1675

File tree

2 files changed

+855
-81
lines changed

2 files changed

+855
-81
lines changed

lesson22.md

Lines changed: 202 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -158,8 +158,9 @@ class Profile(models.Model):
158158

159159
## objects и shell
160160

161-
Для доступа или модификации любых данных, у каждой модели есть атрибут `objects`, который позволяет производить
162-
манипуляции с данными. Он называется менеджер, и при желании его можно переопределить.
161+
Для доступа или модификации любых данных, у каждой модели есть атрибут `objects`. Это **менеджер** (Manager) — специальный объект, который предоставляет интерфейс для работы с базой данных. Именно через менеджер мы вызываем методы `all()`, `filter()`, `get()`, `create()` и другие.
162+
163+
По умолчанию Django создаёт менеджер `objects` для каждой модели автоматически. При необходимости его можно переопределить или добавить дополнительные менеджеры (об этом подробнее в разделе "Кастомные менеджеры" ниже).
163164

164165
Для интерактивного использования кода используется команда
165166

@@ -171,7 +172,7 @@ class Profile(models.Model):
171172

172173
Предварительно я создал несколько объектов через админку.
173174

174-
Для доступа к моделям их нужно импортировать:
175+
Для доступа к моделям их нужно импортировать(после версии 5.0 больше не нужно, все модели будут доступны из коробки):
175176

176177
```python
177178
from blog.models import Topic, Article, Comment
@@ -737,6 +738,204 @@ class MyAwesomModel(models.Model):
737738
super().delete(**kwargs)
738739
```
739740

741+
## Кастомные менеджеры
742+
743+
Как мы узнали ранее, `objects` — это менеджер модели, через который мы обращаемся к базе данных. Django позволяет создавать собственные менеджеры для добавления часто используемых запросов или изменения поведения по умолчанию.
744+
745+
### Зачем нужны кастомные менеджеры?
746+
747+
1. **Инкапсуляция часто используемых запросов** — вместо повторения `filter(status='published')` везде в коде
748+
2. **Изменение базового QuerySet** — например, показывать только активные записи
749+
3. **Добавление методов уровня таблицы** — операции, которые не относятся к конкретному объекту
750+
4. **Разделение логики** — разные менеджеры для разных сценариев использования
751+
752+
### Создание кастомного менеджера
753+
754+
#### Способ 1: Добавление методов в менеджер
755+
756+
```python
757+
# blog/models.py
758+
from django.db import models
759+
760+
761+
class ArticleManager(models.Manager):
762+
def published(self):
763+
"""Возвращает только опубликованные статьи"""
764+
return self.filter(status='published')
765+
766+
def drafts(self):
767+
"""Возвращает только черновики"""
768+
return self.filter(status='draft')
769+
770+
def by_author(self, user):
771+
"""Статьи конкретного автора"""
772+
return self.filter(author=user)
773+
774+
def popular(self, min_comments=10):
775+
"""Статьи с большим количеством комментариев"""
776+
from django.db.models import Count
777+
return self.annotate(
778+
comment_count=Count('comments')
779+
).filter(comment_count__gte=min_comments)
780+
781+
782+
class Article(models.Model):
783+
title = models.CharField(max_length=200)
784+
status = models.CharField(max_length=20)
785+
author = models.ForeignKey('auth.User', on_delete=models.CASCADE)
786+
# ...
787+
788+
objects = ArticleManager() # Заменяем стандартный менеджер
789+
```
790+
791+
Использование:
792+
793+
```python
794+
# Вместо Article.objects.filter(status='published')
795+
Article.objects.published()
796+
797+
# Цепочка вызовов работает!
798+
Article.objects.published().order_by('-created_at')[:5]
799+
800+
# Кастомные методы
801+
Article.objects.by_author(request.user)
802+
Article.objects.popular(min_comments=5)
803+
```
804+
805+
#### Способ 2: Изменение базового QuerySet
806+
807+
Если нужно изменить поведение **всех** запросов через менеджер:
808+
809+
```python
810+
class PublishedManager(models.Manager):
811+
def get_queryset(self):
812+
"""Всегда возвращает только опубликованные статьи"""
813+
return super().get_queryset().filter(status='published')
814+
815+
816+
class Article(models.Model):
817+
title = models.CharField(max_length=200)
818+
status = models.CharField(max_length=20)
819+
# ...
820+
821+
objects = models.Manager() # Стандартный менеджер (все статьи)
822+
published = PublishedManager() # Только опубликованные
823+
```
824+
825+
Использование:
826+
827+
```python
828+
# Все статьи (включая черновики)
829+
Article.objects.all()
830+
831+
# Только опубликованные
832+
Article.published.all()
833+
Article.published.filter(author=user) # Опубликованные статьи автора
834+
```
835+
836+
### Несколько менеджеров в одной модели
837+
838+
Можно использовать несколько менеджеров для разных сценариев:
839+
840+
```python
841+
class Article(models.Model):
842+
title = models.CharField(max_length=200)
843+
status = models.CharField(max_length=20)
844+
is_deleted = models.BooleanField(default=False) # Soft delete
845+
# ...
846+
847+
# Стандартный менеджер — первый будет использоваться по умолчанию
848+
objects = ArticleManager()
849+
850+
# Только опубликованные
851+
published = PublishedManager()
852+
853+
# Все записи, включая удалённые (для админки)
854+
all_objects = models.Manager()
855+
856+
class Meta:
857+
default_manager_name = 'objects' # Явно указываем менеджер по умолчанию
858+
```
859+
860+
### Менеджер с кастомным QuerySet
861+
862+
Для более гибкого подхода можно создать кастомный QuerySet и использовать его в менеджере:
863+
864+
```python
865+
class ArticleQuerySet(models.QuerySet):
866+
def published(self):
867+
return self.filter(status='published')
868+
869+
def drafts(self):
870+
return self.filter(status='draft')
871+
872+
def by_topic(self, topic):
873+
return self.filter(topics=topic)
874+
875+
876+
class ArticleManager(models.Manager):
877+
def get_queryset(self):
878+
return ArticleQuerySet(self.model, using=self._db)
879+
880+
# Проксируем методы QuerySet
881+
def published(self):
882+
return self.get_queryset().published()
883+
884+
def drafts(self):
885+
return self.get_queryset().drafts()
886+
887+
888+
# Или короче — использовать as_manager()
889+
class Article(models.Model):
890+
# ...
891+
objects = ArticleQuerySet.as_manager()
892+
```
893+
894+
Преимущество `QuerySet.as_manager()` — методы можно вызывать в цепочке:
895+
896+
```python
897+
# Это работает!
898+
Article.objects.published().by_topic(python_topic).order_by('-created_at')
899+
```
900+
901+
### Практический пример: Soft Delete
902+
903+
Частый паттерн — "мягкое удаление", когда записи не удаляются физически:
904+
905+
```python
906+
class SoftDeleteManager(models.Manager):
907+
def get_queryset(self):
908+
return super().get_queryset().filter(is_deleted=False)
909+
910+
911+
class SoftDeleteModel(models.Model):
912+
is_deleted = models.BooleanField(default=False)
913+
deleted_at = models.DateTimeField(null=True, blank=True)
914+
915+
objects = SoftDeleteManager() # Только активные
916+
all_objects = models.Manager() # Все, включая удалённые
917+
918+
def delete(self, *args, **kwargs):
919+
"""Мягкое удаление вместо физического"""
920+
self.is_deleted = True
921+
self.deleted_at = timezone.now()
922+
self.save()
923+
924+
def hard_delete(self, *args, **kwargs):
925+
"""Физическое удаление"""
926+
super().delete(*args, **kwargs)
927+
928+
class Meta:
929+
abstract = True
930+
931+
932+
class Article(SoftDeleteModel):
933+
title = models.CharField(max_length=200)
934+
# ...
935+
```
936+
937+
> **Примечание:** В лекции 23 мы увидим пример кастомного менеджера для модели пользователя (`CustomUserManager`), который переопределяет методы `create_user()` и `create_superuser()`.
938+
740939
## Работа с ManyToMany
741940

742941
В наших моделях блога статья связана с темами через ManyToMany:

0 commit comments

Comments
 (0)