summaryrefslogtreecommitdiff
path: root/django
diff options
context:
space:
mode:
authorTim Graham <timograham@gmail.com>2015-03-14 21:25:33 -0400
committerLoïc Bistuer <loic.bistuer@gmail.com>2015-07-28 09:28:25 +0700
commitadc0c4fbac98f9cb975e8fa8220323b2de638b46 (patch)
tree6d00b444423b09a764fa3eb7d5b9f278c6531c0e /django
parentc2e70f02653519db3a49cd48f5158ccad7434d25 (diff)
Fixed #18556 -- Allowed RelatedManager.add() to execute 1 query where possible.
Thanks Loic Bistuer for review.
Diffstat (limited to 'django')
-rw-r--r--django/contrib/contenttypes/fields.py42
-rw-r--r--django/db/models/fields/related.py43
2 files changed, 65 insertions, 20 deletions
diff --git a/django/contrib/contenttypes/fields.py b/django/contrib/contenttypes/fields.py
index 6548c39b4b..0980ed21d1 100644
--- a/django/contrib/contenttypes/fields.py
+++ b/django/contrib/contenttypes/fields.py
@@ -501,15 +501,38 @@ def create_generic_related_manager(superclass, rel):
False,
self.prefetch_cache_name)
- def add(self, *objs):
+ def add(self, *objs, **kwargs):
+ bulk = kwargs.pop('bulk', True)
db = router.db_for_write(self.model, instance=self.instance)
- with transaction.atomic(using=db, savepoint=False):
+
+ def check_and_update_obj(obj):
+ if not isinstance(obj, self.model):
+ raise TypeError("'%s' instance expected, got %r" % (
+ self.model._meta.object_name, obj
+ ))
+ setattr(obj, self.content_type_field_name, self.content_type)
+ setattr(obj, self.object_id_field_name, self.pk_val)
+
+ if bulk:
+ pks = []
for obj in objs:
- if not isinstance(obj, self.model):
- raise TypeError("'%s' instance expected" % self.model._meta.object_name)
- setattr(obj, self.content_type_field_name, self.content_type)
- setattr(obj, self.object_id_field_name, self.pk_val)
- obj.save()
+ if obj._state.adding or obj._state.db != db:
+ raise ValueError(
+ "%r instance isn't saved. Use bulk=False or save "
+ "the object first. but must be." % obj
+ )
+ check_and_update_obj(obj)
+ pks.append(obj.pk)
+
+ self.model._base_manager.using(db).filter(pk__in=pks).update(**{
+ self.content_type_field_name: self.content_type,
+ self.object_id_field_name: self.pk_val,
+ })
+ else:
+ with transaction.atomic(using=db, savepoint=False):
+ for obj in objs:
+ check_and_update_obj(obj)
+ obj.save()
add.alters_data = True
def remove(self, *objs, **kwargs):
@@ -542,13 +565,14 @@ def create_generic_related_manager(superclass, rel):
# could be affected by `manager.clear()`. Refs #19816.
objs = tuple(objs)
+ bulk = kwargs.pop('bulk', True)
clear = kwargs.pop('clear', False)
db = router.db_for_write(self.model, instance=self.instance)
with transaction.atomic(using=db, savepoint=False):
if clear:
self.clear()
- self.add(*objs)
+ self.add(*objs, bulk=bulk)
else:
old_objs = set(self.using(db).all())
new_objs = []
@@ -559,7 +583,7 @@ def create_generic_related_manager(superclass, rel):
new_objs.append(obj)
self.remove(*old_objs)
- self.add(*new_objs)
+ self.add(*new_objs, bulk=bulk)
set.alters_data = True
def create(self, **kwargs):
diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
index c10b2658b5..77b7bcb8d3 100644
--- a/django/db/models/fields/related.py
+++ b/django/db/models/fields/related.py
@@ -765,16 +765,36 @@ def create_foreign_related_manager(superclass, rel):
cache_name = self.field.related_query_name()
return queryset, rel_obj_attr, instance_attr, False, cache_name
- def add(self, *objs):
+ def add(self, *objs, **kwargs):
+ bulk = kwargs.pop('bulk', True)
objs = list(objs)
db = router.db_for_write(self.model, instance=self.instance)
- with transaction.atomic(using=db, savepoint=False):
+
+ def check_and_update_obj(obj):
+ if not isinstance(obj, self.model):
+ raise TypeError("'%s' instance expected, got %r" % (
+ self.model._meta.object_name, obj,
+ ))
+ setattr(obj, self.field.name, self.instance)
+
+ if bulk:
+ pks = []
for obj in objs:
- if not isinstance(obj, self.model):
- raise TypeError("'%s' instance expected, got %r" %
- (self.model._meta.object_name, obj))
- setattr(obj, self.field.name, self.instance)
- obj.save()
+ check_and_update_obj(obj)
+ if obj._state.adding or obj._state.db != db:
+ raise ValueError(
+ "%r instance isn't saved. Use bulk=False or save "
+ "the object first." % obj
+ )
+ pks.append(obj.pk)
+ self.model._base_manager.using(db).filter(pk__in=pks).update(**{
+ self.field.name: self.instance,
+ })
+ else:
+ with transaction.atomic(using=db, savepoint=False):
+ for obj in objs:
+ check_and_update_obj(obj)
+ obj.save()
add.alters_data = True
def create(self, **kwargs):
@@ -835,6 +855,7 @@ def create_foreign_related_manager(superclass, rel):
# could be affected by `manager.clear()`. Refs #19816.
objs = tuple(objs)
+ bulk = kwargs.pop('bulk', True)
clear = kwargs.pop('clear', False)
if self.field.null:
@@ -842,7 +863,7 @@ def create_foreign_related_manager(superclass, rel):
with transaction.atomic(using=db, savepoint=False):
if clear:
self.clear()
- self.add(*objs)
+ self.add(*objs, bulk=bulk)
else:
old_objs = set(self.using(db).all())
new_objs = []
@@ -852,10 +873,10 @@ def create_foreign_related_manager(superclass, rel):
else:
new_objs.append(obj)
- self.remove(*old_objs)
- self.add(*new_objs)
+ self.remove(*old_objs, bulk=bulk)
+ self.add(*new_objs, bulk=bulk)
else:
- self.add(*objs)
+ self.add(*objs, bulk=bulk)
set.alters_data = True
return RelatedManager