Вычисляемые поля Django в пользовательском наборе запросов

Это кажется элементарным вопросом, но я не могу разобраться в этом.

У меня есть модель Recipe, которая использует пользовательский RecipeQueryset в качестве менеджера. Сейчас я много читал о том, где разместить бизнес-логику приложения, некоторые люди предлагают слой "сервисов", другие говорят поместить ее в пользовательский набор запросов, что я и пытаюсь сделать сейчас. Никто из них на самом деле не приводит пример выполнения вычислений, хотя только CRUD функциональность.

На данный момент у меня есть property из total_cost, определенных на модели Recipe, но согласно многим статьям/обсуждениям я должен перенести эту "бизнес-логику" в другое место, например, в пользовательский queryset?

  1. Есть ли способ сделать вычисление в пользовательском методе queryset для набора объектов Recipe и вычислить это total_cost для каждого Recipe и вернуть этот набор с дополнительным полем total_cost?
  2. Как бы я добавил это, чтобы это можно было использовать в моем сериализаторе?

Модель

class RecipeQueryset(models.QuerySet):
    """Custom queryset for Recipe Model"""

    def all_for_user(self, user):
        return self.filter(user=user)
    
    # this is where I think I should put it?
    def total_cost(self, recipe):
        return # some complex calculations across two related models


class Recipe(models.Model):

    user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)
    name = models.CharField(max_length=255, unique=True)
    yield_count = models.FloatField()
    yield_units = models.CharField(max_length=255)

    objects = RecipeQueryset.as_manager()
    
    ....

    # This is where it currently is
     def get_recipe_ingredients(self):
        """Returns all Recipe ingredients associated with the recipe"""
        return self.ingredients.all()

    @property
    def total_cost(self):
        """calculates the entire cost of producing the recipe"""
        total_recipe_cost = 0
        for r_ingredient in self.get_recipe_ingredients():
            ingredient_cost = r_ingredient._get_individual_ingredient_cost()
            total_recipe_cost += ingredient_cost

        return total_recipe_cost

View/Api

class RecipeListApi(APIView):
    class OutputSerializer(serializers.Serializer):
        id = serializers.IntegerField()
        name = serializers.CharField(max_length=255)
        yield_count = serializers.FloatField()
        yield_units = serializers.CharField()
        total_cost = serializers.FloatField() # Returns the total_cost value in the serializer

    def get(self, request):
        recipes = Recipe.objects.all_for_user(user=request.user)
        serializer = self.OutputSerializer(recipes, many=True)

        return Response(serializer.data, status=status.HTTP_200_OK)

Вы можете использовать SerializerMethodField

Здесь находится документация https://www.django-rest-framework.org/api-guide/fields/#serializermethodfield

и я рекомендую использовать ModelSerializer вместо Serializer. Также я рекомендую держать сериализатор вне представления

class OutputSerializer(serializers.ModelSerializer):
    ...
    total_cost = serializers.SerializerMethodField("total_cost")

    def total_cost(self, obj: Recipe) -> int:
        """calculates the entire cost of producing the recipe"""
        total_recipe_cost = 0
        for r_ingredient in obj.get_recipe_ingredients():
            ingredient_cost = r_ingredient._get_individual_ingredient_cost()
            total_recipe_cost += ingredient_cost

        return total_recipe_cost


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