Return two field values from one SerializerMethodField - django

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
```

Related

django get file size and put it in serializer

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

DRF annotate nested serializer

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?

Dynamically create serializer based on model field value

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
...

Serialization of child models

I have models:
class CommonEditor(models.Model):
def __str__(self):
return 'Common Atributes Mask'
class Color(models.Model):
name = models.CharField(max_length=25)
editor = models.ForeignKey(CommonEditor, on_delete=models.PROTECT, null=True)
So I make serialization this way:
class ColorSerializer(serializers.ModelSerializer):
class Meta:
model = Color
fields = '__all__'
class CommonAttributesSerializer(serializers.ModelSerializer):
color = ColorSerializer(many=True, read_only=True)
class Meta:
model = CommonEditor
fields = ('pk', 'color')
And then view:
class CommonAttributeAPIView(generics.ListCreateAPIView):
serializer_class = CommonAttributesSerializer
queryset = CommonEditor.objects.all()
I get only pk of my CommonEditor Model. Why can't i get the full Atributes Mask and how can I fix it? Big thanks!
Default name for reverse foreign key relation is modelname_set or in your case color_set. So try to rename color field to color_set:
class CommonAttributesSerializer(serializers.ModelSerializer):
color_set = ColorSerializer(many=True, read_only=True)
class Meta:
model = CommonEditor
fields = ('pk', 'color_set')
This can also be achieved via SerializerMethodField and can be seen as follow:
class CommonAttributesSerializer(serializers.ModelSerializer):
color = serializers.SerializerMethodField()
class Meta:
model = CommonEditor
fields = ('pk', 'color')
def get_color(self, common_editor):
return ColorSerializer(common_editor.color_set.all(), many=True).data
Documentation: http://www.django-rest-framework.org/api-guide/fields/#serializermethodfield
The CommonAttributesSerializer search for a color attribute in CommonEditor's instance, but it couldn't find. In DRF serializer, a parameter called source will says explicitly where to look for the data. So , change the serializer as below:
class CommonAttributesSerializer(serializers.ModelSerializer):
color = ColorSerializer(many=True, read_only=True, <b>source='color_set'</b>)
class Meta:
model = CommonEditor
fields = ('pk', 'color')
Reference : DRF Fields -source

Django Rest Framework Ordering Filter, order by nested list length

I'm using OrderingFilter globally through settings.py and it works great.
Now I would like to order on the size of a nested list from a ManyToManyField. Is that possible with the default OrderingFilter?
If not, is there a way I can do it with a custom filter, while keeping the query param ordering in the url (http://example.com/recipes/?ordering=). For the sake of consistency.
Oh and the ManyToManyField is a through table one.
These are my models.py:
class Recipe(models.Model):
name = models.CharField(max_length=255)
cook_time = models.FloatField()
ingredients = models.ManyToManyField(IngredientTag, through=Ingredient)
My serializers.py:
class IngredientTagSerializer(serializers.ModelSerializer):
class Meta:
model = IngredientTag
fields = ('id', 'label')
class IngredientSerializer(serializers.ModelSerializer):
class Meta:
model = Ingredient
fields = ('amount', 'unit', 'ingredient_tag')
depth = 1
class RecipeSerializer(serializers.ModelSerializer):
ingredients = IngredientSerializer(source='ingredient_set', many=True)
class Meta:
model = Recipe
fields = ('id', 'url', 'name', 'ingredients', 'cook_time')
read_only_fields = ('owner',)
depth = 2
And my views.py:
class RecipeViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows recipes to be viewed or edited.
"""
queryset = Recipe.objects.all().order_by()
serializer_class = RecipeSerializer
permission_classes = (DRYPermissions,)
ordering_fields = ('cook_time',) #Need ingredient count somewhere?
Thanks!
Try:
class RecipeSerializer(serializers.ModelSerializer):
ingredients = IngredientSerializer(source='ingredient_set', many=True)
ingredients_length = serializers.SerializerMethodField()
class Meta:
model = Recipe
fields = ('id', 'url', 'name', 'ingredients', 'cook_time')
read_only_fields = ('owner',)
depth = 2
def get_ingredients_length(self, obj):
return obj.ingredients.count()
Then order by ingredients_length
EDIT
In model.py, try this:
#property
def ingredient_length(self):
return self.ingredient_set.count()