Opened 3 months ago

Last modified 3 months ago

#31539 assigned New feature

Add support for bulk operations on reverse many-to-one manager

Reported by: Baptiste Mispelon Owned by: Baptiste Mispelon
Component: Database layer (models, ORM) Version: master
Severity: Normal Keywords:
Cc: Triage Stage: Accepted
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

Using the Book and Author models from the docs:

class Author(models.Model):
    name = models.CharField(max_length=100)

class Book(models.Model):
    author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name='books')
    title = models.CharField(max_length=200)

This works and creates two books linked to their author correctly:

toni = Author.objects.create(name="Toni Morrison")

toni.books.create(title="The Bluest Eye")
toni.books.create(title="Beloved")

But if you try to use bulk_create to achieve the same, you get an error:

toni = Author.objects.create(name="Toni Morrison")

toni.books.bulk_create([
    Book(title="The Bluest Eye"),
    Book(title="Beloved"),
])

This fails with an IntegrityError because bulk_create doesn't automatically fill in the value of Book.author (the way it does it for create()).

The documentation for RelatedManager [1] doesn't mention bulk_create() but it's not clear to me if that means the operation is unsupported or not (other methods like count() or filter() are not listed either but are clearly supported). Because of this I wasn't sure whether to mark this ticket as a bug or as a new feature.

Looking at the code [2], I can't find an explanation of why bulk_create or bulk_update are not implemented either.
I've come up with the following implementation which seems to work (I haven't tested it extensively):

def bulk_create(self, objs, **kwargs):
    def set_field_to_instance(instance):
        setattr(instance, self.field.name, self.instance)
        return instance

    objs = map(set_field_to_instance, objs)
    db = router.db_for_write(self.model, instance=self.instance)
    return super(RelatedManager, self.db_manager(db)).bulk_create(objs, **kwargs)

[1] https://docs.djangoproject.com/en/dev/ref/models/relations/#django.db.models.fields.related.RelatedManager
[2] https://github.com/django/django/blob/aff7a58aef0264e5b2740e5df07894ecc0d7a580/django/db/models/fields/related_descriptors.py#L559

Change History (2)

comment:1 Changed 3 months ago by Baptiste Mispelon

Owner: changed from nobody to Baptiste Mispelon
Status: newassigned

I'll try to come up with a PR. If you don't see any activity from me within a week you can assume I've given up and anyone can feel free to reassign the ticket to themselves :)

comment:2 Changed 3 months ago by Simon Charette

Triage Stage: UnreviewedAccepted

I think #27408 should be closed in favour of this ticket as we should only do this automatically for reverse many-to-one managers of saved objects and not systematically for all reverse relationship a model has. I think the scope of this ticket is better defined and more in line with the rest of the Manager interface.

Last edited 3 months ago by Simon Charette (previous) (diff)
Note: See TracTickets for help on using tickets.
Back to Top