How to make a field in Serializer both readable and writeable - python

I am writing a Serializer for my model which will be accessed using GET, POST and PATCH endpoints. I have a property in my model which I am using as a source for a field. However, using source= in serializer field is making it ready-only.
If I remove the source="get_field1" from the
field1 = serializers.NullBooleanField(source="get_field1")
then I am able to update the data.
But I HAVE to use the source to get the right value of the field.
class MyModel(models.Model):
field1 = NullBooleanField(default=None)
#property
get_field1(self):
data = True
# ...some logic
return data
Now I have a serializer that I am using
class MyModelSerializer(serializers.ModelSerializer):
field1 = serializers.NullBooleanField(source="get_field1")
class Meta:
model = MyModel
fields = ('field1')
Now in my API endpoint, I do this
serializer = MyModelSerializer(my_model, data=request.data, partial=True)
if serializer.is_valid():
serializer.save() # <- throws error "can't set attribute"
Also, I would like to mention that the field in the serializer is referred by the property name and not by the field name.
Example: If I do
>> serializer.validated_data
>> 'OrderedDict(['get_field1'], True) # <- shouldn't this by field1 and not get_field1

Answer by #JPG is good, but I feel it's a hacky way.
I would override the to_representation method of the Serializer to achieve your purpose.
Here's what you can do
class MyModelSerializer(serializers.ModelSerializer):
field1 = serializers.NullBooleanField() # get rid of source
def to_representation(self, instance):
data = super(MyModel, self).to_representation(instance)
data.update({
'field1': instance.get_field1
})
return data
class Meta:
model = MyModel
fields = ('field1')
This way you are implicitly providing the source and your field becomes writeable. So every time you GET, POST, or PATCH you'll get the right value.

This can be done by overriding the __init__() method of the serializer. Apart from that, we've to pass some context data to the serializer to distinguish between the GET, POST and PATCH requests.
class MyModelSerializer(serializers.ModelSerializer):
field1 = serializers.NullBooleanField() # remove "source" argument from here
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.context['request'].method == 'GET':
self.fields['field1'] =serializers.NullBooleanField(source= "get_field1")
class Meta:
model = MyModel
fields = ('field1',)
and while serializing the data, don't forget to pass the request as context, as
serializer = MyModelSerializer(my_model, data=request.data, partial=True, context={"request": request})

Related

How to hide field from Json response

I would like to hide from public api simple Serializer field (created without any model). How I can solve this?
same_result = serializers.SerializerMethodField()
You can use write_only argument to achieve this goal :
class YourSerializer(serializers.Serializer):
same_result = serializers.SerializerMethodField(write_only=True)
you can override the to to_representation method
def to_representation(self, instance):
data = super().to_representation(instance)
data.pop('same_result')
return data
I think you could either use the permissions configuration to identify either a public or private user based on membership of a group, then you could switch in the View, something like:
class UserViewSet(viewsets.ViewSet):
def list(self, request):
queryset = User.objects.all()
if request.user.groups.filter(name="private").exists():
serializer = PrivateUserSerializer(queryset, many=True)
else:
serializer = PublicUserSerializer(queryset, many=True)
return Response(serializer.data)
Then you define the fields on your serializer as you wish.

Custom writable field in ModelSerializer

I'd like to add a custom field to a serializer that's used when creating resources. It's not a model field.
I tried the following:
class CampaignSerializer(ModelSerializer):
class Meta:
model = Campaign
fields = ("groups",)
write_only_fields = ("groups",)
groups = ListField(IntegerField(), min_length=1)
def validate(self, data):
# ...
return data
However groups doesn't exist in data in the validate() function. I found out that DRF sets read_only=True for the field, which is definitely not what I want.
Is there a way to specify a writable field, or do I have to resort to the view set's perform_create() method?
The following will work, but is likely not the intended way of doing it:
from rest_framework import serializers
class MySerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = ('foo', ) # also add your normal model fields
foo = serializers.SerializerMethodField()
def get_foo(self, obj):
return ... # do necessary reading stuff
def create(self, validated_data):
# use self.initial_data to access the raw input data sent in POST request
self.initial_data['foo']
... # do necessary validations of 'foo'
instance = super().create(validated_data)
... # do necessary write stuff
return instance
# Do likewise with .update method

lookup field should not required while update but required while create

I am using model viewset in django rest framework.
where lookup field is company
class UserViewSet(viewsets.ModelViewSet):
"""
A viewset for viewing and editing user instances.
"""
serializer_class = UserSerializer
queryset = User.objects.all()
lookup_field = 'company'
Company is required when create user.
POST method - /localhost/user/
but when i trying to update
PUT method - /localhost/user/1/
here 1 is company_id
it is raising error
{
"company": [
"This field is required."
]
}
serializers.py
class UserSerializer(DynamicFieldsModelSerializer):
class Meta:
model = User
fields = "__all__"
If i make company allow_null =True
then it will also not required for create metod.
How can i make company required for create.
and
not required for update
PUT method is update,it's need you upload all your model fields data,
PATCH method is partial_update,it's only need you upload partial model fields data.
View the source code you will see:
def partial_update(self, request, *args, **kwargs):
kwargs['partial'] = True
return self.update(request, *args, **kwargs)
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
instance = self.get_object()
self.before_update(instance)
serializer = self.get_serializer(instance, data=request.data, partial=partial)
....
partial=partial is what matters,or you need PATCH method - /localhost/user/1/

Django rest framework create-only serializer field

I'm have a Django model that serves as a request description. It is created to issue a request by a REST client, serves to record the tasks current status, and record historical requests received by clients.
This model has a few fields that are used to fine-tune and control the requested task (say, a target object and the type of action). Obviously, I'd like the client to control those fields on object creation but not afterwards (you can't change the object once the task started running).
I was hoping for something similar to serializers.ReadOnlyField, so I could have something similar to this:
class TaskSerializer(serializers.ModelSerializer):
owner = serializers.ReadOnlyField(source='owner.username')
task_id = serializers.ReadOnlyField()
target_object = serializers.CreateOnlyField()
but couldn't find it in the documentation or google.
Just to expand on Wim's answer, this is a way to select a different serialiser based on the incoming request method:
class RequestViewSet(viewsets.ModelViewSet):
serializer_class = RequestModelSerializer
model = Request
def get_serializer_class(self):
serializer_class = self.serializer_class
if self.request.method == 'POST':
serializer_class = SerializerWithoutCertainFields
return serializer_class
The answer of #fabio.sussetto put me on the right track. I think my answer is slightly prettier; I don't specify the serializer on the class directly but only in get_serializer_class(). Also, I do not switch it based on the HTTP type (i.e. POST) but rather on the action, update, which I think is more declarative.
class RequestViewSet(viewsets.ModelViewSet):
model = Request
def get_serializer_class(self):
if self.action == 'update':
return serializer_class = SerializerWithoutCertainFields
return RequestModelSerializer
This can be achieved with one serializer by using to_internal_value method
class TaskSerializer(serializers.ModelSerializer):
# Field settings here
def to_internal_value(self, data):
data = super().to_internal_value(data)
# Remove target_object if serializer is updating object
if self.instance:
data.pop('target_object', None)
return data
class Meta:
model = Task
fields = ('owner', 'task_id', 'target_object')
could also be done with a combination of required=False and dropping the field value when updating like in this example:
class SectionSerializer(serializers.ModelSerializer):
# do not require field lesson when updating
lesson = serializers.PrimaryKeyRelatedField(queryset=Lesson.objects.all(), required=False)
# do not allow changing the lesson field
def update(self, instance, validated_data):
validated_data.pop("lesson", None)
return super().update(instance, validated_data)

django rest framework add field when not in list view

I'm using the Django Rest Framework and I'd like to be able to add extra detail to the serializer when a single object is returned, which would be left out of the list view.
In the code below I add the celery_state field to the TestModelSerializer, but I'd only like this field to be added when its returning a single object, not when it's returning the list of TestModel data.
I've looked at the list_serializer_class option but it seems to just use the original model serializer so it will always still include the field even if I try to exclude from there.
What are my options?
class TestModelSerializer(serializers.HyperlinkedModelSerializer):
celery_state = serializers.CharField(source='celery_state', read_only=True)
class Meta:
model = TestModel
class TestModelViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows TestModels to be viewed or edited.
"""
authentication_classes = (SessionAuthentication, BasicAuthentication)
permission_classes = (IsAuthenticatedOrReadOnly,)
queryset = TestModel.objects.all()
serializer_class = TestModelSerializer
Since the serializer class (used by the viewsets) passes many argument, you can use that to control the fields output:
class TestModelSerializer(serializers.HyperlinkedModelSerializer):
# ...
def __init__(self, *args, **kwargs):
super(TestModelSerializer, self).__init__(*args, **kwargs)
if kwargs.get('many', False):
self.fields.pop('celery_state')
Inspired by #mariodev answer:
The other possibility is to override many_init static method in serializer. Acording comments in thie code (https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/serializers.py#L128 ) it is suggested variant.
from rest_framework import serializers
class ExtendedSerializer(serializers.Serializer):
...
#classmethod
def many_init(cls, *args, **kwargs):
kwargs['child'] = cls()
kwargs['child'].fields.pop('extractedFiled')
return serializers.ListSerializer(*args, **kwargs)
You can have an extra serializer called ExtendedTestModelSerializer which would contain the extra fields that you want.
After that, you can use the get_serializer_class method to decide which serializer is used based on request.action -
class TestModelViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows TestModels to be viewed or edited.
"""
authentication_classes = (SessionAuthentication, BasicAuthentication)
permission_classes = (IsAuthenticatedOrReadOnly,)
queryset = TestModel.objects.all()
# serializer_class = TestModelSerializer
get_serializer_class(self):
if self.request.action == 'list':
return TestModelSerializer
return ExtendedTestModelSerializer

Categories