Given the following example model:
class M1(models.Model):
m2_set = models.ManyToManyField('M2')
It is already possible to associate one M1 with many M2s with a single DB query:
However it's more difficult to associate
many M1s with
many M2s, particularly if you want to skip associations that already exist.
# NOTE: Does NOT skip associations that already exist!
m1_and_m2_id_tuples = [(m1_id, m2_id), ...]
M1_M2 = M1.m2_set.through
M1_M2.objects.bulk_create([
M1_M2(m1_id=m1_id, m2_id=m2_id)
for (m1_id, m2_id) in
m1_and_m2_id_tuples
])
What if we could do something like the following instead:
bulk_associate(M1.m2_set, [(m1, m2), ...])
# --- OR ---
bulk_associate_ids(M1.m2_set, [(m1_id, m2_id), ...])
I propose to write and add a
bulk_associate() method to Django. I also propose to add a paired
bulk_disassociate() method.
1. Does this sound like a good idea in general?
In more detail, I propose adding the specific APIs, importable from django.db:
M1 = TypeVar('M1', bound=Model) # M1 extends Model
M2 = TypeVar('M2', bound=Model) # M2 extends Model
def bulk_associate(
M1_m2_set: ManyToManyDescriptor,
m1_m2_tuples: 'List[Tuple[M1, M2]]',
*, assert_no_collisions: bool=True) -> None:
"""
Creates many (M1, M2) associations with O(1) database queries.
If any requested associations already exist, then they will be left alone.
If you assert that none of the requested associations already exist,
you can pass assert_no_collisions=True to save 1 database query.
"""
pass
def bulk_associate_ids(
M1_m2_set: ManyToManyDescriptor,
m1_m2_id_tuples: 'List[Tuple[Any, Any]]',
*, assert_no_collisions: bool=True) -> None:
pass
If assert_no_collisions is False then (1 filter) query and (1 bulk_create) query will be performed.
If assert_no_collisions is True then only (1 bulk_create) will be performed.
def bulk_disassociate(
M1_m2_set: ManyToManyDescriptor,
m1_m2_tuples: 'List[Tuple[M1, M2]]') -> None:
"""
Deletes many (M1, M2) associations with O(1) database queries.
"""
pass
def bulk_disassociate_ids(
M1_m2_set: ManyToManyDescriptor,
m1_m2_id_tuples: 'List[Tuple[Any, Any]]') -> None:
pass
The database connection corresponding to the M1_M2 through-table will be used.
2. Any comments on the specific API or capabilities?
If this sounds good I'd be happy to open an item on Trac and submit a PR.