Reputation: 9745
I want to implement a kind of constant field of a Django model. I want the field to be set on create a model instance (via REST framework API), but on updating this field must be forbidden to change. Is there an elegant way to do it in Django itself or in REST framework serializer options?
Upvotes: 18
Views: 4670
Reputation: 1236
I like to check self.instance
to determine if the serializer is in "create mode" or "update mode". You could also check self.partial
if you want to allow editing the field for non-partial updates.
class MySerializer(Serializer):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.instance:
self.fields['create_only_field'].read_only = True
Dynamically modifying the fields from __init__
seems to be officially endorsed by the docs.
Upvotes: 0
Reputation: 1437
Both above solutions work, but a nice way to create an extensible method for controlling the modify permissions of any CRUD operation is the following:
BaseSerializer
Control in the base serializer class:
class BaseSerializer(DynamicFieldsSerializer):
# This overrides a built-in base class method
def get_extra_kwargs(self):
"""Add additional constraints between CRUD methods to
any particular field
NB: Use the same extra_kwags dict object in all method calls
- important to make changes on central object
"""
extra_kwargs_for_edit = super().get_extra_kwargs()
# Example of making uuid only editable on create, not update
self.add_create_only_constraint_to_field("uuid", extra_kwargs_for_edit)
return extra_kwargs_for_edit
def add_create_only_constraint_to_field(self, key: str, extra_kwargs: dict) -> dict:
"""Ensures key is only writable on create, not update"""
action = self.context["view"].action
if action in ["create"]:
kwargs = extra_kwargs.get(key, {})
kwargs["read_only"] = False
extra_kwargs[key] = kwargs
elif action in ["update", "partial_update"]:
kwargs = extra_kwargs.get(key, {})
kwargs["read_only"] = True
extra_kwargs[key] = kwargs
# You could add other constraints to CRUD operations here
# def another_field_constrained_by_crud_method(self, key: str, extra_kwargs: dict) -> dict:
class SomeModelSerializer(BaseSerializer):
# Your serializer logic here
pass
Thanks to Nicholas Coles for the answer!
Upvotes: 2
Reputation: 2292
Overwrite the update method in the serializer and remove the field:
class MySerializer(serializers.ModelSerializer):
def update(self, instance, validated_data):
validated_data.pop('myfield', None) # prevent myfield from being updated
return super().update(instance, validated_data)
Upvotes: 14