How to Merge Two Model Serialisers in Django Rest Framework - django

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.

Related

Different write and read operations with DRF

I am using Django Rest Framework for a project and I am running into a problem. When the frontend creates a Team they want to reference all relationships with an ID, but when getting the Team, they want the data from the relationship. How can I achieve this?
models:
class Team(models.Model):
class Meta:
db_table = "team"
team_id = models.AutoField(primary_key=True)
name = models.CharField(max_length=100)
organization = models.ForeignKey(Organization, on_delete=models.CASCADE)
class Organization(models.Model):
class Meta:
db_table = "organization"
organization_id = models.AutoField(primary_key=True)
name = models.CharField(max_length=100)
class Position(models.Model):
class Meta:
db_table = "position"
position_id = models.AutoField(primary_key=True)
team = models.ForeignKey(Team, on_delete=models.CASCADE, related_name="positions")
class Player(model.Model):
class Meta:
db_table = "player"
player_id = models.AutoField(primary_key=True)
name = models.CharField(max_length=100)
positions = models.ManyToManyField(Position, related_name="players")
serializers:
class TeamSerializer(serializers.ModelSerializer):
class Meta:
model = Team
fields = ["team_id", "name", "organization", "positions"]
positions = PositionSerializer(many=True) # This is merely for output. There is no need to create a position when a team is created.
organization = OrganizationSerializer() # Since an organization already exists I'd like to just give an organization_id when creating/editing a team.
# I don't think the other serializers matter here but can add them on request.
So when doing POST or PATCH on a team, I'd like the front end to be able to pass this payload
{
"name": "My Team",
"organization": 1
}
but when doing a GET on a team, I'd like the front end to receive this response.
{
"team_id": 1,
"name": "My Team",
"organization": {
"organization_id": 1,
"name": "My Organization"
},
"positions": [{
"position_id": 1,
"players": [{
"player_id": 1,
"name": "Member 1"
}
]
}
Is there a a way to achieve this?
In such situations define two serializers, one is for read operations and one is for write operations.
class TeamWriteSerializer(serializers.ModelSerializer):
# see, here no nested relationships...
class Meta:
model = Team
fields = ["name", "organization"]
class TeamReadSerializer(serializers.ModelSerializer):
class Meta:
model = Team
fields = ["team_id", "name", "organization", "positions"]
positions = PositionSerializer(many=True)
organization = OrganizationSerializer()
and now, use these two serializers properly in your views. For example, I hope you are using the ModelViewSet in views,
class TeamModelViewSet(viewsets.ModelViewSet):
def get_serializer_class(self):
if self.request.method.lower() == 'get':
return TeamReadSerializer
else:
return TeamWriteSerializer

Serialization of related pivot models with Django Rest Framework

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.

How to build a Serializer for a Normalized DataBase

Maybe my question has already been answered, but I could not find anything.
Lets say that Im building a stock inventory system using Django + Django REST for the backend. To supply this I have this two models:
class Shoe(models.Model):
_id = models.AutoField(primary_key=True)
description = models.CharField(null=False, blank = False)
provider = models.CharField(null=False, blank=False)
category = models.CharField(choices=CATEGORIES, null=False, blank=False)
class Sizes(models.Model):
shoe_id = models.ForeignKey(Shoe, on_delete=models.CASCADE)
size = models.IntegerField(choices=SIZE_LIST, null=False, blank=False)
amount = models.IntegerField(null=False,default=0)
My doubt is, how can I (using ModelViewSet, cause based on my experience with DRF is the easiest way) serve JSON files like this:
[
{
"_id": "1",
"description": "Air Max Black",
"provider": "NIKE",
"category": "Casual",
"available_sizes": {36: 400, 37: 250}, #size: amount
"amount": "650" #total amount
},
]
Based on what I understand of DRF to "join" the two models in a JSON I should write a custom serializer right?
Usually my serializers are like
class FooSerializer(serializers.ModelSerializer):
class Meta:
model = Foo
fields = ['some_stuff_here']
Please help me or recomend me reading material for do that, I have read the DRF docs about Serializers but cant understand how to do stuff like this.
You can just use a nested relation:
class ShoeSerializer(serializers.ModelSerializer):
class Meta:
model = Shoe
fields = '__all__' # all for all fields, or a tuple with the fields
class SizeSerializer(serializers.ModelSerializer):
shoe = ShoeSerializer(many=True, read_only=True)
class Meta:
model = Size
fields = '__all__'
But I believe your model is inverted, maybe size should be a fk on shoe model if you want to bring the size of an shoe instance? If that is the case, just invert the serializers.

How to set up double nested serializer in Django Rest Framework

I am trying to figure out how to set up my nested serializers.
# models.py
class Product(models.Model):
sku = models.CharField()
product_name = models.CharField()
class Order(models.Model):
name = models.CharField()
address = models.CharField()
class OrderProduct(models.Model):
order = models.ForeignKey( Order )
product = models.ForeignKey( Product )
quantity = models.IntegerField()
So I want to have an api that can create an order in the form of the following:
{
"name" : "James",
"address" : "100 Main St",
"products" : [
{ "sku" : "1234", "quantity" : 1 }
]
}
I understand that I would need nest OrderProductSerializer inside OrderSerializer, but how do I implement it here when the "products" data use the field "sku" which is not found in the OrderProduct model. Do I do double-nesting? How does that look like?
# serializers.py
class OrderProductSerializer( serializers.ModelSerializer):
class Meta:
model = OrderProduct
exclude = ()
class OrderSerializer(serializers.ModelSerializer):
products = OrderProductsSerializer(many=True)
class Meta:
model = Order
exclude = ()
You are trying to implement Nested Serialize on reverse relationship. So, you have to explicitly provide the relationship name as the parameter to the serializer via source argument.
Try this
class OrderProductSerializer(serializers.ModelSerializer):
class Meta:
model = OrderProduct
fields = '__all__'
class OrderSerializer(serializers.ModelSerializer):
product = OrderProductSerializer(many=True, source='product_set')
class Meta:
model = Order
fields = '__all__'
For more info reffere these docs
1. DRF Nested Relationship
2. DRF-Reverse Realation
3. What is reverse-relationship in Django

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.