Django rest framework serializer dynamic fields with many=True - django

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?

Related

Django Rest Framework filtering against serializer method fields

In my serializers, I have added the custom field "step_type" that grabs a value from another model.
class AccountSerializer(serializers.ModelSerializer):
step_type= serializers.SerializerMethodField()
class Meta:
model = Account
fields = '__all__'
def get_step_type(self, obj):
step = Step.objects.get(step_name=obj.step_name)
return step.step_type
I want to use query parameters to filter my REST API
class AccountViewSet(viewsets.ModelViewSet):
def get_queryset(self):
queryset = Account.objects.all().order_by('-date')
query_step_type = self.request.query_params.get("type")
if query_step_type is not None:
queryset = queryset.filter(step_type=query_step_type)
return queryset
However, this won't work because step_type isn't part of the original model fields. How can I filter the queryset using the step type serializer method field?

Add fields in Serializer dynamically

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/

Django, DRF: To remove a specific field of the serializer only on the first page

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

Problem with POST from fronside. Method does not support writable dotted-source fields by default

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.

Extend django rest framework to allow inheriting context in nested serializers

I'm using Django 1.6 (very soon upgrading to 1.8), Python 2.7, and DRF 3.2.5 (very soon upgrading to latest).
I've got a set of deeply nested serializers (~10 levels deep, with a total of 20-30 models that are serialized).
I'm trying to add a boolean flag to the context, which will determine whether the serialized output hierarchy will be detailed (include all models' fields) or basic (part of the fields only).
I wrote the following code (partial snippet):
from rest_framework import serializers
from app.models import Institute, Department, Member
class MemberSerializer(serializers.ModelSerializer):
def get_fields(self):
fields = super(MemberSerializer, self).get_fields()
if self.context['basic_view']:
for field in ['height', 'weight']:
del fields[field]
return fields
class Meta:
model = Member
fields = ('id', 'birth_date', 'height', 'weight')
class DepartmentSerializer(serializers.ModelSerializer):
members = MemberSerializer(many=True, read_only=True)
def get_fields(self):
fields = super(DepartmentSerializer, self).get_fields()
if self.context['basic_view']:
for field in ['type', 'manager']:
del fields[field]
return fields
class Meta:
model = Department
fields = ('id', 'name', 'type', 'manager', 'members')
class InstituteSerializer(serializers.ModelSerializer):
departments = DepartmentSerializer(many=True, read_only=True)
def get_fields(self):
fields = super(InstituteSerializer, self).get_fields()
if self.context['basic_view']:
for field in ['name', 'type']:
del fields[field]
return fields
class Meta:
model = Institute
fields = ('id', 'name', 'type', 'departments')
def get_entities(is_basic_view):
institutes_list = Institute.objects.all()
serializer = InstituteSerializer(institutes_list, many=True, read_only=True, context={'basic_view': is_basic_view})
return serializer.data
But then found out that the 'context' that is passed from 'get_entities' to 'InstituteSerializer' is not passed-on to the nested serializers.
Meaning that in the example above - InstituteSerializer has 'basic_view' in the 'context', but MemberSerializer & DepartmentSerializer don't.
I found a working solution in context in nested serializers django rest framework : to use SerializerMethodField per nested field (e.g. 'departments'), and in the 'get_' method to manually pass-on the context.
My problem with that solution is that it requires embedding this code 20-30 times in my code, eventually doubling the number of source lines.
My request - if someone has (or can help implement) an extension for serializers.ModelSerializer, which will get an additional parameter upon construction, e.g. 'inherit_context'.
Then the only thing I'll need to change in my classes, for example in 'InstituteSerializer', is the addition of that parameter:
class InstituteSerializer(serializers.ModelSerializer):
departments = DepartmentSerializer(many=True, read_only=True, inherit_context=True)
def get_fields(self):
fields = super(InstituteSerializer, self).get_fields()
if self.context['basic_view']:
for field in ['name', 'type']:
del fields[field]
return fields
class Meta:
model = Institute
fields = ('id', 'name', 'type', 'departments')
Apparently I missed something...
The 'context' is already inherited down to the nested serializers...
However, the reason it didn't work for me, is because as part of my nesting, some of the child serializers were defined via serializers.SerializerMethodField().
And in such as case (only!) the context is not automatically inherited.
The solution is to simply pass-on the 'context', within the 'get_...' method related to each SerializerMethodField:
class ParentSerializer(serializers.ModelSerializer):
child = serializers.SerializerMethodField()
def get_child(self, obj):
child = ....
serializer = ChildSerializer(instance=child, context=self.context)
return serializer.data
P.S - a DRF github issue similar to mine was created a while ago: https://github.com/tomchristie/django-rest-framework/issues/2555