class Flight(models.Model):
field_1
field_2
field_3
class Approach(models.Model):
flight_object(models.ForeignKey, 'Flight')
approach_type
number
Approach is related as an InlineFormset. How can I serialize Approach nested into Flight with the ability to create new approach objects. I'm unsure of the correct terminology which is making this more difficult to realize. My goal is to create a new Flight object with Approach as a related object in the FlightForm from a React Native project.
I assumed the expected response json
{
"field_1": "value1",
"field_2": "value2",
"field_3": "value3",
"approch_set": {
"approach_type": "value",
"number": 22
}
}
for this expected result we will define two serializers.
class ApproachSerializer(serializers.ModelSerializer):
class Meta:
model = Approach
fields = ('approach_type', 'number', )
class FlightSerializer(serializers.ModelSerializer):
approach_set = ApproachSerializer(many=true)
class Meta:
model = Flight
fields = "__all__"
and in view class you can return FlightSerializer data like this.
class FlightApiView(APIVIew):
def get(self, request):
flights = Flight.objects.all()
return Response({'data': FlightSerializer(flights, many=True).data})
for more you can read the documentation
approaches = SerializerMethodField(source='get_approaches')
def get_approaches(self, obj):
approach_queryset = Approach.objects.filter(flight_object=obj.pk)
return ApproachSerializer(approach_queryset, many=True).data
This is what finally worked, just want to come back and share it.
Thanks #shourav
Related
I'm creating a search functionality that returns all model fields like - users, posts, hashtags, images, and videos.
I'm trying to create multiple queryset with their multiple serializers to return in a single response.
this is my expected response result.
{
searchresults{
posts[
],images[
],videos[
],user[
]
}
}
I tried many methods to do this, but the response was unsuccessful.
I also tried that one but that required relationship.
class HashTagResponseSerializer(serializers.ModelSerializer):
class Meta:
model = HashtagName
fields = ["hashtag",]
def get_hashtag(self, obj):
...
class UserFollowSerializer(serializers.ModelSerializer):
hashtag = HashTagResponseSerializer(many=True, read_only=True)
class Meta:
model = User
fields = ['user_uuid', 'post_language', "username", "display_name",
"profile_picture", "hashtag "]
I have a many-to-many relationship in my DB design and I am having trouble with the POST aspect. I currently have a table called Loads, Containers, and Container_Loads (this is the intermediary table).
My question is this:
I want to be able to send a POST request into the ContainerLoad intermediary table and just update that table with the values it requires which are: Load ID (PK of the Load table), Container ID (PK of the Container Table) and # of pallets (unique field to the intermediary table). I am able to GET/retrieve the records just fine, but when I try to send a POST request with a payload such as
{
"id":3,
"pallets":"4",
"containerNumberId":5,
"loadNumberId":53
}
(where containerNumberID and loadNumberID are the existing keys in their respective tables), it seems that my code wants to create a whole new Load entry as well (as it asks me for the remaining fields of the Load model), where as I just want to create an entry in the intermediary table without creating a new entry in the Load table.
So for the purpose of my project, a load can be on many containers [imagine that it's split because all of it couldn't fit on one] and a container can belong to many loads.
My models.py looks like this:
class ContainerLoad(models.Model):
id = models.AutoField(primary_key=True)
load_number = models.ForeignKey(Load,on_delete=models.CASCADE)
container_number = models.ForeignKey(Container,on_delete=models.CASCADE)
pallets = models.CharField(blank=True,null=True,default=0,max_length=20)
class Meta:
db_table = 'ContainerLoad'
#load model shortened for brevity
class Load(models.Model):
id = models.AutoField(primary_key=True)
bnsf_container_number = models.ManyToManyField(Container, through='ContainerLoad',through_fields=('load_number','container_number'))
class Meta:
db_table = "Load"
class Container(models.Model):
id = models.AutoField(primary_key=True)
container_number = models.CharField(max_length=15)
in_use = models.BooleanField()
class Meta:
db_table = "Container"
my serializers.py currently looks like this, the commented out section is from me attempting to get the POST to work)
class ContainerLoadSerializer(WritableNestedModelSerializer):
# load_number_id = LoadSerializer(read_only=False)
# container_number_id = ContainerSerializer(read_only=False)
class Meta:
model = ContainerLoad
fields = "__all__"
depth = 2
class LoadSerializer(WritableNestedModelSerializer):
primary_driver = DriverSerializer(read_only=False)
second_driver = DriverSerializer(allow_null=True,read_only=False)
third_driver = DriverSerializer(allow_null=True,read_only=False)
bnsf_container_number = ContainerSerializer(read_only=False)
pickup_location = LocationSerializer(read_only=False)
delivery_location = LocationSerializer(read_only=False)
broker = BrokerSerializer(read_only=False)
booked_by = EmployeeSerializer(read_only=False)
class Meta:
model = Load
fields = '__all__'
depth = 1
class ContainerSerializer(serializers.ModelSerializer):
container_number = serializers.CharField()
in_use = serializers.BooleanField()
class Meta:
model = Container
fields = '__all__'
depth = 1
And finally the views.py
class ContainerLoadViews(APIView):
def get(self, request, id=None):
if id:
container = ContainerLoad.objects.get(id=id)
serializer = ContainerLoadSerializer(container)
return Response({"status": "success", "data": serializer.data}, status=status.HTTP_200_OK)
else:
containers = ContainerLoad.objects.all()
serializer = ContainerLoadSerializer(containers, many=True)
return Response({"status": "success", "data": serializer.data}, status=status.HTTP_200_OK)
def post(self, request):
serializer = ContainerLoadSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response({"status": "success", "data": serializer.data}, status=status.HTTP_200_OK)
else:
return Response({"status": "Error", "data": serializer.errors}, status=status.HTTP_400_BAD_REQUEST)
If you look at the code of the save method of the BaseSerializer class you will see this:
if self.instance is not None:
self.instance = self.update(self.instance, validated_data)
assert self.instance is not None, (
'`update()` did not return an object instance.'
)
else:
self.instance = self.create(validated_data)
assert self.instance is not None, (
'`create()` did not return an object instance.'
)
How you are not passing the instance in the post function:
serializer = ContainerLoadSerializer(data=request.data)
The save is always calling to create. You should do something like.
try:
instance = ContainerLoad.object.get(id=request.data['id'])
except:
instance = None
serializer = ContainerLoadSerializer(instance=instance, data = request.data)
You have depth is set to 2 in your ContainerLoadSerializer Meta class, which is telling the serializer to generate a nested representation of your models.
https://www.django-rest-framework.org/api-guide/serializers/#specifying-nested-serialization
The default ModelSerializer uses primary keys for relationships, but you can also easily generate nested representations using the depth option:
The depth option should be set to an integer value that indicates the depth of relationships that should be traversed before reverting to a flat representation.
If you remove the depth attribute, the serializer should default back to expecting a primary key value, which is your desired behaviour.
Your serializer should look something like this:
class ContainerLoadSerializer(serializers.ModelSerializer):
class Meta:
model = ContainerLoad
fields = "__all__"
The solution to this was that I needed a nested response when reading the data from the ContainerLoad table but a simple write (not nested) function when POSTing the data.
The solution was to use the to_representation and to_internal_value methods (https://www.django-rest-framework.org/api-guide/serializers/#overriding-serialization-and-deserialization-behavior) available within DRF to override the behavior of the serializers. Here is the code that now works for both GET and POST requests and it is no longer asking me for fields related to the Load or Container models when inserting data.
class ContainerSerializer(serializers.ModelSerializer):
container_number = serializers.CharField()
in_use = serializers.BooleanField()
class ContainerFieldSerializer(serializers.Field):
def to_internal_value(self,value):
return Container.objects.get(id=value)
def to_representation(self,instance):
return ContainerSerializer(instance=instance).data
class Meta:
model = Container
fields = '__all__'
depth = 1
I did the same for the Load Serializer.
and then for my ContainerLoad Serializer I just assign the FK fields to the new classes I created:
class ContainerLoadSerializer(serializers.ModelSerializer):
cl_container = ContainerSerializer.ContainerFieldSerializer()
cl_load = LoadSerializer.LoadFieldSerializer()
class Meta:
model = ContainerLoad
fields = "__all__"
depth = 2
I was following serializer tutorial and came across some code that i did not understand its function.
def get_order_items(self, obj):
items = obj.orderitem_set.all()
print(obj)
print(items)
return OrderItemSerializer(items, many=True).data
What does the order item function do.
The snippet was from the serializer class below:
class OrderSerializer(serializers.ModelSerializer):
order_items = serializers.SerializerMethodField()
"""
Calculate order_items field
"""
def get_order_items(self, obj):
items = obj.orderitem_set.all()
print(obj)
print(items)
return OrderItemSerializer(items, many=True).data
class Meta:
model = Order
fields = ('phone',
'first_name',
'delivery_date',
'delivery_time',
'address',
'comment',
'payment',
'order_items',)
def create(self, validated_data):
items_data = validated_data.pop('order_items')
order = Order.objects.create(**validated_data)
for item_data in items_data:
OrderItem.objects.create(order=order, **item_data)
return order
The order items functions serializes the OrderItem set, essentially converting in into a dictionary (or json) ready for you frontend to use because of the use of .data.
Grab objects -> Serialize -> Convert into json
So the result of that function will be a format that you can send as a response.
This is a MethodSerializer that will obtain the orderitem_set of the given Order object obj. Then these items run through the OrderItemSerializer and the serializer data is collected and returned with the rest of the data of the Order if the user triggers the corresponding view.
Using a subserializer that way is however not recommended. You can use the subserializer directly as a field as is explained in the documentation [Django-rest-doc]:
class OrderSerializer(serializers.ModelSerializer):
order_items = serializers.OrderItemSerializer(source='orderitem_set', many=True) # 🖘 use the OrderItemSerializer directly
class Meta:
model = Order
fields = ('phone', 'first_name', 'delivery_date', 'delivery_time', 'address', 'comment', 'payment', 'order_items')
def create(self, validated_data):
items_data = validated_data.pop('order_items')
order = Order.objects.create(**validated_data)
for item_data in items_data:
OrderItem.objects.create(order=order, **item_data)
return order
models.py
class Game(models.Model):
name = models.CharField(max_length=255)
gamemodes = models.ManyToManyField(GameMode, related_name='gamemodes', blank=True)
class GameMode(models.Model):
name = models.CharField(max_length=255)
serializers.py
class GameModeSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
fields = ['pk', 'name']
model = GameMode
class GameSerializer(serializers.HyperlinkedModelSerializer):
gamemodes = GameModeSerializer(many=True, required=False)
class Meta:
model = Game
fields = ['pk', 'name', 'gamemodes']
def update(self, instance, validated_data):
print("Validated: ", validated_data)
Updating name works perfectly with PATCH. But how can I add a "gamemode" to the Game object in the rest framework with a PATCH request?
On the update function on the serializer, it print all values when I PATCH something, but when I submit "gamemodes" it does not appear in the variable validated_data
Best
With your current serializer setup you need to supply the dicts for gamemodes.
{
"pk": 1,
"name": "Game 1",
"gamemodes": [{"pk": 100, "name": "gamemode 100"}]
}
Alternatively you could add to your GameSerializer to also have a write only field that accepts the pks for the relationship PrimaryKeyRelatedField or SlugRelatedField if you don't want to use the internal ids.
Context
Say we take this example from the DRF relations guide.
# models.py
class Album(models.Model):
album_name = models.CharField(max_length=100)
artist = models.CharField(max_length=100)
class Track(models.Model):
album = models.ForeignKey(Album, related_name='tracks', on_delete=models.CASCADE)
order = models.IntegerField()
title = models.CharField(max_length=100)
duration = models.IntegerField()
class Meta:
unique_together = ('album', 'order')
ordering = ['order']
def __str__(self):
return '%d: %s' % (self.order, self.title)
Using a serializer will get us this output
class AlbumSerializer(serializers.ModelSerializer):
tracks = serializers.StringRelatedField(many=True)
class Meta:
model = Album
fields = ('album_name', 'artist', 'tracks')
Will get us this output:
{
'album_name': 'Things We Lost In The Fire',
'artist': 'Low',
'tracks': [
'1: Sunflower',
'2: Whitetail',
'3: Dinosaur Act',
...
]
}
Question
How can i use the serializer to get the output like this:
{
'album_name': 'Things We Lost In The Fire',
'artist': 'Low',
'tracks': {
1: {order: 1, title: 'Sunflower'},
2: {order:2, title: 'Whitetail'},
3: {order:3, title: 'Dinosaur Act'},
...
}
}
This way we have an object with tracks instead of a numeric array. So i can do this.props.album.tracks[2].title this instead of this.props.album.tracks.find(track => track.order == 2}).title in javascript.
``I have an use case in where this seems to be more convenient in Reactjs.
What i have tried
I thought about overriding the to_representation method. But i see that this will get me an recursive loop.
class TrackSerializer(serializers.ModelSerializer):
def to_representation(self, instance):
print(self)
return '%s: { %s }' % (instance.order, self.to_representation(instance))
class Meta:
fields = '__all__'
model = Track
Furthermore i have searched and read the docs pretty well. But didn't find any solution for what i think should be a pretty logical solution to have out of the box. Making me think that i am wrong and missing something.
Thanks in advance.
Define a new TrackSerializer and use it in AlbumSerializer class as,
class TrackSerializer(serializers.ModelSerializer):
class Meta:
model = Track
fields = ('id', 'order', 'title')
class AlbumSerializer(serializers.ModelSerializer):
tracks = TrackSerializer(many=True)
class Meta:
model = Album
fields = ('album_name', 'artist', 'tracks')
def to_representation(self, instance):
representation = super().to_representation(instance)
representation['tracks'] = {track['id']: track for track in representation['tracks']}
return representation