summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon Charette <charette.s@gmail.com>2019-02-15 01:00:06 -0500
committerTim Graham <timograham@gmail.com>2019-02-21 10:20:47 -0500
commitdd32f9a3a21272e784d434a6f9ca9f07aeedb50a (patch)
tree1db8d91bf3dbbaf61a6f45db6458f3a1df1161f9
parent0ac4e51b2c5056693e72fb9892f0c3dcf2943c25 (diff)
Refs #19544 -- Extracted ManyRelatedManager.add() missing ids logic to a method.
-rw-r--r--django/db/models/fields/related_descriptors.py79
1 files changed, 43 insertions, 36 deletions
diff --git a/django/db/models/fields/related_descriptors.py b/django/db/models/fields/related_descriptors.py
index 02b5bcb62e..96b8a37583 100644
--- a/django/db/models/fields/related_descriptors.py
+++ b/django/db/models/fields/related_descriptors.py
@@ -1051,6 +1051,44 @@ def create_forward_many_to_many_manager(superclass, rel, reverse):
return obj, created
update_or_create.alters_data = True
+ def _get_missing_target_ids(self, source_field_name, target_field_name, db, objs):
+ """
+ Return the subset of ids of `objs` that aren't already assigned to
+ this relationship.
+ """
+ from django.db.models import Model
+ target_ids = set()
+ target_field = self.through._meta.get_field(target_field_name)
+ for obj in objs:
+ if isinstance(obj, self.model):
+ if not router.allow_relation(obj, self.instance):
+ raise ValueError(
+ 'Cannot add "%r": instance is on database "%s", '
+ 'value is on database "%s"' %
+ (obj, self.instance._state.db, obj._state.db)
+ )
+ target_id = target_field.get_foreign_related_value(obj)[0]
+ if target_id is None:
+ raise ValueError(
+ 'Cannot add "%r": the value for field "%s" is None' %
+ (obj, target_field_name)
+ )
+ target_ids.add(target_id)
+ elif isinstance(obj, Model):
+ raise TypeError(
+ "'%s' instance expected, got %r" %
+ (self.model._meta.object_name, obj)
+ )
+ else:
+ target_ids.add(obj)
+ vals = self.through._default_manager.using(db).values_list(
+ target_field_name, flat=True
+ ).filter(**{
+ source_field_name: self.related_val[0],
+ '%s__in' % target_field_name: target_ids,
+ })
+ return target_ids.difference(vals)
+
def _add_items(self, source_field_name, target_field_name, *objs, through_defaults=None):
# source_field_name: the PK fieldname in join table for the source object
# target_field_name: the PK fieldname in join table for the target object
@@ -1058,40 +1096,9 @@ def create_forward_many_to_many_manager(superclass, rel, reverse):
through_defaults = through_defaults or {}
# If there aren't any objects, there is nothing to do.
- from django.db.models import Model
if objs:
- new_ids = set()
- for obj in objs:
- if isinstance(obj, self.model):
- if not router.allow_relation(obj, self.instance):
- raise ValueError(
- 'Cannot add "%r": instance is on database "%s", value is on database "%s"' %
- (obj, self.instance._state.db, obj._state.db)
- )
- fk_val = self.through._meta.get_field(
- target_field_name).get_foreign_related_value(obj)[0]
- if fk_val is None:
- raise ValueError(
- 'Cannot add "%r": the value for field "%s" is None' %
- (obj, target_field_name)
- )
- new_ids.add(fk_val)
- elif isinstance(obj, Model):
- raise TypeError(
- "'%s' instance expected, got %r" %
- (self.model._meta.object_name, obj)
- )
- else:
- new_ids.add(obj)
-
db = router.db_for_write(self.through, instance=self.instance)
- vals = (self.through._default_manager.using(db)
- .values_list(target_field_name, flat=True)
- .filter(**{
- source_field_name: self.related_val[0],
- '%s__in' % target_field_name: new_ids,
- }))
- new_ids.difference_update(vals)
+ missing_target_ids = self._get_missing_target_ids(source_field_name, target_field_name, db, objs)
with transaction.atomic(using=db, savepoint=False):
if self.reverse or source_field_name == self.source_field_name:
@@ -1100,16 +1107,16 @@ def create_forward_many_to_many_manager(superclass, rel, reverse):
signals.m2m_changed.send(
sender=self.through, action='pre_add',
instance=self.instance, reverse=self.reverse,
- model=self.model, pk_set=new_ids, using=db,
+ model=self.model, pk_set=missing_target_ids, using=db,
)
# Add the ones that aren't there already
self.through._default_manager.using(db).bulk_create([
self.through(**through_defaults, **{
'%s_id' % source_field_name: self.related_val[0],
- '%s_id' % target_field_name: obj_id,
+ '%s_id' % target_field_name: target_id,
})
- for obj_id in new_ids
+ for target_id in missing_target_ids
])
if self.reverse or source_field_name == self.source_field_name:
@@ -1118,7 +1125,7 @@ def create_forward_many_to_many_manager(superclass, rel, reverse):
signals.m2m_changed.send(
sender=self.through, action='post_add',
instance=self.instance, reverse=self.reverse,
- model=self.model, pk_set=new_ids, using=db,
+ model=self.model, pk_set=missing_target_ids, using=db,
)
def _remove_items(self, source_field_name, target_field_name, *objs):