django-bulk-hooks 0.1.173__tar.gz → 0.1.175__tar.gz
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-0.1.173 → django_bulk_hooks-0.1.175}/PKG-INFO +1 -1
- {django_bulk_hooks-0.1.173 → django_bulk_hooks-0.1.175}/django_bulk_hooks/queryset.py +107 -133
- {django_bulk_hooks-0.1.173 → django_bulk_hooks-0.1.175}/pyproject.toml +1 -1
- {django_bulk_hooks-0.1.173 → django_bulk_hooks-0.1.175}/LICENSE +0 -0
- {django_bulk_hooks-0.1.173 → django_bulk_hooks-0.1.175}/README.md +0 -0
- {django_bulk_hooks-0.1.173 → django_bulk_hooks-0.1.175}/django_bulk_hooks/__init__.py +0 -0
- {django_bulk_hooks-0.1.173 → django_bulk_hooks-0.1.175}/django_bulk_hooks/conditions.py +0 -0
- {django_bulk_hooks-0.1.173 → django_bulk_hooks-0.1.175}/django_bulk_hooks/constants.py +0 -0
- {django_bulk_hooks-0.1.173 → django_bulk_hooks-0.1.175}/django_bulk_hooks/context.py +0 -0
- {django_bulk_hooks-0.1.173 → django_bulk_hooks-0.1.175}/django_bulk_hooks/decorators.py +0 -0
- {django_bulk_hooks-0.1.173 → django_bulk_hooks-0.1.175}/django_bulk_hooks/engine.py +0 -0
- {django_bulk_hooks-0.1.173 → django_bulk_hooks-0.1.175}/django_bulk_hooks/enums.py +0 -0
- {django_bulk_hooks-0.1.173 → django_bulk_hooks-0.1.175}/django_bulk_hooks/handler.py +0 -0
- {django_bulk_hooks-0.1.173 → django_bulk_hooks-0.1.175}/django_bulk_hooks/manager.py +0 -0
- {django_bulk_hooks-0.1.173 → django_bulk_hooks-0.1.175}/django_bulk_hooks/models.py +0 -0
- {django_bulk_hooks-0.1.173 → django_bulk_hooks-0.1.175}/django_bulk_hooks/priority.py +0 -0
- {django_bulk_hooks-0.1.173 → django_bulk_hooks-0.1.175}/django_bulk_hooks/registry.py +0 -0
|
@@ -24,8 +24,23 @@ class HookQuerySet(models.QuerySet):
|
|
|
24
24
|
objs = list(self)
|
|
25
25
|
if not objs:
|
|
26
26
|
return 0
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
|
|
28
|
+
model_cls = self.model
|
|
29
|
+
ctx = HookContext(model_cls)
|
|
30
|
+
|
|
31
|
+
# Run validation hooks first
|
|
32
|
+
engine.run(model_cls, VALIDATE_DELETE, objs, ctx=ctx)
|
|
33
|
+
|
|
34
|
+
# Then run business logic hooks
|
|
35
|
+
engine.run(model_cls, BEFORE_DELETE, objs, ctx=ctx)
|
|
36
|
+
|
|
37
|
+
# Use Django's standard delete() method
|
|
38
|
+
result = super().delete()
|
|
39
|
+
|
|
40
|
+
# Run AFTER_DELETE hooks
|
|
41
|
+
engine.run(model_cls, AFTER_DELETE, objs, ctx=ctx)
|
|
42
|
+
|
|
43
|
+
return result
|
|
29
44
|
|
|
30
45
|
@transaction.atomic
|
|
31
46
|
def update(self, **kwargs):
|
|
@@ -38,7 +53,9 @@ class HookQuerySet(models.QuerySet):
|
|
|
38
53
|
|
|
39
54
|
# Load originals for hook comparison and ensure they match the order of instances
|
|
40
55
|
# Use the base manager to avoid recursion
|
|
41
|
-
original_map = {
|
|
56
|
+
original_map = {
|
|
57
|
+
obj.pk: obj for obj in model_cls._base_manager.filter(pk__in=pks)
|
|
58
|
+
}
|
|
42
59
|
originals = [original_map.get(obj.pk) for obj in instances]
|
|
43
60
|
|
|
44
61
|
# Apply field updates to instances
|
|
@@ -114,35 +131,30 @@ class HookQuerySet(models.QuerySet):
|
|
|
114
131
|
|
|
115
132
|
# Fire hooks before DB ops
|
|
116
133
|
if not bypass_hooks:
|
|
117
|
-
print(f"DEBUG: Firing BEFORE_CREATE hooks for {model_cls}")
|
|
118
|
-
print(f"DEBUG: Number of objects: {len(objs)}")
|
|
119
|
-
print(f"DEBUG: Object types: {[type(obj) for obj in objs]}")
|
|
120
|
-
print(f"DEBUG: QuerySet type: {type(self)}")
|
|
121
|
-
print(f"DEBUG: Is this HookQuerySet? {isinstance(self, HookQuerySet)}")
|
|
122
134
|
ctx = HookContext(model_cls)
|
|
123
135
|
if not bypass_validation:
|
|
124
|
-
print(f"DEBUG: Running VALIDATE_CREATE hooks")
|
|
125
136
|
engine.run(model_cls, VALIDATE_CREATE, objs, ctx=ctx)
|
|
126
|
-
print(f"DEBUG: Running BEFORE_CREATE hooks")
|
|
127
137
|
engine.run(model_cls, BEFORE_CREATE, objs, ctx=ctx)
|
|
128
|
-
else:
|
|
129
|
-
print(f"DEBUG: Skipping hooks due to bypass_hooks=True for {model_cls}")
|
|
130
138
|
|
|
131
139
|
# For MTI models, we need to handle them specially
|
|
132
140
|
if is_mti:
|
|
133
141
|
# Use our MTI-specific logic
|
|
134
142
|
# Filter out custom parameters that Django's bulk_create doesn't accept
|
|
135
143
|
mti_kwargs = {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
144
|
+
"batch_size": batch_size,
|
|
145
|
+
"ignore_conflicts": ignore_conflicts,
|
|
146
|
+
"update_conflicts": update_conflicts,
|
|
147
|
+
"update_fields": update_fields,
|
|
148
|
+
"unique_fields": unique_fields,
|
|
141
149
|
}
|
|
142
150
|
# Remove custom hook kwargs if present in self.bulk_create signature
|
|
143
151
|
result = self._mti_bulk_create(
|
|
144
152
|
objs,
|
|
145
|
-
**{
|
|
153
|
+
**{
|
|
154
|
+
k: v
|
|
155
|
+
for k, v in mti_kwargs.items()
|
|
156
|
+
if k not in ["bypass_hooks", "bypass_validation"]
|
|
157
|
+
},
|
|
146
158
|
)
|
|
147
159
|
else:
|
|
148
160
|
# For single-table models, use Django's built-in bulk_create
|
|
@@ -151,6 +163,7 @@ class HookQuerySet(models.QuerySet):
|
|
|
151
163
|
|
|
152
164
|
# Use Django's original QuerySet to avoid recursive calls
|
|
153
165
|
from django.db.models import QuerySet
|
|
166
|
+
|
|
154
167
|
original_qs = QuerySet(model_cls, using=self.db)
|
|
155
168
|
result = original_qs.bulk_create(
|
|
156
169
|
objs,
|
|
@@ -163,37 +176,19 @@ class HookQuerySet(models.QuerySet):
|
|
|
163
176
|
|
|
164
177
|
# Fire AFTER_CREATE hooks
|
|
165
178
|
if not bypass_hooks:
|
|
166
|
-
print(f"DEBUG: Firing AFTER_CREATE hooks for {model_cls}")
|
|
167
|
-
print(f"DEBUG: Number of objects: {len(objs)}")
|
|
168
|
-
print(f"DEBUG: QuerySet type: {type(self)}")
|
|
169
|
-
print(f"DEBUG: Is this HookQuerySet? {isinstance(self, HookQuerySet)}")
|
|
170
179
|
engine.run(model_cls, AFTER_CREATE, objs, ctx=ctx)
|
|
171
|
-
else:
|
|
172
|
-
print(f"DEBUG: Skipping AFTER_CREATE hooks due to bypass_hooks=True for {model_cls}")
|
|
173
180
|
|
|
174
181
|
return result
|
|
175
182
|
|
|
176
183
|
@transaction.atomic
|
|
177
|
-
def bulk_update(
|
|
184
|
+
def bulk_update(
|
|
185
|
+
self, objs, fields, bypass_hooks=False, bypass_validation=False, **kwargs
|
|
186
|
+
):
|
|
178
187
|
"""
|
|
179
188
|
Bulk update objects in the database.
|
|
180
189
|
"""
|
|
181
|
-
import inspect
|
|
182
|
-
print(f"DEBUG: QuerySet.bulk_update called with:")
|
|
183
|
-
print(f" - self: {type(self)}")
|
|
184
|
-
print(f" - objs: {type(objs)}")
|
|
185
|
-
print(f" - fields: {fields}")
|
|
186
|
-
print(f" - bypass_hooks: {bypass_hooks}")
|
|
187
|
-
print(f" - bypass_validation: {bypass_validation}")
|
|
188
|
-
print(f" - kwargs: {kwargs}")
|
|
189
|
-
print(f"DEBUG: Method signature: {inspect.signature(self.bulk_update)}")
|
|
190
|
-
|
|
191
190
|
model_cls = self.model
|
|
192
|
-
|
|
193
|
-
print(f"DEBUG: bypass_hooks value: {bypass_hooks}")
|
|
194
|
-
print(f"DEBUG: QuerySet type: {type(self)}")
|
|
195
|
-
print(f"DEBUG: Is this HookQuerySet? {isinstance(self, HookQuerySet)}")
|
|
196
|
-
|
|
191
|
+
|
|
197
192
|
if not objs:
|
|
198
193
|
return []
|
|
199
194
|
|
|
@@ -207,7 +202,9 @@ class HookQuerySet(models.QuerySet):
|
|
|
207
202
|
# Use the base manager to avoid recursion
|
|
208
203
|
original_map = {
|
|
209
204
|
obj.pk: obj
|
|
210
|
-
for obj in model_cls._base_manager.filter(
|
|
205
|
+
for obj in model_cls._base_manager.filter(
|
|
206
|
+
pk__in=[obj.pk for obj in objs]
|
|
207
|
+
)
|
|
211
208
|
}
|
|
212
209
|
originals = [original_map.get(obj.pk) for obj in objs]
|
|
213
210
|
|
|
@@ -219,12 +216,7 @@ class HookQuerySet(models.QuerySet):
|
|
|
219
216
|
|
|
220
217
|
# Then run business logic hooks
|
|
221
218
|
if not bypass_hooks:
|
|
222
|
-
print(f"DEBUG: Firing BEFORE_UPDATE hooks for {model_cls}")
|
|
223
|
-
print(f"DEBUG: Number of objects: {len(objs)}")
|
|
224
|
-
print(f"DEBUG: Object types: {[type(obj) for obj in objs]}")
|
|
225
219
|
engine.run(model_cls, BEFORE_UPDATE, objs, originals, ctx=ctx)
|
|
226
|
-
else:
|
|
227
|
-
print(f"DEBUG: Skipping hooks due to bypass_hooks=True for {model_cls}")
|
|
228
220
|
|
|
229
221
|
# Automatically detect fields that were modified during BEFORE_UPDATE hooks
|
|
230
222
|
modified_fields = self._detect_modified_fields(objs, originals)
|
|
@@ -239,50 +231,15 @@ class HookQuerySet(models.QuerySet):
|
|
|
239
231
|
|
|
240
232
|
# Call the base implementation to avoid re-triggering this method
|
|
241
233
|
# Filter out custom parameters that Django's bulk_update doesn't accept
|
|
242
|
-
django_kwargs = {
|
|
234
|
+
django_kwargs = {
|
|
235
|
+
k: v
|
|
236
|
+
for k, v in kwargs.items()
|
|
237
|
+
if k not in ["bypass_hooks", "bypass_validation"]
|
|
238
|
+
}
|
|
243
239
|
super().bulk_update(chunk, fields, **django_kwargs)
|
|
244
240
|
|
|
245
241
|
if not bypass_hooks:
|
|
246
|
-
print(f"DEBUG: Firing AFTER_UPDATE hooks for {model_cls}")
|
|
247
|
-
print(f"DEBUG: Number of objects: {len(objs)}")
|
|
248
242
|
engine.run(model_cls, AFTER_UPDATE, objs, originals, ctx=ctx)
|
|
249
|
-
else:
|
|
250
|
-
print(f"DEBUG: Skipping AFTER_UPDATE hooks due to bypass_hooks=True for {model_cls}")
|
|
251
|
-
|
|
252
|
-
return objs
|
|
253
|
-
|
|
254
|
-
@transaction.atomic
|
|
255
|
-
def bulk_delete(
|
|
256
|
-
self, objs, batch_size=None, bypass_hooks=False, bypass_validation=False
|
|
257
|
-
):
|
|
258
|
-
if not objs:
|
|
259
|
-
return []
|
|
260
|
-
|
|
261
|
-
model_cls = self.model
|
|
262
|
-
|
|
263
|
-
if any(not isinstance(obj, model_cls) for obj in objs):
|
|
264
|
-
raise TypeError(
|
|
265
|
-
f"bulk_delete expected instances of {model_cls.__name__}, but got {set(type(obj).__name__ for obj in objs)}"
|
|
266
|
-
)
|
|
267
|
-
|
|
268
|
-
ctx = HookContext(model_cls)
|
|
269
|
-
|
|
270
|
-
if not bypass_hooks:
|
|
271
|
-
# Run validation hooks first
|
|
272
|
-
if not bypass_validation:
|
|
273
|
-
engine.run(model_cls, VALIDATE_DELETE, objs, ctx=ctx)
|
|
274
|
-
|
|
275
|
-
# Then run business logic hooks
|
|
276
|
-
engine.run(model_cls, BEFORE_DELETE, objs, ctx=ctx)
|
|
277
|
-
|
|
278
|
-
pks = [obj.pk for obj in objs if obj.pk is not None]
|
|
279
|
-
|
|
280
|
-
# Call the base QuerySet implementation to avoid recursion
|
|
281
|
-
# The hooks have already been fired above, so we don't need them again
|
|
282
|
-
super().bulk_delete(objs, batch_size=batch_size)
|
|
283
|
-
|
|
284
|
-
if not bypass_hooks:
|
|
285
|
-
engine.run(model_cls, AFTER_DELETE, objs, ctx=ctx)
|
|
286
243
|
|
|
287
244
|
return objs
|
|
288
245
|
|
|
@@ -350,7 +307,11 @@ class HookQuerySet(models.QuerySet):
|
|
|
350
307
|
Sets auto_now_add/auto_now fields for each model in the chain.
|
|
351
308
|
"""
|
|
352
309
|
# Remove custom hook kwargs before passing to Django internals
|
|
353
|
-
django_kwargs = {
|
|
310
|
+
django_kwargs = {
|
|
311
|
+
k: v
|
|
312
|
+
for k, v in kwargs.items()
|
|
313
|
+
if k not in ["bypass_hooks", "bypass_validation"]
|
|
314
|
+
}
|
|
354
315
|
if inheritance_chain is None:
|
|
355
316
|
inheritance_chain = self._get_inheritance_chain()
|
|
356
317
|
|
|
@@ -383,9 +344,9 @@ class HookQuerySet(models.QuerySet):
|
|
|
383
344
|
|
|
384
345
|
# Step 1: Do O(n) normal inserts into parent tables to get primary keys back
|
|
385
346
|
# Get bypass_hooks from kwargs
|
|
386
|
-
bypass_hooks = kwargs.get(
|
|
387
|
-
bypass_validation = kwargs.get(
|
|
388
|
-
|
|
347
|
+
bypass_hooks = kwargs.get("bypass_hooks", False)
|
|
348
|
+
bypass_validation = kwargs.get("bypass_validation", False)
|
|
349
|
+
|
|
389
350
|
for obj in batch:
|
|
390
351
|
parent_instances = {}
|
|
391
352
|
current_parent = None
|
|
@@ -393,32 +354,35 @@ class HookQuerySet(models.QuerySet):
|
|
|
393
354
|
parent_obj = self._create_parent_instance(
|
|
394
355
|
obj, model_class, current_parent
|
|
395
356
|
)
|
|
396
|
-
|
|
357
|
+
|
|
397
358
|
# Fire parent hooks if not bypassed
|
|
398
359
|
if not bypass_hooks:
|
|
399
360
|
ctx = HookContext(model_class)
|
|
400
361
|
if not bypass_validation:
|
|
401
362
|
engine.run(model_class, VALIDATE_CREATE, [parent_obj], ctx=ctx)
|
|
402
363
|
engine.run(model_class, BEFORE_CREATE, [parent_obj], ctx=ctx)
|
|
403
|
-
|
|
364
|
+
|
|
404
365
|
# Use Django's base manager to create the object and get PKs back
|
|
405
366
|
# This bypasses hooks and the MTI exception
|
|
406
367
|
field_values = {
|
|
407
368
|
field.name: getattr(parent_obj, field.name)
|
|
408
369
|
for field in model_class._meta.local_fields
|
|
409
|
-
if hasattr(parent_obj, field.name)
|
|
370
|
+
if hasattr(parent_obj, field.name)
|
|
371
|
+
and getattr(parent_obj, field.name) is not None
|
|
410
372
|
}
|
|
411
|
-
created_obj = model_class._base_manager.using(self.db).create(
|
|
412
|
-
|
|
373
|
+
created_obj = model_class._base_manager.using(self.db).create(
|
|
374
|
+
**field_values
|
|
375
|
+
)
|
|
376
|
+
|
|
413
377
|
# Update the parent_obj with the created object's PK
|
|
414
378
|
parent_obj.pk = created_obj.pk
|
|
415
379
|
parent_obj._state.adding = False
|
|
416
380
|
parent_obj._state.db = self.db
|
|
417
|
-
|
|
381
|
+
|
|
418
382
|
# Fire AFTER_CREATE hooks for parent
|
|
419
383
|
if not bypass_hooks:
|
|
420
384
|
engine.run(model_class, AFTER_CREATE, [parent_obj], ctx=ctx)
|
|
421
|
-
|
|
385
|
+
|
|
422
386
|
parent_instances[model_class] = parent_obj
|
|
423
387
|
current_parent = parent_obj
|
|
424
388
|
parent_objects_map[id(obj)] = parent_instances
|
|
@@ -436,10 +400,10 @@ class HookQuerySet(models.QuerySet):
|
|
|
436
400
|
if all_child_objects:
|
|
437
401
|
# Get the base manager's queryset
|
|
438
402
|
base_qs = child_model._base_manager.using(self.db)
|
|
439
|
-
|
|
403
|
+
|
|
440
404
|
# Use Django's exact approach: call _prepare_for_bulk_create then partition
|
|
441
405
|
base_qs._prepare_for_bulk_create(all_child_objects)
|
|
442
|
-
|
|
406
|
+
|
|
443
407
|
# Implement our own partition since itertools.partition might not be available
|
|
444
408
|
objs_without_pk, objs_with_pk = [], []
|
|
445
409
|
for obj in all_child_objects:
|
|
@@ -447,44 +411,50 @@ class HookQuerySet(models.QuerySet):
|
|
|
447
411
|
objs_with_pk.append(obj)
|
|
448
412
|
else:
|
|
449
413
|
objs_without_pk.append(obj)
|
|
450
|
-
|
|
414
|
+
|
|
451
415
|
# Use Django's internal _batched_insert method
|
|
452
416
|
opts = child_model._meta
|
|
453
417
|
# For child models in MTI, we need to include the foreign key to the parent
|
|
454
418
|
# but exclude the primary key since it's inherited
|
|
455
|
-
|
|
419
|
+
|
|
456
420
|
# Include all local fields except generated ones
|
|
457
421
|
# We need to include the foreign key to the parent (business_ptr)
|
|
458
422
|
fields = [f for f in opts.local_fields if not f.generated]
|
|
459
|
-
|
|
423
|
+
|
|
460
424
|
with transaction.atomic(using=self.db, savepoint=False):
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
425
|
+
if objs_with_pk:
|
|
426
|
+
returned_columns = base_qs._batched_insert(
|
|
427
|
+
objs_with_pk,
|
|
428
|
+
fields,
|
|
429
|
+
batch_size=len(objs_with_pk), # Use actual batch size
|
|
430
|
+
)
|
|
431
|
+
for obj_with_pk, results in zip(objs_with_pk, returned_columns):
|
|
432
|
+
for result, field in zip(results, opts.db_returning_fields):
|
|
433
|
+
if field != opts.pk:
|
|
434
|
+
setattr(obj_with_pk, field.attname, result)
|
|
435
|
+
for obj_with_pk in objs_with_pk:
|
|
436
|
+
obj_with_pk._state.adding = False
|
|
437
|
+
obj_with_pk._state.db = self.db
|
|
438
|
+
|
|
439
|
+
if objs_without_pk:
|
|
440
|
+
# For objects without PK, we still need to exclude primary key fields
|
|
441
|
+
fields = [
|
|
442
|
+
f
|
|
443
|
+
for f in fields
|
|
444
|
+
if not isinstance(f, AutoField) and not f.primary_key
|
|
445
|
+
]
|
|
446
|
+
returned_columns = base_qs._batched_insert(
|
|
447
|
+
objs_without_pk,
|
|
448
|
+
fields,
|
|
449
|
+
batch_size=len(objs_without_pk), # Use actual batch size
|
|
450
|
+
)
|
|
451
|
+
for obj_without_pk, results in zip(
|
|
452
|
+
objs_without_pk, returned_columns
|
|
453
|
+
):
|
|
454
|
+
for result, field in zip(results, opts.db_returning_fields):
|
|
455
|
+
setattr(obj_without_pk, field.attname, result)
|
|
456
|
+
obj_without_pk._state.adding = False
|
|
457
|
+
obj_without_pk._state.db = self.db
|
|
488
458
|
|
|
489
459
|
# Step 3: Update original objects with generated PKs and state
|
|
490
460
|
pk_field_name = child_model._meta.pk.name
|
|
@@ -537,15 +507,19 @@ class HookQuerySet(models.QuerySet):
|
|
|
537
507
|
value = getattr(source_obj, field.name, None)
|
|
538
508
|
if value is not None:
|
|
539
509
|
setattr(child_obj, field.name, value)
|
|
540
|
-
|
|
510
|
+
|
|
541
511
|
# Set parent links for MTI
|
|
542
512
|
for parent_model, parent_instance in parent_instances.items():
|
|
543
513
|
parent_link = child_model._meta.get_ancestor_link(parent_model)
|
|
544
514
|
if parent_link:
|
|
545
515
|
# Set both the foreign key value (the ID) and the object reference
|
|
546
516
|
# This follows Django's pattern in _set_pk_val
|
|
547
|
-
setattr(
|
|
548
|
-
|
|
517
|
+
setattr(
|
|
518
|
+
child_obj, parent_link.attname, parent_instance.pk
|
|
519
|
+
) # Set the foreign key value
|
|
520
|
+
setattr(
|
|
521
|
+
child_obj, parent_link.name, parent_instance
|
|
522
|
+
) # Set the object reference
|
|
549
523
|
|
|
550
524
|
# Handle auto_now_add and auto_now fields like Django does
|
|
551
525
|
for field in child_model._meta.local_fields:
|
|
@@ -558,4 +532,4 @@ class HookQuerySet(models.QuerySet):
|
|
|
558
532
|
elif hasattr(field, "auto_now") and field.auto_now:
|
|
559
533
|
field.pre_save(child_obj, add=True)
|
|
560
534
|
|
|
561
|
-
return child_obj
|
|
535
|
+
return child_obj
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "django-bulk-hooks"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.175"
|
|
4
4
|
description = "Hook-style hooks for Django bulk operations like bulk_create and bulk_update."
|
|
5
5
|
authors = ["Konrad Beck <konrad.beck@merchantcapital.co.za>"]
|
|
6
6
|
readme = "README.md"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|