Change serializer class for single method in Django Rest Framework - django

If I want to override a method in my viewset to change the serializer_class for only a single method, how can I do that.
I tried passing serializer_class=CustomSerializer but it doesn't have any effect.
class VoteViewset(viewsets.ModelViewSet):
queryset = Vote.objects.all()
# Use normal serializer for other methods
serializer_class = VoteSerializer
def list(self, request, *args, **kwargs):
# Use custom serializer for list method
return viewsets.ModelViewSet.list(self, request, serializer_class=VoteWebSerializer, *args, **kwargs)
Basically do the same list method as the inherited viewset, but use a different serializer to parse the data.
The main reason for this is because javascript does not handle 64 bit integers, so I need to return the BigInteger fields as a string instead of integer.

Override the get_serializer_class(...) method as
class VoteViewset(viewsets.ModelViewSet):
queryset = Vote.objects.all()
def get_serializer_class(self):
if self.action == "list":
return VoteWebSerializer
return VoteSerializer

What JPG answered is the correct way. Another thing you could do is overriding the list method like you are doing but modifying the default behavior using the serializer that you want.
It would be something like:
def list(self, request, *args, **kwargs):
queryset = self.get_queryset()
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
You can check the default methods in the source code or I recommend using Classy DRF. For example here you can see what DRF is doing in the list method of a ModelViewSet doing and use that as a starting point.

Related

DRF SerializerMethodField method only running when serializer variable opened in debugger

My SerializerMethodField method is only printing HERE when I have a breakpoint at the return in the get method, and open the serializer variable after it has triggered OR when serializer.data is called (in which case it prints the expected data, but validated_data is still empty).
View:
class EventAddPeople(generics.GenericAPIView):
serializer_class = EventAddPeopleSerializer_Read
def get(self, request, *args, **kwargs):
serializer = EventAddPeopleSerializer_Read(data=request.GET)
serializer.is_valid(raise_exception=True)
print(serializer.validated_data)
return HttpResponse(serializer.validated_data)
Serializer:
class EventAddPeopleSerializer_Read(serializers.Serializer):
event_id = serializers.SerializerMethodField(method_name='get_event_id')
person_ids = serializers.SerializerMethodField()
def get_event_id(self, obj):
print("HERE")
return "TEST00"
def get_person_ids(self, obj):
print("HERE")
return "TEST00"
class Meta:
fields = ('event_id', 'person_ids')
Your get method is not called (may be).
check by just printing('anything') in your get method
check Methos for genericApiViews
Thanks
First thing, request.data is applicable for non-GET requests. You are not supposed to send data in the payload section with HTTP GET. If you want to send data with GET method, pass it through URL query parameters
So, the url will become, /api/my/end-point/?event_id=1&person_ids=3
and you need to pass this query param to serializer as,
serializer = EventAddPeopleSerializer_Read(data=request.GET)
Second thing, you've missed to add the Meta class in the serializer
class EventAddPeopleSerializer_Read(serializers.Serializer):
# other code
class Meta:
fields = ('event_id', 'person_ids')

Django CBV : get() and get_context_data()

I would like to get some indicators about get() and get_context_data() classes because I get an issue and I'm trying to understand why.
I have a Django DetailView which let to display some statistics with multiple querysets. In the same class, I have a query string which shows the result from a get queryset.
My code looks like this :
class StatsView(DetailView):
""" Create statistics pageview """
template_name = 'app/stats.html'
def get(self, request):
return render(request, self.template_name, context)
def set_if_not_none(self, mapping, key, value):
if value is not None:
if len(value) != 0:
mapping[key] = value
def get_context_data(self, **kwargs):
return context_data
Like this, get_context_data() function doesn't work, but when I set get() in comment, it works fine. I think there is a small misunderstanding from myself.
Maybe I don't use the good django generic display view or maybe it's not possible to use get() and get_context_data() together in the same class ?
Thank you
I read the Django documentation but I would like to get explanations from you
EDIT:
I'm trying to pass querysets from get() method to get_context_data(). Then I removed get() method, I changed DetailView by TemplateView and it works with just get_context_data(). But how I can add a "skeleton" without get() method ?
I'm trying to pass querysets from get() method to get_context_data()
class StatsView(DetailView):
""" Create statistics pageview """
template_name = 'app/stats.html'
def get(self, request, *args, **kwargs):
queryset = SampleModel.objects.all()
return render(request, self.template_name, context=self.get_context_data(queryset=queryset))
def set_if_not_none(self, mapping, key, value):
if value is not None:
if len(value) != 0:
mapping[key] = value
def get_context_data(self, **kwargs):
qs = kwargs.get('queryset')
# do something
If your overriding get_context_data() method, it's advaisable to call the super() method as
class StatsView(DetailView):
# your code
def get_context_data(self, **kwargs):
data = super(StatsView, self).get_context_data(**kwargs)
data.update({"foo": "bar"})
return data
I would like to get some indicators about get() and get_context_data()
I think it's nicely answered here already , When to use get, get_queryset, get_context_data in Django?

Django SerializerMethodField can't save a decimal

So, according to the docs, SerializerMethodField is a read-only field.
Well in my case, it's interfering with my write:
# old value is 2.5
data={'score': 1.7}
serializer = ScoreTraitSerializer(
score_trait, data=data, partial=True)
if serializer.is_valid():
new_score_trait = serializer.save()
Now if I inspect the new_score_trait, my score is still 2.5.
The serializer looks as such:
score = serializers.SerializerMethodField()
def get_score(self, obj):
if isinstance(obj.score, decimal.Decimal):
return float(obj.score)
else:
return obj.score
If I comment out my SerializerMethodField, I can save the new decimal value (but can't serialize it).
So ... am I using my serializer correctly? Why does my write to the serializer hitting the SerializerMethodField?
Thanks in advance
SerializerMethodField is a read-only field.Only used for to_representation, it's used for list/retrieve not create/update.
the serializer field score must conflict with model field score,try change it to:
float_score = serializers.SerializerMethodField(required=False)
def get_float_score (self, obj):
if isinstance(obj.score, decimal.Decimal):
return float(obj.score)
else:
return obj.score
See the source code you will know why:
class SerializerMethodField(Field):
"""
A read-only field that get its representation from calling a method on the
parent serializer class. The method called will be of the form
"get_{field_name}", and should take a single argument, which is the
object being serialized.
For example:
class ExampleSerializer(self):
extra_info = SerializerMethodField()
def get_extra_info(self, obj):
return ... # Calculate some data to return.
"""
def __init__(self, method_name=None, **kwargs):
self.method_name = method_name
kwargs['source'] = '*'
kwargs['read_only'] = True
super(SerializerMethodField, self).__init__(**kwargs)

How to dynamically alter field mapping in django restframework's serializer that uses nested serializers?

I use the following serializer in most requests such as GET, POST etc:
class PrescriptionSerializer(serializer.ModelSerializer):
tags = TagSerializer()
setting = SettingSerializer()
But, I want to map setting field to SettingUpdateSerializer() if request.action is UPDATE(=PUT/PATCH). Without diving PrescriptionGetSerializer and PrescriptionUpdateSerialzer and using them accordingly, is there a way to dynamically map serializer-nesting field to other serializer, as below?
class PrescriptionSerializer(serializer.ModelSerializer):
tags = TagSerializer()
setting = SettingUpdateSerializer()
I though about using self.fields.pop on __init__, but this way it is only possible by using different different field names such as update_setting and get_setting.
Thanks for the help in advance.
I think the most clear solution is creating two separate serializer. And chose what serializer to use in a view layer depends on the http verb. If you use viewset it is easy to implement in a get_serializer_class method.
class SomeViewSet(viewsets.ModelViewset):
def get_serializer_class(self):
if self.action === 'update':
return UpdatePrescriptionSerializer
return PrescriptionSerializer
Now when you'll call a get_serializer in actions methods you'll get serializer depends on the action.
But you could also do something like you said:
class PrescriptionSerializer(serializer.ModelSerializer):
def __init__(self, *args, **kwargs):
super(PrescriptionSerializer, self).__init__(*args, **kwargs)
if self.context['request'].method == 'PUT':
self.fields['setting'] = SettingUpdateSerializer()
else:
self.fields['setting'] = SettingSerializer()
tags = TagSerializer()
Just ensure that you pass a request to serializer context. If you use get_serializer method in a viewset then it is already passed.

How to do a PUT (partial update) using generics in Django-Rest-Framework?

If I have a class view that looks like this,
class MovieDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Movie.objects.all()
serializer_class = MovieSerializer
how do I make the serialize accept partial updates? currently where it stands Put will erase an existing data for said object.
If you are using the DRF route, use PATCH method instead of PUT.
if you write the urls configuration by yourself,
dispatch it to partial_update method in your RetrieveUpdateDestroyAPIView view.
If you get the serialize by yourself,
pass the partial=True to your Serializer
partial = kwargs.pop('partial', False)
serializer = self.get_serializer(instance, data=request.data, partial=partial)
Or you can just overwrite the get_serializer() method as:
def get_serializer(self, *args, **kwargs):
kwargs['partial'] = True
return super(MovieDetail, self).get_serializer(*args, **kwargs)
It is especially useful when the front-end guy use the ngResource of the AngularJS to call your API, which only supports the 'put' instead of the 'patch' by default.
Hope it helps.