django nested relation - many-to-many serialization - python

I have been struggling to add many-to-many relation in the django serializer than be accessed through the view.
I created 2 classes in the model file "MODISLevel1" & "FileProperties". Where MODISLevel1 has a field called "filesProperties" pointing to the "FileProperties" class.
The model looks like:
class FileProperties(models.Model):
FileName = models.CharField(blank=True, max_length=1000)
FileID = models.CharField(blank=True, max_length=1000)
FileSize = models.CharField(blank=True, max_length=1000)
Updated = models.CharField(blank=True, max_length=1000)
GeoBox = models.CharField(blank=True, max_length=1000)
def __str__(self):
return self.FileName
class Meta:
ordering = ('FileName',)
class MODISLevel1(models.Model):
ProductLevel = models.CharField(max_length=7,
choices=ProductLevelsChoices,
default='MOD03')
SpacecraftType = models.CharField(max_length=5,
choices=SpacecraftTypeChoices,
default='Terra')
StartTimespan = models.TextField()
EndTimespan = models.TextField()
AOI = models.TextField()
DegreeNumbers = models.TextField()
filesProperties = models.ManyToManyField(FileProperties)
def __str__(self):
return self.ProductLevel
class Meta:
ordering = ('ProductLevel',)
As for the serializer I checked http://django-rest-framework.org/api-guide/relations.html#nested-relationships which tells that nested relationships should be created in the following way:
class FilePropertiesSerializer(serializers.ModelSerializer):
class Meta:
model = FileProperties
fields = ('id', 'FileName', 'FileID', 'FileSize', 'Updated', 'GeoBox')
class MODISLevel1Serializer(serializers.ModelSerializer):
filesProperties = FilePropertiesSerializer(many=True)
class Meta:
model = MODISLevel1
fields = ('id', 'ProductLevel', 'SpacecraftType', 'StartTimespan', 'EndTimespan', 'AOI', 'DegreeNumbers', 'filesProperties')
for the the view file I tried different ways to make it work but with no success.
1-Creating an instance from the Model "MODISLevel1", filling its fields, saving it, then filling the m2m field through iteration:
modisLevel1 = MODISLevel1()
modisLevel1.ProductLevel = productLevel
modisLevel1.SpacecraftType = spacecraftType
modisLevel1.StartTimespan = startTimespan
modisLevel1.EndTimespan = endTimespan
modisLevel1.AOI = AOI
modisLevel1.DegreeNumbers = degreeNumbers
modisLevel1.save()
#Inside a loop
f = FileProperties()
f.FileID = fileID
f.FileName = fileName
f.Updated = updated
f.GeoBox = geoBox
f.FileSize = fileSize
f.save()
modisLevel1.filesProperties.add(f)
serializer = MODISLevel1Serializer(data=modisLevel1)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
the error that i got is:
{
"non_field_errors": [
"Invalid data"
]
}
but for sure it's added in the database as I executed the save command "modisLevel1.save()", but that's not what I need.
2-Creating a json file and appending the 'filesProperties' to it through the iteration:
jsonFile = {u"ProductLevel": productLevel, u"SpacecraftType": spacecraftType, u"StartTimespan": startTimespan,
u"EndTimespan":endTimespan, u"AOI": AOI, u"DegreeNumbers": u"POINT(22.5,28.34) POINT(28.64,28.34) POINT(29.4,19.16) POINT(19.19,19.3067)",
u"filesProperties": [] }
#inside the loop
jsonFile["filesProperties"].append({u"FileName":fileName, u"FileID":fileID, u"FileSize":fileSize, u"Updated":updated, u"GeoBox":geoBox})
serializer = MODISLevel1Serializer(data=jsonFile)
and this one throws an error that the base model has to have a value before setting the m2m field:
Cannot add "": instance is on database "default", value is on database "None"
3-Create a dict and parse the Model to this dictionary, and then convert the FileProperties to the dictionary and append it to the previously created one:
modisLevel1 = MODISLevel1()
modisLevel1.ProductLevel = productLevel
modisLevel1.SpacecraftType = spacecraftType
modisLevel1.StartTimespan = startTimespan
modisLevel1.EndTimespan = endTimespan
modisLevel1.AOI = AOI
modisLevel1.DegreeNumbers = degreeNumbers
modisLevel1.save()
dict = model_to_dict(modisLevel1)
#FIXME!
dict['filesProperties'] = [{"FileName": "fileName", "FileID": "fileID", "FileSize": "fileSize", "Updated": "updated", "GeoBox": "geoBox"},
{"FileName": "fileName", "FileID": "fileID", "FileSize": "fileSize", "Updated": "updated", "GeoBox": "geoBox"},
{"FileName": "fileName", "FileID": "fileID", "FileSize": "fileSize", "Updated": "updated", "GeoBox": "geoBox"}]
serializer = MODISLevel1Serializer(data=dict)
and this one throws the same error as the previous one.
I did check some other posts that might be close to this issue, but none of them did work with me:
django rest nested relation in post/put
My json post request looks like the following:
[
{
"ProductLevel": "MOD03",
"SpacecraftType": "Terra",
"StartTimespan": "2013.11.02",
"EndTimespan": "2013.11.02",
"AOI": "swath",
"DegreeNumbers": "POINT(22.5,28.34) POINT(28.64,28.34) POINT(29.4,19.16) POINT(19.19,19.3067)"
}
]
Please notice that in the 'view.py' file I am able to store values in the database but if I bypass the serializer, like the examples that I've shown above.
So did somebody been through the same problem and know how to get this fixed? will be appreciated!

Related

The serializer field might be named incorrectly and not match any attribute or key on the `QuerySet` instance

I am using Django Rest and my request parameter contains:
[
{
"job_role": 2,
"technology": 1
},
{
"job_role": 1,
"technology": 1
},
{
"job_role": 2,
"technology": 1
}
]
My models are:
class Technology(models.Model):
name = models.CharField(max_length=50)
def __str__(self):
return self.name
class JobRole(models.Model):
role_name = models.CharField(max_length=100)
def __str__(self):
return self.role_name
class ExpertPricing(models.Model):
role_name = models.OneToOneField(JobRole, related_name="role", on_delete=models.SET_NULL, null=True)
experience_in_years = models.PositiveBigIntegerField()
technology = models.OneToOneField(Technology, related_name="technology", on_delete=models.SET_NULL, null=True)
salary_per_month = models.PositiveBigIntegerField()
My view looks like this:
class PricingView(APIView):
def post(self, request):
datas = request.data
data_list = []
for data in datas:
job_role_id = data["job_role"]
technology_id = data["technology"]
job_role = JobRole.objects.get(pk=job_role_id)
technology = Technology.objects.get(pk=technology_id)
expert_pricing = ExpertPricing.objects.filter(role_name=job_role, technology=technology)
if expert_pricing:
data_list.append(expert_pricing)
serializer = ExpertPricingSerializer(data_list, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
serializers.py
class TechnologySerializer(serializers.ModelSerializer):
class Meta:
model = Technology
fields = ("id", "name")
class JobRoleSerializer(serializers.ModelSerializer):
class Meta:
model = JobRole
fields = ("id","role_name")
class ExpertPricingSerializer(serializers.ModelSerializer):
role = JobRoleSerializer(many=False, read_only=True)
technology = TechnologySerializer(many=False, read_only=True)
class Meta:
model = ExpertPricing
fields = "__all__"
I am unable to understand why data_list is not being serialized.
the error says:
AttributeError: Got AttributeError when attempting to get a value for field `experience_in_years` on serializer `ExpertPricingSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `QuerySet` instance.
Original exception text was: 'QuerySet' object has no attribute 'experience_in_years'.
Since you defined in your model that those two fields (experience_in_years and salary_per_month) can not be empty, it seems like you need to do one of these things:
Send experience_in_years and salary_per_month fields in your request too.
Give a default value to those fields
Make it null=True, blank=True
If you do 2 or 3 those solutions require migration, keep that in mind, after doing one of those things you should be good to go

Django ManyToMany field as json format

I'm trying to get data as json format. I've one ManyToMany field which is returning just id. But I need that contents too. Here is my models.py
class Pricing(models.Model):
name = models.CharField(max_length = 100)
price = models.CharField(max_length = 100)
def __str__(self):
return self.name+' and '+self.price
class Service(models.Model):
name = models.CharField(max_length=100)
price = models.ManyToManyField(Pricing, blank=True)
def __str__(self):
return self.name
And also the views.py which is returning json format data
def all_service_json(request, name):
data = serializers.serialize("json", Service.objects.filter(name__icontains=name))
return HttpResponse(data)
Now Getting the output like below
[
{
"model": "myapp.service",
"pk": 2,
"fields":
{
"name": "Service name",
"price": [1, 2]
}
}
]
But want like below
[
{
"model": "myapp.service",
"pk": 2,
"fields":
{
"name": "Service name",
"price":
{
1: "Price 1",
2: "Price 2"
}
}
}
]
Creating ModelSerializer objects from within Django Rest Framework will let you display nested object data:
http://www.django-rest-framework.org/api-guide/serializers/#dealing-with-nested-objects
# myapp/serializers.py
...
from rest_framework import serializers
class PricingSerializer(serializers.ModelSerializer):
class Meta:
fields = '__all__'
model = Pricing
class ServiceSerializer(serializers.ModelSerializer):
price = PricingSerializer(read_only=True, many=True)
class Meta:
fields = '__all__'
model = Service
# myapp/views.py
def all_service_json(request, name):
services = Service.objects.filter(name__icontains=name)
data = ServiceSerializer(services, many=True).data
return HttpResponse(data)
As #robert mentioned using nested serializers will fix your issue.
But note that by default nested serializers are read-only. So If you
want to support write operations to a nested serializer field you'll
need to add create() and/or update() methods in order to explicitly
specify how the child relationships should be saved.
Writable Service Serializer
class ServiceSerializer(serializers.ModelSerializer):
price = PricingSerializer(many=True)
class Meta:
fields = '__all__'
model = Service
# sample create
def create(self, validated_data):
prices_data = validated_data.pop('price')
service = Service.objects.create(**validated_data)
for price_data in prices_data:
Price.objects.create(service=service, **price_data)
return service
# add update here
myapp/views.py
def all_service_json(request, name):
services = Service.objects.filter(name__icontains=name)
serializer = ServiceSerializer(services)
return HttpResponse(serializer.data)
So in your case all you have to do is to add depth = 1 and you will get nested representations.
Docs
The default ModelSerializer uses primary keys for relationships, but
you can also easily generate nested representations using the depth
option:
class AccountSerializer(serializers.ModelSerializer):
class Meta:
model = Account
fields = ['id', 'account_name', 'users', 'created']
depth = 1
I just start learning Django from last 8 hours and I stuck into this situation where many to many relationship is returning id instead of child data. I wrote some custom code. For me it solve my problem, hope this helps someone.
from django.core.serializers import serialize
import json
def modelToDict(model):
jsn = serialize("json", model) # convert to json
mydict = json.loads(jsn) # again convert to dictionary
return mydict
def all_service_json(request, name):
data = Service.objects.filter(name__icontains=name)
dictdata = modelToDict(data)
for i in range(len(dictdata)):
price = modelToDict(data[i].price.all())
dictdata[i]['fields']['price'] = price
return HttpResponse(json.dumps(`dictdata`), content_type="application/json")

Unable in getting data from list containing many objects

I am trying request.data.get('student_name') but it says that list has no attribute get. I just want to get the name of all students before passing to the serializer. I am sending the POST request data in the form of
[
{"student_name": "jack", "last_name": "cale", "fathers_name":"carlos"},
{"student_name": "alex", "last_name": "magasa", "fathers_name":"greg"},
{"student_name": "sia", "last_name": "gunns", "fathers_name":"brett"},
{"student_name": "jacob", "last_name": "woods", "fathers_name":"john"}
]
my views.py
#api_view(['POST'])
def add_students(request):
student_name = request.data.get('student_name')
fathers_name = request.data.get('fathers_name')
serializer = StudentsSerializer(data=request.data, many=True)
if serializer.is_valid():
serializer.save()
return Response("success")
else:
return Response(serializer.errors)
my serializers.py
class StudentsSerializer(serializers.ModelSerializer):
class Meta:
model = Students
fields = ('student_name', 'last_name', 'fathers_name')
my models.py
class Students(models.Model):
student_name = models.CharField(max_length=100, null=True)
last_name = models.CharField(max_length=100, null=True)
fathers_name = models.CharField(max_length=100, null=True)
You are trying to get value from list so you have to iterate through list
for data in request.data:
student_name = data.get('student_name')
father_name = data.get('father_name')
This seems like a question about the format of the request object. My two cents is that it's worth throwing in an import ipdb; ipdb.set_trace() at the beginning of the method, and then using print and dir to get an idea of what the object looks like. Since Django has so many django-specific objects, this helps in a lot of cases.

How to define a nested list in django for json response

I am new to Django and so I thought of creating basic app that represents operations in a shipping company. I have a WorkOrder which contains Shipments. So my models.py contains the following :
class WorkOrder (models.Model):
status = models.CharField(max_length=300,default = "New")
source = models.CharField(max_length=300)
destination = models.CharField(max_length=
material = models.CharField(max_length=300)
shipmentlist = [] //PROBLEMATIC CODE
class Shipment (models.Model):
expected_startDate = models.DateTimeField(default=timezone.now)
expected_endDate = models.DateTimeField(default=timezone.now)
shipment_status = models.CharField(max_length=300,default = "Not Started")
I have 2 serializers WorkOrderSerializer and ShipmentSerializer which i have defined in serialzers.py. I want to return a list of shipment contained within a Work Order object.
class WorkOrderSerializer
generated_date = models.DateTimeField(default=timezone.now)
status = models.CharField(max_length=300, default="New")
source = models.CharField(max_length=300)
destination = models.CharField(max_length=300)
material = models.CharField(max_length=300)
shipmentlist = ShipmentSerializer(many=True)
class ShipmentSerializer
expected_startDate = models.DateTimeField(default=timezone.now)
expected_endDate = models.DateTimeField(default=timezone.now)
shipment_status = models.CharField(max_length=300, default="Not Started")
I am following the model specified here.
http://www.django-rest-framework.org/api-guide/serializers/#dealing-with-nested-objects
In my views.py I am calling the serializer when I get the request like this
def workorder_operations(request,workorder_pk):
workorder = Work_Order.objects.filter(pk=workorder_pk)
serializer = Work_Order_Serializer(workorder)
What this produces is a json that looks like the following
{
"shipmentlist":[]
}
I am completely confused about two things :
why it shows ONLY shipment object and not the others even if it cannot parse the data or something.
why doesn't shipment get populated.
When I used the ModelSerializer instead and defined the serializer this way it all worked perfectly fine:
class ShipmentSerializer(serializers.ModelSerializer):
class Meta:
model = Shipment
fields =('expected_startDate','expected_endDate','shipment_status')
class WorkOrderSerializer(serializers.ModelSerializer):
class Meta:
model = WorkOrder
fields =('request_id','generated_date','status', 'source','destination','material')
What I want to know is how do I represent a nested list of objects so that they can be serialized properly.
I want my json for a WorkOrder object to look something like this: (Note: the variable names in the json and models might have a mismatch so please overlook that as I have stripped some of the variables to not complicate the example here.)
{
"id": "WO20170912",
"source": "BBSR",
"destination": "RKL",
"customer_id": 1,
"material": "Granite",
"weight": 19,
"status": "ALLOCATED",
"shipments": [
{
"id":"SH01234",
"work_order_id": "WO20170912",
"source": "BBSR",
"destination": "RKL"
},
{
"id":"SH01255",
"work_order_id": "WO20170912",
"source": "BBSR",
"destination": "RKL"
}
]
}
Add a ManyToManyField in WorkOrder model,
class Shipment (models.Model):
expected_startDate = models.DateTimeField(default=timezone.now)
expected_endDate = models.DateTimeField(default=timezone.now)
shipment_status = models.CharField(max_length=300,default = "Not Started")
class WorkOrder (models.Model):
status = models.CharField(max_length=300,default = "New")
source = models.CharField(max_length=300)
destination = models.CharField(max_length=
material = models.CharField(max_length=300)
shipments = models.ManyToManyField(Shipment, related_name='shipments')
Serializers would be like,
class ShipmentSerializer(serializers.ModelSerializer):
class Meta:
model = Shipment
fields = [f.name for f in model._meta.fields]
class WorkOrderSerializer(serializers.ModelSerializer):
shipments = ShipmentSerializer(many=True)
class Meta:
model = WorkOrder
fields = [f.name for f in model._meta.fields] + ['shipments']

In Django Rest, not able to serialize object which has One-to-Many mapping

I am beginner in Django Rest framework. I want to implement One to Many object mapping like following json schema:
{
"from_date": "2017-08-06T12:30",
"to_date": "2017-08-06T12:30",
"coupon_name": "WELCOME100",
"min_booking_value": 150,
"applicable_days": [
{
"from_time": "13:00",
"to_time": "15:00",
"applicable_day": 2
},
{
"from_time": "16:00",
"to_time": "18:00",
"applicable_day": 3
}
]
}
For above json schema, I have created following Django Model classes:
class Coupon(models.Model):
coupon_id = models.AutoField(primary_key=True)
from_date = models.DateTimeField()
to_date = models.DateTimeField()
coupon_name = models.TextField()
min_booking_value = models.FloatField()
def __unicode__(self):
return 'Coupon id: ' + str(self.coupon_id)
class CouponApplicableDays(models.Model):
from_time = models.TimeField()
to_time = models.TimeField()
applicable_day = models.IntegerField()
And following serializer class above models:
class CouponApplicableDaysSerializer(serializers.ModelSerializer):
class Meta:
model = CouponApplicableDays
fields = ('from_time', 'to_time', 'applicable_day')
class CouponSerializer(serializers.ModelSerializer):
coupon_applicable_days = CouponApplicableDaysSerializer(required=True, many=True)
class Meta:
model = Coupon
fields = ('coupon_id', 'from_date', 'to_date', 'coupon_name', 'min_booking_value', 'coupon_applicable_days',)
def create(self, validated_data):
coupon_applicable_days_data = validated_data.pop("coupon_applicable_days")
coupon = Coupon.objects.create(**validated_data)
CouponApplicableDays.objects.create(coupon=coupon, **coupon_applicable_days_data)
return coupon
When I save data using coupon-serializer. It saves only in Coupon table not in CouponApplicableDays.
I know, I have messed up somewhere but I don't know where. Can you guys please look into above code and tell me how can I solve this?
You have a list here
coupon_applicable_days_data = validated_data.pop("coupon_applicable_days")
Either iterate over the list and create the objects, like this:
for applicable_day in coupon_applicable_days_data:
CouponApplicableDays.objects.create(coupon=coupon, **applicable_day)
or use the bulk_create method
https://docs.djangoproject.com/en/1.11/ref/models/querysets/#bulk-create
CouponApplicableDays.objects.bulk_create(
[CouponApplicableDays(coupon=coupon, **aplicable_day)
for applicable_day in coupon_applicable_days_data]
)
Be aware that the bulk_create will not trigger pre_save/post_save signals.

Categories