Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Fixed a regression with get_or_create and virtual fields.

refs #20429

Thanks Simon Charette for the report and review.
  • Loading branch information...
commit 3f416f637918cc162877be95a59d50825b203089 1 parent c7364a1
Tim Graham timgraham authored andrewgodwin committed
Showing with 31 additions and 12 deletions.
  1. +8 −12 django/db/models/query.py
  2. +23 −0 tests/generic_relations/tests.py
20 django/db/models/query.py
View
@@ -411,7 +411,7 @@ def get_or_create(self, defaults=None, **kwargs):
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 @@ def update_or_create(self, defaults=None, **kwargs):
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 @@ def update_or_create(self, defaults=None, **kwargs):
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 @@ def _create_object_from_params(self, lookup, params):
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="-"):
"""
23 tests/generic_relations/tests.py
View
@@ -263,6 +263,29 @@ def test_generic_inline_formsets_initial(self):
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
Please sign in to comment.
Something went wrong with that request. Please try again.