django-bulk-hooks 0.2.16__py3-none-any.whl → 0.2.19__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of django-bulk-hooks might be problematic. Click here for more details.

@@ -6,15 +6,14 @@ a clean, simple API for the QuerySet to use.
6
6
  """
7
7
 
8
8
  import logging
9
+
10
+ from django.core.exceptions import FieldDoesNotExist
9
11
  from django.db import transaction
10
12
  from django.db.models import QuerySet
11
- from django.core.exceptions import FieldDoesNotExist
12
13
 
13
- from django_bulk_hooks.helpers import (
14
- build_changeset_for_create,
15
- build_changeset_for_update,
16
- build_changeset_for_delete,
17
- )
14
+ from django_bulk_hooks.helpers import build_changeset_for_create
15
+ from django_bulk_hooks.helpers import build_changeset_for_delete
16
+ from django_bulk_hooks.helpers import build_changeset_for_update
18
17
 
19
18
  logger = logging.getLogger(__name__)
20
19
 
@@ -196,7 +195,8 @@ class BulkOperationCoordinator:
196
195
  old_records_map = self.analyzer.fetch_old_records_map(objs)
197
196
 
198
197
  # Build changeset
199
- from django_bulk_hooks.changeset import ChangeSet, RecordChange
198
+ from django_bulk_hooks.changeset import ChangeSet
199
+ from django_bulk_hooks.changeset import RecordChange
200
200
 
201
201
  changes = [
202
202
  RecordChange(
@@ -222,7 +222,7 @@ class BulkOperationCoordinator:
222
222
 
223
223
  @transaction.atomic
224
224
  def update_queryset(
225
- self, update_kwargs, bypass_hooks=False, bypass_validation=False
225
+ self, update_kwargs, bypass_hooks=False, bypass_validation=False,
226
226
  ):
227
227
  """
228
228
  Execute queryset.update() with full hook support.
@@ -265,7 +265,7 @@ class BulkOperationCoordinator:
265
265
  or use bulk_update() directly (which has true before semantics).
266
266
  """
267
267
  from django_bulk_hooks.context import get_bypass_hooks
268
-
268
+
269
269
  # Fast path: no hooks at all
270
270
  if bypass_hooks or get_bypass_hooks():
271
271
  return QuerySet.update(self.queryset, **update_kwargs)
@@ -277,7 +277,7 @@ class BulkOperationCoordinator:
277
277
  )
278
278
 
279
279
  def _execute_queryset_update_with_hooks(
280
- self, update_kwargs, bypass_validation=False
280
+ self, update_kwargs, bypass_validation=False,
281
281
  ):
282
282
  """
283
283
  Execute queryset update with full hook lifecycle support.
@@ -297,22 +297,22 @@ class BulkOperationCoordinator:
297
297
  old_instances = list(self.queryset)
298
298
  if not old_instances:
299
299
  return 0
300
-
300
+
301
301
  old_records_map = {inst.pk: inst for inst in old_instances}
302
-
302
+
303
303
  # Step 2: Execute native Django update
304
304
  # Use stored reference to parent class method - clean and simple
305
305
  update_count = QuerySet.update(self.queryset, **update_kwargs)
306
-
306
+
307
307
  if update_count == 0:
308
308
  return 0
309
-
309
+
310
310
  # Step 3: Fetch new state (after database update)
311
311
  # This captures any Subquery/F() computed values
312
312
  # Use primary keys to fetch updated instances since queryset filters may no longer match
313
313
  pks = [inst.pk for inst in old_instances]
314
314
  new_instances = list(self.model_cls.objects.filter(pk__in=pks))
315
-
315
+
316
316
  # Step 4: Build changeset
317
317
  changeset = build_changeset_for_update(
318
318
  self.model_cls,
@@ -320,54 +320,54 @@ class BulkOperationCoordinator:
320
320
  update_kwargs,
321
321
  old_records_map=old_records_map,
322
322
  )
323
-
323
+
324
324
  # Mark as queryset update for potential hook inspection
325
- changeset.operation_meta['is_queryset_update'] = True
326
- changeset.operation_meta['allows_modifications'] = True
327
-
325
+ changeset.operation_meta["is_queryset_update"] = True
326
+ changeset.operation_meta["allows_modifications"] = True
327
+
328
328
  # Step 5: Get MTI inheritance chain
329
329
  models_in_chain = [self.model_cls]
330
330
  if self.mti_handler.is_mti_model():
331
331
  models_in_chain.extend(self.mti_handler.get_parent_models())
332
-
332
+
333
333
  # Step 6: Run VALIDATE hooks (if not bypassed)
334
334
  if not bypass_validation:
335
335
  for model_cls in models_in_chain:
336
336
  model_changeset = self._build_changeset_for_model(changeset, model_cls)
337
337
  self.dispatcher.dispatch(
338
- model_changeset,
339
- "validate_update",
340
- bypass_hooks=False
338
+ model_changeset,
339
+ "validate_update",
340
+ bypass_hooks=False,
341
341
  )
342
-
342
+
343
343
  # Step 7: Run BEFORE_UPDATE hooks with modification tracking
344
344
  modified_fields = self._run_before_update_hooks_with_tracking(
345
- new_instances,
346
- models_in_chain,
347
- changeset
345
+ new_instances,
346
+ models_in_chain,
347
+ changeset,
348
348
  )
349
-
349
+
350
350
  # Step 8: Auto-persist BEFORE_UPDATE modifications
351
351
  if modified_fields:
352
352
  self._persist_hook_modifications(new_instances, modified_fields)
353
-
353
+
354
354
  # Step 9: Take snapshot before AFTER_UPDATE hooks
355
355
  pre_after_hook_state = self._snapshot_instance_state(new_instances)
356
-
356
+
357
357
  # Step 10: Run AFTER_UPDATE hooks (read-only side effects)
358
358
  for model_cls in models_in_chain:
359
359
  model_changeset = self._build_changeset_for_model(changeset, model_cls)
360
360
  self.dispatcher.dispatch(
361
- model_changeset,
362
- "after_update",
363
- bypass_hooks=False
361
+ model_changeset,
362
+ "after_update",
363
+ bypass_hooks=False,
364
364
  )
365
-
365
+
366
366
  # Step 11: Auto-persist AFTER_UPDATE modifications (if any)
367
367
  after_modified_fields = self._detect_modifications(new_instances, pre_after_hook_state)
368
368
  if after_modified_fields:
369
369
  self._persist_hook_modifications(new_instances, after_modified_fields)
370
-
370
+
371
371
  return update_count
372
372
 
373
373
  def _run_before_update_hooks_with_tracking(self, instances, models_in_chain, changeset):
@@ -383,16 +383,16 @@ class BulkOperationCoordinator:
383
383
  """
384
384
  # Snapshot current state
385
385
  pre_hook_state = self._snapshot_instance_state(instances)
386
-
386
+
387
387
  # Run BEFORE_UPDATE hooks
388
388
  for model_cls in models_in_chain:
389
389
  model_changeset = self._build_changeset_for_model(changeset, model_cls)
390
390
  self.dispatcher.dispatch(
391
- model_changeset,
392
- "before_update",
393
- bypass_hooks=False
391
+ model_changeset,
392
+ "before_update",
393
+ bypass_hooks=False,
394
394
  )
395
-
395
+
396
396
  # Detect modifications
397
397
  return self._detect_modifications(instances, pre_hook_state)
398
398
 
@@ -407,26 +407,26 @@ class BulkOperationCoordinator:
407
407
  Dict mapping pk -> {field_name: value}
408
408
  """
409
409
  snapshot = {}
410
-
410
+
411
411
  for instance in instances:
412
412
  if instance.pk is None:
413
413
  continue
414
-
414
+
415
415
  field_values = {}
416
416
  for field in self.model_cls._meta.get_fields():
417
417
  # Skip relations that aren't concrete fields
418
418
  if field.many_to_many or field.one_to_many:
419
419
  continue
420
-
420
+
421
421
  field_name = field.name
422
422
  try:
423
423
  field_values[field_name] = getattr(instance, field_name)
424
424
  except (AttributeError, FieldDoesNotExist):
425
425
  # Field not accessible (e.g., deferred field)
426
426
  field_values[field_name] = None
427
-
427
+
428
428
  snapshot[instance.pk] = field_values
429
-
429
+
430
430
  return snapshot
431
431
 
432
432
  def _detect_modifications(self, instances, pre_hook_state):
@@ -441,23 +441,23 @@ class BulkOperationCoordinator:
441
441
  Set of field names that were modified
442
442
  """
443
443
  modified_fields = set()
444
-
444
+
445
445
  for instance in instances:
446
446
  if instance.pk not in pre_hook_state:
447
447
  continue
448
-
448
+
449
449
  old_values = pre_hook_state[instance.pk]
450
-
450
+
451
451
  for field_name, old_value in old_values.items():
452
452
  try:
453
453
  current_value = getattr(instance, field_name)
454
454
  except (AttributeError, FieldDoesNotExist):
455
455
  current_value = None
456
-
456
+
457
457
  # Compare values
458
458
  if current_value != old_value:
459
459
  modified_fields.add(field_name)
460
-
460
+
461
461
  return modified_fields
462
462
 
463
463
  def _persist_hook_modifications(self, instances, modified_fields):
@@ -472,10 +472,10 @@ class BulkOperationCoordinator:
472
472
  """
473
473
  logger.info(
474
474
  f"Hooks modified {len(modified_fields)} field(s): "
475
- f"{', '.join(sorted(modified_fields))}"
475
+ f"{', '.join(sorted(modified_fields))}",
476
476
  )
477
477
  logger.info("Auto-persisting modifications via bulk_update")
478
-
478
+
479
479
  # Use Django's bulk_update directly (not our hook version)
480
480
  # Create a fresh QuerySet to avoid recursion
481
481
  fresh_qs = QuerySet(model=self.model_cls, using=self.queryset.db)
@@ -567,7 +567,7 @@ class BulkOperationCoordinator:
567
567
  ChangeSet for the target model
568
568
  """
569
569
  from django_bulk_hooks.changeset import ChangeSet
570
-
570
+
571
571
  # Create new changeset with target model but same record changes
572
572
  return ChangeSet(
573
573
  model_cls=target_model_cls,
@@ -577,12 +577,12 @@ class BulkOperationCoordinator:
577
577
  )
578
578
 
579
579
  def _execute_with_mti_hooks(
580
- self,
581
- changeset,
582
- operation,
583
- event_prefix,
584
- bypass_hooks=False,
585
- bypass_validation=False
580
+ self,
581
+ changeset,
582
+ operation,
583
+ event_prefix,
584
+ bypass_hooks=False,
585
+ bypass_validation=False,
586
586
  ):
587
587
  """
588
588
  Execute operation with hooks for entire MTI inheritance chain.
@@ -658,7 +658,7 @@ class BulkOperationCoordinator:
658
658
  if (field.is_relation and
659
659
  not field.many_to_many and
660
660
  not field.one_to_many and
661
- hasattr(field, 'attname') and
661
+ hasattr(field, "attname") and
662
662
  field.attname == field_name):
663
663
  # This is a FK field being updated by its attname (e.g., business_id)
664
664
  # Add the relationship name (e.g., 'business') to skip list
@@ -667,4 +667,4 @@ class BulkOperationCoordinator:
667
667
  # If field lookup fails, skip it
668
668
  continue
669
669
 
670
- return fk_relationships
670
+ return fk_relationships
@@ -8,6 +8,7 @@ It returns plans (data structures) that the BulkExecutor executes.
8
8
  """
9
9
 
10
10
  import logging
11
+
11
12
  from django.db.models import AutoField
12
13
 
13
14
  logger = logging.getLogger(__name__)
@@ -142,30 +143,30 @@ class MTIHandler:
142
143
  Returns:
143
144
  MTICreatePlan object
144
145
  """
145
- from django_bulk_hooks.operations.mti_plans import MTICreatePlan, ParentLevel
146
-
146
+ from django_bulk_hooks.operations.mti_plans import MTICreatePlan
147
+
147
148
  if not objs:
148
149
  return None
149
-
150
+
150
151
  inheritance_chain = self.get_inheritance_chain()
151
152
  if len(inheritance_chain) <= 1:
152
153
  raise ValueError("build_create_plan called on non-MTI model")
153
-
154
+
154
155
  batch_size = batch_size or len(objs)
155
-
156
+
156
157
  # Use provided classification (no more DB query here!)
157
158
  if existing_record_ids is None:
158
159
  existing_record_ids = set()
159
160
  if existing_pks_map is None:
160
161
  existing_pks_map = {}
161
-
162
+
162
163
  # Set PKs on existing objects so they can be updated
163
164
  if existing_pks_map:
164
165
  for obj in objs:
165
166
  if id(obj) in existing_pks_map:
166
- setattr(obj, 'pk', existing_pks_map[id(obj)])
167
- setattr(obj, 'id', existing_pks_map[id(obj)])
168
-
167
+ obj.pk = existing_pks_map[id(obj)]
168
+ obj.id = existing_pks_map[id(obj)]
169
+
169
170
  # Build parent levels
170
171
  parent_levels = self._build_parent_levels(
171
172
  objs,
@@ -174,13 +175,13 @@ class MTIHandler:
174
175
  unique_fields=unique_fields,
175
176
  update_fields=update_fields,
176
177
  )
177
-
178
+
178
179
  # Build child object templates (without parent links - executor adds them)
179
180
  child_objects = []
180
181
  for obj in objs:
181
182
  child_obj = self._create_child_instance_template(obj, inheritance_chain[-1])
182
183
  child_objects.append(child_obj)
183
-
184
+
184
185
  return MTICreatePlan(
185
186
  inheritance_chain=inheritance_chain,
186
187
  parent_levels=parent_levels,
@@ -211,38 +212,38 @@ class MTIHandler:
211
212
  List of ParentLevel objects
212
213
  """
213
214
  from django_bulk_hooks.operations.mti_plans import ParentLevel
214
-
215
+
215
216
  parent_levels = []
216
217
  parent_instances_map = {} # Maps obj id() -> {model_class: parent_instance}
217
-
218
+
218
219
  for level_idx, model_class in enumerate(inheritance_chain[:-1]):
219
220
  parent_objs_for_level = []
220
-
221
+
221
222
  for obj in objs:
222
223
  # Get current parent from previous level
223
224
  current_parent = None
224
225
  if level_idx > 0:
225
226
  prev_parents = parent_instances_map.get(id(obj), {})
226
227
  current_parent = prev_parents.get(inheritance_chain[level_idx - 1])
227
-
228
+
228
229
  # Create parent instance
229
230
  parent_obj = self._create_parent_instance(obj, model_class, current_parent)
230
231
  parent_objs_for_level.append(parent_obj)
231
-
232
+
232
233
  # Store in map
233
234
  if id(obj) not in parent_instances_map:
234
235
  parent_instances_map[id(obj)] = {}
235
236
  parent_instances_map[id(obj)][model_class] = parent_obj
236
-
237
+
237
238
  # Determine upsert parameters for this level
238
239
  level_update_conflicts = False
239
240
  level_unique_fields = []
240
241
  level_update_fields = []
241
-
242
+
242
243
  if update_conflicts and unique_fields:
243
244
  # Filter unique_fields and update_fields to only those in this model
244
245
  model_fields_by_name = {f.name: f for f in model_class._meta.local_fields}
245
-
246
+
246
247
  # Normalize unique fields
247
248
  normalized_unique = []
248
249
  for uf in unique_fields or []:
@@ -250,19 +251,19 @@ class MTIHandler:
250
251
  normalized_unique.append(uf)
251
252
  elif uf.endswith("_id") and uf[:-3] in model_fields_by_name:
252
253
  normalized_unique.append(uf[:-3])
253
-
254
+
254
255
  # Check if this model has a matching constraint
255
256
  if normalized_unique and self._has_matching_constraint(model_class, normalized_unique):
256
257
  # Filter update fields
257
258
  filtered_updates = [
258
259
  uf for uf in (update_fields or []) if uf in model_fields_by_name
259
260
  ]
260
-
261
+
261
262
  if filtered_updates:
262
263
  level_update_conflicts = True
263
264
  level_unique_fields = normalized_unique
264
265
  level_update_fields = filtered_updates
265
-
266
+
266
267
  # Create parent level
267
268
  parent_level = ParentLevel(
268
269
  model_class=model_class,
@@ -273,7 +274,7 @@ class MTIHandler:
273
274
  update_fields=level_update_fields,
274
275
  )
275
276
  parent_levels.append(parent_level)
276
-
277
+
277
278
  return parent_levels
278
279
 
279
280
  def _has_matching_constraint(self, model_class, normalized_unique):
@@ -281,28 +282,28 @@ class MTIHandler:
281
282
  try:
282
283
  from django.db.models import UniqueConstraint
283
284
  constraint_field_sets = [
284
- tuple(c.fields) for c in model_class._meta.constraints
285
+ tuple(c.fields) for c in model_class._meta.constraints
285
286
  if isinstance(c, UniqueConstraint)
286
287
  ]
287
288
  except Exception:
288
289
  constraint_field_sets = []
289
-
290
+
290
291
  # Check unique_together
291
292
  ut = getattr(model_class._meta, "unique_together", ()) or ()
292
293
  if isinstance(ut, tuple) and ut and not isinstance(ut[0], (list, tuple)):
293
294
  ut = (ut,)
294
295
  ut_field_sets = [tuple(group) for group in ut]
295
-
296
+
296
297
  # Check individual field uniqueness
297
298
  unique_field_sets = []
298
299
  for field in model_class._meta.local_fields:
299
300
  if field.unique and not field.primary_key:
300
301
  unique_field_sets.append((field.name,))
301
-
302
+
302
303
  # Compare as sets
303
304
  provided_set = set(normalized_unique)
304
305
  all_constraint_sets = constraint_field_sets + ut_field_sets + unique_field_sets
305
-
306
+
306
307
  for group in all_constraint_sets:
307
308
  if provided_set == set(group):
308
309
  return True
@@ -321,13 +322,13 @@ class MTIHandler:
321
322
  Parent model instance (not saved)
322
323
  """
323
324
  parent_obj = parent_model()
324
-
325
+
325
326
  # Copy field values from source
326
327
  for field in parent_model._meta.local_fields:
327
328
  if hasattr(source_obj, field.name):
328
329
  value = getattr(source_obj, field.name, None)
329
330
  if value is not None:
330
- if (field.is_relation and not field.many_to_many and
331
+ if (field.is_relation and not field.many_to_many and
331
332
  not field.one_to_many):
332
333
  # Handle FK fields
333
334
  if hasattr(value, "pk") and value.pk is not None:
@@ -336,7 +337,7 @@ class MTIHandler:
336
337
  setattr(parent_obj, field.attname, value)
337
338
  else:
338
339
  setattr(parent_obj, field.name, value)
339
-
340
+
340
341
  # Link to parent if exists
341
342
  if current_parent is not None:
342
343
  for field in parent_model._meta.local_fields:
@@ -344,22 +345,22 @@ class MTIHandler:
344
345
  field.remote_field.model == current_parent.__class__):
345
346
  setattr(parent_obj, field.name, current_parent)
346
347
  break
347
-
348
+
348
349
  # Copy object state
349
- if hasattr(source_obj, '_state') and hasattr(parent_obj, '_state'):
350
+ if hasattr(source_obj, "_state") and hasattr(parent_obj, "_state"):
350
351
  parent_obj._state.adding = source_obj._state.adding
351
- if hasattr(source_obj._state, 'db'):
352
+ if hasattr(source_obj._state, "db"):
352
353
  parent_obj._state.db = source_obj._state.db
353
-
354
+
354
355
  # Handle auto_now_add and auto_now fields
355
356
  for field in parent_model._meta.local_fields:
356
- if getattr(field, 'auto_now_add', False):
357
+ if getattr(field, "auto_now_add", False):
357
358
  if getattr(parent_obj, field.name) is None:
358
359
  field.pre_save(parent_obj, add=True)
359
360
  setattr(parent_obj, field.attname, field.value_from_object(parent_obj))
360
- elif getattr(field, 'auto_now', False):
361
+ elif getattr(field, "auto_now", False):
361
362
  field.pre_save(parent_obj, add=True)
362
-
363
+
363
364
  return parent_obj
364
365
 
365
366
  def _create_child_instance_template(self, source_obj, child_model):
@@ -376,22 +377,22 @@ class MTIHandler:
376
377
  Child model instance (not saved, no parent links)
377
378
  """
378
379
  child_obj = child_model()
379
-
380
+
380
381
  # Copy field values (excluding AutoField and parent links)
381
382
  for field in child_model._meta.local_fields:
382
383
  if isinstance(field, AutoField):
383
384
  continue
384
-
385
+
385
386
  # Skip parent link fields - executor will set these
386
- if field.is_relation and hasattr(field, 'related_model'):
387
+ if field.is_relation and hasattr(field, "related_model"):
387
388
  # Check if this field is a parent link
388
389
  if child_model._meta.get_ancestor_link(field.related_model) == field:
389
390
  continue
390
-
391
+
391
392
  if hasattr(source_obj, field.name):
392
393
  value = getattr(source_obj, field.name, None)
393
394
  if value is not None:
394
- if (field.is_relation and not field.many_to_many and
395
+ if (field.is_relation and not field.many_to_many and
395
396
  not field.one_to_many):
396
397
  if hasattr(value, "pk") and value.pk is not None:
397
398
  setattr(child_obj, field.attname, value.pk)
@@ -399,22 +400,22 @@ class MTIHandler:
399
400
  setattr(child_obj, field.attname, value)
400
401
  else:
401
402
  setattr(child_obj, field.name, value)
402
-
403
+
403
404
  # Copy object state
404
- if hasattr(source_obj, '_state') and hasattr(child_obj, '_state'):
405
+ if hasattr(source_obj, "_state") and hasattr(child_obj, "_state"):
405
406
  child_obj._state.adding = source_obj._state.adding
406
- if hasattr(source_obj._state, 'db'):
407
+ if hasattr(source_obj._state, "db"):
407
408
  child_obj._state.db = source_obj._state.db
408
-
409
+
409
410
  # Handle auto_now_add and auto_now fields
410
411
  for field in child_model._meta.local_fields:
411
- if getattr(field, 'auto_now_add', False):
412
+ if getattr(field, "auto_now_add", False):
412
413
  if getattr(child_obj, field.name) is None:
413
414
  field.pre_save(child_obj, add=True)
414
415
  setattr(child_obj, field.attname, field.value_from_object(child_obj))
415
- elif getattr(field, 'auto_now', False):
416
+ elif getattr(field, "auto_now", False):
416
417
  field.pre_save(child_obj, add=True)
417
-
418
+
418
419
  return child_obj
419
420
 
420
421
  # ==================== MTI BULK UPDATE PLANNING ====================
@@ -433,48 +434,49 @@ class MTIHandler:
433
434
  Returns:
434
435
  MTIUpdatePlan object
435
436
  """
436
- from django_bulk_hooks.operations.mti_plans import MTIUpdatePlan, ModelFieldGroup
437
-
437
+ from django_bulk_hooks.operations.mti_plans import ModelFieldGroup
438
+ from django_bulk_hooks.operations.mti_plans import MTIUpdatePlan
439
+
438
440
  if not objs:
439
441
  return None
440
-
442
+
441
443
  inheritance_chain = self.get_inheritance_chain()
442
444
  if len(inheritance_chain) <= 1:
443
445
  raise ValueError("build_update_plan called on non-MTI model")
444
-
446
+
445
447
  batch_size = batch_size or len(objs)
446
-
448
+
447
449
  # Handle auto_now fields
448
450
  for obj in objs:
449
451
  for model in inheritance_chain:
450
452
  for field in model._meta.local_fields:
451
- if getattr(field, 'auto_now', False):
453
+ if getattr(field, "auto_now", False):
452
454
  field.pre_save(obj, add=False)
453
-
455
+
454
456
  # Add auto_now fields to update list
455
457
  auto_now_fields = set()
456
458
  for model in inheritance_chain:
457
459
  for field in model._meta.local_fields:
458
- if getattr(field, 'auto_now', False):
460
+ if getattr(field, "auto_now", False):
459
461
  auto_now_fields.add(field.name)
460
-
462
+
461
463
  all_fields = list(fields) + list(auto_now_fields)
462
-
464
+
463
465
  # Group fields by model
464
466
  field_groups = []
465
467
  for model_idx, model in enumerate(inheritance_chain):
466
468
  model_fields = []
467
-
469
+
468
470
  for field_name in all_fields:
469
471
  try:
470
472
  field = self.model_cls._meta.get_field(field_name)
471
473
  if field in model._meta.local_fields:
472
474
  # Skip auto_now_add fields for updates
473
- if not getattr(field, 'auto_now_add', False):
475
+ if not getattr(field, "auto_now_add", False):
474
476
  model_fields.append(field_name)
475
477
  except Exception:
476
478
  continue
477
-
479
+
478
480
  if model_fields:
479
481
  # Determine filter field
480
482
  if model_idx == 0:
@@ -487,13 +489,13 @@ class MTIHandler:
487
489
  parent_link = model._meta.parents[parent_model]
488
490
  break
489
491
  filter_field = parent_link.attname if parent_link else "pk"
490
-
492
+
491
493
  field_groups.append(ModelFieldGroup(
492
494
  model_class=model,
493
495
  fields=model_fields,
494
496
  filter_field=filter_field,
495
497
  ))
496
-
498
+
497
499
  return MTIUpdatePlan(
498
500
  inheritance_chain=inheritance_chain,
499
501
  field_groups=field_groups,