django-bulk-hooks 0.1.86__py3-none-any.whl → 0.1.88__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/conditions.py +88 -63
- django_bulk_hooks/manager.py +132 -44
- {django_bulk_hooks-0.1.86.dist-info → django_bulk_hooks-0.1.88.dist-info}/METADATA +1 -1
- {django_bulk_hooks-0.1.86.dist-info → django_bulk_hooks-0.1.88.dist-info}/RECORD +6 -6
- {django_bulk_hooks-0.1.86.dist-info → django_bulk_hooks-0.1.88.dist-info}/LICENSE +0 -0
- {django_bulk_hooks-0.1.86.dist-info → django_bulk_hooks-0.1.88.dist-info}/WHEEL +0 -0
django_bulk_hooks/conditions.py
CHANGED
|
@@ -195,90 +195,104 @@ class HookCondition:
|
|
|
195
195
|
def __invert__(self):
|
|
196
196
|
return NotCondition(self)
|
|
197
197
|
|
|
198
|
+
def get_required_fields(self):
|
|
199
|
+
"""
|
|
200
|
+
Returns a set of field names that this condition needs to evaluate.
|
|
201
|
+
Override in subclasses to specify required fields.
|
|
202
|
+
"""
|
|
203
|
+
return set()
|
|
198
204
|
|
|
199
|
-
|
|
200
|
-
|
|
205
|
+
|
|
206
|
+
class IsEqual(HookCondition):
|
|
207
|
+
def __init__(self, field, value):
|
|
201
208
|
self.field = field
|
|
202
209
|
self.value = value
|
|
203
|
-
self.only_on_change = only_on_change
|
|
204
210
|
|
|
205
211
|
def check(self, instance, original_instance=None):
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
if original_instance is None:
|
|
209
|
-
return False
|
|
210
|
-
previous = resolve_dotted_attr(original_instance, self.field)
|
|
211
|
-
return previous == self.value and current != self.value
|
|
212
|
-
else:
|
|
213
|
-
return current != self.value
|
|
212
|
+
current_value = resolve_dotted_attr(instance, self.field)
|
|
213
|
+
return current_value == self.value
|
|
214
214
|
|
|
215
|
+
def get_required_fields(self):
|
|
216
|
+
return {self.field.split('.')[0]}
|
|
215
217
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
+
|
|
219
|
+
class IsNotEqual(HookCondition):
|
|
220
|
+
def __init__(self, field, value):
|
|
218
221
|
self.field = field
|
|
219
222
|
self.value = value
|
|
220
|
-
self.only_on_change = only_on_change
|
|
221
223
|
|
|
222
224
|
def check(self, instance, original_instance=None):
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
if original_instance is None:
|
|
226
|
-
return False
|
|
227
|
-
previous = resolve_dotted_attr(original_instance, self.field)
|
|
228
|
-
return previous != self.value and current == self.value
|
|
229
|
-
else:
|
|
230
|
-
return current == self.value
|
|
225
|
+
current_value = resolve_dotted_attr(instance, self.field)
|
|
226
|
+
return current_value != self.value
|
|
231
227
|
|
|
228
|
+
def get_required_fields(self):
|
|
229
|
+
return {self.field.split('.')[0]}
|
|
232
230
|
|
|
233
|
-
|
|
234
|
-
|
|
231
|
+
|
|
232
|
+
class WasEqual(HookCondition):
|
|
233
|
+
def __init__(self, field, value):
|
|
235
234
|
self.field = field
|
|
236
|
-
self.
|
|
235
|
+
self.value = value
|
|
237
236
|
|
|
238
237
|
def check(self, instance, original_instance=None):
|
|
239
|
-
if
|
|
238
|
+
if original_instance is None:
|
|
240
239
|
return False
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
return (current != previous) == self.has_changed
|
|
240
|
+
original_value = resolve_dotted_attr(original_instance, self.field)
|
|
241
|
+
return original_value == self.value
|
|
244
242
|
|
|
243
|
+
def get_required_fields(self):
|
|
244
|
+
return {self.field.split('.')[0]}
|
|
245
245
|
|
|
246
|
-
|
|
247
|
-
|
|
246
|
+
|
|
247
|
+
class HasChanged(HookCondition):
|
|
248
|
+
def __init__(self, field, has_changed=True):
|
|
248
249
|
"""
|
|
249
|
-
Check if a field's
|
|
250
|
-
|
|
250
|
+
Check if a field's value has changed or remained the same.
|
|
251
|
+
|
|
252
|
+
Args:
|
|
253
|
+
field: The field name to check
|
|
254
|
+
has_changed: If True (default), condition passes when field has changed.
|
|
255
|
+
If False, condition passes when field has remained the same.
|
|
256
|
+
This is useful for:
|
|
257
|
+
- Detecting stable/unchanged fields
|
|
258
|
+
- Validating field immutability
|
|
259
|
+
- Ensuring critical fields remain constant
|
|
260
|
+
- State machine validations
|
|
251
261
|
"""
|
|
252
262
|
self.field = field
|
|
253
|
-
self.
|
|
254
|
-
self.only_on_change = only_on_change
|
|
263
|
+
self.has_changed = has_changed
|
|
255
264
|
|
|
256
265
|
def check(self, instance, original_instance=None):
|
|
257
266
|
if original_instance is None:
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
267
|
+
# For new instances:
|
|
268
|
+
# - If we're checking for changes (has_changed=True), return False since there's no change yet
|
|
269
|
+
# - If we're checking for stability (has_changed=False), return True since it's technically unchanged
|
|
270
|
+
return not self.has_changed
|
|
271
|
+
|
|
272
|
+
current_value = resolve_dotted_attr(instance, self.field)
|
|
273
|
+
original_value = resolve_dotted_attr(original_instance, self.field)
|
|
274
|
+
return (current_value != original_value) == self.has_changed
|
|
275
|
+
|
|
276
|
+
def get_required_fields(self):
|
|
277
|
+
return {self.field.split('.')[0]}
|
|
265
278
|
|
|
266
279
|
|
|
267
280
|
class ChangesTo(HookCondition):
|
|
268
281
|
def __init__(self, field, value):
|
|
269
|
-
"""
|
|
270
|
-
Check if a field's value has changed to `value`.
|
|
271
|
-
Only returns True when original value != value and current value == value.
|
|
272
|
-
"""
|
|
273
282
|
self.field = field
|
|
274
283
|
self.value = value
|
|
275
284
|
|
|
276
285
|
def check(self, instance, original_instance=None):
|
|
277
286
|
if original_instance is None:
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
287
|
+
current_value = resolve_dotted_attr(instance, self.field)
|
|
288
|
+
return current_value == self.value
|
|
289
|
+
|
|
290
|
+
current_value = resolve_dotted_attr(instance, self.field)
|
|
291
|
+
original_value = resolve_dotted_attr(original_instance, self.field)
|
|
292
|
+
return current_value == self.value and current_value != original_value
|
|
293
|
+
|
|
294
|
+
def get_required_fields(self):
|
|
295
|
+
return {self.field.split('.')[0]}
|
|
282
296
|
|
|
283
297
|
|
|
284
298
|
class IsGreaterThan(HookCondition):
|
|
@@ -322,30 +336,41 @@ class IsLessThanOrEqual(HookCondition):
|
|
|
322
336
|
|
|
323
337
|
|
|
324
338
|
class AndCondition(HookCondition):
|
|
325
|
-
def __init__(self,
|
|
326
|
-
self.
|
|
327
|
-
self.
|
|
339
|
+
def __init__(self, condition1, condition2):
|
|
340
|
+
self.condition1 = condition1
|
|
341
|
+
self.condition2 = condition2
|
|
328
342
|
|
|
329
343
|
def check(self, instance, original_instance=None):
|
|
330
|
-
return
|
|
331
|
-
instance, original_instance
|
|
344
|
+
return (
|
|
345
|
+
self.condition1.check(instance, original_instance)
|
|
346
|
+
and self.condition2.check(instance, original_instance)
|
|
332
347
|
)
|
|
333
348
|
|
|
349
|
+
def get_required_fields(self):
|
|
350
|
+
return self.condition1.get_required_fields() | self.condition2.get_required_fields()
|
|
351
|
+
|
|
334
352
|
|
|
335
353
|
class OrCondition(HookCondition):
|
|
336
|
-
def __init__(self,
|
|
337
|
-
self.
|
|
338
|
-
self.
|
|
354
|
+
def __init__(self, condition1, condition2):
|
|
355
|
+
self.condition1 = condition1
|
|
356
|
+
self.condition2 = condition2
|
|
339
357
|
|
|
340
358
|
def check(self, instance, original_instance=None):
|
|
341
|
-
return
|
|
342
|
-
instance, original_instance
|
|
359
|
+
return (
|
|
360
|
+
self.condition1.check(instance, original_instance)
|
|
361
|
+
or self.condition2.check(instance, original_instance)
|
|
343
362
|
)
|
|
344
363
|
|
|
364
|
+
def get_required_fields(self):
|
|
365
|
+
return self.condition1.get_required_fields() | self.condition2.get_required_fields()
|
|
366
|
+
|
|
345
367
|
|
|
346
368
|
class NotCondition(HookCondition):
|
|
347
|
-
def __init__(self,
|
|
348
|
-
self.
|
|
369
|
+
def __init__(self, condition):
|
|
370
|
+
self.condition = condition
|
|
349
371
|
|
|
350
372
|
def check(self, instance, original_instance=None):
|
|
351
|
-
return not self.
|
|
373
|
+
return not self.condition.check(instance, original_instance)
|
|
374
|
+
|
|
375
|
+
def get_required_fields(self):
|
|
376
|
+
return self.condition.get_required_fields()
|
django_bulk_hooks/manager.py
CHANGED
|
@@ -17,15 +17,82 @@ from django_bulk_hooks.queryset import HookQuerySet
|
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
class BulkHookManager(models.Manager):
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
# Default chunk sizes - can be overridden per model
|
|
21
|
+
DEFAULT_CHUNK_SIZE = 200
|
|
22
|
+
DEFAULT_RELATED_CHUNK_SIZE = 500 # Higher for related object fetching
|
|
23
|
+
|
|
24
|
+
def __init__(self):
|
|
25
|
+
super().__init__()
|
|
26
|
+
self._chunk_size = self.DEFAULT_CHUNK_SIZE
|
|
27
|
+
self._related_chunk_size = self.DEFAULT_RELATED_CHUNK_SIZE
|
|
28
|
+
self._prefetch_related_fields = set()
|
|
29
|
+
self._select_related_fields = set()
|
|
30
|
+
|
|
31
|
+
def configure(self, chunk_size=None, related_chunk_size=None,
|
|
32
|
+
select_related=None, prefetch_related=None):
|
|
33
|
+
"""
|
|
34
|
+
Configure bulk operation parameters for this manager.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
chunk_size: Number of objects to process in each bulk operation chunk
|
|
38
|
+
related_chunk_size: Number of objects to fetch in each related object query
|
|
39
|
+
select_related: List of fields to always select_related in bulk operations
|
|
40
|
+
prefetch_related: List of fields to always prefetch_related in bulk operations
|
|
41
|
+
"""
|
|
42
|
+
if chunk_size is not None:
|
|
43
|
+
self._chunk_size = chunk_size
|
|
44
|
+
if related_chunk_size is not None:
|
|
45
|
+
self._related_chunk_size = related_chunk_size
|
|
46
|
+
if select_related:
|
|
47
|
+
self._select_related_fields.update(select_related)
|
|
48
|
+
if prefetch_related:
|
|
49
|
+
self._prefetch_related_fields.update(prefetch_related)
|
|
50
|
+
|
|
51
|
+
def _load_originals_optimized(self, pks, fields_to_fetch=None):
|
|
52
|
+
"""
|
|
53
|
+
Optimized loading of original instances with smart batching and field selection.
|
|
54
|
+
"""
|
|
55
|
+
queryset = self.model.objects.filter(pk__in=pks)
|
|
56
|
+
|
|
57
|
+
# Only select specific fields if provided
|
|
58
|
+
if fields_to_fetch:
|
|
59
|
+
queryset = queryset.only('pk', *fields_to_fetch)
|
|
60
|
+
|
|
61
|
+
# Apply configured related field optimizations
|
|
62
|
+
if self._select_related_fields:
|
|
63
|
+
queryset = queryset.select_related(*self._select_related_fields)
|
|
64
|
+
if self._prefetch_related_fields:
|
|
65
|
+
queryset = queryset.prefetch_related(*self._prefetch_related_fields)
|
|
66
|
+
|
|
67
|
+
# Batch load in chunks to avoid memory issues
|
|
68
|
+
all_originals = []
|
|
69
|
+
for i in range(0, len(pks), self._related_chunk_size):
|
|
70
|
+
chunk_pks = pks[i:i + self._related_chunk_size]
|
|
71
|
+
chunk_originals = list(queryset.filter(pk__in=chunk_pks))
|
|
72
|
+
all_originals.extend(chunk_originals)
|
|
73
|
+
|
|
74
|
+
return all_originals
|
|
75
|
+
|
|
76
|
+
def _get_fields_to_fetch(self, objs, fields):
|
|
77
|
+
"""
|
|
78
|
+
Determine which fields need to be fetched based on what's being updated
|
|
79
|
+
and what's needed for hooks.
|
|
80
|
+
"""
|
|
81
|
+
fields_to_fetch = set(fields)
|
|
82
|
+
|
|
83
|
+
# Add fields needed by registered hooks
|
|
84
|
+
from django_bulk_hooks.registry import get_hooks
|
|
85
|
+
hooks = get_hooks(self.model, "before_update") + get_hooks(self.model, "after_update")
|
|
86
|
+
|
|
87
|
+
for handler_cls, method_name, condition, _ in hooks:
|
|
88
|
+
if condition:
|
|
89
|
+
# If there's a condition, we need all fields it might access
|
|
90
|
+
fields_to_fetch.update(condition.get_required_fields())
|
|
91
|
+
|
|
92
|
+
return fields_to_fetch
|
|
24
93
|
|
|
25
94
|
@transaction.atomic
|
|
26
|
-
def bulk_update(
|
|
27
|
-
self, objs, fields, bypass_hooks=False, bypass_validation=False, **kwargs
|
|
28
|
-
):
|
|
95
|
+
def bulk_update(self, objs, fields, bypass_hooks=False, bypass_validation=False, **kwargs):
|
|
29
96
|
if not objs:
|
|
30
97
|
return []
|
|
31
98
|
|
|
@@ -37,35 +104,42 @@ class BulkHookManager(models.Manager):
|
|
|
37
104
|
)
|
|
38
105
|
|
|
39
106
|
if not bypass_hooks:
|
|
40
|
-
#
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
107
|
+
# Determine which fields we need to fetch
|
|
108
|
+
fields_to_fetch = self._get_fields_to_fetch(objs, fields)
|
|
109
|
+
|
|
110
|
+
# Load originals efficiently
|
|
111
|
+
pks = [obj.pk for obj in objs if obj.pk is not None]
|
|
112
|
+
originals = self._load_originals_optimized(pks, fields_to_fetch)
|
|
113
|
+
|
|
114
|
+
# Create a mapping for quick lookup
|
|
115
|
+
original_map = {obj.pk: obj for obj in originals}
|
|
116
|
+
|
|
117
|
+
# Align originals with new instances
|
|
118
|
+
aligned_originals = [original_map.get(obj.pk) for obj in objs]
|
|
44
119
|
|
|
45
120
|
ctx = HookContext(model_cls)
|
|
46
121
|
|
|
47
122
|
# Run validation hooks first
|
|
48
123
|
if not bypass_validation:
|
|
49
|
-
engine.run(model_cls, VALIDATE_UPDATE, objs,
|
|
124
|
+
engine.run(model_cls, VALIDATE_UPDATE, objs, aligned_originals, ctx=ctx)
|
|
50
125
|
|
|
51
126
|
# Then run business logic hooks
|
|
52
|
-
engine.run(model_cls, BEFORE_UPDATE, objs,
|
|
127
|
+
engine.run(model_cls, BEFORE_UPDATE, objs, aligned_originals, ctx=ctx)
|
|
53
128
|
|
|
54
129
|
# Automatically detect fields that were modified during BEFORE_UPDATE hooks
|
|
55
|
-
modified_fields = self._detect_modified_fields(objs,
|
|
130
|
+
modified_fields = self._detect_modified_fields(objs, aligned_originals)
|
|
56
131
|
if modified_fields:
|
|
57
|
-
# Convert to set for efficient union operation
|
|
58
132
|
fields_set = set(fields)
|
|
59
133
|
fields_set.update(modified_fields)
|
|
60
134
|
fields = list(fields_set)
|
|
61
135
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
136
|
+
# Process in chunks
|
|
137
|
+
for i in range(0, len(objs), self._chunk_size):
|
|
138
|
+
chunk = objs[i:i + self._chunk_size]
|
|
65
139
|
super(models.Manager, self).bulk_update(chunk, fields, **kwargs)
|
|
66
140
|
|
|
67
141
|
if not bypass_hooks:
|
|
68
|
-
engine.run(model_cls, AFTER_UPDATE, objs,
|
|
142
|
+
engine.run(model_cls, AFTER_UPDATE, objs, aligned_originals, ctx=ctx)
|
|
69
143
|
|
|
70
144
|
return objs
|
|
71
145
|
|
|
@@ -114,42 +188,52 @@ class BulkHookManager(models.Manager):
|
|
|
114
188
|
|
|
115
189
|
@transaction.atomic
|
|
116
190
|
def bulk_create(self, objs, bypass_hooks=False, bypass_validation=False, **kwargs):
|
|
191
|
+
if not objs:
|
|
192
|
+
return []
|
|
193
|
+
|
|
117
194
|
model_cls = self.model
|
|
195
|
+
result = []
|
|
118
196
|
|
|
119
197
|
if any(not isinstance(obj, model_cls) for obj in objs):
|
|
120
198
|
raise TypeError(
|
|
121
199
|
f"bulk_create expected instances of {model_cls.__name__}, but got {set(type(obj).__name__ for obj in objs)}"
|
|
122
200
|
)
|
|
123
201
|
|
|
124
|
-
result = []
|
|
125
|
-
|
|
126
202
|
if not bypass_hooks:
|
|
127
203
|
ctx = HookContext(model_cls)
|
|
128
204
|
|
|
129
|
-
#
|
|
205
|
+
# Process validation in chunks to avoid memory issues
|
|
130
206
|
if not bypass_validation:
|
|
131
|
-
|
|
207
|
+
for i in range(0, len(objs), self._chunk_size):
|
|
208
|
+
chunk = objs[i:i + self._chunk_size]
|
|
209
|
+
engine.run(model_cls, VALIDATE_CREATE, chunk, ctx=ctx)
|
|
132
210
|
|
|
133
|
-
#
|
|
134
|
-
|
|
211
|
+
# Process before_create hooks in chunks
|
|
212
|
+
for i in range(0, len(objs), self._chunk_size):
|
|
213
|
+
chunk = objs[i:i + self._chunk_size]
|
|
214
|
+
engine.run(model_cls, BEFORE_CREATE, chunk, ctx=ctx)
|
|
135
215
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
216
|
+
# Perform bulk create in chunks
|
|
217
|
+
for i in range(0, len(objs), self._chunk_size):
|
|
218
|
+
chunk = objs[i:i + self._chunk_size]
|
|
219
|
+
created_chunk = super(models.Manager, self).bulk_create(chunk, **kwargs)
|
|
220
|
+
result.extend(created_chunk)
|
|
139
221
|
|
|
140
222
|
if not bypass_hooks:
|
|
141
|
-
|
|
223
|
+
# Process after_create hooks in chunks
|
|
224
|
+
for i in range(0, len(result), self._chunk_size):
|
|
225
|
+
chunk = result[i:i + self._chunk_size]
|
|
226
|
+
engine.run(model_cls, AFTER_CREATE, chunk, ctx=ctx)
|
|
142
227
|
|
|
143
228
|
return result
|
|
144
229
|
|
|
145
230
|
@transaction.atomic
|
|
146
|
-
def bulk_delete(
|
|
147
|
-
self, objs, batch_size=None, bypass_hooks=False, bypass_validation=False
|
|
148
|
-
):
|
|
231
|
+
def bulk_delete(self, objs, batch_size=None, bypass_hooks=False, bypass_validation=False):
|
|
149
232
|
if not objs:
|
|
150
233
|
return []
|
|
151
234
|
|
|
152
235
|
model_cls = self.model
|
|
236
|
+
chunk_size = batch_size or self._chunk_size
|
|
153
237
|
|
|
154
238
|
if any(not isinstance(obj, model_cls) for obj in objs):
|
|
155
239
|
raise TypeError(
|
|
@@ -159,21 +243,25 @@ class BulkHookManager(models.Manager):
|
|
|
159
243
|
ctx = HookContext(model_cls)
|
|
160
244
|
|
|
161
245
|
if not bypass_hooks:
|
|
162
|
-
#
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
246
|
+
# Process hooks in chunks
|
|
247
|
+
for i in range(0, len(objs), chunk_size):
|
|
248
|
+
chunk = objs[i:i + chunk_size]
|
|
249
|
+
|
|
250
|
+
if not bypass_validation:
|
|
251
|
+
engine.run(model_cls, VALIDATE_DELETE, chunk, ctx=ctx)
|
|
252
|
+
engine.run(model_cls, BEFORE_DELETE, chunk, ctx=ctx)
|
|
253
|
+
|
|
254
|
+
# Collect PKs and delete in chunks
|
|
169
255
|
pks = [obj.pk for obj in objs if obj.pk is not None]
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
model_cls._base_manager.filter(pk__in=pks).delete()
|
|
256
|
+
for i in range(0, len(pks), chunk_size):
|
|
257
|
+
chunk_pks = pks[i:i + chunk_size]
|
|
258
|
+
model_cls._base_manager.filter(pk__in=chunk_pks).delete()
|
|
174
259
|
|
|
175
260
|
if not bypass_hooks:
|
|
176
|
-
|
|
261
|
+
# Process after_delete hooks in chunks
|
|
262
|
+
for i in range(0, len(objs), chunk_size):
|
|
263
|
+
chunk = objs[i:i + chunk_size]
|
|
264
|
+
engine.run(model_cls, AFTER_DELETE, chunk, ctx=ctx)
|
|
177
265
|
|
|
178
266
|
return objs
|
|
179
267
|
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
django_bulk_hooks/__init__.py,sha256=2PcJ6xz7t7Du0nmLO_5732G6u_oZTygogG0fKESRHHk,1082
|
|
2
|
-
django_bulk_hooks/conditions.py,sha256=
|
|
2
|
+
django_bulk_hooks/conditions.py,sha256=zeq1q4YgBV29kSkCpcAySA1CIMbVbL4TzNPVaESTsXw,13359
|
|
3
3
|
django_bulk_hooks/constants.py,sha256=3x1H1fSUUNo0DZONN7GUVDuySZctTR-jtByBHmAIX5w,303
|
|
4
4
|
django_bulk_hooks/context.py,sha256=HVDT73uSzvgrOR6mdXTvsBm3hLOgBU8ant_mB7VlFuM,380
|
|
5
5
|
django_bulk_hooks/decorators.py,sha256=zstmb27dKcOHu3Atg7cauewCTzPvUmq03mzVKJRi56o,7230
|
|
6
6
|
django_bulk_hooks/engine.py,sha256=CUcdBvXE6mq8fBhMP_Q5Z5oygJiVVAuCjNTf-X7o2j0,2826
|
|
7
7
|
django_bulk_hooks/enums.py,sha256=Zo8_tJzuzZ2IKfVc7gZ-0tWPT8q1QhqZbAyoh9ZVJbs,381
|
|
8
8
|
django_bulk_hooks/handler.py,sha256=Qpg_zT6SsQiTlhduvzXxPdG6uynjyR2fBjj-R6HZiXI,4861
|
|
9
|
-
django_bulk_hooks/manager.py,sha256
|
|
9
|
+
django_bulk_hooks/manager.py,sha256=DcVosEA4RS79KSYgw3Z14_a9Sd8CfxNNc5F3eSb8xc0,11459
|
|
10
10
|
django_bulk_hooks/models.py,sha256=9KvWkmrR0wbTHN6r7-FrSSO9ViS83NvG7iXLBw_iDZs,4793
|
|
11
11
|
django_bulk_hooks/queryset.py,sha256=7lLqhZ-XOYsZ1I3Loxi4Nhz79M8HlTYE413AW8nyeDI,1330
|
|
12
12
|
django_bulk_hooks/registry.py,sha256=Vh78exKYcdZhM27120kQm-iXGOjd_kf9ZUYBZ8eQ2V0,683
|
|
13
|
-
django_bulk_hooks-0.1.
|
|
14
|
-
django_bulk_hooks-0.1.
|
|
15
|
-
django_bulk_hooks-0.1.
|
|
16
|
-
django_bulk_hooks-0.1.
|
|
13
|
+
django_bulk_hooks-0.1.88.dist-info/LICENSE,sha256=dguKIcbDGeZD-vXWdLyErPUALYOvtX_fO4Zjhq481uk,1088
|
|
14
|
+
django_bulk_hooks-0.1.88.dist-info/METADATA,sha256=ecbVGBMosEeVhBnCLEhL1eyZpchnxXc88hiHdNx4-HM,9051
|
|
15
|
+
django_bulk_hooks-0.1.88.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
16
|
+
django_bulk_hooks-0.1.88.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|