I have a nested serializer initialized with many=True and would like to add a number of annotated fields to the output using SerializerMethodField().
How can I annotate the OrderLineSerializer queryset without overriding the manager's default get_queryset method?
class OrderLineSerializer(serializers.ModelSerializer):
annotated_field_1 = serializers.SerializerMethodField()
annotated_field_2 = serializers.SerializerMethodField()
annotated_field_3 = serializers.SerializerMethodField()
class Meta:
model = OrderLine
fields = (
'annotated_field_1',
'annotated_field_2',
'annotated_field_3',
)
def get_annotated_field_1(self, instance):
return str(instance.annotated_field_1)
class OrderSerializer(serializers.ModelSerializer):
lines = OrderLineSerializer(many=True)
class Meta:
model = Order
fields = (
'id'
'lines'
)
class OrderAPIViewSet(viewsets.ModelViewSet):
queryset = Order.objects.all()
serializer_class = OrderSerializer
You can use a SerializerMethodField in your OrderSerializer. Something like this:
class OrderSerializer(serializers.ModelSerializer):
lines = serializers.SerializerMethodField()
def get_lines(self, instance):
annotated_lines = instance.lines.annotate(annotated_field_1=...)
return OrderLineSerializer(annotated_lines, many=True).data
class Meta:
model = Order
fields = (
'id'
'lines'
)
This feels weird though, is this something you truly need to do with annotation rather than with a property, for example?
Related
serializers.py
class ModFileSerializer(serializers.ModelSerializer):
size = serializers.CharField(default=os.path.getsize('file'), max_length=16)
class Meta:
model = models.ModFile
fields = '__all__'
read_only_fields = (
'size',
)
models.py
class ModFile(models.Model):
downloads = models.IntegerField(default=0)
file = models.FileField(upload_to='mods/')
created_at = models.DateTimeField(auto_now_add=True)
Here I have a serializer for the ModFile model and the only thing that's missing is the file's size, I know os.path.getsize() needs the exact location of the file so how do I actually access the file field from the model in order to pass in the getsize() function or is there a better way to do it?
class ModFileSerializer(serializers.ModelSerializer):
size = serializers.SerializerMethodField()
def get_size(self, obj):
return os.path.getsize(obj.file.path)
class Meta:
model = models.ModFile
fields = '__all__'
read_only_fields = (
'size',
)
I think this would work any time you use this serializer.
obj in the get_size method is the instance itself.
Add a to_representation method to your serializer which is much easy.
class ModFileSerializer(serializers.ModelSerializer):
class Meta:
model = models.ModFile
fields = '__all__'
def to_representation(self, instance):
rep = super(ModFileSerializer, self).to_representation(instance)
rep['size'] = instance.file.size
return rep
I have following serializer:
class SampleSerializer(serializers.ModelSerializer):
status = serializers.SerializerMethodField()
label = serializers.SerializerMethodField()
field3 = serializers.SerializerMethodField()
field4 = serializers.SerializerMethodField()
field5 = serializers.SerializerMethodField()
class Meta:
model = Sample
fields = (
'status', 'label', 'field3', 'field4, 'field5'
)
The problem is, the conditions used to obtain the first two fields are same. So I don't want to run the same codes again in two serializer method fields. I want to get both of the values from the same serializer method.
How can I efficeintly get values of both of the fields from only one serializer method field?
You can use the method_name of the SerializerMEthodField.
class SampleSerializer(serializers.ModelSerializer):
status = serializers.SerializerMethodField(method_name='get_foo')
label = serializers.SerializerMethodField(method_name='get_foo')
class Meta:
model = Sample
fields = ('status', 'label')
def get_foo(self, instance):
# your code here
Yes, you can return two or more values in one serializermethodfield,
class SampleSerializer(serializers.ModelSerializer):
status = serializers.SerializerMethodField(method_name='get_foo')
class Meta:
model = Sample
fields = ('status',)
def get_foo(self, instance):
return instance.status, instance.label
```
I have a model like so:
class A:
name = models.CharField()
group = models.ForeignKey('SomeModel', null=True, blank=True)
When I serialize this, I would like the serielizer to have different formats based on whether the 'group' field is blank or not. Of course this can be achieved by having different serializers for different formats and calling them as required in the View layer:
class TypeASerializer(serializers.ModelSerializer)
class Meta:
model = A
fields = ('id', 'name')
class TypeBSerializer(serializers.ModelSerializer)
class Meta:
model = A
fields = ('id', 'name', 'group')
But I wanted to handle it in the serializer layer itself and have a single serializer for this. Is that possible?
Serializer.instance may be None in some cases.
And get_fields() is called only once because Serializer.fields is cached from django-rest-framework 3.10: https://github.com/encode/django-rest-framework/commit/7232586c7caf66f20f56b36f1c6a9c9648eb94a4
In other words, when a serializer is used as a list by many=True (in ListModelMixin, or as a field of another serializer), the fields of all items in the list are determined by the first instance.
In that case, the solution is to override to_representation():
class TypeASerializer(serializers.ModelSerializer)
class Meta:
model = A
fields = ('id', 'name', 'group')
def to_representation(self, instance):
ret = super().to_representation(instance)
if not instance.group:
del ret['group']
return ret
This solution is a little inefficient because all fields and values are obtained from super().to_presentation() but some of them are removed again. You can consider fully implementing to_representation() without calling super's.
you can override the get_fields methods of serializer
class YourSerializer(serializers.ModelSerializer):
id = serializers.SerializerMethodField()
name = serializers.SerializerMethodField()
group = serializers.SerializerMethodField()
class Meta:
model = A
fields = ('id', 'name', 'group')
def get_fields(self):
fields = super().get_fields()
# delete all the unnecessary fields according to your logic.
if self.instance.group: # if this is detials view other wise pass object in context
del fields['group']
return fields
You can declare every field of your serializer as SerializerMethodField as follows:
class YourSerializer(serializers.ModelSerializer):
id = serializers.SerializerMethodField()
name = serializers.SerializerMethodField()
group = serializers.SerializerMethodField()
class Meta:
model = A
fields = ('id', 'name', 'group')
def id(self, obj):
if yourcondition(obj.group):
return obj.id
return another_value
...
In Django Rest Framework, how do you filter a serializer when it's nested in another serializer?
My filters are imposed in the DRF viewsets, but when you call a serializer from inside another serializer, the viewset of the nested serializer never gets called, so the nested results appear unfiltered.
I have tried adding a filter on originating viewset, but it doesn't seem to filter the nested results because the nested results get called as a separate pre-fretched query. (The nested serializer is a reverse lookup, you see.)
Is it possible to add a get_queryset() override in the nested serializer itself (moving it out of the viewset), to add the filter there? I've tried that, too, with no luck.
This is what I tried, but it doesn't even seem to get called:
class QuestionnaireSerializer(serializers.ModelSerializer):
edition = EditionSerializer(read_only=True)
company = serializers.StringRelatedField(read_only=True)
class Meta:
model = Questionnaire
def get_queryset(self):
query = super(QuestionnaireSerializer, self).get_queryset(instance)
if not self.request.user.is_staff:
query = query.filter(user=self.request.user, edition__hide=False)
return query
You can subclass the ListSerializer and overwrite the to_representation method.
By default the to_representation method calls data.all() on the nested queryset. So you effectively need to make data = data.filter(**your_filters) before the method is called. Then you need to add your subclassed ListSerializer as the list_serializer_class on the meta of the nested serializer.
subclass ListSerializer, overwriting to_representation and then calling super
add subclassed ListSerializer as the meta list_serializer_class on the nested Serializer
Here is the relevant code for your sample.
class FilteredListSerializer(serializers.ListSerializer):
def to_representation(self, data):
data = data.filter(user=self.context['request'].user, edition__hide=False)
return super(FilteredListSerializer, self).to_representation(data)
class EditionSerializer(serializers.ModelSerializer):
class Meta:
list_serializer_class = FilteredListSerializer
model = Edition
class QuestionnaireSerializer(serializers.ModelSerializer):
edition = EditionSerializer(read_only=True)
company = serializers.StringRelatedField(read_only=True)
class Meta:
model = Questionnaire
While all the above answers work, I find the use of Django's Prefetch object the easiest way of all.
Say a Restaurant obj has a lot of MenuItems, some of which are is_removed == True, and you only want those that are not removed.
In RestaurantViewSet, do something like
from django.db.models import Prefetch
queryset = Restaurant.objects.prefetch_related(
Prefetch('menu_items', queryset=MenuItem.objects.filter(is_removed=False), to_attr='filtered_menu_items')
)
In RestaurantSerializer, do something like
class RestaurantSerializer(serializers.ModelSerializer):
menu_items = MenuItemSerializer(source='filtered_menu_items', many=True, read_only=True)
Tested many solutions from SO and other places.
Found only one working solution for Django 2.0 + DRF 3.7.7.
Define a method in model which has nested class. Craft a filter that will fit your needs.
class Channel(models.Model):
name = models.CharField(max_length=40)
number = models.IntegerField(unique=True)
active = models.BooleanField(default=True)
def current_epg(self):
return Epg.objects.filter(channel=self, end__gt=datetime.now()).order_by("end")[:6]
class Epg(models.Model):
start = models.DateTimeField()
end = models.DateTimeField(db_index=True)
title = models.CharField(max_length=300)
description = models.CharField(max_length=800)
channel = models.ForeignKey(Channel, related_name='onair', on_delete=models.CASCADE)
.
class EpgSerializer(serializers.ModelSerializer):
class Meta:
model = Epg
fields = ('channel', 'start', 'end', 'title', 'description',)
class ChannelSerializer(serializers.ModelSerializer):
onair = EpgSerializer(many=True, read_only=True, source="current_epg")
class Meta:
model = Channel
fields = ('number', 'name', 'onair',)
Pay attention to source="current_epg" and you'll get the point.
I find it easier, and more straight forward, to use a SerializerMethodField on the serializer field you want to filter.
So you would do something like this.
class CarTypesSerializer(serializers.ModelSerializer):
class Meta:
model = CarType
fields = '__all__'
class CarSerializer(serializers.ModelSerializer):
car_types = serializers.SerializerMethodField()
class Meta:
model = Car
fields = '__all__'
def get_car_types(self, instance):
# Filter using the Car model instance and the CarType's related_name
# (which in this case defaults to car_types_set)
car_types_instances = instance.car_types_set.filter(brand="Toyota")
return CarTypesSerializer(car_types_instances, many=True).data
This saves you from having to create many overrides of the serializers.ListSerializer if you need different filtering criteria for different serializers.
It also has the extra benefit of seeing exactly what the filter does within the serializer instead of diving into a subclass definition.
Of course the downside is if you have a serializer with many nested objects that all need to be filtered in some way. It could cause the serializer code to greatly increase. It's up to you how you would like to filter.
Hope this helps!
When a serializer is instantiated and many=True is passed, a
ListSerializer instance will be created. The serializer class then
becomes a child of the parent ListSerializer
This method takes the target of the field as the value argument, and
should return the representation that should be used to serialize the
target. The value argument will typically be a model instance.
Below is the example of the nested serializer
class UserSerializer(serializers.ModelSerializer):
""" Here many=True is passed, So a ListSerializer instance will be
created"""
system = SystemSerializer(many=True, read_only=True)
class Meta:
model = UserProfile
fields = ('system', 'name')
class FilteredListSerializer(serializers.ListSerializer):
"""Serializer to filter the active system, which is a boolen field in
System Model. The value argument to to_representation() method is
the model instance"""
def to_representation(self, data):
data = data.filter(system_active=True)
return super(FilteredListSerializer, self).to_representation(data)
class SystemSerializer(serializers.ModelSerializer):
mac_id = serializers.CharField(source='id')
system_name = serializers.CharField(source='name')
serial_number = serializers.CharField(source='serial')
class Meta:
model = System
list_serializer_class = FilteredListSerializer
fields = (
'mac_id', 'serial_number', 'system_name', 'system_active',
)
In view:
class SystemView(viewsets.GenericViewSet, viewsets.ViewSet):
def retrieve(self, request, email=None):
data = get_object_or_404(UserProfile.objects.all(), email=email)
serializer = UserSerializer(data)
return Response(serializer.data)
The following worked for me, from self.context['view'], You can get the filter params inside the serializer and use it however you want.
class ShipmentDocumentSerializer(serializers.ModelSerializer):
class Meta:
model = Document
fields = ['id', 'created_date', 'consignment', 'document', 'org', 'title' ]
class ShipmentDocumentTypeSerializer(serializers.ModelSerializer):
documents = serializers.SerializerMethodField()
class Meta:
model = DocumentType
fields = ['id', 'type', 'documents']
def get_documents(self, instance):
consignment_id=self.context['view'].kwargs['consignment_id']
queryset = Document.objects.filter(consignment__id=consignment_id)
return ShipmentDocumentSerializer(queryset, many=True).data
I have a ModelSerializer, but by default it serializes all the objects in my model. I would like to limit this queryset to only the most recent 500 (as opposed to all 50 million). How do I do this?
What I have currently is the following:
class MyModelSerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
The reason I don't think I can just specify the queryset in my viewset is that this is in fact the nested portion of another serializer.
models.py
class Container(models.Model):
size = models.CharField(max_length=20)
shape = models.CharField(max_length=20)
class Item(models.Model):
container = models.ForeignKey(Container, related_name='items')
name = models.CharField(max_length=20)
color = models.CharField(max_length=20)
views.py
class ContainerViewSet(viewsets.ModelViewSet):
queryset = Container.objects.all() # only a handful of containers
serializer_class = ContainerSerializer
serializers.py
class ItemSerializer(serializers.ModelSerializer):
class Meta:
model = Item
fields = ('name', 'color')
class ContainerSerializer(serializers.ModelSerializer):
items = ItemSerializer(many=True) # millions of items per container
class Meta:
model = Container
fields = ('size', 'shape', 'items')
In your View Set you may specify the queryset like follows:
from rest_framework import serializers, viewsets
class MyModelSerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
class MyModelViewSet(viewsets.ModelViewSet):
queryset = MyModel.objects.all()[:500]
serializer_class = MyModelSerializer
I think what you are looking for is the SerializerMethodField.
So your code would look as follows:
class ContainerSerializer(serializers.ModelSerializer):
items = SerializerMethodField('get_items')
class Meta:
model = Container
fields = ('size', 'shape', 'items')
def get_items(self, container):
items = Item.objects.filter(container=container)[:500] # Whatever your query may be
serializer = ItemSerializer(instance=items, many=True)
return serializer.data
The one catch is that the SerializerMethodField is read only.
You may use source parameter
class Container(models.Model):
...
def get_items(self):
return self.items[:500]
and in serializer
items = ItemSerializer(many=True, source='get_items', )