Skip to content

Save reverse OneToOne relations, possible fix for issue #5996 #7547

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed

Conversation

maerteijn
Copy link
Contributor

@maerteijn maerteijn commented Sep 23, 2020

This PR is an idea of how #5996 could be solved. It saves reverse relations in the create and update methods of ModelSerializer, as the serialized data with reverse OneToOne relations can give you the impression that the data was saved in the database while in fact it isn't.

Example models and serializers (als included in the tests):

models:

class OneToOneTarget(RESTFrameworkModel):
    name = models.CharField(max_length=100)

class NullableOneToOneSource(RESTFrameworkModel):
    name = models.CharField(max_length=100)
    target = models.OneToOneField(
        OneToOneTarget, null=True, blank=True,
        related_name='nullable_source', on_delete=models.CASCADE)

serializers:

class OneToOneTargetSerializer(serializers.ModelSerializer):
    nullable_source = serializers.SlugRelatedField(
        slug_field='name',
        queryset=NullableOneToOneSource.objects.all(),
        allow_null=True
    )

    class Meta:
        model = OneToOneTarget
        fields = '__all__'


class NullableOneToOneSourceSerializer(serializers.ModelSerializer):
    target = serializers.SlugRelatedField(
        slug_field='name',
        queryset=OneToOneTarget.objects.all(),
        allow_null=True
    )

    class Meta:
        model = NullableOneToOneSource
        fields = '__all__'

Now if you do something like:

data = {'id': 2, 'name': 'target-2', 'nullable_source': 'source-1'}

serializer = OneToOneTargetSerializer(data=data)
serializer.save()

# the serializer says it updated the nullable_source field
assert serializer.data == data
assert serializer.data['nullable_source'] == 'source-1'

# and it says the instance is a NullableOneToOneSource
assert isinstance(
    serializer.instance.nullable_source, NullableOneToOneSource)

# make sure it was really updated in the database
instance = OneToOneTarget.objects.get(pk=2)
instance.refresh_from_db()
assert instance.nullable_source
*** tests.models.OneToOneTarget.nullable_source.RelatedObjectDoesNotExist: OneToOneTarget has no nullable_source.

When you would do this with plain Django models:

target = OneToOneTarget(name="target=2", nullable_source=NullableOneToOneSource.objects.get(name="source-1"))
target.save()
target.nullable_source
<NullableOneToOneSource: NullableOneToOneSource object (1)>

target.refresh_from_db()
target.nullable_source
*** tests.models.OneToOneTarget.nullable_source.RelatedObjectDoesNotExist: OneToOneTarget has no nullable_source.

The reverse relation for the OneToOne field is not saved either. So this is default behaviour of Django, and debatable if this should be solved by DRF or not (ManyToMany relations get special treatment too, so OneToOne should be supported as well ?)

If this would not be something DRF should solve then the documentation could be extended to explain why this works as it works now and suggest a solution like overriding the save method of the specific model (or connect to the post_save signal) to save the reverse relation.

@maerteijn
Copy link
Contributor Author

maerteijn commented Sep 23, 2020

And Django<=2.2 behave differently on this issue then Django>3 as seen in the testresults now, so I guess this is not the correct direction or needs more work.
Any feedback regarding this issue would be very welcome.

@maerteijn maerteijn closed this Sep 28, 2020
@maerteijn maerteijn deleted the 5996-save-reverse-relations branch September 28, 2020 10:04
@calummackervoy
Copy link

@maerteijn do you know why Django 2.2 behaves differently on this issue than Django>3? I'm looking to use your fix from this issue on another library (djangoldp), currently using Django 2.2, but when we upgrade to Django 3 we will need to change the behaviour

@maerteijn
Copy link
Contributor Author

Hi @calummackervoy , ehm not anymore, it was 1,5 years ago 🙂I wouldn’t patch restframework for this but explicitly save the reverse relation in a post_save signal. Then you don’t need any patching and you are then compatible with django 2, 3 and 4.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants