Search code examples
pythondjangodjango-models

Django 5 update_or_create reverse one to one field


On Django 4.x

Code is working as expected

from django.db import models

class Project(models.Model):
    rough_data = models.OneToOneField(
        "Data",
        related_name="rough_project",
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
    )
    final_data = models.OneToOneField(
        "Data",
        related_name="final_project",
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
    )


class Data(models.Model):
    pass

data, created = Data.objects.update_or_create(
            rough_project=project, defaults=data
        )    

On Django 5.x:

ValueError: The following fields do not exist in this model: rough_project

I do not see any changes related to this in changelog

In Django 4.2 and below it worked as one line with update_or_create and in 5.0 it stopped working, so instead I need to do something like this:

data = getattr(project, "rough_data", None)
if data:
   # update fields here
else:
   # create Data object

Solution

  • In Django 4.x, it did not raise an exception but also did not save the relationship if the object was created — so it was actually not working as expected.

    It appears to work as expected only if project.save() is called afterwards.

    1You could fix it to proactively save the relationship instead, by subclassing QuerySet and overriding the create method, and then specifying Data objects to use your Manager:

    class CreateSaveReverseOneToOneFieldsQuerySet(models.QuerySet):
        def create(self, **kwargs):
            """
            Create a new object with the given kwargs, saving it to the database and returning the created object.
            Also save objects in reverse OneToOne fields — see https://stackoverflow.com/questions/78863608/django-5-update-or-create-reverse-one-to-one-field.
            """
            obj = self.model(**kwargs)
            self._for_write = True
            obj.save(force_insert=True, using=self.db)
    
            # Save reverse OneToOne fields
            reverse_one_to_one_fields = frozenset(kwargs).intersection(self.model._meta._reverse_one_to_one_field_names)
            for field_name in reverse_one_to_one_fields:
                getattr(obj, field_name).save()
    
            return obj
    
    
    class DataManager(models.manager.BaseManager.from_queryset(CreateSaveReverseOneToOneFieldsQuerySet)):
        pass
    
    
    class Data(models.Model):
        objects = DataManager()