Rest Framework deserialize one field and serialize model object - django

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?

Related

Post method to pass data to multiple model serializers

I am new to Django and have a json object whose data has to be split and stored in 3 different Django models. I'm trying to figure out if I'm doing this correctly in using the view file and serializers in Django.
The json object that I receive into the views file looks like below:
[
{
"contact_entry":
{
"employee_name" : "Tom Hanks",
"employee_type" : "Full-time permanent"
},
"address" :
{
"line1": "1435 Manhattan Ave",
"line2": "Apt 123",
"city": "New York",
"state": "New York",
"country":"US",
},
"phone_number" :
{
"work_number": "9901948626"
"home_number": "9908847555"
}
}
]
I have three django models as shown below:
class ContactEntry(models.Model):
employee_name = models.CharField(max_length=128)
employee_type = models.CharField(max_length=128)
class Address(models.Model):
contact_entry = models.ForeignKey(ContactEntry, on_delete=models.CASCADE)
line1 = models.CharField(max_length=128)
line2 = models.CharField(max_length=128)
city = models.CharField(max_length=128)
state = models.CharField(max_length=128)
country = models.CharField(max_length=128)
class PhoneNumber(model.Model):
contact_entry = models.ForeignKey(ContactEntry, on_delete=models.CASCADE)
work_number = models.IntegerField(max_length=10)
home_number = models.IntegerField(max_length=10)
Each of them has a ModelSerializer as shown below:
class ContactEntrySerializer(serializers.ModelSerializer):
class Meta:
model = ContactEntry
fields = '__all__'
class AddressSerializer(serializers.ModelSerializer):
class Meta:
model = Address
fields = '__all__'
class PhoneNumberSerializer(serializers.ModelSerializer):
class Meta:
model = PhoneNumber
fields = '__all__'
I have defined one serializer (inherited from serializers.serializer) that combines the above 3 serializers as shown below:
class CombinedSerializer(serializers.Serializer):
contact_entry = ContactEntrySerializer()
phone_number = PhoneNumberSerializer()
address = AddressSerializer()
The post method in my view is as shown below:
class CombinedContactView(APIView):
def post(self, request, *args, **kwargs):
# Validate JSON data with the serializer
serializer = CombinedSerializer(data=request.data, many=True)
serializer.is_valid(raise_exception=True)
validated_data = serializer.validated_data.pop()
contact_entry_data = validated_data['contact_entry']
address_data = validated_data['address']
phone_number_data = validated_data['phone_number']
# Create model for contact book entry
contact_entry_model = ContactEntry(**contact_entry_data)
contact_entry_model.save()
# Create model for address
address_data['contact_entry'] = contact_entry_model
address_model = Address(**address_data)
address_model.save()
# Create model for phone_number
phone_number_data['contact_entry'] = contact_entry_model
phone_number_model = PhoneNumber(**phone_number_data)
phone_number_model.save()
return HttpResponse(status=201)
The above code actually runs fine. I see the contact_entry object created and the address and phone_numbers correctly created (with a foreign key relationship to the contact_entry object). However, I'm concerned that I'm doing this in a roundabout way with lots of unnecessary code in my views file. Is there a more straightforward way to do this?
if you change your serializers to these you'll be OK:
class ContactEntrySerializer(serializers.ModelSerializer):
class Meta:
model = ContactEntry
fields = '__all__'
class AddressSerializer(serializers.ModelSerializer):
contact_entry = ContactEntrySerializer(required=False)
class Meta:
model = Address
fields = '__all__'
class PhoneNumberSerializer(serializers.ModelSerializer):
contact_entry = ContactEntrySerializer(required=False)
class Meta:
model = PhoneNumber
fields = '__all__'
just notice that i changed contact_entry on AddressSerializer and PhoneNumberSerializer to required=False so you can pass contact_entry validation and these two serializers. and after it your view do the rest.

DRF update the nested foreign key of an object

I am trying to wrap my head around this for too long already :(
I need the following output for my frontend (specially the ID & name field in combination):
{
"serial": "e3461fb0",
"shipment": {
"id": 1,
"name": "via rotterdam"
}
}
model:
class Shipment(models.Model):
name = models.CharField("name", max_length = 128)
date = models.DateField()
class Product(models.Model):
serial = models.CharField("serial", max_length = 31, unique = True)
shipment = models.ForeignKey(Shipment, on_delete = models.CASCADE, blank = True, null = True)
serializer:
class ShipmentSerializer(serializers.ModelSerializer):
class Meta:
model = Shipment
fields = ["id", "name",]
class ProductSerializer(serializers.ModelSerializer):
shipment = ShipmentSerializer()
def update(self, instance, validated_data):
print("TEST:", instance, validated_data)
return super().update(instance, validated_data)
class Meta:
model = Product
fields = ["serial", "shipment",]
lookup_field = "serial"
read_only_fields = ["serial",]
ViewSet:
class ProductViewSet(ModelViewSet):
serializer_class = ProductSerializer
lookup_field = "serial"
http_method_names = ["get", "patch", "put"]
def get_queryset(self):
return Product.objects.all()
my problem here is the following: Lets say a product ist linked to a shipment and what I want now is to update that product by linking it to another shipment by using the id. But even in the HTML view of DRF I only get to see the name attribute of shipment. How can I only show/use the id here? I know I could modify the __str__ method of the model to return a string representation and split it later in the frontend, but I would like to avoid that if possible.
I was thinking of something like this:
Id field is what DRF make itself, and it's read-only by default
If you want to update that, you should override this field. Something like:
class ShipmentSerializer(serializers.ModelSerializer):
id = serializers.IntegerField()
name = serializers.CharField(read_only=True)
class Meta:
model = Shipment
fields = ["id", "name",]

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

How to retrieve foreign key field in Django rest framework?

Given the model and serializer classes below, when I retrieve Track details, it'll only show the Track title but not the related Artist.
How would I also show the Artist name when retrieving Track details?
models.py
class Artist (models.Model):
name = models.CharField(max_length=100)
def __str__(self):
return self.name
class Track (models.Model):
artist = models.ForeignKey(Artist, blank=True, null=True, on_delete=models.SET_NULL, verbose_name="Artist")
title = models.CharField(max_length=100, verbose_name="Title")
def __str__(self):
return self.title
serializers.py
class ArtistSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(read_only=True)
name = serializers.CharField()
class Meta:
model = Artist
fields = ('id', 'name')
class TrackSerializer(serializers.ModelSerializer):
class Meta:
model = Track
fields = '__all__'
I think you need custom field, try this serializer:
class TrackSerializer(serializers.ModelSerializer):
class Meta:
model = Track
fields = ('title', 'artist','artist_name')
artist_name = serializers.SerializerMethodField('get_artists_name')
def get_artists_name(self, obj):
return obj.artist.name
It produce something like this.
[
{
"title": "Don't let me down",
"artist": 2,
"artist_name": "The Beatles"
},
{
"title": "Summertime",
"artist": 1,
"artist_name": "Ella Fitzgerald"
}
]
Try this serializer,
class ArtistSerializer(serializers.ModelSerializer):
class Meta:
model = Artist
fields = '__all__' # or array of fieldnames like ['name_1', 'name_2']
class TrackSerializer(serializers.ModelSerializer):
artist = ArtistSerializer()
class Meta:
model = Track
fields = ('title', 'artist')
Inorder to retrieve Artist details, which is a ForeignKey model, you need to use a nested serializer in django-rest-framework.
By using the TrackSerializer with a nested ArtistSerializer, the retrieved data would look something like this,
{
"title": "Some_Title",
"artist": {
"id": 2, #or id of the artist.
"name": "Artist_name"
}
}
As you can see in the official django rest framework documentations
You should define a serializer field for nested items
First create your Artist (nested item) serializer
class ArtistSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(read_only=True)
name = serializers.CharField()
class Meta:
model = Artist
fields = ('id', 'name')
Then you can use it on related model serializers
class TrackSerializer(serializers.ModelSerializer):
artist = ArtistSerializer()
class Meta:
model = Track
fields = ('title', 'artist')
In the current version of DRF you can simply do this
class TrackSerializer(serializers.ModelSerializer):
artist = serializers.StringRelatedField()
class Meta:
model = Track
fields = '__all__'
StringRelatedField may be used to represent the target of the relationship using its __str__ method.
REF

Save nested object with serializer in django rest framework

I'm getting a Json response to save it in my database, I need to get the items in line_items object. My Serializer and View works fine if I remove the line_items attribute in the model, but when I try to get that object and save it in the database nothing happens. Maybe I'm missing something in my serializer?
Json Structure:
{
"id": 123456,
"email": "jon#doe.ca",
"created_at": "2017-03-29T15:56:48-04:00",
"line_items": [
{
"id": 56789,
"title": "Aviator sunglasses",
"quantity": 1
},
{
"id": 98765,
"title": "Mid-century lounger",
"quantity": 1
}
]
}
My model:
class Line(models.Model):
title = models.CharField(max_length=100)
quantity = models.IntegerField()
class Order(models.Model):
name = models.CharField(max_length=255)
created_at = models.DateTimeField()
total_price = models.DecimalField(max_digits=6,decimal_places=2)
line_items = models.ForeignKey(Line)
My Serializer:
class OrderSerializer(ModelSerializer):
class Meta:
model = Order
fields = '__all__'
My View:
#api_view(['POST'])
def orders(request):
if request.method == 'POST':
json_str = json.dumps(request.data)
resp = json.loads(json_str)
serializer = OrderSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
You have a list in the field-key line_items in your response, as per your models you can't accommodate the data in the tables, what you need is ManyToMany relation between Order and Line,
class Order(models.Model):
name = models.CharField(max_length=255)
created_at = models.DateTimeField()
total_price = models.DecimalField(max_digits=6,decimal_places=2)
line_items = models.ManyToManyField(Line)
and in your serializer,
class OrderSerializer(ModelSerializer):
class Meta:
model = Order
fields = [f.name for f in model._meta.fields] + ['line_items']
def create(self, validated_data):
line_items = validated_data.pop('line_items')
instance = super(OrderSerializer, self).create(validated_data)
for item in line_items:
instance.line_items.add(item['id'])
instance.save()
return instance
Just add depth=1 in your serializer. It will do-
class OrderSerializer(ModelSerializer):
class Meta:
depth = 1
fields = '__all__'
I think you should add an explicit line_items field to OrderSerializer. Like this:
class OrderSerializer(ModelSerializer):
line_items = LineSerializer(many=True)
class Meta:
model = Order
fields = '__all__'