Reputation: 4391
I have a serializer in Django REST framework defined as follows:
class QuestionSerializer(serializers.Serializer):
id = serializers.CharField()
question_text = QuestionTextSerializer()
topic = TopicSerializer()
Now I have two API views that use the above serializer:
class QuestionWithTopicView(generics.RetrieveAPIView):
# I wish to include all three fields - id, question_text
# and topic in this API.
serializer_class = QuestionSerializer
class QuestionWithoutTopicView(generics.RetrieveAPIView):
# I want to exclude topic in this API.
serializer_class = ExamHistorySerializer
One solution is to write two different serializers. But there must be a easier solution to conditionally exclude a field from a given serializer.
Upvotes: 44
Views: 28398
Reputation: 163
We add a set_fields and set_exclude_fields to each serializer so that they are easily customizable. Initially we created a new serializer for each API, but this let to a wildgrowth of serializers.
It looks like this:
class YourModelSerializer(serializers.ModelSerializer):
class Meta:
model = YourModel
fields = ("__all__")
def __init__(self, *args, **kwargs):
set_fields(self, kwargs)
set_exclude_fields(self, kwargs)
super(YourModelSerializer, self).__init__(*args, **kwargs)
Then in a util file for shared functions among serializers we have these two functions:
def set_fields(serializer, kwargs):
fields = kwargs.pop("fields", None)
if fields:
serializer_fields = {field_name: serializer.fields[field_name] for field_name in fields if field_name in serializer.fields}
serializer.fields = serializer_fields
def set_exclude_fields(serializer, kwargs):
exclude_fields = kwargs.pop("exclude_fields", None)
if exclude_fields:
for field_name in exclude_fields:
serializer.fields.pop(field_name, None)
You call it like this:
serializer = YourModelSerializer(fields=["field"], exclude_fields=["field"])
Keep in mind though that this could lead to unpredictable outputs of your APIs if you always customize the fields. That could be problematic for the frontend.
Upvotes: 0
Reputation: 1
You can use to representation method and just pop values:
def to_representation(self, instance):
"""Convert `username` to lowercase."""
ret = super().to_representation(instance)
ret.pop('username') = ret['username'].lower()
return ret
you can find them here
Upvotes: 0
Reputation: 10680
You can set fields
and exclude
properties of Meta
Here is an Example:
class DynamicFieldsModelSerializer(serializers.ModelSerializer):
class Meta:
model = User
exclude = ['id', 'email', 'mobile']
def __init__(self, *args, **kwargs):
super(DynamicFieldsModelSerializer, self).__init__(*args, **kwargs)
# @note: For example based on user, we will send different fields
if self.context['request'].user == self.instance.user:
# Or set self.Meta.fields = ['first_name', 'last_name', 'email', 'mobile',]
self.Meta.exclude = ['id']
Upvotes: 3
Reputation: 86
Extending above answer to a more generic one
class QuestionSerializer(serializers.Serializer):
def __init__(self, *args, **kwargs):
fields = kwargs.pop('fields', None)
super(QuestionSerializer, self).__init__(*args, **kwargs)
if fields is not None:
allowed = set(fields.split(','))
existing = set(self.fields)
for field_name in existing - allowed:
self.fields.pop(field_name)
class QuestionWithoutTopicView(generics.RetrieveAPIView):
def get_serializer(self, *args, **kwargs):
kwargs['context'] = self.get_serializer_context()
fields = self.request.GET.get('display')
serializer_class = self.get_serializer_class()
return serializer_class(fields=fields,*args, **kwargs)
def get_serializer_class(self):
return QuestionSerializer
Now we can give a query parameter called display to output any custom display format http://localhost:8000/questions?display=param1,param2
Upvotes: 2
Reputation: 590
Creating a new serializer is the way to go. By conditionally removing fields in a serializer you are adding extra complexity and making you code harder to quickly diagnose. You should try to avoid mixing the responsibilities of a single class.
Following basic object oriented design principles is the way to go.
QuestionWithTopicView is a QuestionWithoutTopicView but with an additional field.
class QuestionSerializer(serializers.Serializer):
id = serializers.CharField()
question_text = QuestionTextSerializer()
topic = TopicSerializer()
class TopicQuestionSerializer(QuestionSerializer):
topic = TopicSerializer()
Upvotes: 24
Reputation: 2910
Have you tried this technique
class QuestionSerializer(serializers.Serializer):
def __init__(self, *args, **kwargs):
remove_fields = kwargs.pop('remove_fields', None)
super(QuestionSerializer, self).__init__(*args, **kwargs)
if remove_fields:
# for multiple fields in a list
for field_name in remove_fields:
self.fields.pop(field_name)
class QuestionWithoutTopicView(generics.RetrieveAPIView):
serializer_class = QuestionSerializer(remove_fields=['field_to_remove1' 'field_to_remove2'])
If not, once try it.
Upvotes: 70