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

@@ -1,3 +1,5 @@
1
+
2
+
1
3
  from django.db import models, transaction
2
4
  from django.db.models import AutoField
3
5
 
@@ -20,9 +22,12 @@ from django_bulk_hooks.queryset import HookQuerySet
20
22
  class BulkHookManager(models.Manager):
21
23
  CHUNK_SIZE = 200
22
24
 
25
+
23
26
  def get_queryset(self):
24
27
  return HookQuerySet(self.model, using=self._db)
25
28
 
29
+
30
+
26
31
  @transaction.atomic
27
32
  def bulk_update(
28
33
  self, objs, fields, bypass_hooks=False, bypass_validation=False, **kwargs
@@ -72,6 +77,90 @@ class BulkHookManager(models.Manager):
72
77
 
73
78
  return objs
74
79
 
80
+
81
+
82
+ @transaction.atomic
83
+ def bulk_create(
84
+ self,
85
+ objs,
86
+ batch_size=None,
87
+ ignore_conflicts=False,
88
+ update_conflicts=False,
89
+ update_fields=None,
90
+ unique_fields=None,
91
+ bypass_hooks=False,
92
+ bypass_validation=False,
93
+ ):
94
+ """
95
+ Insert each of the instances into the database. Behaves like Django's bulk_create,
96
+ but supports multi-table inheritance (MTI) models. All arguments are supported and
97
+ passed through to the correct logic. For MTI, only a subset of options may be supported.
98
+ """
99
+ model_cls = self.model
100
+
101
+ if batch_size is not None and batch_size <= 0:
102
+ raise ValueError("Batch size must be a positive integer.")
103
+
104
+ # Check that the parents share the same concrete model with our model to detect inheritance pattern
105
+ for parent in model_cls._meta.all_parents:
106
+ if parent._meta.concrete_model is not model_cls._meta.concrete_model:
107
+ # We allow this for MTI, but not for single-table
108
+ break
109
+
110
+ if not objs:
111
+ return objs
112
+
113
+ if any(not isinstance(obj, model_cls) for obj in objs):
114
+ raise TypeError(
115
+ f"bulk_create expected instances of {model_cls.__name__}, but got {set(type(obj).__name__ for obj in objs)}"
116
+ )
117
+
118
+ # Set auto_now_add/auto_now fields before DB ops
119
+ self._set_auto_now_fields(objs, model_cls)
120
+
121
+ # Fire hooks before DB ops
122
+ if not bypass_hooks:
123
+ ctx = HookContext(model_cls)
124
+ if not bypass_validation:
125
+ engine.run(model_cls, VALIDATE_CREATE, objs, ctx=ctx)
126
+ engine.run(model_cls, BEFORE_CREATE, objs, ctx=ctx)
127
+
128
+ # MTI detection: if inheritance chain > 1, use MTI logic
129
+ inheritance_chain = self._get_inheritance_chain()
130
+ if len(inheritance_chain) <= 1:
131
+ # Single-table: use Django's standard bulk_create
132
+ # Pass through all supported arguments
133
+ result = super(models.Manager, self).bulk_create(
134
+ objs,
135
+ batch_size=batch_size,
136
+ ignore_conflicts=ignore_conflicts,
137
+ update_conflicts=update_conflicts,
138
+ update_fields=update_fields,
139
+ unique_fields=unique_fields,
140
+ )
141
+ else:
142
+ # Multi-table: use workaround (parent saves, child bulk)
143
+ # Only batch_size is supported for MTI; others will raise NotImplementedError
144
+ if ignore_conflicts or update_conflicts or update_fields or unique_fields:
145
+ raise NotImplementedError(
146
+ "bulk_create with ignore_conflicts, update_conflicts, update_fields, or unique_fields is not supported for multi-table inheritance models."
147
+ )
148
+ result = self._mti_bulk_create(
149
+ objs, inheritance_chain, batch_size=batch_size
150
+ )
151
+
152
+ if not bypass_hooks:
153
+ engine.run(model_cls, AFTER_CREATE, result, ctx=ctx)
154
+
155
+ return result
156
+
157
+
158
+
159
+
160
+
161
+
162
+ # --- Private helper methods (moved to bottom for clarity) ---
163
+
75
164
  def _detect_modified_fields(self, new_instances, original_instances):
76
165
  """
77
166
  Detect fields that were modified during BEFORE_UPDATE hooks by comparing
@@ -109,47 +198,6 @@ class BulkHookManager(models.Manager):
109
198
 
110
199
  return modified_fields
111
200
 
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
201
  def _get_inheritance_chain(self):
154
202
  """
155
203
  Get the complete inheritance chain from root parent to current model.
@@ -172,12 +220,16 @@ class BulkHookManager(models.Manager):
172
220
  def _mti_bulk_create(self, objs, inheritance_chain, **kwargs):
173
221
  """
174
222
  Implements workaround: individual saves for parents, bulk create for child.
223
+ Sets auto_now_add/auto_now fields for each model in the chain.
175
224
  """
176
225
  batch_size = kwargs.get("batch_size") or len(objs)
177
226
  created_objects = []
178
227
  with transaction.atomic(using=self.db, savepoint=False):
179
228
  for i in range(0, len(objs), batch_size):
180
229
  batch = objs[i : i + batch_size]
230
+ # Set auto_now fields for each model in the chain
231
+ for model in inheritance_chain:
232
+ self._set_auto_now_fields(batch, model)
181
233
  batch_result = self._process_mti_batch(
182
234
  batch, inheritance_chain, **kwargs
183
235
  )
@@ -272,6 +324,19 @@ class BulkHookManager(models.Manager):
272
324
  setattr(child_obj, parent_link.name, parent_instance)
273
325
  return child_obj
274
326
 
327
+ def _set_auto_now_fields(self, objs, model):
328
+ """
329
+ Set auto_now_add and auto_now fields on objects before bulk_create.
330
+ """
331
+ from django.utils import timezone
332
+ now = timezone.now()
333
+ for obj in objs:
334
+ for field in model._meta.local_fields:
335
+ if getattr(field, 'auto_now_add', False) and getattr(obj, field.name, None) is None:
336
+ setattr(obj, field.name, now)
337
+ if getattr(field, 'auto_now', False):
338
+ setattr(obj, field.name, now)
339
+
275
340
  @transaction.atomic
276
341
  def bulk_delete(
277
342
  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.116
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=RZbfRf1H1KHsWvvvtQhIN9yDLVN1KbLaCkU9r7WWJg8,15706
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.116.dist-info/LICENSE,sha256=dguKIcbDGeZD-vXWdLyErPUALYOvtX_fO4Zjhq481uk,1088
15
+ django_bulk_hooks-0.1.116.dist-info/METADATA,sha256=4KODRNtf6mTPWNsVLI8IH1yjGFoaGnPL0LsaCgCaTFs,6939
16
+ django_bulk_hooks-0.1.116.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
17
+ django_bulk_hooks-0.1.116.dist-info/RECORD,,