Francois Paulsen
Francois Paulsen

Reputation: 175

Django calculated fields on custom queryset

This seems like a basic question but for the life of me I cannot seem to figure this out.

I have a Recipe model that uses a custom RecipeQueryset as a manager. Now I've been doing a lot of reading on where to put business logic of the application some people suggest a "services" layer others say to put it in a custom queryset which is what I am trying to accomplish now. None of them really give an example of doing calculations though only CRUD functionality.

At the moment I have a property of total_cost defined on the Recipe model but according to a lot of articles/discussions I should be moving this "business logic" to elsewhere to the custom queryset for example?

  1. Is there a way to do the calculation in a custom queryset method for a queryset of Recipe objects and calculate that total_cost for each Recipe and return that queryset with the extra total_cost field?
  2. How would I then add this so it can be used in my serializer?

Model

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)

Upvotes: 1

Views: 727

Answers (1)

Adilet Usonov
Adilet Usonov

Reputation: 136

You can use SerializerMethodField

here is the docs https://www.django-rest-framework.org/api-guide/fields/#serializermethodfield

and I recommend to use ModelSerializer instead of Serializer. Also I recommend keep serializer out of the view

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


Upvotes: 1

Related Questions