#34884: Half bug/half enhancement : inconsistent behavior of get_or_create()
regarding related attributes cache
-------------------------------------+-------------------------------------
     Reporter:  Laurent Lyaudet      |                    Owner:  nobody
         Type:  Uncategorized        |                   Status:  closed
    Component:  Database layer       |                  Version:  dev
  (models, ORM)                      |
     Severity:  Normal               |               Resolution:  invalid
     Keywords:  ORM get_or_create    |             Triage Stage:
                                     |  Unreviewed
    Has patch:  0                    |      Needs documentation:  0
  Needs tests:  0                    |  Patch needs improvement:  0
Easy pickings:  0                    |                    UI/UX:  0
-------------------------------------+-------------------------------------

Comment (by Tim Graham):

 Hi Laurent,

 To echo what Natalian said, please avoid comments like "in case some
 bikeshedding occurs that puts to trash coherency and efficiency in yet
 another open source software where some people against free software have
 influence". It adds nothing helpful. Assuming bad faith on the part of the
 Django community is no way to introduce yourself and get people to want to
 work with you. Thank you.

 As for the issue, I think it's a bit of a niche case. I mocked up a patch
 using your proposal. I'm not sure that the extra complexity is worthwhile,
 as well as the performance penalty of iterating through the `kwargs`
 (which is unnecessary when a foreign key isn't present). The patch may
 need consideration for other relations like `GenericForeignKey`. I'm not
 sure offhand. I wonder if `update_or_create()` has a similar
 inconsistency. I'm afraid that trying to make the ORM smarter about this
 might open a can of worms.

 Cheers!

 {{{ #!diff
 diff --git a/django/db/models/query.py b/django/db/models/query.py
 index 1125302933..818d31faac 100644
 --- a/django/db/models/query.py
 +++ b/django/db/models/query.py
 @@ -939,11 +939,20 @@ class QuerySet(AltersData):
          Return a tuple of (object, created), where created is a boolean
          specifying whether an object was created.
          """
 +        from django.db.models.fields.related import ForeignKey
          # The get() needs to be targeted at the write database in order
          # to avoid potential transaction consistency problems.
          self._for_write = True
          try:
 -            return self.get(**kwargs), False
 +            obj, created = self.get(**kwargs), False
 +            for key, value in kwargs.items():
 +                try:
 +                    if isinstance(obj._meta.get_field(key), ForeignKey):
 +                        # isinstance handles OneToOneField also.
 +                        setattr(obj, key, value)
 +                except exceptions.FieldDoesNotExist:
 +                    pass
 +            return obj, created
          except self.model.DoesNotExist:
              params = self._extract_model_params(defaults, **kwargs)
              # Try to create an object using passed params.
 @@ -953,6 +962,7 @@ class QuerySet(AltersData):
                      return self.create(**params), True
              except IntegrityError:
                  try:
 +                    # Another get() case that would need to be updated.
                      return self.get(**kwargs), False
                  except self.model.DoesNotExist:
                      pass
 diff --git a/tests/get_or_create/tests.py b/tests/get_or_create/tests.py
 index 0b56d6b1a2..4aac3ff580 100644
 --- a/tests/get_or_create/tests.py
 +++ b/tests/get_or_create/tests.py
 @@ -129,8 +129,19 @@ class GetOrCreateTests(TestCase):
          self.assertEqual(book.authors.count(), 2)

          # Try creating a book through an author.
 -        _, created = ed.books.get_or_create(name="Ed's Recipes",
 publisher=p)
 +        eds_recipes, created = ed.books.get_or_create(name="Ed's
 Recipes", publisher=p)
          self.assertTrue(created)
 +        # In the created=True case, the publisher is set on the new
 object.
 +        with self.assertNumQueries(0):
 +            self.assertEqual(eds_recipes.publisher.name, 'Acme
 Publishing')
 +
 +        # In the case of created=False, the publisher isn't set on the
 object.
 +        eds_recipes, created = ed.books.get_or_create(name="Ed's
 Recipes", publisher=p)
 +        self.assertFalse(created)
 +        # This assertion fails due to the query required to fetch the
 +        # publisher, demonstrating #34884.
 +        with self.assertNumQueries(0):
 +            self.assertEqual(eds_recipes.publisher.name, 'Acme
 Publishing')

          # Now Ed has two Books, Fred just one.
          self.assertEqual(ed.books.count(), 2)
 }}}

-- 
Ticket URL: <https://code.djangoproject.com/ticket/34884#comment:6>
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 [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-updates/0107018af33e75c0-a460775a-c7d3-49e9-8a8a-795349a1877e-000000%40eu-central-1.amazonses.com.

Reply via email to