django-bulk-hooks 0.1.115__py3-none-any.whl → 0.1.117__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.

@@ -72,6 +72,83 @@ class BulkHookManager(models.Manager):
72
72
 
73
73
  return objs
74
74
 
75
+ @transaction.atomic
76
+ def bulk_create(
77
+ self,
78
+ objs,
79
+ batch_size=None,
80
+ ignore_conflicts=False,
81
+ update_conflicts=False,
82
+ update_fields=None,
83
+ unique_fields=None,
84
+ bypass_hooks=False,
85
+ bypass_validation=False,
86
+ ):
87
+ """
88
+ Insert each of the instances into the database. Behaves like Django's bulk_create,
89
+ but supports multi-table inheritance (MTI) models. All arguments are supported and
90
+ passed through to the correct logic. For MTI, only a subset of options may be supported.
91
+ """
92
+ model_cls = self.model
93
+
94
+ if batch_size is not None and batch_size <= 0:
95
+ raise ValueError("Batch size must be a positive integer.")
96
+
97
+ # Check that the parents share the same concrete model with our model to detect inheritance pattern
98
+ for parent in model_cls._meta.all_parents:
99
+ if parent._meta.concrete_model is not model_cls._meta.concrete_model:
100
+ # We allow this for MTI, but not for single-table
101
+ break
102
+
103
+ if not objs:
104
+ return objs
105
+
106
+ if any(not isinstance(obj, model_cls) for obj in objs):
107
+ raise TypeError(
108
+ f"bulk_create expected instances of {model_cls.__name__}, but got {set(type(obj).__name__ for obj in objs)}"
109
+ )
110
+
111
+ # Set auto_now_add/auto_now fields before DB ops
112
+ self._set_auto_now_fields(objs, model_cls)
113
+
114
+ # Fire hooks before DB ops
115
+ if not bypass_hooks:
116
+ ctx = HookContext(model_cls)
117
+ if not bypass_validation:
118
+ engine.run(model_cls, VALIDATE_CREATE, objs, ctx=ctx)
119
+ engine.run(model_cls, BEFORE_CREATE, objs, ctx=ctx)
120
+
121
+ # MTI detection: if inheritance chain > 1, use MTI logic
122
+ inheritance_chain = self._get_inheritance_chain()
123
+ if len(inheritance_chain) <= 1:
124
+ # Single-table: use Django's standard bulk_create
125
+ # Pass through all supported arguments
126
+ result = super(models.Manager, self).bulk_create(
127
+ objs,
128
+ batch_size=batch_size,
129
+ ignore_conflicts=ignore_conflicts,
130
+ update_conflicts=update_conflicts,
131
+ update_fields=update_fields,
132
+ unique_fields=unique_fields,
133
+ )
134
+ else:
135
+ # Multi-table: use workaround (parent saves, child bulk)
136
+ # Only batch_size is supported for MTI; others will raise NotImplementedError
137
+ if ignore_conflicts or update_conflicts or update_fields or unique_fields:
138
+ raise NotImplementedError(
139
+ "bulk_create with ignore_conflicts, update_conflicts, update_fields, or unique_fields is not supported for multi-table inheritance models."
140
+ )
141
+ result = self._mti_bulk_create(
142
+ objs, inheritance_chain, batch_size=batch_size
143
+ )
144
+
145
+ if not bypass_hooks:
146
+ engine.run(model_cls, AFTER_CREATE, result, ctx=ctx)
147
+
148
+ return result
149
+
150
+ # --- Private helper methods (moved to bottom for clarity) ---
151
+
75
152
  def _detect_modified_fields(self, new_instances, original_instances):
76
153
  """
77
154
  Detect fields that were modified during BEFORE_UPDATE hooks by comparing
@@ -109,47 +186,6 @@ class BulkHookManager(models.Manager):
109
186
 
110
187
  return modified_fields
111
188
 
112
- @transaction.atomic
113
- def bulk_create(self, objs, bypass_hooks=False, bypass_validation=False, **kwargs):
114
- """
115
- Enhanced bulk_create that handles multi-table inheritance (MTI) and single-table models.
116
- Falls back to Django's standard bulk_create for single-table models.
117
- Fires hooks as usual.
118
- """
119
- model_cls = self.model
120
-
121
- if not objs:
122
- return []
123
-
124
- if any(not isinstance(obj, model_cls) for obj in objs):
125
- raise TypeError(
126
- f"bulk_create expected instances of {model_cls.__name__}, but got {set(type(obj).__name__ for obj in objs)}"
127
- )
128
-
129
- # Fire hooks before DB ops
130
- if not bypass_hooks:
131
- ctx = HookContext(model_cls)
132
- if not bypass_validation:
133
- engine.run(model_cls, VALIDATE_CREATE, objs, ctx=ctx)
134
- engine.run(model_cls, BEFORE_CREATE, objs, ctx=ctx)
135
-
136
- # MTI detection: if inheritance chain > 1, use MTI logic
137
- inheritance_chain = self._get_inheritance_chain()
138
- if len(inheritance_chain) <= 1:
139
- # Single-table: use Django's standard bulk_create
140
- result = []
141
- for i in range(0, len(objs), self.CHUNK_SIZE):
142
- chunk = objs[i : i + self.CHUNK_SIZE]
143
- result.extend(super(models.Manager, self).bulk_create(chunk, **kwargs))
144
- else:
145
- # Multi-table: use workaround (parent saves, child bulk)
146
- result = self._mti_bulk_create(objs, inheritance_chain, **kwargs)
147
-
148
- if not bypass_hooks:
149
- engine.run(model_cls, AFTER_CREATE, result, ctx=ctx)
150
-
151
- return result
152
-
153
189
  def _get_inheritance_chain(self):
154
190
  """
155
191
  Get the complete inheritance chain from root parent to current model.
@@ -172,12 +208,16 @@ class BulkHookManager(models.Manager):
172
208
  def _mti_bulk_create(self, objs, inheritance_chain, **kwargs):
173
209
  """
174
210
  Implements workaround: individual saves for parents, bulk create for child.
211
+ Sets auto_now_add/auto_now fields for each model in the chain.
175
212
  """
176
213
  batch_size = kwargs.get("batch_size") or len(objs)
177
214
  created_objects = []
178
215
  with transaction.atomic(using=self.db, savepoint=False):
179
216
  for i in range(0, len(objs), batch_size):
180
217
  batch = objs[i : i + batch_size]
218
+ # Set auto_now fields for each model in the chain
219
+ for model in inheritance_chain:
220
+ self._set_auto_now_fields(batch, model)
181
221
  batch_result = self._process_mti_batch(
182
222
  batch, inheritance_chain, **kwargs
183
223
  )
@@ -272,6 +312,23 @@ class BulkHookManager(models.Manager):
272
312
  setattr(child_obj, parent_link.name, parent_instance)
273
313
  return child_obj
274
314
 
315
+ def _set_auto_now_fields(self, objs, model):
316
+ """
317
+ Set auto_now_add and auto_now fields on objects before bulk_create.
318
+ """
319
+ from django.utils import timezone
320
+
321
+ now = timezone.now()
322
+ for obj in objs:
323
+ for field in model._meta.local_fields:
324
+ if (
325
+ getattr(field, "auto_now_add", False)
326
+ and getattr(obj, field.name, None) is None
327
+ ):
328
+ setattr(obj, field.name, now)
329
+ if getattr(field, "auto_now", False):
330
+ setattr(obj, field.name, now)
331
+
275
332
  @transaction.atomic
276
333
  def bulk_delete(
277
334
  self, objs, batch_size=None, bypass_hooks=False, bypass_validation=False
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: django-bulk-hooks
3
- Version: 0.1.115
3
+ Version: 0.1.117
4
4
  Summary: Hook-style hooks for Django bulk operations like bulk_create and bulk_update.
5
5
  Home-page: https://github.com/AugendLimited/django-bulk-hooks
6
6
  License: MIT
@@ -6,12 +6,12 @@ django_bulk_hooks/decorators.py,sha256=tckDcxtOzKCbgvS9QydgeIAWTFDEl-ch3_Q--ruEG
6
6
  django_bulk_hooks/engine.py,sha256=3HbgV12JRYIy9IlygHPxZiHnFXj7EwzLyTuJNQeVIoI,1402
7
7
  django_bulk_hooks/enums.py,sha256=Zo8_tJzuzZ2IKfVc7gZ-0tWPT8q1QhqZbAyoh9ZVJbs,381
8
8
  django_bulk_hooks/handler.py,sha256=xZt8iNdYF-ACz-MnKMY0co6scWINU5V5wC1lyDn844k,4854
9
- django_bulk_hooks/manager.py,sha256=sQ9xDCo3MRp2CNwsfo_a_43LzlWlSdqqoz04oUp6sx8,13351
9
+ django_bulk_hooks/manager.py,sha256=3WtqgKBqL8uWkOeGnw0xPp3AGpaxJSbT63ltgDxb2fU,15747
10
10
  django_bulk_hooks/models.py,sha256=7RG7GrOdHXFjGVPV4FPRZVNMIHHW-hMCi6hn9LH_hVI,3331
11
11
  django_bulk_hooks/priority.py,sha256=HG_2D35nga68lBCZmSXTcplXrjFoRgZFRDOy4ROKonY,376
12
12
  django_bulk_hooks/queryset.py,sha256=iet4z-9SKhnresA4FBQbxx9rdYnoaOWbw9LUlGftlP0,1466
13
13
  django_bulk_hooks/registry.py,sha256=-mQBizJ06nz_tajZBinViKx_uP2Tbc1tIpTEMv7lwKA,705
14
- django_bulk_hooks-0.1.115.dist-info/LICENSE,sha256=dguKIcbDGeZD-vXWdLyErPUALYOvtX_fO4Zjhq481uk,1088
15
- django_bulk_hooks-0.1.115.dist-info/METADATA,sha256=uA5Qw2PzVWnyDRiGHqr6pF3AdeoQ4xhMUh_eJSoOSRs,6939
16
- django_bulk_hooks-0.1.115.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
17
- django_bulk_hooks-0.1.115.dist-info/RECORD,,
14
+ django_bulk_hooks-0.1.117.dist-info/LICENSE,sha256=dguKIcbDGeZD-vXWdLyErPUALYOvtX_fO4Zjhq481uk,1088
15
+ django_bulk_hooks-0.1.117.dist-info/METADATA,sha256=b1mTw0rJZLbei3sm2bN480iVoU5KzXOs6D-_qyeVcrQ,6939
16
+ django_bulk_hooks-0.1.117.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
17
+ django_bulk_hooks-0.1.117.dist-info/RECORD,,