Alter read_only+default behaviour by carltongibson · Pull Request #5886 · encode/django-rest-framework (original) (raw)

Since ≈forever read_only fields with a default have set that default in validated_data on create and update operations.

This has led to issues as we have tried to Fix default value handling for dotted sources #5375: uses such as source=a.b.c, default='x' where e.g. b may be null were returning None, rather than the default x.

#5375 resolved the serialisation issues with dotted sourcedefault will be used if any step in the chain is None — but revealed a latent issue for deserialisation when using read_only plus a default. This comes up in a couple of ways:

Both of these cases come up because the default means read_only fields end up in _writable_fields.

Whilst we've been doing it ≈forever, it's still a Que? here. How on earth is a read_only field writable?

This PR adjusts the _writable_fields check to always exclude read_only fields.


This is a backwards incompatible change.

The previous use-case was for e.g. setting the current user as the owner of a object on create.

This change will require an extra workaround — to explicitly add the read_only fields with a writable default to validated_data, probably by overriding save.

class MySerializer(serializers.ModelSerializer): user = serializers.PrimaryKeyRelatedField(read_only=True, default=CreateOnlyDefault(CurrentUserDefault())) ...

def save(self, **kwargs):
    """Include default for read_only `user` field"""
    kwargs["user"] = self.fields["user"].get_default()
    return super().save(**kwargs)

Possibly we could add some convenience to this? (I'm a bit loathe to add API here.)

Note that in the tutorial we demonstrate this kind of this at the view level:

def perform_create(self, serializer):
    serializer.save(owner=self.request.user)

My overall take here is that the previous use-case saves a line or two, and is a convenience, but it inhibits correctness with the dotted source issue, so I think we need to swallow the pill on this one and make the change.


TODO: