|
15 | 15 | from .settings import settings |
16 | 16 |
|
17 | 17 | # typing imports |
18 | | -from typing import Any, Dict, Iterable, List, Set, Type, cast |
| 18 | +from typing import Iterable, Type, cast |
19 | 19 | from django.db.models import Model |
20 | 20 |
|
21 | 21 |
|
@@ -105,116 +105,58 @@ def postdelete_handler(sender: Type[Model], instance: Model, **kwargs) -> None: |
105 | 105 | querysize=settings.COMPUTEDFIELDS_QUERYSIZE |
106 | 106 | ) |
107 | 107 |
|
108 | | - |
109 | | -def merge_pk_maps( |
110 | | - obj1: Dict[Type[Model], List[Any]], |
111 | | - obj2: Dict[Type[Model], List[Any]] |
112 | | -) -> Dict[Type[Model], List[Any]]: |
113 | | - """ |
114 | | - Merge pk map in `obj2` on `obj1`. |
115 | | - Updates obj1 inplace and also returns it. |
116 | | - """ |
117 | | - for model, data in obj2.items(): |
118 | | - m2_pks, m2_fields = data |
119 | | - m1_pks, m1_fields = obj1.setdefault(model, [set(), set()]) |
120 | | - m1_pks.update(m2_pks) |
121 | | - m1_fields.update(m2_fields) |
122 | | - return obj1 |
123 | | - |
124 | | - |
125 | | -def merge_qs_maps( |
126 | | - obj1: Dict[Type[Model], List[Any]], |
127 | | - obj2: Dict[Type[Model], List[Any]] |
128 | | -) -> Dict[Type[Model], List[Any]]: |
129 | | - """ |
130 | | - Merge queryset map in `obj2` on `obj1`. |
131 | | - Updates obj1 inplace and also returns it. |
132 | | - """ |
133 | | - for model, [qs2, fields2] in obj2.items(): |
134 | | - query_field = obj1.setdefault(model, [model._base_manager.none(), set()]) |
135 | | - query_field[0] = query_field[0].union(qs2) # or'ed querysets |
136 | | - query_field[1].update(fields2) # add fields |
137 | | - return obj1 |
138 | | - |
139 | 108 | # M2M tests: test_full.tests.test05_m2m test_full.tests.test06_m2mback test_full.tests.test_43.TestBetterM2M test_full.tests.test_m2m_advanced test_full.tests.test_norelated.TestNoReverse test_full.tests.test_proxymodels.TestProxyModelsM2M |
140 | 109 | def m2m_handler(sender: Type[Model], instance: Model, **kwargs) -> None: |
141 | 110 | """ |
142 | 111 | ``m2m_change`` handler. |
143 | 112 |
|
144 | 113 | Works like the other handlers but on the corresponding |
145 | 114 | m2m actions. |
146 | | -
|
147 | | - .. NOTE:: |
148 | | - The handler triggers updates for both ends of the m2m relation, |
149 | | - which might lead to massive database interaction. |
150 | 115 | """ |
151 | 116 | fields = active_resolver._m2m.get(sender) |
152 | 117 | # exit early if we have no update rule on the through model |
153 | 118 | if not fields: |
154 | 119 | return |
155 | 120 |
|
156 | | - # since the graph does not handle the m2m through model |
157 | | - # we have to trigger updates for both ends (left and right side) |
158 | 121 | reverse = kwargs['reverse'] |
159 | 122 | left = fields['right'] if reverse else fields['left'] # fieldname on instance |
160 | 123 | right = fields['left'] if reverse else fields['right'] # fieldname on model |
161 | 124 | action = kwargs.get('action') |
162 | | - model = kwargs['model'] |
163 | 125 |
|
164 | 126 | if action == 'post_add': |
165 | | - pks_add: Set[Any] = kwargs['pk_set'] |
166 | | - data_add: Dict[Type[Model], List[Any]] = active_resolver._querysets_for_update( |
167 | | - type(instance), instance, update_fields={left}) |
168 | | - other_add: Dict[Type[Model], List[Any]] = active_resolver._querysets_for_update( |
169 | | - model, model._base_manager.filter(pk__in=pks_add), update_fields={right}, m2m=instance) |
170 | | - if other_add: |
171 | | - merge_qs_maps(data_add, other_add) |
172 | | - if data_add: |
173 | | - with transaction.atomic(): |
174 | | - for queryset, update_fields in data_add.values(): |
175 | | - active_resolver.bulk_updater( |
176 | | - queryset, |
177 | | - update_fields, |
178 | | - querysize=settings.COMPUTEDFIELDS_QUERYSIZE |
179 | | - ) |
| 127 | + active_resolver.update_dependent( |
| 128 | + sender.objects.filter(**{left: instance.pk, right+'__in': kwargs['pk_set']}), |
| 129 | + sender, |
| 130 | + update_local=False, |
| 131 | + querysize=settings.COMPUTEDFIELDS_QUERYSIZE |
| 132 | + ) |
180 | 133 |
|
181 | 134 | elif action == 'pre_remove': |
182 | | - pks_remove: Set[Any] = kwargs['pk_set'] |
183 | | - data_remove: Dict[Type[Model], List[Any]] = active_resolver._querysets_for_update( |
184 | | - type(instance), instance, update_fields={left}, pk_list=True) |
185 | | - other_remove: Dict[Type[Model], List[Any]] = active_resolver._querysets_for_update( |
186 | | - model, model._base_manager.filter(pk__in=pks_remove), update_fields={right}, pk_list=True, m2m=instance) |
187 | | - if other_remove: |
188 | | - merge_pk_maps(data_remove, other_remove) |
189 | | - if data_remove: |
190 | | - get_M2M_REMOVE()[instance] = data_remove |
| 135 | + get_M2M_REMOVE()[instance] = active_resolver.preupdate_dependent( |
| 136 | + sender.objects.filter(**{left: instance.pk, right+'__in': kwargs['pk_set']}) |
| 137 | + ) |
191 | 138 |
|
192 | 139 | elif action == 'post_remove': |
193 | | - updates_remove: Dict[Type[Model], List[Any]] = get_M2M_REMOVE().pop(instance, None) |
194 | | - if updates_remove: |
| 140 | + old = get_M2M_REMOVE().pop(instance, None) |
| 141 | + if old: |
195 | 142 | with transaction.atomic(): |
196 | | - for _model, [pks, update_fields] in updates_remove.items(): |
| 143 | + for _model, [pks, update_fields] in old.items(): |
197 | 144 | active_resolver.bulk_updater( |
198 | 145 | _model._base_manager.filter(pk__in=pks), |
199 | 146 | update_fields, |
200 | 147 | querysize=settings.COMPUTEDFIELDS_QUERYSIZE |
201 | 148 | ) |
202 | 149 |
|
203 | 150 | elif action == 'pre_clear': |
204 | | - data: Dict[Type[Model], List[Any]] = active_resolver._querysets_for_update( |
205 | | - type(instance), instance, update_fields={left}, pk_list=True) |
206 | | - other: Dict[Type[Model], List[Any]] = active_resolver._querysets_for_update( |
207 | | - model, getattr(instance, left).all(), update_fields={right}, pk_list=True, m2m=instance) |
208 | | - if other: |
209 | | - merge_pk_maps(data, other) |
210 | | - if data: |
211 | | - get_M2M_CLEAR()[instance] = data |
| 151 | + get_M2M_CLEAR()[instance] = active_resolver.preupdate_dependent( |
| 152 | + sender.objects.filter(**{left: instance.pk}) |
| 153 | + ) |
212 | 154 |
|
213 | 155 | elif action == 'post_clear': |
214 | | - updates_clear: Dict[Type[Model], List[Any]] = get_M2M_CLEAR().pop(instance, None) |
215 | | - if updates_clear: |
| 156 | + old = get_M2M_CLEAR().pop(instance, None) |
| 157 | + if old: |
216 | 158 | with transaction.atomic(): |
217 | | - for _model, [pks, update_fields] in updates_clear.items(): |
| 159 | + for _model, [pks, update_fields] in old.items(): |
218 | 160 | active_resolver.bulk_updater( |
219 | 161 | _model._base_manager.filter(pk__in=pks), |
220 | 162 | update_fields, |
|
0 commit comments