serializing only certain fields from a queryset in django serializer class - python

I have a queryset which I obtain from get_queryset(). What we know is, the returns of queryset gives the list of objects which contains all the fields of the model. Now I don't want to serialize all the fields from the model and show all of them in the response. I want to serialize only few fields and show in the api response.
for eg:
def get_queryset(self):
"""""
filtering happens here on the query parameters.
"""
abc = self.request.GET.get('abc',None)
Now I have a defualt list function where I have to call serializer class only with the specific fields.
def list(self, request, *args, **kwargs):
queryset = self.get_queryset()
# data ={
# "name":queryset.
# }
# serializer = ExampleSerializer(data,many=True)
#serializer = serializers.serialize("json",queryset=queryset,fields=['id','name','address'])
return Response(serializer, status=status.HTTP_200_OK)
When I do print queryset it gives complex queryset and when I do print(type(queryset)),it gives the following
<class 'django.db.models.query.QuerySet'>
Now how to serialize name and address fields only to the exampleserializer class?? I did some digging and tried to do the following
#serializer = serializers.serialize("json",queryset=queryset,fields=['id','name','address'])
but it does not give the output in the required format not like regular json. Also it gives model: Example in the response of every object.

Did you try this?
queryset = self.get_queryset().values('name', 'address')

I'm not sure I fully understand what you are trying to fetch as your code is incomplete, but it seems that what you need is a ModelSerializer.
get_queryset() should be used to retrieve a queryset of objects that will be used by the serializer thanks to DRF inheritance & mixins system:
# Serializer
class ExampleSerializer(serializers.ModelSerializer):
class Meta:
model = Example
fields = ('id', 'name', 'address')
# View
class ExampleList(ListAPIView):
serializer_class = ExampleSerializer
def get_queryset(self):
return Example.objects.filter(...)

Related

Serializing model queryset showing empty [OrderedDict()]

I am building a blog app with React and Django and I am serializing model's instances saved by particular user, First I am just trying to test with .all() then I am planning to filter by specific user But when I serialize queryset with Serializer like:
class BlogSerializerApiView(viewsets.ModelViewSet):
serializer_class = BlogSerializer
def get_queryset(self, *args, **kwargs):
queryset = Blog.objects.all()
output_serializer = BlogSerializer(queryset, many=True)
print(output_serializer.data)
return "Testing"
It is showing in console:
[OrderedDict(), OrderedDict()]
and when I access it like
print(output_serializer)
Then it is showing:
BlogSerializer(<QuerySet [<Blog: user_1 - Blog_title>, <Blog: user_2 - second_blog_title>]>, many=True):
serializer.py:
class BlogSerializer(serializers.Serializer):
class Meta:
model = Blog
fields = ['title']
models.py:
class Blog(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=30, default='')
def __str__(self):
return f"{self.user} - {self.title}"
What I am trying to do:
I am trying to serialize queryset to show on page in react frontend, I will relate with specific user later.
I have tried many times by changing CBV serialization method by generics.ListAPIView instead of viewsets.ModelViewSet but still same thing.
There is a concept error here. The get_queryset function is not supposed to return serialized data. It must return a QuerySet of model objects.
To achieve what you want you can just do:
class BlogSerializerApiView(viewsets.ModelViewSet):
serializer_class = BlogSerializer
def get_queryset(self, *args, **kwargs):
return Blog.objects.all()
The Django Rest Framework will take care of serializing data.
In fact, you can even do it way more simple. Defining the view's queryset field like this:
class BlogSerializerApiView(viewsets.ModelViewSet):
queryset = Blog.objects.all()
serializer_class = BlogSerializer
Additional:
You said you will relate to current user later. You could achieve that in fact in the get_queryset method filtering aginst the user
class BlogSerializerApiView(viewsets.ModelViewSet):
serializer_class = BlogSerializer
def get_queryset(self, *args, **kwargs):
return Blog.objects.filter(user_id=USER_ID)
Hope this helps!
I was using
class BlogSerializer(serializers.Serializer):
.......
so it was showing empty results (no idea why, I think its deprecated)
After replaceing it with
class BlogSerializer(serializers.HyperlinkedModelSerializer):
It worked

Optimizing DRF post request handling by preventing full response serialization back

I have defined AViewSet and ASerializer for my AModel:
class AModel(Model):
name = CharField(16)
text = TextField()
related = ForeignField('ARelated')
class AViewSet(ModelViewSet):
queryset = AModel.objects.all()
serializer_class = ASerializer
class ASerializer(Serializer):
class Meta(object):
model = AModel
fields = '__all__'
I wrote a RESTful client that posts a lot of data to that view/endpoint in multiple requests, creating many AModel records. I have noticed, however that a significant part of the server time is spent on generating the response, and upon googling for a bit I found several references to the nested relationship hazard which seems like a decent fix, but got me wondering:
I already know what I posted and I don't need the pks, so could I prevent that serialization response from happening entirely? Can I instead just serialize the number of rows inserted?
Taking a look at DRF's CreateModelMixin class:
class CreateModelMixin(object):
"""
Create a model instance.
"""
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
I realized I could override the create method and reimplement it without returning the serializer.data as part of the response, so it'll look similar to this:
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response({}, status=status.HTTP_201_CREATED, headers=headers)
I have two questions regarding this approach:
Does this practice of preventing the full serialization of objects created with a POST makes sense wrt RESTful design patterns, approach, ideology, etc?
Will this actually avoid selecting all the related data (as well as execute any SerializerMethodFields, etc?
Let's take the basic ModelViewset and ModelSerializer combination here :) It will be like,
# serializers.py
class SampleSerializer(serializers.ModelSerializer):
class Meta:
model = SampleModel
fields = '__all__'
# views.py
class SampleViewset(viewsets.ModelViewSet):
queryset = SampleModel.objects.all()
serializer_class = SampleSerializer
Why DRF returning all the data back to client ?
Here, the SampleViewset is using SampleSerializer everytime and it will serialilze all fields defined in the serializer class. As per the current configuration
What is the possible solution?
The possible solution is for this is stop serialization process of certain fields by some means :)
How to do?
As far as I knew, this can be done in two ways.
1. Use a minimal SampleSerializer class for POST method
2. override the to_representation() method of SampleSerializer on POST requests
Method-1 : Use different serializer
Define a new serializer class with fields which are you wish to send and retrive while POST request
class SampleSerializerMinimal(serializers.ModelSerializer):
class Meta:
model = SampleModel
fields = ('id', 'name', 'age')
Now, we've to tell viewsets to use this serializer for POST methods, it can be done in get_serializer_class() of viewset
class SampleViewset(viewsets.ModelViewSet):
queryset = SampleModel.objects.all()
serializer_class = SampleSerializer
def get_serializer_class(self):
if self.request.method == 'POST':
return SampleSerializerMinimal
return SampleSerializer
Method-2 : Override the to_representation() method
class SampleSerializer(serializers.ModelSerializer):
class Meta:
model = SampleModel
fields = '__all__'
def to_representation(self, instance):
try:
if self.context['view'].request.method == 'POST':
return {
"id": instance.id,
"name": instance.name,
"age": instance.age
}
except KeyError:
return super(SampleSerializer, self).to_representation(instance)
return super(SampleSerializer, self).to_representation(instance)
What is the best way ?
I felt Method-1 is more DRF way of doing things, but you can't add id only to the fields because, POST request may require more fields.
The Method-2 to is also good, but its not much clean if you want to return n fields, and writing it in your to_representation() method
UPDATE-1
Method-3: Combination of method-1 and method-2
# serializer.py
class SampleSerializerMinimal(serializers.ModelSerializer):
class Meta:
model = SampleModel
fields = ('id', 'name', 'age')
def to_representation(self, instance):
"""
we don't have to check the request method, because DRF routing only POSt requests to this serializer
"""
return {"id": instance.id}
# views.py
class SampleViewset(viewsets.ModelViewSet):
queryset = SampleModel.objects.all()
serializer_class = SampleSerializer
def get_serializer_class(self):
if self.action.method == 'POST':
return SampleSerializerMinimal
return SampleSerializer
UPDATE-2
Does this practice of preventing the full serialization of objects created with a POST makes sense wrt RESTful design patterns, approach, ideology, etc?
the .data is calling to_representation() method, which calls the related objects and all other fields in the serializer. ( Source code of data property of serializer ) So, if you can avoid that .data call, it would be nice!.
Since I've seen many API responses with single detail like {"status":true} after a POST request, I don't think your approch overkill the DRF patterns and other stuff
Will this actually avoid selecting all the related data (as well as execute any SerializerMethodFields, etc?
Yes. As I said above, It won't call the serializations process unless calling the .data

How to use different Django Rest Framework serializers for the same request in Generic Views?

I'm using Django Rest Framework in an API project and am trying to figure out if there's a way to use two different serializers with the generic views (e.g. CreateAPIView). I want to use one serializer for deserializing a POST request, and a different one for serializing the resulting response.
Here's what I'm trying to do; I'll illustrate using the Album/Track examples from the docs:
The model that I'm working with has a ForeignKey relationship. In the API, I'd like to just be able to include the FK in the request when assigning the relationship, so in the serializer I'm using a PrimaryKeyRelatedField, similar to how the AlbumSerializer handles the relationship to Tracks:
class CreateAlbumSerializer(serializers.ModelSerializer):
tracks = serializers.PrimaryKeyRelatedField(many=True)
class Meta:
model = Album
fields = ('album_name', 'artist', 'tracks')
However, on the response, I'd like to include a full representation of the Album using a ModelSerializer, not just the PK, slug, etc., something like this:
class AlbumSerializer(serializers.ModelSerializer):
tracks = TrackSerializer(many=True, read_only=True)
class Meta:
model = Album
fields = ('album_name', 'artist', 'tracks')
class TrackSerializer(serializers.ModelSerializer):
class Meta:
model = Album
fields = ('order', 'title', 'duration')
The generic DRF views allow you to either specify the serializer_class or override the get_serializer_class method, but I can't figure out how to use that to accomplish what I'm after.
Is there something obvious that I'm missing? This seems like a reasonable thing to want to do, but I can't seem to grok how to get it done.
Approach #1
Overwrite the generic mixins in the DRF ViewSet. For example:
class MyViewSet(CreateModelMixin, MultipleSerializersViewMixin, ViewSet):
serializer_class = CreateAlbumSerializer
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
saved = self.perform_create(serializer)
serializer = self.get_serializer(instance=saved, serializer_class=AlbumSerializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
def perform_create(self, serializer):
return serializer.save()
MultipleSerializersViewMixin is taken from django-rest-framework-braces.
Approach #2
Customize to_representation of the CreateAlbumSerializer. For example:
class MyViewSet(CreateModelMixin, ViewSet):
serializer_class = CreateAlbumSerializer
class CreateAlbumSerializer(serializers.ModelSerializer):
tracks = serializers.PrimaryKeyRelatedField(many=True)
class Meta:
model = Album
fields = ('album_name', 'artist', 'tracks')
def to_representation(self, instance):
data = super(CreateAlbumSerializer, self).to_representation(instance)
data['tracks'] = TrackSerializer(instance=instance.tracks).data
return data
Comparison
I personally like approach #1 instead of #2 even though it is more verbose since it does not leak any of the custom create/response logic to serializers. I think serializer should just know how to serialize and all custom requirements to pick different serializers for the job should be done in the views.

django rest framework add field when not in list view

I'm using the Django Rest Framework and I'd like to be able to add extra detail to the serializer when a single object is returned, which would be left out of the list view.
In the code below I add the celery_state field to the TestModelSerializer, but I'd only like this field to be added when its returning a single object, not when it's returning the list of TestModel data.
I've looked at the list_serializer_class option but it seems to just use the original model serializer so it will always still include the field even if I try to exclude from there.
What are my options?
class TestModelSerializer(serializers.HyperlinkedModelSerializer):
celery_state = serializers.CharField(source='celery_state', read_only=True)
class Meta:
model = TestModel
class TestModelViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows TestModels to be viewed or edited.
"""
authentication_classes = (SessionAuthentication, BasicAuthentication)
permission_classes = (IsAuthenticatedOrReadOnly,)
queryset = TestModel.objects.all()
serializer_class = TestModelSerializer
Since the serializer class (used by the viewsets) passes many argument, you can use that to control the fields output:
class TestModelSerializer(serializers.HyperlinkedModelSerializer):
# ...
def __init__(self, *args, **kwargs):
super(TestModelSerializer, self).__init__(*args, **kwargs)
if kwargs.get('many', False):
self.fields.pop('celery_state')
Inspired by #mariodev answer:
The other possibility is to override many_init static method in serializer. Acording comments in thie code (https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/serializers.py#L128 ) it is suggested variant.
from rest_framework import serializers
class ExtendedSerializer(serializers.Serializer):
...
#classmethod
def many_init(cls, *args, **kwargs):
kwargs['child'] = cls()
kwargs['child'].fields.pop('extractedFiled')
return serializers.ListSerializer(*args, **kwargs)
You can have an extra serializer called ExtendedTestModelSerializer which would contain the extra fields that you want.
After that, you can use the get_serializer_class method to decide which serializer is used based on request.action -
class TestModelViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows TestModels to be viewed or edited.
"""
authentication_classes = (SessionAuthentication, BasicAuthentication)
permission_classes = (IsAuthenticatedOrReadOnly,)
queryset = TestModel.objects.all()
# serializer_class = TestModelSerializer
get_serializer_class(self):
if self.request.action == 'list':
return TestModelSerializer
return ExtendedTestModelSerializer

How Can I Use Two Different Model Serializers With the Same Model?

I'm using django-rest-framework. I have a model with a relation. I would like to just display the count of related items when a user hits the /modelname/ URL, but show the full related set when a user hits a specific model instance at /modelname/1/.
I can almost get what I want.
I have two serializers, like so:
class DataSetSerializer(serializers.ModelSerializer):
revisions = serializers.RelatedField(source='datasetrevision_set', many=True)
class Meta:
model = DataSet
fields = ('id', 'title', 'revisions')
class ShortDataSetSerializer(serializers.ModelSerializer):
class Meta:
model = DataSet
fields = ('id', 'title', 'revisions')
If I use the short version, I get the count of revisions (it's a calculated field). If I use the long version, I get the full list of related items as "revisions".
Short:
[{"id": 1, "title": "My Data Set", "revisions": 0}]
Long:
[{"id": 1, "title": "My Data Set", "revisions": ["Data Set v1", "Data Set v2"]}]
What I want to do is be able to switch between them based on query parameters (url). I tried to set the serializer_class to the ShortDataSetSerializer when the ID was not present, but it overrode all cases, not just the non-ID case.
class DataSetViewSet(viewsets.ModelViewSet):
serializer_class = DataSetSerializer
model = DataSet
def get_queryset(self):
try:
id = self.kwargs['id']
queryset = DataSet.objects.filter(id=id)
except KeyError:
queryset = DataSet.objects.all()
# We want to only list all of the revision data if we're viewing a
# specific set, but this overrides for all cases, not just the one
# we want.
self.serializer_class = ShortDataSetSerializer
return queryset
Is there a way I can make this work? I realize I may be approaching this in a totally ridiculous manner, but it seems like there should be an easy solution.
The data example I gave rather abbreviated compared to the real data I'm working with. The end goal is to show a subset of fields in list view, and every field in the GET for a specific ID. This is a read-only API, so I don't need to worry about POST/PUT/DELETE.
You could do it by overriding the get_serializer_class method:
class DataSetViewSet(viewsets.ModelViewSet):
model = DataSet
def get_queryset(self):
queryset = DataSet.objects.all()
if self.kwargs.get('id'):
queryset = queryset.filter(pk=self.kwargs.get('id'))
return queryset
def get_serializer_class(self):
return DataSetSerializer if 'id' in self.kwargs else ShortDataSetSerializer
I think one easy solution for this problem would be to use class based generic views instead of a viewset.
You can use a list create api view with serializer_class as ShortDataSetSerializer. So when you get the list of data it will have the count of revisions. Also if you want the post request to work on the same url you will then have to override the get_serializer_class method to set the serializer_class based on request type.
For the retrieve view you can use the serializer_class as DataSetSerializer. It will have a list of revisions instead of count.
Checkout generic views api guide on DRF docs website.
Also, you can override the list and retrieve methods on the viewset, but I would prefer to use class based views since DRF has a lot of additional functionalities attached to the request functions like get, put etc.(or list, detail) and it is better not to override them.
Thank you, Benjamin. That didn't do what quite I was looking for. Ultimately what I had to do was this (with the same serializers as above):
class DataSetViewSet(viewsets.ModelViewSet):
model = DataSet
def list(self, request):
queryset = DataSet.objects.all()
serializer = ShortDataSetSerializer(queryset, many=True)
return Response(serializer.data)
def detail(self, request, id=None):
queryset = DataSet.objects.get(id=id)
serializer = DataSetSerializer(queryset)
return Response(serializer.data)
And in the urls.py:
url(r'^sets/$', views.DataSetViewSet.as_view({'get': 'list'})),
url(r'^sets/(?P<id>\d+)/$', views.DataSetViewSet.as_view({'get': 'detail'})),

Categories