Opened 2 days ago

Last modified 22 hours ago

#36235 assigned Bug

RelatedManager.all().get_or_create() does not work

Reported by: Nick Pope Owned by: JaeHyuckSa
Component: Database layer (models, ORM) Version: dev
Severity: Normal Keywords: get_or_create, related, manager
Cc: Triage Stage: Accepted
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description (last modified by Nick Pope)

When accessing the queryset for a related manager, .get_or_create() and .update_or_create() lose context of the related instance.

Calling on the related manager works as expected:

publisher.books.get_or_create(name="The Very Hungry Caterpillar")

This is the case that was fixed by #3121 and #23611.

But calling on the queryset causes an IntegrityError to be raised:

publisher.books.all().get_or_create(name="The Very Hungry Caterpillar")

This can be a subtle failure that is hard to understand, especially if publisher.books.all() is assigned to a variable earlier.

The challenge here is that the overridden methods on the manager set kwargs[self.field.name] = self.instance.

We'd need to be able to pass this information down to the queryset. It looks like this might already be available in _known_related_objects which is set by RelatedManager._apply_rel_filters(), so that would be a good starting point for investigation.

We would also need to check whether something needs to be done to select the correct database as RelatedManager.get_or_create() has handling for this:

            db = router.db_for_write(self.model, instance=self.instance)

If this is something we can't fix reliably, then we should update the admonition under .get_or_create() in the docs and probably update .update_or_create() to make this and other issues related to use through RelatedManager more clear.

Change History (6)

comment:1 by Nick Pope, 2 days ago

The following test can be added to GetOrCreateTests in tests/get_or_create/tests.py below test_get_or_create_on_related_manager:

    def test_get_or_create_on_related_queryset(self):
        p = Publisher.objects.create(name="Acme Publishing")
        # Create a book through the publisher.
        book, created = p.books.all().get_or_create(name="The Book of Ed & Fred")
        self.assertTrue(created)
        # The publisher should have one book.
        self.assertEqual(p.books.count(), 1)

comment:2 by Nick Pope, 2 days ago

Description: modified (diff)

comment:3 by Sarah Boyce, 2 days ago

Triage Stage: UnreviewedAccepted

Thank you for the report and test!

comment:4 by JaeHyuckSa, 28 hours ago

Has patch: set
Owner: set to JaeHyuckSa
Status: newassigned

comment:5 by JaeHyuckSa, 28 hours ago

Has patch: unset

comment:6 by JaeHyuckSa, 22 hours ago

Has patch: set
Note: See TracTickets for help on using tickets.
Back to Top