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(), но тоже возникает ошибка. Как ещё можно оптимизировать эти запросы?


Ответы (0 шт):