From f7290581fe2106c08d97215ab93e27cf6b27e100 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 20 Aug 2013 14:13:43 -0400 Subject: [PATCH] Fixed a regression with get_or_create and virtual fields. refs #20429 Thanks Simon Charette for the report and review. --- django/db/models/query.py | 20 ++++++++------------ tests/generic_relations/tests.py | 23 +++++++++++++++++++++++ 2 files changed, 31 insertions(+), 12 deletions(-) diff --git a/django/db/models/query.py b/django/db/models/query.py index 836d394e9b6..67780a49915 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -411,7 +411,7 @@ class QuerySet(object): Returns a tuple of (object, created), where created is a boolean specifying whether an object was created. """ - lookup, params, _ = self._extract_model_params(defaults, **kwargs) + lookup, params = self._extract_model_params(defaults, **kwargs) self._for_write = True try: return self.get(**lookup), False @@ -425,7 +425,8 @@ class QuerySet(object): Returns a tuple (object, created), where created is a boolean specifying whether an object was created. """ - lookup, params, filtered_defaults = self._extract_model_params(defaults, **kwargs) + defaults = defaults or {} + lookup, params = self._extract_model_params(defaults, **kwargs) self._for_write = True try: obj = self.get(**lookup) @@ -433,12 +434,12 @@ class QuerySet(object): obj, created = self._create_object_from_params(lookup, params) if created: return obj, created - for k, v in six.iteritems(filtered_defaults): + for k, v in six.iteritems(defaults): setattr(obj, k, v) sid = transaction.savepoint(using=self.db) try: - obj.save(update_fields=filtered_defaults.keys(), using=self.db) + obj.save(using=self.db) transaction.savepoint_commit(sid, using=self.db) return obj, False except DatabaseError: @@ -469,22 +470,17 @@ class QuerySet(object): def _extract_model_params(self, defaults, **kwargs): """ Prepares `lookup` (kwargs that are valid model attributes), `params` - (for creating a model instance) and `filtered_defaults` (defaults - that are valid model attributes) based on given kwargs; for use by + (for creating a model instance) based on given kwargs; for use by get_or_create and update_or_create. """ defaults = defaults or {} - filtered_defaults = {} lookup = kwargs.copy() for f in self.model._meta.fields: - # Filter out fields that don't belongs to the model. if f.attname in lookup: lookup[f.name] = lookup.pop(f.attname) - if f.attname in defaults: - filtered_defaults[f.name] = defaults.pop(f.attname) params = dict((k, v) for k, v in kwargs.items() if LOOKUP_SEP not in k) - params.update(filtered_defaults) - return lookup, params, filtered_defaults + params.update(defaults) + return lookup, params def _earliest_or_latest(self, field_name=None, direction="-"): """ diff --git a/tests/generic_relations/tests.py b/tests/generic_relations/tests.py index 2b52ebac567..253eb76e328 100644 --- a/tests/generic_relations/tests.py +++ b/tests/generic_relations/tests.py @@ -263,6 +263,29 @@ class GenericRelationsTests(TestCase): formset = GenericFormSet(initial=initial_data) self.assertEqual(formset.forms[0].initial, initial_data[0]) + def test_get_or_create(self): + # get_or_create should work with virtual fields (content_object) + quartz = Mineral.objects.create(name="Quartz", hardness=7) + tag, created = TaggedItem.objects.get_or_create(tag="shiny", + defaults={'content_object': quartz}) + self.assertTrue(created) + self.assertEqual(tag.tag, "shiny") + self.assertEqual(tag.content_object.id, quartz.id) + + def test_update_or_create_defaults(self): + # update_or_create should work with virtual fields (content_object) + quartz = Mineral.objects.create(name="Quartz", hardness=7) + diamond = Mineral.objects.create(name="Diamond", hardness=7) + tag, created = TaggedItem.objects.update_or_create(tag="shiny", + defaults={'content_object': quartz}) + self.assertTrue(created) + self.assertEqual(tag.content_object.id, quartz.id) + + tag, created = TaggedItem.objects.update_or_create(tag="shiny", + defaults={'content_object': diamond}) + self.assertFalse(created) + self.assertEqual(tag.content_object.id, diamond.id) + class CustomWidget(forms.TextInput): pass