I have a View in which I receive the request and it returns the serialized data
views.py
class AllotmentReportsView(APIView):
permission_classes = (permissions.IsAuthenticated,)
def get(self, request):
sfields = request.GET['sfields'] #I can get the fields in params
serializer = AllotReportSerializer(items, many=True)
return Response(serializer.data, status=status.HTTP_201_CREATED)
serializer.py
class AllotReportSerializer(serializers.ModelSerializer):
send_from_warehouse = serializers.SlugRelatedField(read_only=True, slug_field='name')
transport_by = serializers.SlugRelatedField(read_only=True, slug_field='name')
sales_order = AllotSOSerializer(many=False)
flows = AllotFlowsSerializer(many=True)
class Meta:
model = Allotment
fields = ( 'transaction_no', 'dispatch_date', 'sales_order',
'is_delivered', 'send_from_warehouse', 'transport_by',
'flows', )
Instead of defining the fields in serializer can I pass the fields dynamically from the view sfields and pass them to the serializer ?
It is not necessary to describe fields in ModelSerializer class. Django will generate it automatically according model information:
class AllotReportSerializer(serializers.ModelSerializer):
class Meta:
model = Allotment
fields = ( 'transaction_no', 'dispatch_date', 'sales_order',
'is_delivered', 'send_from_warehouse', 'transport_by',
'flows', )
is enough
If you want to add fields that not exists in model, I guess it is possible with meta classes and setattr() function. But that is looking meaningless. Also you need to add logic how to dynamically set field type and parameters.
You do not need to describe the fields in the ModelSerializer -
class AllotReportSerializer(serializers.ModelSerializer):
class Meta:
model = Allotment
fields = ( 'transaction_no', 'dispatch_date', 'sales_order',
'is_delivered', 'send_from_warehouse', 'transport_by',
'flows', )
extra_kwargs = {
"url": {
"lookup_field": "slug",
"view_name": "api-your-model-name-detail",
}
you can define extra kwargs if you want in the above way.
This is enough.
If you want to add all the fields in your model, you can do this -
class Meta:
model = Your model name
fields = "__all__"
You can read more about it here - https://www.django-rest-framework.org/api-guide/serializers/
Related
How can I remove field3 and field4 only on the first page?
I need something that can be dynamically reused as I plan to use it in multiple views.
How can I dynamically delete field3 and field4 without creating multiple serializers?
class CustomSerializer(serializers.ModelSerializer):
class Meta:
model = Model
fields = ('field', 'field2', 'field3', 'field4')
class CustomSerializer2(serializers.ModelSerializer):
class Meta:
model = Model
fields = ('field5', 'field6', 'field3', 'field4')
class CustomSerializer2(serializers.ModelSerializer):
class Meta:
model = Model
fields = ('field7', 'field8', 'field3', 'field4')
class CustomView(ListAPIView):
serializer_class = CustomSerializer
class CustomView2(ListAPIView):
serializer_class = CustomSerializer2
class CustomView3(ListAPIView):
serializer_class = CustomSerializer3
Try using serializer context. This way you can assess request data and params in you serializer methods such as to_representation which is useful in your case.
PS. This code was written in stackoverflow window, it may include some mistakes, but it shows an approach
class CustomView(ListAPIView):
def get_serializer(self, *args, **kwargs):
serializer_class = self.get_serializer_class()
kwargs['context'] = {'request': self.request}
return serializer_class(*args, **kwargs)
class CustomSerializer2(serializers.ModelSerializer):
class Meta:
model = Model
fields = ['field1', 'field2', 'field3']
def to_representation(self, instance, **kwargs):
if self.context.get('request').query_params.get('page') == 1:
return {'field1': instance.field1, 'field2': instance.field2}
return {'field1': instance.field1, 'field3': instance.field3}
You can write two different serializers the first one with field3 & field4 and other one does not include these two fields. Then from your views use get_serializer_class() method to select the appropriate serializer for the provided page.
You will need some thing like this in your views -
class CustomSerializerWithFields(serializers.ModelSerializer):
class Meta:
model = Model
fields = ('field', 'field2', 'field3', 'field4')
class CustomSerializerWithoutFields(serializers.ModelSerializer):
class Meta:
model = Model
fields = ('field', 'field2')
class CustomView(ListAPIView):
def get_serializer_class(self):
if self.request.query_params["page"]==1:
return CustomSerialierWithoutFields
return CustomSerializerWithFields
I want my serializer return fields dynamically depends on value of some model field. So I override get_fields method.
Here's the code example:
class TestSerializer(serializers.ModelSerializer):
options = serializers.StringRelatedField()
blanks = serializers.StringRelatedField()
class Meta:
model = MyModel
fields = ('id', 'category', 'options', 'blanks')
def get_fields(self):
fields = super().get_fields()
if self.instance.category == 'FirstCategory':
del fields['options']
else:
del fields['blanks']
return fields
This works fine when to serialize a single model, but if pass many=True for multiple models, it fails. The self.instance is a QuerySet.
How can I deal with this situation?
I create some dotted source field in my serializer. I did it cause have to display name value of foreign key not pk value. But when I trying to POST from frontend djang throws this : AssertionError at /api/my-api/
The .create() method does not support writable dotted-source fields by default.
Write an explicit .create() method for serializer MySerializer, or set read_only=True on dotted-source serializer fields.
So, when I set read_only = True my POST from frontend to request null for every field from dotted-source serializer fields.
This is my serializer:
class FcaWorksSerializer(serializers.ModelSerializer):
fell_form = serializers.CharField(source="fell_form.name" )
#...
main_type = serializers.CharField(source="main_type.name")
class Meta:
model = FcaWorks
fields = ('id_fca','wkod', 'main_type','fell_form','fell_type','kind',\
'sortiment','vol_drew','use_type','fca_res','ed_izm','vol_les','act_name',\
'obj_type','use_area','indicator','comment','date_report')
How I can to solve this problem?
Override the __init__() method of the serializer to adjust the serializer condition
class FcaWorksSerializer(serializers.ModelSerializer):
fell_form = serializers.CharField()
# ...
main_type = serializers.CharField()
class Meta:
model = FcaWorks
fields = ('id_fca', 'wkod', 'main_type', 'fell_form', 'fell_type', 'kind',
'sortiment', 'vol_drew', 'use_type', 'fca_res', 'ed_izm', 'vol_les', 'act_name',
'obj_type', 'use_area', 'indicator', 'comment', 'date_report')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.context['request'].method == 'GET':
self.fields['fell_form'].source = "fell_form.name"
self.fields['main_type'].source = "main_type.name"
def create(self, validated_data):
# here you will get the data
fell_form = validated_data['fell_form']
main_type = validated_data['main_type']
From the docs, there are multiple ways to deal with ForeignKey relations. You don't have to make your own create method if the Foreignkey relations are not "many-to-many".
In your case you can use one of the following:
SlugRelatedField
PrimaryKeyRelatedField
class FcaWorksSerializer(serializers.ModelSerializer):
fell_form = serializers.SlugRelatedField(slug_field="name", queryset = ***"""The queryset that fell_form came from"""*** )
#...
main_type = serializers.SlugRelatedField(slug_field="name", queryset = ***"""The queryset main_type came from"""***)
class Meta:
model = FcaWorks
fields = ('id_fca','wkod', 'main_type','fell_form','fell_type','kind',\
'sortiment','vol_drew','use_type','fca_res','ed_izm','vol_les','act_name',\
'obj_type','use_area','indicator','comment','date_report')
Then PrimaryKeyRelated Field usage:
class FcaWorksSerializer(serializers.ModelSerializer):
fell_form = serializers.PrimaryKeyRelatedField(source ="fell_form.name", queryset = ***"""The queryset that fell_form came from"""*** )
#...
main_type = serializers.PrimaryKeyRelatedField(source="main_type.name", queryset = ***"""The queryset main_type came from"""***)
This has worked for me when I had the same problem, however like previously stated for "Many-to-Many" field you have to explicitly write the create and update methods.
I have list [1, 2, 3]
and TestModel queryset
[ {'pk':1,'text':'one'}, {'pk':2,'text':'two'}, {'pk':3,'text':'three'}]
and I have model and serializer like following
class TestMode(models.Model):
text = models.CharField(max_length=10)
class TestModelSerializer(serializer.ModelSerializer):
class Meta:
model = TestModel
fields = ('pk', 'text')
I want to make data like
[{'pk':1, 'text':'one', 'number':1}, {..., 'number':2}, {..., 'number':3}]
I make another serializer
class WrapperSerializer(serializer.ModelSerializer):
number = serializer.IntegerField()
class Meta:
model = TestModel
fields = ('pk', 'text')
I try to like following, but i think it's not cool
serialized_data = TestModelSerializer(qs, many=True).data
for index, data in enumerate(serializerd_data):
data['number'] = list[index]
serializer = WrapperSerializer(serialized_data, many=True)
How can i do that??? i don't have another idea... please help me
If you want to add a calculated value (read-only) to your serializer, you have two options:
On the model, define a #property number:
#property
def number(self):
return self.friends.count() # or whatever you need to calculate the number
Then in your serializer, you can just use number as any other field, but need to specify the type of serializer (e.g. IntegerField).
on the serializer use a SerializerMethodField as described here:
number = serializers.SerializerMethodField()
def get_number(self, obj):
return obj.friends.count()
I am trying to figure out the best way to add annotated fields, such as any aggregated (calculated) fields to DRF (Model)Serializers. My use case is simply a situation where an endpoint returns fields that are NOT stored in a database but calculated from a database.
Let's look at the following example:
models.py
class IceCreamCompany(models.Model):
name = models.CharField(primary_key = True, max_length = 255)
class IceCreamTruck(models.Model):
company = models.ForeignKey('IceCreamCompany', related_name='trucks')
capacity = models.IntegerField()
serializers.py
class IceCreamCompanySerializer(serializers.ModelSerializer):
class Meta:
model = IceCreamCompany
desired JSON output:
[
{
"name": "Pete's Ice Cream",
"total_trucks": 20,
"total_capacity": 4000
},
...
]
I have a couple solutions that work, but each have some issues.
Option 1: add getters to model and use SerializerMethodFields
models.py
class IceCreamCompany(models.Model):
name = models.CharField(primary_key=True, max_length=255)
def get_total_trucks(self):
return self.trucks.count()
def get_total_capacity(self):
return self.trucks.aggregate(Sum('capacity'))['capacity__sum']
serializers.py
class IceCreamCompanySerializer(serializers.ModelSerializer):
def get_total_trucks(self, obj):
return obj.get_total_trucks
def get_total_capacity(self, obj):
return obj.get_total_capacity
total_trucks = SerializerMethodField()
total_capacity = SerializerMethodField()
class Meta:
model = IceCreamCompany
fields = ('name', 'total_trucks', 'total_capacity')
The above code can perhaps be refactored a bit, but it won't change the fact that this option will perform 2 extra SQL queries per IceCreamCompany which is not very efficient.
Option 2: annotate in ViewSet.get_queryset
models.py as originally described.
views.py
class IceCreamCompanyViewSet(viewsets.ModelViewSet):
queryset = IceCreamCompany.objects.all()
serializer_class = IceCreamCompanySerializer
def get_queryset(self):
return IceCreamCompany.objects.annotate(
total_trucks = Count('trucks'),
total_capacity = Sum('trucks__capacity')
)
This will get the aggregated fields in a single SQL query but I'm not sure how I would add them to the Serializer as DRF doesn't magically know that I've annotated these fields in the QuerySet. If I add total_trucks and total_capacity to the serializer, it will throw an error about these fields not being present on the Model.
Option 2 can be made work without a serializer by using a View but if the model contains a lot of fields, and only some are required to be in the JSON, it would be a somewhat ugly hack to build the endpoint without a serializer.
Possible solution:
views.py
class IceCreamCompanyViewSet(viewsets.ModelViewSet):
queryset = IceCreamCompany.objects.all()
serializer_class = IceCreamCompanySerializer
def get_queryset(self):
return IceCreamCompany.objects.annotate(
total_trucks=Count('trucks'),
total_capacity=Sum('trucks__capacity')
)
serializers.py
class IceCreamCompanySerializer(serializers.ModelSerializer):
total_trucks = serializers.IntegerField()
total_capacity = serializers.IntegerField()
class Meta:
model = IceCreamCompany
fields = ('name', 'total_trucks', 'total_capacity')
By using Serializer fields I got a small example to work. The fields must be declared as the serializer's class attributes so DRF won't throw an error about them not existing in the IceCreamCompany model.
I made a slight simplification of elnygreen's answer by annotating the queryset when I defined it. Then I don't need to override get_queryset().
# views.py
class IceCreamCompanyViewSet(viewsets.ModelViewSet):
queryset = IceCreamCompany.objects.annotate(
total_trucks=Count('trucks'),
total_capacity=Sum('trucks__capacity'))
serializer_class = IceCreamCompanySerializer
# serializers.py
class IceCreamCompanySerializer(serializers.ModelSerializer):
total_trucks = serializers.IntegerField()
total_capacity = serializers.IntegerField()
class Meta:
model = IceCreamCompany
fields = ('name', 'total_trucks', 'total_capacity')
As elnygreen said, the fields must be declared as the serializer's class attributes to avoid an error about them not existing in the IceCreamCompany model.
You can hack the ModelSerializer constructor to modify the queryset it's passed by a view or viewset.
class IceCreamCompanySerializer(serializers.ModelSerializer):
total_trucks = serializers.IntegerField(readonly=True)
total_capacity = serializers.IntegerField(readonly=True)
class Meta:
model = IceCreamCompany
fields = ('name', 'total_trucks', 'total_capacity')
def __new__(cls, *args, **kwargs):
if args and isinstance(args[0], QuerySet):
queryset = cls._build_queryset(args[0])
args = (queryset, ) + args[1:]
return super().__new__(cls, *args, **kwargs)
#classmethod
def _build_queryset(cls, queryset):
# modify the queryset here
return queryset.annotate(
total_trucks=...,
total_capacity=...,
)
There is no significance in the name _build_queryset (it's not overriding anything), it just allows us to keep the bloat out of the constructor.