Django Rest Framework - Ordering a nested reverse lookup serializer - django

Is it possible to order/sort a serializer availability that is both a reverse lookup and nested inside unit serializer? For example, I wish to order availability by start_time instead of id.
Below is what I tried but I get .order_by('-start_time')' ^ SyntaxError: invalid syntax
Serializers
class AvailabilitySerializer(serializers.ModelSerializer):
staff = StaffSerializer()
class Meta:
model = Availability
fields = ['id','start_time', 'end_time','staff']
class ManagerSerializer(serializers.ModelSerializer):
class Meta:
model = Manager
fields = ['company', 'logo']
class UnitSerializer(serializers.ModelSerializer):
availability = AvailabilitySerializer(source='availability_set.order_by('-start_time')', many=True)
manager = ManagerSerializer()
class Meta:
model = Unit
fields = ['id', 'address', 'manager', 'availability']

You can override the field and provide the serializer with your own queryset and then return the data:
from rest_framework.serializers import SerializerMethodField
class UnitSerializer(serializers.ModelSerializer):
availability = SerializerMethodField()
manager = ManagerSerializer()
class Meta:
model = Unit
fields = ['id', 'address', 'manager', 'availability']
def get_availability(self, instance):
queryset = instance.availability_set.order_by('-start_time')
return AvailabilitySerializer(queryset, many=True).data
SerializerMethodField

Related

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, limiting fields on foreignkey relationship when serializer depth = 1

I am using the Django REST Framework and I have a serializer as follows:
class UserProfileSerializer(serializers.ModelSerializer):
class Meta:
model = UserProfile
depth = 1
fields = ['user','team','correct','wrong','percentage']
The problem if this passes all user data (including a hashed password). How do I limit the fields being passed?
I have a UserSerializer as follows (which holds the only fields I really want):
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['first_name','last_name','username']
Instead of depth option declare user field explicitly in UserProfileSerializer and use UserSerializer for this field:
class UserProfileSerializer(serializers.ModelSerializer):
user = UserSerializer()
class Meta:
model = UserProfile
fields = ['user','team','correct','wrong','percentage']
Or try to override build_nested_field like this:
class UserProfileSerializer(serializers.ModelSerializer):
class Meta:
model = UserProfile
depth = 1
fields = ['user','team','correct','wrong','percentage']
def build_nested_field(self, field_name, relation_info, nested_depth):
if field_name == 'user':
field_class = UserSerializer
field_kwargs = get_nested_relation_kwargs(relation_info)
return field_class, field_kwargs
return super().build_nested_field(field_name, relation_info, nested_depth)

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

Using rest django rest framework for creating new object

I have the following models
class Peri(models.Model):
date = models.DateField()
customer = models.ForeignKey(Customer)
class PeriTask(models.Model):
#fields
peri = models.ForeignKey(Peri)
My serializers are the following
class PeriSerializer(serializers.HyperlinkedModelSerializer):
customer = serializers.PrimaryKeyRelatedField(read_only=True)
class Meta:
model = Peri
fields = ('id', 'date', 'url', 'peritasks', 'customer')
class PeriTaskSerialiazer(serializers.HyperlinkedModelSerializer):
tooth = serializers.PrimaryKeyRelatedField(read_only=True)
class Meta:
model = PeriTask
fields = ('id', 'task_type', 'implant', 'furcation', 'bleeding1', 'bleeding2', 'bleeding3', 'plaque1', 'plaque2',
'gingival_margin1', 'gingival_margin2', 'gingival_margin3', 'probing_depth1', 'probing_depth2',
'probing_depth3', 'tooth', 'url', )
and my viewsets are
class PeriodontogrammaViewSet(ModelViewSet):
serializer_class = PeriSerializer
queryset = Peri.objects.all()
class PeriTaskViewSet(ModelViewSet):
serializer_class = PeriTaskSerialiazer
queryset = PeriTask.objects.all()
But when I try to create a new peri using the api it gives me the following integrity error
NOT NULL constraint failed: peri_peri.customer_id
My json data that beeing posted are
{"date": "2014-12-17",
"customer": 27
}
I haven't created a serializer for customer since I am not interested in having api for my other models.
In your serializer, you've set the customer key to read_only:
customer = serializers.PrimaryKeyRelatedField(read_only=True)
Try setting it to False or just simply removing this whole line (which seems superfluous to me)

django REST framework - limited queryset for nested ModelSerializer?

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', )