Serialization of related pivot models with Django Rest Framework - django

I am learning Django, and are using Django Rest Framework. In my application, I have three different models
Bar (holds information about a bar, has multiple beers through the BarBeer model)
Beer (holds information about a beer)
BarBeer (connection between a bar and a beer, has pivot fields such as alcohol, type, price and volume)
This is how the different models are defined:
class Bar(models.Model):
name = models.CharField(max_length=60)
location = models.PointField()
address = models.CharField(max_length=60)
description = models.TextField(default='')
beers = models.ManyToManyField('api.Beer', through='api.BarBeer')
class Beer(models.Model):
name = models.CharField(max_length=60)
alcohol = models.FloatField(default=0)
class BarBeer(models.Model):
bar = models.ForeignKey(Bar, on_delete=models.CASCADE)
beer = models.ForeignKey(Beer, on_delete=models.CASCADE)
price = models.FloatField(default=0)
type = EnumField(Type, default=Type.Cask)
volume = models.IntegerField(default=0)
Now I want to serialize a given bar with all the beers for that particular bar including the extra fields in the pivot model BarBeer. For example, below is what I'd like the output to be (note the extra three fields on the beer, that comes from the BarBeer model):
{
"id": 1,
"name": "A bar",
"beers": [
{
"id": 1,
"name": "Ship Full of IPA",
"alcohol": 6.5,
"type": "bottle",
"price": "35",
"volume": "33"
}
]
}
I can't figure out how to get the extra fields from the pivot model as part of the serialized output. This is what my serializer looks like right now:
class BarDetailsSerializer(serializers.ModelSerializer):
class Meta:
model = Bar
fields = ('id', 'name', 'beers')
depth = 3

Firstly beers = models.ManyToManyField('api.Beer', through='api.BarBeer') this field is unneccessary, because you have already created a table named BarBeer. ManyToManyField means adding exra table. So, if we assume this field is not exist and you have BarBeer table, you can do this with using BarBeerSerializer like that:
serializers.py
class BarBeerSerializer(serializers.ModelSerializer):
name = serializers.SerializerMethodField()
alchol = serializers.SerializerMethodField()
class Meta:
model = BarBeer
fields = ['id','name','alchol','type','price','volume']
def get_name(self,obj):
return obj.beer.name
def get_alchol(self,obj):
return obj.beer.alchol
class BarSerializer(serializers.ModelSerializer):
beers = serializers.SerializerMethodField()
class Meta:
model = Bar
fields = ['id', 'name', 'beers']
def get_beers(self,obj:Bar):
beers = obj.barbeer_set.all()
return BarBeerSerializer(beers,many=True).data
If there is an error, please ask it on comment.

One way to do this is you can create a custom serializer for the BarBeer model and pass the other two models serializers in it something like :~
class BarDetailsSerializer(serializers.Serializer):
bar = BarSerializer()
beer = BeerSerializer()
price = serializers.FloatField(required=True)
type = serializers.CharField(required=True)
volume = serializers.IntegerField(required=True)
just pass your BarBeer object to this serializer and it would return all the data points, even for the objects connected through Foreign Key.
Another way to achieve this as the exact same response that you mentioned in the question would be to create a renderer for you api and format and structure the data as you want in it accordingly.

Related

How to Merge Two Model Serialisers in Django Rest Framework

I want to merge the results of two models. I have two models as below.
First is Product and the other is ProductChannelListing
class Product(models.Model):
name = models.CharField()
category = models.ForeignKey()
# has many other fields which I want in the response
class ProductChannelListing(models.Model):
product = models.ForeignKey()
channel = models.ForeignKey()
is_visible = models.BooleanField()
price = models.IntegerField()
I want the result in the following way.
{
"name": "ABC",
"category": {},
"is_visible": true,
"price": 200,
**
}
I want make the query efficient query such that it should not make so many queries.
from .models import Product, ProductChannelListing
class ProductSerializer(model.serializer):
class Meta:
model: Product
fields = '__all__'
class ProductChannelListingSerializer(model.serializer):
product = ProductSerializer(ready_only=True)
class Meta:
model: ProductChannelListing
fields = '__all__'
Do the same for channel also, and u will get all fields visible at one viewpoint with serializer_class as ProductChannelListingSerializer.

Rest Framework deserialize one field and serialize model object

Hi I want to deserialize only using 1 field. However, I want to serialize it as an object depending on the model.
Suppose I have:
#models.py
class Product(models.Model):
name = models.CharField()
amount = models.IntegerField()
description = models.TextField()
class Query(models.Model):
name = models.CharField()
product = models.ForeignKey(Product)
...
#serializers.py
class ProductSerializer(serializers.ModelSerializer):
class Meta:
model = Product
fields = '__all__'
class QuerySerializer(serializers.ModelSerializer):
product = ProductSerializer()
class Meta:
model = Query
fields = '__all__'
I want to POST/deserialize something like this on the QuerySerializer:
{
"name": "Query 1",
"product": "Banana",
...
}
and I want something like this in return for serializer:
{
"name": "Query 1",
"product": {
"name": "Banana",
"amount": 1,
"description": "Banana description"
}
...
}
I know a way is overriding to_internal_value but I do not like it since it messes up with ValidationErrrors.
I also get this as a result:
{'product': {'non_field_errors':
['Invalid data. Expected a dictionary, but got str.']}}
First of all, make the name field of Product as unique to avoid unnecessary complications.
class Product(models.Model):
name = models.CharField(unique=True)
amount = models.IntegerField()
description = models.TextField()
and change your serializer as,
class QuerySerializer(serializers.ModelSerializer):
product = serializers.CharField(write_only=True)
class Meta:
model = Query
fields = '__all__'
def create(self, validated_data):
product_name = validated_data.pop('product')
product_instance = Product.objects.get(name=product_name)
return Query.objects.create(product=product_instance, **validated_data)
def to_representation(self, instance):
rep = super().to_representation(instance)
rep['product'] = ProductSerializer(instance.product).data
return rep
Reference: DRF: Simple foreign key assignment with nested serializers?

DRF Relation with two independent columns

I have DB structure like this:
common_org:
id,
code,
title
special_org:
id,
code,
address
Okay. For this I've created models:
class CommonOrg(models.Model):
code = models.CharField()
title = models.CharField()
class SpecialOrg(models.Model):
code = models.CharField(null=True)
address= models.CharField()
Now I want to output SpecialOrg as usual, but if I have CommonOrg.code == SpecialOrg.code, then attach CommonOrg to SpecialOrg like this:
{
"id": 1,
"code": "XYZ",
"address": "ADDRESS",
"common_org": {
"id": 2,
"code": "XYZ",
"title": "TITLE"
}
}
Now I have solution with serializers.RelatedField:
class CommonOrgField(serializers.RelatedField):
def to_representation(self, value):
class _CommonOrgSerializer(serializers.ModelSerializer):
class Meta:
model = CommonOrg
fields = '__all__'
representation = None
try:
common_org = CommonOrg.objects.get(code=value)
representation = _CommonOrgSerializer(common_org).data
except CommonOrg.DoesNotExist:
pass
return representation
class SpecialOrgSerializer(serializers.ModelSerializer):
class Meta:
model = SpecialOrg
fields = '__all__'
common_org = CommonOrgField(read_only=True, source='code')
But it looks ugly for me.
So the question is: what is the right approach to implement it in DRF? Database is not mine and I cannot to alter it.
In most cases where I am to add a read only field to a serializer, when the field is not related to the current model at database level, I'd use a serializer method field. You could use serializer method field like this in your case:
class SpecialOrgSerializer(serializers.ModelSerializer):
common_org = serializers.SerializerMethodField()
class Meta:
model = SpecialOrg
fields = '__all__'
def get_common_org(self, obj):
try:
common_org = CommonOrg.objects.get(code=value)
except CommonOrg.DoesNotExist:
return None
return _CommonOrgSerializer(common_org).data

Filtering a reverse relationship by user, and returning only one object in Django Rest Framework

Given these models:
class Product(models.Model):
name = CharField(max_length=255)
class Order(models.Model):
product = models.ForeignKey(Product)
user = models.ForeignKey(User)
quantity = models.PositiveIntegerField()
A user can only have a single Order object per product. I would like an API call to show a list of products, with the user's order where available. Is there a way to do that?
The default serialisation lists ALL orders under order_set. I did get a bit ahead with this to filter by user:
class FilteredOrderSerializer(serialisers.ListSerializer):
def to_representation(self, data):
data = data.filter(user=self.context['request'].user)
return super(FilteredOrderSerializer, self).to_representation(data)
class OrderSerializer(serializers.ModelSerializer):
class Meta:
model = Order
list_serializer_class = FilteredOrderSerializer
class ProductSerializer(serializers.ModelSerializer):
order_set = OrderSerializer(many=True, read_only=True)
class Meta:
model = Product
So now it only shows the authenticated user's order, but the resulting JSON looks something like
[
{
"name": "prod1",
"order_set": [
{
"quantity": 4
}
],
}
]
i.e. the field is still called order_set and it's a list, while I would like it to be called order and be either an object or null. So I'm not sure where this filtering should take place, nor how to define the field.
Edit: I'm using a simple viewset
class ProductViewSet(view sets.ReadOnlyModelViewSet):
queryset = Product.objects.all()
serializer_class = ProductSerializer
You need to add related_name field in your orders so you can
product.orders.all()
Then this should do.
class Order(models.Model):
product = models.ForeignKey(Product, related_name='orders')
user = models.ForeignKey(User)
quantity = models.PositiveIntegerField()
class ProductSerializer(serializers.ModelSerializer):
order = serializers.SerializerMethodField()
class Meta:
model = Product
fields = ('name', 'order')
def get_order(self, object):
try:
order = object.orders.get(user=self.context['request'].user)
return SceneDetailSerializer(order).data
except Order.DoesNotExist:
return None
Update: You can try out serializer method field. Not sure if self contains context and user references in it. You need to remove listfield from your order serializer. Let me know if it works.

Django REST displayed filtered nested resource

I'm attempting to return JSON in a simple GET that includes a row from a joined table. The models:
class School(models.Model):
id = models.CharField(max_length=18,primary_key=True)
name = models.CharField(max_length=255,blank=False)
class Meta:
db_table='school'
class FKTable(models.Model):
school = models.ForeignKey(School,blank=False, db_column='school_id', on_delete=models.CASCADE)
name = models.CharField(max_length=45, blank=False)
value = models.CharField(max_length=255, blank=True, null=True)
class Meta:
db_table='fk_table'
And the serializer:
class SchoolListSerializer(serializers.ModelSerializer):
id = serializers.CharField(source='id')
name = serializers.CharField(source='name')
fkColumn = ????
class Meta:
model = models.School
fields = ('id','name','fkColumn')
The problem is I want to also filter the nested resource that I join against on the 'name' column so I don't know what to put for 'fkColumn' in the serializer. In SQL it would look something like this:
SELECT school.*, fk_table.value from school INNER JOIN fk_table on
fk_table.school_id = school.id AND fk_table.name = 'some name'
The end result JSON I want is:
{
"schools": [
{
"id": "someId",
"name": "someName",
"fkColumn": "Value"
}]}
RelatedField does not seem to work and none of the documentation seems to give a solid answer on how to do this. Any ideas?
Revised after clarification. Define an fkeySerializer:
class fkeySerializer(serializers.ModelSerializer):
...
Then link this in your main serializer:
fkey = fkeySerializer(many=True)
class Meta:
model = models.School
fields = ('id','name','fkColumn',)
You can use a MethodField serializer here. For example:
fkColumn = serializers.SerializerMethodField()
def get_fkColumn(self, obj):
return obj.fkColumn
class Meta:
model = models.School
fields = ('id','name','fkColumn',)
As an aside, perhaps there's a good reason for it, but it might be easier to rename this table to be more easily readable