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.
- django_bulk_hooks/manager.py +106 -41
- {django_bulk_hooks-0.1.115.dist-info → django_bulk_hooks-0.1.116.dist-info}/METADATA +1 -1
- {django_bulk_hooks-0.1.115.dist-info → django_bulk_hooks-0.1.116.dist-info}/RECORD +5 -5
- {django_bulk_hooks-0.1.115.dist-info → django_bulk_hooks-0.1.116.dist-info}/LICENSE +0 -0
- {django_bulk_hooks-0.1.115.dist-info → django_bulk_hooks-0.1.116.dist-info}/WHEEL +0 -0
django_bulk_hooks/manager.py
CHANGED
|
@@ -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
|
|
@@ -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=
|
|
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.
|
|
15
|
-
django_bulk_hooks-0.1.
|
|
16
|
-
django_bulk_hooks-0.1.
|
|
17
|
-
django_bulk_hooks-0.1.
|
|
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,,
|
|
File without changes
|
|
File without changes
|