django: assigning foreign key before target model is saved - django

Assume A and B are django Models, where A has a foreign key field to B:
a = A()
b = B()
a.my_b = b
b.save()
a.save()
Surprisingly a is saved with null in the my_b foreign key field. If you check a.my_b before or after the saves it does refer to the b instance, however it seems that django evaluates the foreign key id itself upon a.my_b = b assignment and not upon a.save(). In complex systems and flows it may be hard to change the order of commands, so simply saving b before assigning it to a could be impractical...
Adding the incredible statement a.my_b=a.my_b between the save commands works, but seems to me a horrid workaround.
Is there a way to configure django to re-evaluate foreign keys upon saving model instances?
Is there a different approach that might solve this issue?

There's a Django ticket that describes this issue, #8892.
In the meantime, there's no configuration setting that changes the behaviour. You'll have to change the order of your save and assignment operations, or do the extra assignment.

Related

Detect duplicate inserts when adding many-to-many relation

Let's assume there are two models, A and B:
class A(models.Model):
name = models.CharField(max_length=100)
class B(models.Model):
children = models.ManyToManyField(A)
I'm using b.children.add() method to add instance of A to b:
a = A.objects.get(pk=SOMETHING)
b.children.add(a)
As far as I know, Django by default doesn't allow duplicate many-to-many relationship. So I cannot add same instance of A more than once.
But the problem is here, I fetch instances of A with another query, then loop around them and add them one by one. How can I detect a duplicate relation? Does add() method return something useful?
A look at the source code reveals that Django first checks to see if there are any entries that already exist in the database, and then only adds the new ones. It doesn't return any information to the caller, though.
It's not clear if you actually need to detect duplicates, or if you just want to make sure that they're not being added to the database? If it's the latter then everything's fine. If it's the former, there's no way around hitting the database. If you're really concerned about performance you could always perform the check and update the through table yourself (i.e. re-implement add()).

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?
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.

How to modify a queryset and save it as new objects?

I need to query for a set of objects for a particular Model, change a single attribute/column ("account"), and then save the entire queryset's objects as new objects/rows. In other words, I want to duplicate the objects, with a single attribute ("account") changed on the duplicates. I'm basically creating a new account and then going through each model and copying a previous account's objects to the new account, so I'll be doing this repeatedly, with different models, probably using django shell. How should I approach this? Can it be done at the queryset level or do I need to loop through all the objects?
i.e.,
MyModel.objects.filter(account="acct_1")
# Now I need to set account = "acct_2" for the entire queryset,
# and save as new rows in the database
From the docs:
If the object’s primary key attribute is not set, or if it’s set but a
record doesn’t exist, Django executes an INSERT.
So if you set the id or pk to None it should work, but I've seen conflicting responses to this solution on SO: Duplicating model instances and their related objects in Django / Algorithm for recusrively duplicating an object
This solution should work (thanks #JoshSmeaton for the fix):
models = MyModel.objects.filter(account="acct_1")
for model in models:
model.id = None
model.account = "acct_2"
model.save()
I think in my case, I have a OneToOneField on the model that I'm testing on, so it makes sense that my test wouldn't work with this basic solution. But, I believe it should work, so long as you take care of OneToOneField's.

Circular dependency in Django ForeignKey?

I have two models in Django:
A:
b = ForeignKey("B")
B:
a = ForeignKey(A)
I want these ForeignKeys to be non-NULL.
However, I cannot create the objects because they don't have a PrimaryKey until I save(). But I cannot save without having the other objects PrimaryKey.
How can I create an A and B object that refer to each other?
I don't want to permit NULL if possible.
If this is really a bootstrapping problem and not something that will reoccur during normal usage, you could just create a fixture that will prepopulate your database with some initial data. The fixture-handling code includes workarounds at the database layer to resolve the forward-reference issue.
If it's not a bootstrapping problem, and you're going to want to regularly create these circular relations among new objects, you should probably either reconsider your schema--one of the foreign keys is probably unnecessary.
It sounds like you're talking about a one-to-one relationship, in which case it is unnecessary to store the foreign key on both tables. In fact, Django provides nice helpers in the ORM to reference the corresponding object.
Using Django's OneToOneField:
class A(models.Model):
<snip>
class B(models.Model):
a = OneToOneField(A)
Then you can simply reference them like so:
a = A()
a.save()
b = B(a=a)
b.save()
print a.b
print b.a
In addition, you may look into django-annoying's AutoOneToOneField, which will auto-create the associated object on save if it doesn't exist on the instance.
If your problem is not a one-to-one relationship, you should clarify because there is almost certainly a better way to model the data than mutual foreign keys. Otherwise, there is not a way to avoid setting a required field on save.

Django - Fetching foreign key

Model A has a ForeignKey to model B - I would like to fetch A instances and compare them to each other where the key to B is one of the comparison parameters.
Django defers fetching B related info, so if I want to optimize my code and fetch in advance the info I need I can do one of the following:
Use .select_related('B') - which will fetch all related B instances
Use .select_related('B__id') - which will fetch only the ids of all related B instances
AFAIK both require a join, where all I really needed was A.B_id which is a column in the database, as that is all I wanted to compare.
Am I missing something straightforward here?What am I missing here? Can I fetch A.B_id directly? Is
Firstly, your assertion is wrong: select_related('B__id') doesn't do anything. The double-underscore in a select_related call is only for following subsequent joins: so if B had a ForeignKey to C, select_related('B__C') would follow the second JOIN as well.
Secondly, I'm confused by your optimisation requirement. As you say, you just want B_id: so no JOIN is required, and neither is any optimisation. If you just get your A objects in the normal way, you can refer to the b_id field on each of them directly:
a_objects = A.objects.all()
for obj in a_objects:
print a.b_id
Here only a single db call is made, with no JOINs.