Django Rest Framework, оптимизация запросов через подзапросы
Вопрос об оптимизации ORM запросов Django при использовании DRF. Есть следующие модели:
class Ingredient(models.Model):
name = models.CharField(
verbose_name='Название ингредиента',
max_length=200,
help_text='Название ингредиента',
)
measurement_unit = models.CharField(
verbose_name='Единица измерения',
max_length=200,
help_text='Единица измерения',
)
class Recipe(models.Model):
author = models.ForeignKey(
User,
verbose_name='Автор рецепта',
on_delete=models.CASCADE,
related_name='recipes',
help_text='Автора рецепта',
)
name = models.CharField(
verbose_name='Название рецепта',
max_length=200,
help_text='Название рецепта',
)
image = models.ImageField(
verbose_name='Изображение рецепта',
upload_to='recipes/images',
help_text='Изображение рецепта',
)
text = models.TextField(
verbose_name='Текст рецепта',
help_text='Текст рецепта',
)
ingredients = models.ManyToManyField(
Ingredient,
verbose_name='Ингредиенты рецепта',
through='RecipeIngredient',
related_name='recipes',
help_text='Ингредиенты рецепта',
)
tags = models.ManyToManyField(
Tag,
verbose_name='Тег рецепта',
related_name='recipes',
help_text='Тег рецепта',
)
cooking_time = models.IntegerField(
verbose_name='Время приготовления',
validators=[
MinValueValidator(
1,
message='Время приготовления минимум 1 минута'
)
],
help_text='Время приготовления',
)
pub_date = models.DateTimeField(
verbose_name='Дата публикации',
auto_now_add=True,
help_text='Дата публикации',
)
class RecipeIngredient(models.Model):
recipe = models.ForeignKey(
Recipe,
verbose_name='Рецепт',
on_delete=models.CASCADE,
related_name='ingredient',
help_text='Рецепт',
)
ingredient = models.ForeignKey(
Ingredient,
verbose_name='Ингредиент',
on_delete=models.CASCADE,
related_name='ingredient',
help_text='Ингредиент',
)
amount = models.IntegerField(
verbose_name='Количество',
validators=[
MinValueValidator(
1,
message='Минимальное количество не меньше чем 1'
)
],
help_text='Количество',
)
serializer.py:
class RecipeIngredientsSerializer(serializers.ModelSerializer):
'''Сериализация игредиентов входящих в рецепты.'''
id = serializers.ReadOnlyField(source='ingredient.id')
name = serializers.ReadOnlyField(source='ingredient.name')
measurement_unit = serializers.ReadOnlyField(
source='ingredient.measurement_unit'
)
class Meta:
model = RecipeIngredient
fields = ('id', 'name', 'measurement_unit', 'amount')
class RecipeListSerializer(serializers.ModelSerializer):
'''Сериализация списка рецептов.'''
tags = TagSerializer(many=True, read_only=True)
author = CustomUserSerializer(read_only=True)
ingredients = RecipeIngredientsSerializer(many=True, source='ingredient')<-get_ingredients
is_favorited = serializers.BooleanField(default=False, read_only=True, source='favorit')
is_in_shopping_cart = serializers.BooleanField(default=False, read_only=True, source='shoppings')
class Meta:
model = Recipe
fields = (
'id', 'tags', 'author', 'ingredients', 'is_favorited',
'is_in_shopping_cart', 'name', 'image', 'text', 'cooking_time'
)
views.py:
class RecipeViewset(viewsets.ModelViewSet):
'''Представление рецептов'''
permission_classes = (IsAuthorOrReadOnly | IsAdminOrReadOnly,)
filter_backends = (DjangoFilterBackend,)
filterset_class = RecipeFilter
def get_queryset(self):
queryset = Recipe.objects.select_related('author').prefetch_related('tags').annotate(get_ingredients=Subquery(Recipe.objects.filter(ingredient__recipe=OuterRef('id')).values('id')[:1])).all()
if self.request.user.is_authenticated:
queryset = queryset.annotate(
favorit=Exists(queryset.filter(favorite__user=self.request.user, favorite__recipe=OuterRef('id'))),
shoppings=Exists(queryset.filter(shopping__user=self.request.user, shopping__recipe=OuterRef('id'))),
return queryset
на выходе получается JSON из 6 подобных элементов, на одну страницу:
"count": 40,
"next": "http://127.0.0.1:8000/api/recipes/?page=2",
"previous": null,
"results": [
{
"id": 72,
"tags": [
{
"id": 1,
"name": "Завтрак",
"color": "#82ac64",
"slug": "breakfast"
}
],
"author": {
"email": "[email protected]",
"id": 2,
"username": "ivan",
"first_name": "ivan",
"last_name": "ivanov",
"is_subscribed": false
},
"ingredients": [
{
"id": 666,
"name": "колбаски сырокопченые",
"measurement_unit": "шт.",
"amount": 111
},
{
"id": 999,
"name": "мидии в раковинах",
"measurement_unit": "г",
"amount": 97
}
],
"is_favorited": false,
"is_in_shopping_cart": false,
"name": "Проверка пермиссий. Васей",
"image": "http://127.0.0.1:8000/backend_media/recipes/images/7e9c0ab7-2a28-46d8-b4bf-9cbb59fa262b.jpg",
"text": "Создан с целью проверки пермиссий. Редактирую Васей.",
"cooking_time": 20
},
Проблема следующая, при рендеринге каждого объекта рецепта, django каждый раз берёт ингредиенты из базы, и получается шесть лишних запросов. Как оптимизировать можно это, подобно полям в сериализаторе is_favorited, is_in_shopping_cart? Там использован подзапрос. Пробую что то подобное, сделать подзапрос в get_queryset(), формируя новое свойство get_ingredients, и передать get_ingredients в поле сериализатора ingredients = RecipeIngredientsSerializer(many=True, source='get_ingredients'). Но передаётся не сам объект, а число по id, что ведёт к ошибке int is not iterable. Пробовал через Recipe.objects.get(), но тоже возникает ошибка. Как ещё можно оптимизировать эти запросы?