Opened 8 years ago
Last modified 5 years ago
#27408 closed New feature
Make QuerySet.bulk_create() populate fields on related models — at Version 3
Reported by: | Jarek Glowacki | Owned by: | nobody |
---|---|---|---|
Component: | Database layer (models, ORM) | Version: | dev |
Severity: | Normal | Keywords: | bulk_create foreign key id |
Cc: | Triage Stage: | Someday/Maybe | |
Has patch: | no | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | no | UI/UX: | no |
Description (last modified by )
As of dj110 bulk_create
returns pks for the objects it has created. Great! I want more.
Going to hop right into the use case:
class A(models.Model): pass class B(models.Model): a = models.ForeignKey(A, on_delete=models.PROTECT)
a = A() b = B(a=a) # doesn't set `a_id`, since `a` has no id assigned yet. A.objects.bulk_create([a]) # sets `a`'s id. B.objects.bulk_create([b]) # error!
The first bulk_create
call sets the id on a
, but b.a_id
remains None
.
When running the second bulk_create
, even though b
has an a
set, it fails to save a
onto the b
instance, due to a_id
being None.
So if you imagine the pregeneration of several related models in a big loop, followed by several consecutive bulk_create
calls we run into trouble. It's nicer than having to manually feed in ids during the loop as we needed to in the past, but it still needs some magic.
My current solution is running a little function between each bulk_create
call, to fill the foreignkey ids in:
def fill_foreignkey_ids(objects, fields): for f in fields: for o in objects: setattr(o, '%s_id' % f, getattr(o, f).id)
Which makes:
a = A() b = B(a=a) # doesn't set `a_id`, since `a` has no id assigned yet. bulk_a = [a] bulk_b = [b] A.objects.bulk_create(bulk_a) # sets `a`'s id. fill_foreignkey_ids(bulk_b, ['a']) B.objects.bulk_create(bulk_b) # now it works. Lovely.
But this is quite ugly.
This is more of a brainstorm ticket, as I'm not sure how to solve this nicely. Expecting every unsaved model instance to update its foreignkey ids when a bulk_create
inserts them sounds seems silly. But maybe we could have the bulk_create
method check whether b.a.id
is set when b.a_id
isn't?
I can submit a PR if this sounds like a reasonable thing to do. It feels a bit magic, but it would greatly improve the usefulness of this new feature.
Change History (3)
comment:1 by , 8 years ago
comment:2 by , 8 years ago
Yep, that fails too.
The difference is use case. For single save
s, it's easy to pass the id through. For bulk_create
s it's a bit messier to do that.
I suggested only updating bulk_create
(to automatically figure out what to do in that second call) as I wasn't sure it was feasible to make this work in the general case - but maybe by putting some more magic in the foreignkey field's __get__
could work?
comment:3 by , 8 years ago
Description: | modified (diff) |
---|---|
Summary: | Multiple consecutive `bulk_create`s with FK. → Make QuerySet.bulk_create() populate fields on related models |
Triage Stage: | Unreviewed → Someday/Maybe |
I'm not sure if this is a good idea due to magic/complexity, but if we had a patch, we could better evaluate it. I guess your use case doesn't allow constructing B
after A.objects.bulk_create()
?
I haven't tried it myself but I suspect this has little to do with
bulk_create
itself. Does the following fails in a similar manner?