Освоение манипулирования множественными наборами запросов в Django

Django - это популярный веб-фреймворк, позволяющий создавать динамичные и интерактивные веб-приложения с помощью языка Python. Django следует паттерну «модель-шаблон-представление» (MTV), который разделяет данные, представление и логические слои приложения. Django также предоставляет множество функций, таких как аутентификация, безопасность, управление базами данных и интерфейс администратора, чтобы сделать веб-разработку проще и быстрее.

Одной из самых мощных функций Django является QuerySet, который представляет собой коллекцию объектов из базы данных. QuerySet можно строить, фильтровать, нарезать или вообще передавать, не обращаясь к базе данных. Никаких действий с базой данных не происходит до тех пор, пока мы не выполним какие-либо действия по оценке QuerySet, например итерацию, нарезку, вызов len() или преобразование в список.

В этом блоге мы узнаем, как освоить множественные манипуляции с QuerySet в Django, которые помогут нам оптимизировать запросы к базе данных, повысить производительность приложения и упростить код. Мы рассмотрим следующие темы:

  • Как выстроить цепочку из нескольких фильтров на QuerySet
  • Как объединить несколько QuerySet с помощью объектов Q
  • Как аннотировать и агрегировать данные на QuerySet
  • Как предварительно получить и выбрать связанные данные в QuerySet
  • Как использовать пользовательские методы и менеджеры QuerySet

Как создать цепочку из нескольких фильтров на QuerySet

Одна из самых распространенных операций с набором QuerySet - это фильтрация по каким-либо критериям. Например, если у нас есть модель Post, представляющая запись в блоге, мы можем отфильтровать ее по автору, категории, дате публикации или любому другому полю. Мы можем использовать метод filter() в QuerySet для применения одного или нескольких фильтров, например, так:

# Получить все посты Ракеша
posts = Post.objects.filter(author='Rakesh')
# Получить все посты в категории 'Python'
posts = Post.objects. filter(category='Python')
# Получить все посты, опубликованные в 2023 году
posts = Post.objects.filter(publish_date__year=2023)

Мы также можем выстроить несколько фильтров на одном наборе QuerySet, что приведет к логической операции AND. Например, если мы хотим получить все посты Джона в категории „Python“, опубликованные в 2023 году, мы можем сделать следующее:

# Получить все посты Джона в категории 'Python', опубликованные в 2023 году
posts = Post.objects.filter(author='John').filter(category='Python'). filter(publish_date__year=2023)

Альтернативно мы можем передать несколько аргументов с ключевыми словами в метод filter(), что даст тот же эффект:

# Получить все сообщения пользователя John в категории „Python“, опубликованные в 2023 году
posts = Post.objects. filter(author='John', category='Python', publish_date__year=2023)

Обратите внимание, что порядок фильтров не имеет значения, поскольку они будут объединены в один SQL-запрос при оценке QuerySet.

Как объединить несколько QuerySet'ов с помощью объектов Q

Иногда нам может понадобиться объединить несколько QuerySet'ов с помощью операции логического ИЛИ или логического НЕ. Например, если мы хотим получить все сообщения от Джона или Джейн или все сообщения, которые не относятся к категории „Python“, мы не можем использовать только метод filter(), поскольку он поддерживает только логическое И. Вместо этого мы можем использовать объект Q, который является специальным объектом, представляющим сложное логическое выражение. Мы можем импортировать объект Q из django.db.models и использовать его для построения QuerySet, как показано ниже:

# Импортируйте объект Q
from django.db.models import Q
# Получить все сообщения пользователя John or Jane
posts = Post.objects.filter(Q(author='John') | Q(author='Jane'))
# Получить все сообщения, которые не относятся к категории 'Python'
posts = Post.objects.filter(~Q(category='Python'))

Мы также можем комбинировать объекты Q с обычными фильтрами и использовать круглые скобки для их группировки. Например, если мы хотим получить все сообщения Джона или Джейн в категории ‘Python’, опубликованные в 2023 году, мы можем сделать следующее:

# Получить все сообщения Джона или Джейн в категории 'Python', опубликованные в 2023 году
posts = Post.objects. filter((Q(author='John') | Q(author='Jane')) & Q(category='Python') & Q(publish_date__year=2023))

Обратите внимание, что объект Q поддерживает следующие операторы:

  • | для логического OR
  • & для логического AND
  • ~ для логического NOT

Как аннотировать и агрегировать данные в QuerySet

Another useful operation on a QuerySet is to annotate and aggregate data. Annotation means adding extra data to each object in the QuerySet, while aggregation means calculating a summary value from the QuerySet. For example, if we want to add the number of comments to each post, or calculate the average number of comments per post, we can use the annotate() and aggregate() methods on the QuerySet, respectively. We can also import the functions from django.db.models that perform the calculations, such as Count, Sum, Avg, Min, Max, etc. For example:

# Import the functions
from django.db.models import Count, Avg
# Annotate each post with the number of comments
posts = Post.objects.annotate(num_comments=Count('comments'))
# Aggregate the average number of comments per post
avg_comments = Post.objects.aggregate(avg_comments=Avg('comments'))

Обратите внимание, что метод annotate() возвращает QuerySet с дополнительными данными, а метод aggregate() возвращает словарь с итоговым значением.

Как предварительно получить и выбрать связанные данные в QuerySet

Еще одной распространенной операцией в QuerySet является получение связанных данных из других моделей. Например, если у нас есть модель Comment, которая представляет комментарий к посту, и она имеет внешний ключ к модели Post, мы можем захотеть получить все комментарии вместе с постами, или наоборот. Есть два способа сделать это: prefetch_related() и select_related(). Оба метода позволяют сократить количество запросов к базе данных и повысить производительность нашего приложения, но работают они по-разному.

Метод prefetch_related() работает путем выполнения отдельного запроса к базе данных для каждой связанной модели, а затем объединяет результаты в Python. Это полезно, когда у нас есть отношения «один ко многим» или «многие ко многим», например, у поста есть несколько комментариев, или у поста есть несколько тегов. Например:

# Get all posts with their comments
posts = Post.objects.prefetch_related('comments')
# Get all posts with their tags
posts = Post.objects.prefetch_related('tags')

Метод select_related() работает путем выполнения одного запроса к базе данных с SQL JOIN для каждой связанной модели. Это полезно, когда у нас есть отношения «один к одному» или «многие к одному», например, у комментария есть один пост, или у поста есть один автор. Например:

# Get all comments with their posts
comments = Comment.objects.select_related('post')
# Get all posts with their authors
posts = Post.objects.select_related('author')

Обратите внимание, что методы prefetch_related() и select_related() можно объединить с другими методами QuerySet, такими как filter(), order_by() и т. д. Они также могут быть вложены, чтобы получить несколько уровней связанных данных, например, пост с автором, у которого есть профиль. Например:

# Получение всех постов с их авторами и профилями
posts = Post.objects.select_related('author__profile')

Как использовать пользовательские методы и менеджеры QuerySet

Одна из лучших практик Django - хранить бизнес-логику в моделях, а не в представлениях или шаблонах. Это делает код более многоразовым, удобным в обслуживании и тестировании. Одним из способов достижения этой цели является использование пользовательских методов и менеджеров QuerySet.

Пользовательский метод QuerySet - это метод, который мы определяем для класса QuerySet и который мы можем использовать для любого QuerySet. Например, если мы хотим создать метод, который будет фильтровать посты по текущему году, мы можем сделать следующее:

# Define a custom QuerySet class
class PostQuerySet(models.QuerySet):
# Define a custom QuerySet method
def current_year(self):
# Get the current year
year = datetime.now().year
# Return the filtered QuerySet
return self.filter(publish_date__year=year)
# Define a model that uses the custom QuerySet class
class Post(models.Model):
# Define the fields
title = models.CharField(max_length=100)
content = models.TextField()
author = models.ForeignKey(User, on_delete=models.CASCADE)
category = models.CharField(max_length=50)
publish_date = models.DateTimeField(auto_now_add=True)
# Define the objects attribute as the custom QuerySet class
objects = PostQuerySet.as_manager()

Теперь мы можем использовать метод current_year() на любом QuerySet модели Post, например, так:

# Get all posts published in the current year
posts = Post.objects.current_year()
# Get all posts by John published in the current year
posts = Post.objects.filter(author='John').current_year()

Пользовательский менеджер - это класс, который наследуется от models.Manager и который мы можем использовать для создания и доступа к QuerySets. Например, если мы хотим создать менеджер, который возвращает только опубликованные посты, мы можем сделать следующее:

# Define a custom manager class
class PublishedPostManager(models.Manager):
# Define a custom manager method
def get_queryset(self):
# Return the filtered QuerySet
return super().get_queryset().filter(status='published')
# Define a model that uses the custom manager class
class Post(models.Model):
# Define the fields
title = models.CharField(max_length=100)
content = models.TextField()
author = models.ForeignKey(User, on_delete=models.CASCADE)
category = models.CharField(max_length=50)
publish_date= models.DateTimeField(auto_now_add=True)

Define the status choices

STATUS_CHOICES = (
    ('draft', 'Draft'),
    ('published', 'Published'),
)

Определите поле статуса

status = models. CharField(max_length=10, choices=STATUS_CHOICES, default='draft' )

Определите атрибут published как класс пользовательского менеджера

published = PublishedPostManager()

Теперь мы можем использовать менеджер published для доступа только к опубликованным постам, например, так:

# Get all published posts
posts = Post.published.all()
# Get all published posts by John
posts = Post.published.filter(author='John')

Обратите внимание, что мы можем определить несколько менеджеров для одной и той же модели и использовать менеджер объектов по умолчанию для доступа ко всем объектам, независимо от их статуса.

Заключение

В этой статье мы узнали, как освоить манипулирование множеством QuerySet в Django, что поможет нам оптимизировать запросы к базе данных, повысить производительность приложения и упростить код.

Вернуться на верх