#27408: Make QuerySet.bulk_create() populate fields on related models -------------------------------------+------------------------------------- Reporter: Jarek Glowacki | Owner: nobody Type: New feature | Status: new Component: Database layer | Version: master (models, ORM) | Severity: Normal | Resolution: Keywords: bulk_create foreign | Triage Stage: key id | Someday/Maybe Has patch: 0 | Needs documentation: 0 Needs tests: 0 | Patch needs improvement: 0 Easy pickings: 0 | UI/UX: 0 -------------------------------------+------------------------------------- Changes (by Tim Graham):
* stage: Unreviewed => Someday/Maybe Old description: > 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.bulk_create([a]) # sets `a`'s id. > B.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.bulk_create(bulk_a) # sets `a`'s id. > fill_foreignkey_ids(bulk_b, ['a']) > B.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. New description: 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. -- Comment: 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()`? -- Ticket URL: <https://code.djangoproject.com/ticket/27408#comment:3> Django <https://code.djangoproject.com/> The Web framework for perfectionists with deadlines. -- You received this message because you are subscribed to the Google Groups "Django updates" group. To unsubscribe from this group and stop receiving emails from it, send an email to django-updates+unsubscr...@googlegroups.com. To post to this group, send email to django-updates@googlegroups.com. To view this discussion on the web visit https://groups.google.com/d/msgid/django-updates/065.d96568b1296af38a6090e86f9aede01b%40djangoproject.com. For more options, visit https://groups.google.com/d/optout.