Instantiating a Serializer consisting of multiple ModelSerializers - Django Rest Framework - python

I have two ModelSerializers:
class VariantSerializer(serializers.ModelSerializer):
class Meta:
model = Variant
fields = (
'id',
'name',
... and so on)
and
class PictureItemSerializer(serializers.ModelSerializer):
class Meta:
model = PictureItem
fields = (
'id',
... and so forth)
What I want to do is to return a JSON including a single instance of the VariantSerializer, and multiple instances of the PictureItemSerializer.
I created another serializer as such:
class PictureItemUploadSerializer(serializers.Serializer):
variant = VariantSerializer()
pictureItems = PictureItemSerializer(many=True)
But I'm having difficulties in instantiating this "combination" of a serializer if you will.
This does not work:
p = PictureItemUploadSerializer()
p.variant = variant_serializer
p.pictureItems = picture_item_serializer
return Response(p.data, status=status.HTTP_201_CREATED)
as it yields empty data:
{
"variant": {
"name": ""
},
"pictureItems": []
}

You need to pass the data dictionary to the serializer when you are creating an instance of the serializer.
Lets say variant_data contains data for Variant model and picture_items_data contains the list of PictureItem model data.
Create a data dictionary containing them.
data = {'variant': variant_data, 'pictureItems': picture_items_data}
Then pass this data to the serializer.
p = PictureItemUploadSerializer(data=data) # pass the data dictionary
p.is_valid(raise_exception=True) # check if serializer is valid
return Response(p.data, status=status.HTTP_201_CREATED) # return response

Related

Django Rest Framework: How to modify output structure?

Is there a way to group fields in Serializer/ModelSerializer or to modify JSON structure?
There is a Location model:
class Location(Model):
name_en = ...
name_fr = ...
...
If I use ModelSerializer I get plain representation of the object fields like:
{'name_en':'England','name_fr':'Angleterre'}
I want to group some fields under "names" key so I get
{'names':{'name_en':'England','name_fr':'Angleterre'}}
I know I can create custom fields but I want to know if there is a more straightforward way. I tried
Meta.fields = {'names':['name_en','name_fr']...}
which doesn't work
I think it is better using a property. Here is the whole example.
class Location(models.Model):
name_en = models.CharField(max_length=50)
name_fr = models.CharField(max_length=50)
#property
def names(self):
lst = {field.name: getattr(self, field.name)
for field in self.__class__._meta.fields
if field.name.startswith('name_')}
return lst
In views:
class LocationViewSet(viewsets.ModelViewSet):
model = models.Location
serializer_class = serializers.LocationSerializer
queryset = models.Location.objects.all()
And in serializers:
class LocationSerializer(serializers.ModelSerializer):
class Meta:
model = Location
fields = ('id', 'names')
My result for my fake data:
[{
"id": 1,
"names": {
"name_en": "England",
"name_fr": "Angleterre"}
}]
Try to create a wrapper serialzer and place the LocationSerializer inside it
class LocationSerialzer(serializers.ModelSerialzer):
name_en = ...
name_fr = ...
...
class MySerializer(serializers.ModelSerializer):
name = LocationSerialzer()
...
Using the above method , you can apply your own customization without being limited to drf custom fields.
You could also not use a property on the model and but use a SerializerMethodField on your serializer like in this implementation.
We used here a _meta.fields, like in the other implementation, to get all the fields that starts with name_ so we can dynamically get the output you desired
class LocationSerializer(serializers.ModelSerializer):
names = serializers.SerializerMethodField()
def get_names(self, obj):
lst = {field.name: getattr(obj, field.name)
for field in obj.__class__._meta.fields
if field.name.startswith('name_')}
return lst
class Meta:
model = Location
fields = ('id', 'names')

How to serialise the Model with the list containing querysets of that model?

class DummyModel(models.Model):
dummyId = models.AutoField(primary_key=True)
assetId = models.CharField(max_length=250)
bounds = models.CharField(max_length=1000)
Now i have dummylist containing queryset of DummyModel
dummylist=[<QuerySet [<DummyModel>]>, <QuerySet [<DummyModel>]>]
Now i have serialiser like this
class DummySerialiser(serialiser.ModelSerialiser):
class Meta:
model=DummyModel
Now i need to generate a json in with list of dummymodel
{
{"dummyId":1,
....
},
{"dummyId":2,
....
}
}
Any pointers on this would be a great help. Thanks in advance.!!
create your serializer like this
class DummySerialiser(serialiser.ModelSerialiser):
class Meta:
model = DummyModel
fields = ('dummyId', 'assetId', 'bounds',)
In your view use many=True to serialize list of objects
queryset = DummyModel.objects.all() # assume this is your queryset
serializer = DummySerialiser(queryset, many=True)
dummylist = serializer.data # you will get your required json data
Add many=True in serializer, while serializing the data to serialize a list of objects
my_serializer = DummySerialiser(dummylist,many=True)
You'll get the required data from my_serializer.data

Django Rest framework: custom API response

I have Char objects with ManyToMany relationships to Source objects. Because a Char can appear in many Sources and many Sources can contain multiple Chars. The MtM relationship goes through a through table which also contains page number. In my API response, which I built using the Django REST framework I want to avoid resolving the full Source title, author etc. for every Char. Rather, in order to reduce the size of the JSON response, I want to refer it by id and include a sources section so the client can look it up.
I.e. a client visiting /api/char/26 should get the following response:
"chars": [
{
"id": 26,
"name": "龜",
"locations": [
{
"page": 136,
"source": 1
},
{
"page": 162,
"source": 1
}
]
}
],
"sources": [
{
"id": 1,
"title": "Bruksanvisning Foamglass",
"author": "Bluppfisk"
}
]
Here's the API view:
class CharAPIView(generics.RetrieveAPIView):
queryset = Char.objects.all()
serializer_class = CharSerializer
and the Serializers:
class CharSerializer(serializers.ModelSerializer):
locations = serializers.SerializerMethodField()
class Meta:
model = Char
fields = ('id', 'name', 'locations',)
depth = 1
def get_locations(self, obj):
qset = CharInSource.objects.filter(char=obj)
return [CharInSourceSerializer(m).data for m in qset]
class CharInSourceSerializer(serializers.ModelSerializer):
class Meta:
model = CharInSource
fields = ('page', 'source',)
The problem is I do not know how to hook into the generics.RetrieveAPIView class so it will include a list of relevant sources. I've been digging through the source, but I cannot figure out how to even get the pk value.
In the end, I ended up solving it as follows, by overwriting the retrieve method of my view.
class CharAPIView(generics.RetrieveAPIView):
queryset = Char.objects.all()
def retrieve(self, *args, **kwargs):
instance = self.get_object()
char = CharSerializer(instance).data
qset = Source.objects.all()
sources = [SourceSerializer(m).data for m in [i for i in instance.location.all()]]
return Response({
'char': char,
'sources': sources,
})
This could be accomplished with another SerializerMethodField on your CharSerializer and creating a SourceSerializer; no extension of the base methods to GenericAPIView or RetrieveModelMixin.
def SourceSerializer(ModelSerializer):
class Meta:
model = Source
fields = ('id', 'title', 'author') # assuming author is not also a
# ForeignKey, otherwise returns an id
def CharSerializer(...):
....
sources = SerializerMethodField()
def get_sources(self, obj):
return SourceSerializer(
Source.objects.filter(chars__in=[obj.id]).distinct(),
many=True).data
class Meta:
fields = (...,'sources')
Assuming the attribute to the MTM model related_name is chars you can use chars__in and pass a list of Char ids; which in this case is the single char we are referencing. This would however contain the sources within each char object instead of outside as you had indicated by the question. However, I imagine you would want to know which sources have which char, as my solution would provide.
Without seeing the exact structure of your models, I cannot be certain of exactly how you should retrieve the Source objects. I feel like you could also replace the queryset with obj.sources.all() instead of the clunky __in query in the SourceSerializer.

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")

Django Rest Framework - Append JSON to Model Serialization

I have a model is pretty straight forward and I've created a property on the model to essentially return child data as JSON. The property is simple:
#property
def question_data(self):
from api.models import TemplateQuestion
questions = TemplateQuestion.objects.filter(template__id=self.template.id)
question_dict = [obj.as_dict() for obj in questions]
return(json.dumps(question_dict, separators=(',', ': ')))
Which does its job and outputs valid JSON. That said I'm at a total loss of how to add that property to the Serializer as JSON and not a string like
{
"questions": "[{\"sequence\": 2,\"next_question\": \"\",\"modified_at\": \"2016-01-27T19:59:07.531872+00:00\",\"id\": \"7b64784e-a41d-4019-ba6e-ed8b31f99480\",\"validators\": []},{\"sequence\": 1,\"next_question\": null,\"modified_at\": \"2016-01-27T19:58:56.587856+00:00\",\"id\": \"99841d91-c459-45ff-9f92-4f75c904fe1e\",\"validators\": []}]"
}
It's stringifying the JSON and which I need as proper JSON.
Serializer is probably too basic but I haven't worked with DRF in a while and have never tried to append JSON to the serialized output.
class BaseSerializer(serializers.ModelSerializer):
class Meta:
abstract = True
class SurveySerializer(BaseSerializer):
included_serializers = {
'landing_page': 'api.serializers.LandingPageSerializer',
'trigger': 'api.serializers.TriggerSerializer',
'template': 'api.serializers.TemplateSerializer'
}
questions = serializers.ReadOnlyField(source='question_data')
class Meta:
model = Survey
fields = ('id',
'name',
'slug',
'template',
'landing_page',
'trigger',
'trigger_active',
'start_date',
'end_date',
'description',
'fatigue_limit',
'url',
'questions',)
meta_fields = ('created_at', 'modified_at')
I'll add that I'm also bolting on the Django Rest Framework JSON API formatting but I think that in the end I'm just not getting how to append JSON to a model's serialization without it being returned as a string.
You should not be dumping the results of the method to JSON. Just return the dict; DRF's serializer will take care of converting it.

Categories