0

I want to render a dynamically selected object using an intermediate model and a custom query_param.

For instance,I want to make a query like this:

http://api.url/v1/recipes/19/?location=1 and obtain a serialized Recipe object with it's ingredients based on the location send as query_params:

Recipe object:

{
    "id": 19,
    "ingredients": [
        {
            "id": 35,           # Product id
            "name": "Tomato",   # Product name
            "price": 2.0,       # Product price
            "quantity": 3,
            "supplier": 12,     # Supplier at location=1
        },
        {
            "id": 36,           # Product id
            "name": "Cheese",   # Product name
            "price": 5.0,       # Product price
            "quantity": 2,
            "supplier": 12,
        },
    ],
    "title": "Pizza"
}

If I change the ?location= query param I want to obtain the same recipe but getting the products of another Supplier.

This is my current schema. But any suggestion is very appreciated:

class Recipe(models.Model):
    title = models.CharField(max_length=255)

class IngredientRecipe(models.Model):
    recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE)
    quantity = models.FloatField(default=0.0)
    product_name = models.CharField(max_length=255)

    class Meta:
        unique_together = ['product_name', 'recipe']

class Supplier(models.Model):
    name = models.CharField(_('Name'), max_length=255)
    location = models.ForeignKey(Location, on_delete=models.CASCADE)

class Product(models.Model):
    name = models.CharField(max_length=255)
    supplier = models.ForeignKey(Supplier, on_delete=models.CASCADE)
    price = models.FloatField()

I'm trying to serialize Recipe objects with its related ingredients based on supplier's location:

class IngredientSerializer(serializers.ModelSerializer):
    class Meta:
        model = Product
        fields = '__all__'

class RecipeSerializer(serializers.ModelSerializer):
    ingredients = serializers.SerializerMethodField()

    class Meta:
        model = Recipe
        fields = '__all__'

    def get_ingredients(self, obj):
        location_id = self.context['request'].query_params.get('location', None)
        product_names = IngredientRecipe.objects.filter(recipe__id=obj.id).values_list('product_name')

        # Here I got the related Products by name and if specified, the supplier's location:
        if location_id:
            qs = Product.objects.filter(
                (Q(supplier__location_id=location_id) | Q(supplier_id__isnull=True)) &
                Q(is_active=True) & Q(name__in=product_names)).values_list('id', 'name', 'price')
        else:
            qs = Product.objects.filter(Q(is_active=True) & Q(name__in=product_names)).values_list('id', 'name',
                                                                                                   'price')
        
        # Here I try to remove the duplicates and keed the cheapest price:
        duplicated_qs = list(set(qs))
        duplicated_qs = sorted(duplicated_qs, key=lambda tup: tup[2])
        keep = set()
        res = []
        for sub in duplicated_qs:
            if sub[1] not in keep:
                res.append(sub)
                keep.add(sub[1])
        ingredients_ids = [item[0] for item in res]

        # Once I have a product list I want to serialize it with the quantity information
        # and here is where I got stuck, cause I don't know how to render the context passed. 
        qs = Product.objects.filter(id__in=ingredients_ids)

        serializer = IngredientSerializer(qs, many=True, context={'quantity': obj.quantity})
        return serializer.data

Thank you all in advance!

1 Answers1

2

I usually do it like this, by overriding the get_queryset method:

# In your view:
class RecipeView(viewsets.ModelViewSet):
    def get_queryset(self):
        result = Recipe.objects.all()

        location = self.request.GET.get("location", None)
        if location:
            result = result.filter(location__id=location)

        return result
lucutzu33
  • 3,470
  • 1
  • 10
  • 24