django get file size and put it in serializer - django

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

Related

Return two field values from one SerializerMethodField

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

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?

override create in serializer

Im overriding the create method in serializer but it seems it is not getting into this function when i do a POST request:
My serializer:
class ElsUserSerializer(serializers.ModelSerializer):
class Meta:
model = ElsUser
fields = ['id', 'first_name', 'last_name', "email"]
read_only_fields = ['id']
class RequisiteItemSerializser(serializers.ModelSerializer):
created_by = ElsUserSerializer()
class Meta:
model = RequisiteItem
fields = ['id', 'enrollable', 'completed_within_days', 'completed_within_type','date_completed_by', 'created_by' ]
read_only_fields = ['id']
class RequisiteSerializer(serializers.ModelSerializer):
created_by = ElsUserSerializer()
items = RequisiteItemSerializser(many=True)
class Meta:
model = Requisite
fields = ('id', 'name', 'description', 'items', 'enrolments', 'created_by', 'timestamp')
read_only_fields = ['id']
depth = 1
def create(self, validated_data):
print("SELF INTITIAL DATA", self.initial_data)
helper = RequisiteHelper()
requisite = helper.create(self.initial_data,self.context['request'])
print("LOGGES IN USER", self.context['request'].user.username)
return requisite
I have moved the methods to a separate helper class:
My helper class:
class RequisiteHelper:
def create(self, initial_data, context):
name = initial_data['name']
description = initial_data['description']
items = initial_data['items']
enrolments = initial_data['enrolments']
requisite = Requisite.objects.create(name=name, description=description, created_by=context.user)
self.create_items(requisite, items, context)
self.create_enrolments(requisite, enrolments)
requisite.save()
return requisite
def create_items(self, requisite, items, context):
for item in items:
requisite_item = RequisiteItem()
enrollable = Enrollable.objects.get(id=item.enrollable.id)
requisite_item.enrollable = enrollable
requisite_item.created_by = context.user
requisite_item.completed_within_days = item.completed_within_days
cw_type = CompletedWithinType.objects.get(short_name = item.completed_within_type)
requisite_item.completed_within_type = cw_type
requisite_item.save()
requisite.items.add(requisite_item)
def create_enrolments(self, requisite, enrolments):
for enrolment in enrolments:
requisite.add(enrolment)
When i create a Requisite by post method,it keeps showing created_by is required. Im saving created_by automatically as logged in user by overrriding the create method. The print statements are also not displaying. What could i be doing wrong?
In views.py i have:
class ListCreateRequisite(generics.ListCreateAPIView):
serializer_class = RequisiteSerializer
queryset = Requisite.objects.all()
You create method is not calling because created_by model field validation of require-field is catch before it when serailizer.is_valid() called. You need to assign that value, before calling serializer or in run time or even you can change created_by requirements in model defination. That will allow you to by pass this catch.

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

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