How to serializer a customized query set result? - django

In django model serializer, the typical use case is like below
class SalesManSerializer(serializers.ModelSerializer):
class Meta:
module = models.SalesMan
fields = ('id', 'name')
The id, name are all from the salse man model.
But if I have another model called Order which referenced to SalseMan. I want to query how many order each sales man get.
The SQL would be like
SELECT ID, NAME, COUNT(ORDER.ID) FROM SALSE_MAN, ORDER WHERE ORDER.UID = SALSE_MAN.ID GROUP BY SALSE_MAN.ID
How to serializer this query?

Model serializers serialize only model fields by default. But good news is that you always can add custom fields to your serializer.
Don't know what your models exactly are, but in code it may look like:
class SalesManSerializer(serializers.ModelSerializer):
total_orders = serializers.IntegerField()
class Meta:
module = models.SalesMan
fields = ('id', 'name', 'total_orders')
Serializers are responsible only for serializing. They don't construct querysets for us. That said you need to annotate query set yourself. Either in a view or wherever the queryset will be initialized and passed to the serializer you can use annotation to annotate object with count of orders.
For ex:
class SalesManViewSet(viewsets.ModelViewSet):
serializer_class = SalesManSerializer
def get_queryset(self):
return SalesMan.objects.annotate(
total_trucks = Count('orders'),
)

you can add it using a methodfield
class SalesManSerializer(serializers.ModelSerializer):
count = serializers.SerializerMethodField()
class Meta:
model = models.SalesMan
fields = ('id', 'name', 'count')
def get_count(self, instance):
return Order.objects.count()

Related

How to use serializer method field to work on primary key related data

I want to use serializer method field to fetch data with some business rule
class Product(models.Model):
name = models.CharField()
class Stock(models.Model):
product = models.ForeignKey(Product, on_delete=models.Cascade, related_name='stock')
current = models.BooleanField(default=True)
quantity = models.IntegerField()
class ProductSerializer(serializers.ModelSerializer):
productstock = serializers.SerializerMethodField()
#GET LATEST CURRENT STOCK-QUANTITU
class Meta:
model = Product
fields = [
'name',
'productstock'
]
I want to get an output like this:
{
name:'laptop',
productstock:18
}
I was not able check it right now but it should be something like this:
class ProductSerializer(serializers.ModelSerializer):
productstock = serializers.SerializerMethodField()
#GET LATEST CURRENT STOCK-QUANTITU
class Meta:
model = Product
fields = [
'name',
'productstock'
]
def get_productstock(self, obj):
stock = Stock.objects.filter(product__name=obj.name)
return stock.quantity
Explanation:
the serializer method is always built up by get_ + variable_name and takes the curren object as an argument. Then you should be able to filter your Stock model and the productstock value will become what you return by this method. Hope it helps you.
Update
I guess now I got your point. You should do the filtering before you pass your queryset through the serializer. Therefore I would recommend to override the get_queryset method. In your view just add the below function and it just filters only the products with current=True. My example is with APIView but it works with every DRF View as they all inherit the APIView.
class YourViewClass(APIView):
...
def get_queryset(self):
return Product.objects.filter(stock__current=True)
Explanation
Within the filter method you refer to your related_name of the foreign key field and then use double underscore plus the field you want to filter on. Hope it works for you.

Django Rest Framwork - Select only some fields in reverse lookup queryset

I'm trying to retrieve only some of the fields in the "Appointments" associated to a rental property "Unit". From the UnitSerializer, I call a SerializerMethodField() to do a reverse lookup for the "appointment" field. This works out well. However, the queryset returns all the fields (id, time, unit, staff, prospect) in each object, when I only need a few (id, time).
I tried .values() on the queryset like so:
queryset = instance.appointment_set.values('id', 'appointment_time')
But I get "Got KeyError when attempting to get a value for field unit on serializer AppointmentSerializer.\nThe serializer field might be named incorrectly and not match any attribute or key on the dict instance.\nOriginal exception text was: unit."
Note sure if you need all the code, but here's the essential.
Models
class Appointment(models.Model):
appointment_time = models.DateTimeField()
unit = models.ForeignKey(Unit, on_delete=models.CASCADE)
staff = models.ForeignKey(Staff, on_delete=models.CASCADE)
prospect = models.ForeignKey(Prospect, on_delete=models.CASCADE)
Serializers
class AppointmentSerializer(serializers.ModelSerializer):
class Meta:
model = Appointment
fields = ['id','appointment_time']
class UnitSerializer(serializers.ModelSerializer):
appointment = SerializerMethodField()
class Meta:
model = Unit
fields = ['id', 'address', 'appointment']
def get_appointment(self, instance):
cutoff = _datetime.date.today() + timedelta(hours=72)
queryset = instance.appointment_set.exclude(appointment_time__gt=cutoff)
return AppointmentSerializer(queryset, many=True).data
There is a better way to handle reverse relationship in serializer:
class UnitSerializer(ModelSerializer):
appointment = AppointmentSerializer(many=True, source='appointment_set')
class Meta:
model = Unit
fields = ['id', 'address', 'appointment']

Django rest framework serializer - Serialize single model field multiple times

I would like to serialize one related field multiple times in output json. The first should contain pk of related object, and the second representation should be hyperlink. Any fancy way how to do it? I know SerializerMethodField, but I find it non-elegant approach.
My models:
class Person(models.Model):
first_name = models.CharField()
...
class Order(models.Model):
title = models.CharField()
person = models.ForeignKey(Person, related_name='orders')
What I want:
Serialize my Order model like this:
{
"title": "Alice in wonderland",
"person": 1, # represents persons's primary key
"person_url": "/person-detail/1"
}
What I tried / My serializer:
class OrderSerializer(serializers.ModelSerializer):
person = serializers.IntegerField()
person_url = serializers.HyperlinkedRelatedField(
view_name='myapp:user-profile',
lookup_field='pk'
)
class Meta:
model = Order
fields = ['title', 'person', 'person_url']
read_only_fields = ('__all__',)
But it this case Django was logically bitching about missing person_url field in database. How to proceed?
Your approach is pretty close. Couple of notes:
you don't need to specify the person integer field if you want the pk, you get that for free (ModelSerializer will automatically create a PrimaryKeyRelatedField called person)
you don't have to specify the fields here. DRF gives you the automatically generated ones plus explicitly defined fields on the serializer
lookup_field defaults to pk
assuming that your view name is correct (I can't see your urls), all you're missing from your HyperlinkRelatedField is a source attribute
Putting it together, something like this should work:
class OrderSerializer(serializers.ModelSerializer):
person_url = serializers.HyperlinkedRelatedField(
view_name='myapp:user-profile',
source='person',
read_only=True
)
class Meta:
model = Order
fields='__all__'
read_only_fields = ('__all__',)
NOTE: for my urls.py, I have a view name that looks more like person-detail.

Add Serializer on Reverse Relationship - Django Rest Framework

I have a Cart model and a CartItem model. The CartItem model has a ForeignKey to the Cart model.
Using Django Rest Framework I have a view where the API user can display the Cart, and obviously then I want to include the CartItem in the respone.
I set up my Serializer like this:
class CartSerializer(serializers.ModelSerializer):
user = UserSerializer(read_only=True)
cartitem_set = CartItemSerializer(read_only=True)
class Meta:
model = Cart
depth = 1
fields = (
'id',
'user',
'date_created',
'voucher',
'carrier',
'currency',
'cartitem_set',
)
My problem is the second line, cartitem_set = CartItemSerializer(read_only=True).
I get AttributeErrors saying 'RelatedManager' object has no attribute 'product'. ('product' is a field in the CartItem model. If I exclude product from the CartItemSerializer I just get a new AttributeError with the next field and so on. No matter if I only leave 1 or all fields in the Serializer, I will get a error.
My guess is that for some reason Django REST Framework does not support adding Serializers to reverse relationships like this. Am I wrong? How should I do this?
PS
The reason why I want to use the CartItemSerializer() is because I want to have control of what is displayed in the response.
Ahmed Hosny was correct in his answer. It required the many parameter to be set to True to work.
So final version of the CartSerializer looked like this:
class CartSerializer(serializers.ModelSerializer):
cartitem_set = CartItemSerializer(read_only=True, many=True) # many=True is required
class Meta:
model = Cart
depth = 1
fields = (
'id',
'date_created',
'voucher',
'carrier',
'currency',
'cartitem_set',
)
It's important to define a related name in your models, and to use that related name in the serializer relationship:
class Cart(models.Model):
name = models.CharField(max_length=500)
class CartItem(models.Model):
cart = models.ForeignKey(Cart, related_name='cart_items')
items = models.IntegerField()
Then in your serializer definition you use those exact names:
class CartSerializer(serializers.ModelSerializer):
cart_items = CartItemSerializer(read_only=True)
class Meta:
model = Cart
fields = ('name', 'cart_items',)
It would be wise to share your whole code, that is model and serializers classes. However, perhaps this can help debug your error,
My serializer classes
class CartItemSerializer(serializers.ModelSerializer):
class Meta:
model = CartItem
fields = ('id')
class CartSerializer(serializers.ModelSerializer):
#take note of the spelling of the defined var
_cartItems = CartItemSerializer()
class Meta:
model = Cart
fields = ('id','_cartItems')
Now for the Models
class CartItem(models.Model):
_cartItems = models.ForeignKey(Subject, on_delete=models.PROTECT)
#Protect Forbids the deletion of the referenced object. To delete it you will have to delete all objects that reference it manually. SQL equivalent: RESTRICT.
class Meta:
ordering = ('id',)
class Cart(models.Model):
class Meta:
ordering = ('id',)
For a detailed overview of relationships in django-rest-framework, please refer their official documentation

How do you filter a nested serializer in Django Rest Framework?

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