jozxyqk
jozxyqk

Reputation: 17304

Is there any way around saving models that reference each other twice?

My issue is when saving new models that need to reference each other, not just using a related_name lookup, such as this:

class Many:
    owner = models.ForeignKey('One')

class One:
    current = models.OneToOneField('Many')

By default, these have null=False and, please correct me if I'm wrong, using these are impossible until I change one of the relationships:

    current = models.OneToOneField('Many', null=True)

The reason is because you can't assign a model to a relationship unless its already saved. Otherwise resulting in ValueError: 'Cannot assign "<...>": "..." instance isn't saved in the database.'.

But now when I create a pair of these objects I need to save twice:

many = Many()
one = One()
one.save()
many.owner = one
many.save()
one.current = many
one.save()

Is this the right way to do it, or is there another way around saving twice?

Upvotes: 4

Views: 1826

Answers (1)

spectras
spectras

Reputation: 13552

There is no way around it, you need to save one of the objects twice anyway.

This is because, at the database level, you need to save an object to get its ID. There is no way to tell a sql database "save those 2 objects and assign the ids to those fields on the other object". So if you were to do it manually, you would INSERT the first object with NULL for the FK, get its ID back, INSERT the second object with the ID of the first one, get its ID back, then UPDATE the first object to set the FK. You would encapsulate the whole thing in a transaction.

So what you're doing with the ORM is the closest you can get. You may want to add the following on top of that:

1) Use a transaction for the changes, like this:

from django.db import transaction

with transaction.atomic():
    many, one = Many(), One()
    one.save()
    many.owner = one
    many.save()
    one.current = many
    one.save(update_fields=['current']) # slight optimization here

2) Now this is encapsulated in a transaction, you would want to remove the null=True. But you cannot, as those are, unfortunately, checked immediately. [edit: it appears Oracle might support deferring the NOT NULL check, so if you're using Oracle you can try dropping the null=True and it should work.]

You'll probably want to check how your code reacts if at a later point, when reading the db, if for some reason (manual editing, bugged insert somewhere, ...) one.current.owner != one.

Upvotes: 5

Related Questions