I want to perform some data manipulations before sending back a JSON response with DRF.
Situation
My model is:
class ThirdParty(models.Model):
label = models.CharField(verbose_name=_("Third party label"), null=False, blank=False, default=DEFAUT_LABEL, max_length=255)
class CashFlow(TimeStampedModel):
date = models.DateField(verbose_name=_("Due date"), null=True, blank=True)
forecasted_value = models.DecimalField(verbose_name=_("Forecasted value"), null=True, blank=True, max_digits=11, decimal_places=2)
third_party = models.ForeignKey(ThirdParty, null=False, blank=False, related_name='cashflows')
Currently I have two serializers:
class CashFlowSerializer(serializers.ModelSerializer):
third_party = serializers.PrimaryKeyRelatedField(many=False, read_only=True, allow_null=True)
class Meta:
model = CashFlow
fields = ('id', 'date', 'forecasted_value', 'edited_value', 'third_party')
class ThirdPartyReadSerializer(serializers.ModelSerializer):
cashflows = CashFlowSerializer(many=True, read_only=True)
class Meta:
model = ThirdParty
fields = ('id', 'label', 'category', 'cashflows',)
And my ThirdParty view is correctly returning a nice JSON as:
{
"id": 15,
"label": "Adeo",
"category": 7,
"cashflows": [
{
"id": 1,
"date": "2016-11-01",
"forecasted_value": "2000.00",
"edited_value": null,
"third_party": 15
},
{
"id": 2,
"date": "2017-01-17",
"forecasted_value": "3000.00",
"edited_value": null,
"third_party": 15
},
{
"id": 3,
"date": "2017-01-31",
"forecasted_value": "1000.00",
"edited_value": null,
"third_party": 15
}
]
}
Question
I want to group the cash flows by month and add their values.
Question is: what is the best way to do it?
The expected result is:
{
"id": 15,
"label": "Adeo",
"category": 7,
"cashflows": [
{
"date": "2016-11-01",
"forecasted_value": "2000.00",
"edited_value": null,
"third_party": 15
},
{
"date": "2017-01-01",
"forecasted_value": "4000.00",
"third_party": 15
}
]
}
And that will be a read-only serializer.
Use the serializer's to_representation:
def to_representation(self, obj):
data = super().to_representation(obj)
# manipulate data['cashflows'] to group by month
return data
Related
I have two related models (Product and Recipe). There is a many-to-many relationship defined between them (with a through model). I am able to query the 'through' model fields using the below serializer, but I am unable to make this work the other way around (from Product to Recipe)
models.py:
class Product(models.Model):
version = models.CharField(max_length=4)
displayname = models.CharField(max_length=50,validators=[alphanumeric])
...
class Recipe(models.Model):
...
ingredients = models.ManyToManyField(Product, through='RecipeInput', related_name='recipe_input')
products = models.ManyToManyField(Product, through='RecipeOutput',related_name='recipe_output')
class RecipeInput(models.Model):
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE)
product = models.ForeignKey(Product, on_delete=models.CASCADE)
amount = models.IntegerField(default=1)
amount_min = models.FloatField(blank=True, null=True)
class RecipeOutput(models.Model):
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE)
product = models.ForeignKey(Product, on_delete=models.CASCADE)
amount = models.IntegerField(default=1)
amount_min = models.FloatField(blank=True, null=True)
mj = models.FloatField(blank=True, null=True)
serializers.py:
class RecipeInputSerializer(serializers.HyperlinkedModelSerializer):
product_id = serializers.ReadOnlyField(source='product.id')
product_name = serializers.ReadOnlyField(source='product.displayname')
class Meta:
model = RecipeInput
fields = ('product_id',"product_name", 'amount', 'amount_min', )
class RecipeOutputSerializer(serializers.HyperlinkedModelSerializer):
product_id = serializers.ReadOnlyField(source='product.id')
product_name = serializers.ReadOnlyField(source='product.displayname')
class Meta:
model = RecipeOutput
fields = ('product_id',"product_name", 'amount', 'amount_min', 'mj', )
class ProductInputSerializer(serializers.HyperlinkedModelSerializer):
recipe_id = serializers.ReadOnlyField(source='recipe.id')
recipe_name = serializers.ReadOnlyField(source='recipe.displayname')
class Meta:
model = RecipeInput
fields = ('recipe_id','recipe_name', 'amount_min', 'amount', )
class RecipeSerializer(serializers.ModelSerializer):
ingredients = RecipeInputSerializer(source='recipeinput_set', many=True)
products = RecipeOutputSerializer(source='recipeoutput_set', many=True)
class Meta:
model = Recipe
fields = "__all__"
depth = 1
class ProductSerializer(serializers.ModelSerializer):
products_in = ProductInputSerializer(source='recipe_input', many=True)
class Meta:
model = Product
fields = "__all__"
depth = 1
Now, this works for Recipe, I get the following output:
[
{
"id": 1239,
"ingredients": [
{
"product_id": 1787,
"product_name": "Automated Wiring",
"amount": 15,
"amount_min": 7.5
},
{
"product_id": 1799,
"product_name": "Circuit Board",
"amount": 10,
"amount_min": 5.0
}
],
"products": [
{
"product_name": "Adaptive Control Unit",
"amount": 2,
"amount_min": 1.0,
"mj": 3300.0
}
],
...
},
{
"id": 1240,
"ingredients": [
{
"product_id": 1809,
"product_name": "Copper Sheet",
"amount": 5,
"amount_min": 25.0
},
{
"product_id": 1871,
"product_name": "Quickwire",
"amount": 20,
"amount_min": 100.0
}
],
"products": [
{
"product_name": "AI Limiter",
"amount": 1,
"amount_min": 5.0,
"mj": 180.0
}
],
...
}
]
But, for Product, I am not able to query the related model (Recipe) and the values of the through model. In the serializers.py I defined products_in but it doesnt show recipe_id, recipe_name, etc.
[
{
"id": 2541,
"products_in": [
{
"amount_min": null
}
],
...
},
{
"id": 2542,
"products_in": [
{
"amount_min": null
},
{
"amount_min": null
}
],
...
}
]
I am trying to get data in a particular format but i'm not able to get the desired output.
My Model:
class Category(models.Model):
name = models.CharField(max_length=40)
class Expense(models.Model):
category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name="category_name")
description = models.CharField(max_length=200)
total_amount = models.IntegerField()
class Expense_Details(models.Model):
expense = models.ForeignKey(Expense, on_delete=models.CASCADE, related_name="payment")
user = models.IntegerField()
amount = models.FloatField()
type = models.CharField(max_length=100) ---->type is owe or lend
When I request /api/expenses/:
Expected Output
{
“total_expenses”: 10,
“Expenses”:
[{
“id”: 1,
“category”: 1,
“created_by”: 1, ------> user logged id
“description”: “lunch”,
“total_amount”: “105”,
“owe”: [{
“user_id”: 1,
“amount”: 10
},
{
“user_id”: 2,
“amount”: 95
}],
“lend”: [{
“user_id”: 3,
“amount”: 10
},
{
“user_id”: 4,
“amount”: 95
}],
}, ...
]
}
My output:
{
"results": [
{
"id": 1,
"category": 1,
"description": "lunch at the burj al arab",
"total_amount": 105,
"payment": [
{
"user": 1,
"amount": -10
},
{
"user": 2,
"amount": -95
},
{
"user": 3,
"amount": 10
},
{
"user": 4,
"amount": 95
}
]
}
]
}
My Serializer:
class ExpenseDetailsSerializer(serializers.ModelSerializer):
class Meta:
model = Expense_Details
fields = ['user', 'amount']
class ExpenseSerializer(serializers.ModelSerializer):
payment = serializers.SerializerMethodField()
def get_payment(self, obj):
return ExpenseDetailsSerializer(obj.payment.all(), many=True).data
class Meta:
model = Expense
fields = ['id', 'category', 'description', 'total_amount', 'payment',]
What Query should I use to get Output in the above format? How will my serializer look like? How can I separate own and lend? Also I have stored own and lend with + and - sign to differentiate between them.
Use a ListField for the same.
Documentation: https://www.django-rest-framework.org/api-guide/fields/#listfield
Also refer How to serialize an 'object list' in Django REST Framework
Here you can try something like:
class ExpenseSerializer(serializers.Serializer):
payment = serializers.ListField(child=ExpenseDetailsSerializer())
def get_payment(self, obj):
return ExpenseDetailsSerializer(obj.payment.all(), many=True).data
class Meta:
model = Expense
fields = ['id', 'category', 'description', 'total_amount', 'payment',]
I've looked into this question, but I think it's different.
Let me explain a bit further. I have a serializer called DetailTrackSerializer to serialize my Track model, and I've nested a TaggedSerializer in DetailTrackSerializer.
class DetailTrackSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(read_only=True)
title = serializers.CharField(max_length=120)
link = serializers.URLField(max_length=120)
tagged_set = TaggedSerializer(many=True)
artist = ArtistSerializer()
class Meta:
model = Track
fields = ('id', 'artist', 'title', 'link', 'tagged_set',)
class TaggedSerializer(serializers.ModelSerializer):
tag = TagSerializer()
class Meta:
model = Tagged
fields = ('tag',)
class TagSerializer(serializers.ModelSerializer):
class Meta:
model = Tag
fields = ('name',)
Currently, this DetailTrackSerializer is returning a json like this
{
"tracks": [
{
"id": 168,
"artist": {
"id": 163,
"name": "Gob"
},
"title": "Face the Ashes",
"link": "",
"tagged_set": [
{
"tag": {
"id": 1356,
"name": "punk rock"
}
},
{
"tag": {
"id": 1356,
"name": "punk rock"
}
},
{
"tag": {
"id": 1356,
"name": "punk rock"
}
},
...
The list goes on, if there are 100 "punk rock" tag in this track, it will shows up 100 times and there may be another tag also not only "punk rock". What I need is something like this
{
"tracks": [
{
"id": 168,
"artist": {
"id": 163,
"name": "Gob"
},
"title": "Face the Ashes",
"link": "",
"tagged_set": [
{
"tag": {
"id": 1356,
"name": "punk rock"
},
"frequency": 100,
},
{
"tag": {
"id": 546,
"name": "pop"
},
"frequency": 236,
},
...
Each tag only appears once, and has its frequency.
Note: I'm using Django Rest Framework as well
Edit: models.py
class Tagged(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, default=1)
track = models.ForeignKey(Track, on_delete=models.CASCADE)
tag = models.ForeignKey(Tag, on_delete=models.CASCADE)
class Tag(models.Model):
name = models.CharField(max_length=255, unique=True)
class Track(models.Model):
artist = models.ForeignKey(Artist, on_delete=models.CASCADE)
title = models.CharField(max_length=255)
link = models.URLField(max_length=255, blank=True)
tags = models.ManyToManyField(Tag, through='Tagged', blank=True)
From your Tagged I understood that there are big chances of Data Redundancy, that's why your tagged_set is showing multiple times.
What I'm trying to say is, this is not a Representation Problem with your serializer, rather than it's an Implementation Problem with your Models.
So, unique_together attribute will solve the problem, as
class Tagged(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, default=1)
track = models.ForeignKey(Track, on_delete=models.CASCADE)
tag = models.ForeignKey(Tag, on_delete=models.CASCADE)
class Meta:
unique_together = ('track', 'tag')
After changing the models, please do makemigrations and migration.
Note: While doing migration you may come acrross django.db.utils.IntegrityError: UNIQUE constraint failed exception. So, delete all entries in the Tagged model
After reading through Django's docs about querysets, this is the solution I came up with
class DetailTrackSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(read_only=True)
title = serializers.CharField(max_length=120)
link = serializers.URLField(max_length=120)
tags_frequency = serializers.SerializerMethodField()
artist = ArtistSerializer()
def get_tags_frequency(self, track):
tags = track.tags.all()
return tags.values('id', 'name').annotate(Count('id'))
class Meta:
model = Track
fields = ('id', 'artist', 'title', 'link', 'tags_frequency',)
which will give me json representation like this
{
"tracks": [
{
"id": 168,
"artist": {
"id": 163,
"name": "Gob"
},
"title": "Face the Ashes",
"link": "",
"tags_frequency": [
{
"name": "punk rock",
"id": 1356,
"id__count": 100
},
{
"name": "punk",
"id": 1357,
"id__count": 60
}
]
},
{
"id": 169,
"artist": {
"id": 164,
"name": "Jeff And Sheri Easter"
},
"title": "The Moon And I (Ordinary Day Album Version)",
"link": "",
"tags_frequency": []
},
Edwin Harly, you have some data overlap between Track, Tag and Tagged model. If you accept, I suggest you remove Tagged model. and if you want to save which user create tag, add user field in Tag model.
class Tag(models.Model):
name = models.CharField(max_length=255, unique=True)
user = models.ForeignKey(User, on_delete=models.CASCADE, default=1)
class Track(models.Model):
artist = models.ForeignKey(Artist, on_delete=models.CASCADE)
title = models.CharField(max_length=255)
link = models.URLField(max_length=255, blank=True)
tags = models.ManyToManyField(Tag, through='Tagged', blank=True)
Then, you can your serializers like this:
class DetailTrackSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(read_only=True)
title = serializers.CharField(max_length=120)
link = serializers.URLField(max_length=120)
tags = TagSerializer(many=True)
artist = ArtistSerializer()
class Meta:
model = Track
fields = ('id', 'artist', 'title', 'link', 'tags',)
class TagSerializer(serializers.ModelSerializer):
class Meta:
model = Tag
fields = ('name',)
I'm struggling to write a Django GET that returns the following looking response:
{
"lists": [
{
"id": "123",
"list_order": [
{
"id": "123_1",
"order": 1,
"list_id": "123",
"item_id": 9876,
"item": {
"id": 9876,
"name": "item1",
"location": "California"
}
},
{
"id": "123_2",
"order": 2,
"list_id": "123",
"item_id": 2484,
"item": {
"id": 2484,
"name": "item2",
"location": "California"
}
}
],
"updated_date": "2018-03-15T00:00:00Z"
}
]
}
Given a list_id, the response returns the basic information on the list ("id", "updated_date"), as well as the order of items in the list. Inside each item in the list order, it also grabs the related item details (nested in "item"). I'm able to get this response without the "item" details ("id", "name", "location" fields) and with no error:
{
"lists": [
{
"id": "123",
"list_order": [
{
"id": "123_1",
"order": 1,
"list_id": "123",
"item_id": 9876
},
{
"id": "123_2",
"order": 2,
"list_id": "123",
"item_id": 2484
}
],
"updated_date": "2018-03-15T00:00:00Z"
}
]
}
Again there is no error, and I can retrieve the first nested level without any issue. The problem is getting the "item" information to show within each "list_order". Below are my models, serializers, and views.
models.py
class Lists(models.Model):
id = models.CharField(null=False, primary_key=True, max_length=900)
updated_date = models.DateTimeField(blank=True, null=True)
class Meta:
managed = False
db_table = 'tbl_lists'
class Items(models.Model):
id = models.BigIntegerField(primary_key=True)
name = models.TextField(blank=True, null=True)
location = models.TextField(blank=True, null=True)
class Meta:
managed = False
db_table = 'tbl_items'
class ListOrder(models.Model):
id = models.CharField(null=False, primary_key=True, max_length=900)
list_id = models.ForeignKey(Lists, db_column='list_id', related_name='list_order')
item_id = models.ForeignKey(Items, db_column='item_id', related_name='items')
order = models.BigIntegerField(blank=True, null=True)
class Meta:
managed = False
db_table = 'tbl_list_order'
serializers.py
class ItemsSerializer(serializers.ModelSerializer):
class Meta:
model = Items
fields = '__all__'
class ListOrderSerializer(serializers.ModelSerializer):
item = ItemsSerializer(many=False, read_only=True)
class Meta:
model = ListOrder
fields = '__all__'
class ListsSerializer(serializers.ModelSerializer):
list_order = ListOrderSerializer(many=True, read_only=True)
class Meta:
model = Lists
fields = '__all__'
views.py
class ListsViewSet(generics.ListCreateAPIView):
"""
API endpoint that returns a list with its meta-information
"""
queryset = Lists.objects.all()
serializer_class = ListsSerializer
def get_queryset(self):
list_id = self.kwargs['list_id']
filters = [Q(id=list_id)]
return Lists.objects.filter(*filters)
def list(self, request, list_id):
queryset = self.get_queryset()
list_serializer = ListsSerializer(queryset, many=True)
return Response({ 'lists': list_serializer.data })
I'm pretty new to Django and like what it offers so far, though maybe I'm thinking of doing this in too much of a "SQL" way. I've read about select_related() and prefetch_related(), but not sure how I would apply it to this case. Any assistance is greatly appreciated and let me know if there's any other information I can provide.
In your ListOrderSerializer you are trying to serialize item. while in ListOrder model you used the field name item_id
Solution:
In ListOrderSerializer:
class ListOrderSerializer(serializers.ModelSerializer):
item_id = ItemsSerializer(many=False, read_only=True)
...
I have the following Django model structure:
class TypeOfIngredient(models.Model):
name = models.CharField(max_length=200,unique=True,null=False)
slug = models.SlugField(unique=True)
class Ingredient(models.Model):
name = models.CharField(max_length=200,unique=True,null=False)
slug = models.SlugField(unique=True)
typeofingredient = models.ForeignKey(TypeOfIngredient, related_name='typeof_ingredient',null=True, blank=True,on_delete=models.PROTECT)
Serializer:
class IngredientListSerializer(ModelSerializer):
class Meta:
model = Ingredient
fields = '__all__'
With the above serializer i see the following api output:
"results": [
{
"id": 1,
"name": "adrak",
"slug": "adrak",
"typeofingredient": null
},
{
"id": 2,
"name": "banana",
"slug": "banana",
"typeofingredient": 1
},
How to get "typeofingredient": "fruit" where fruit is the name field of the typeofingredient. What i am getting is the id.
I tried nested:
class IngredientListSerializer(ModelSerializer):
class Meta:
model = Ingredient
fields = '__all__'
depth = 1
Then i get the api output as:
"results": [
{
"id": 1,
"name": "adrak",
"slug": "adrak",
"typeofingredient": null
},
{
"id": 2,
"name": "banana",
"slug": "banana",
"typeofingredient": {
"id": 1,
"name": "fruit",
"slug": "fruit"
}
},
Here is showing all the details of the typeofingredient. Rather than this can i have directly "typeofingredient": "fruit"
Use serializers.ReadOnlyField
class IngredientListSerializer(ModelSerializer):
typeofingredient = serializers.ReadOnlyField(source='typeofingredient.name')
class Meta:
model = Ingredient
fields = '__all__'
You can add str method on models.py
class TypeOfIngredient(models.Model):
name = models.CharField(max_length=200,unique=True,null=False)
slug = models.SlugField(unique=True)
def __str__(self):
return str(self.name)
class Ingredient(models.Model):
name = models.CharField(max_length=200,unique=True,null=False)
slug = models.SlugField(unique=True)
typeofingredient = models.ForeignKey(TypeOfIngredient, related_name='typeof_ingredient',null=True, blank=True,on_delete=models.PROTECT)