django-bulk-hooks 0.1.103__py3-none-any.whl → 0.1.105__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 +150 -1
- {django_bulk_hooks-0.1.103.dist-info → django_bulk_hooks-0.1.105.dist-info}/METADATA +3 -3
- {django_bulk_hooks-0.1.103.dist-info → django_bulk_hooks-0.1.105.dist-info}/RECORD +5 -5
- {django_bulk_hooks-0.1.103.dist-info → django_bulk_hooks-0.1.105.dist-info}/WHEEL +1 -1
- {django_bulk_hooks-0.1.103.dist-info → django_bulk_hooks-0.1.105.dist-info}/LICENSE +0 -0
django_bulk_hooks/manager.py
CHANGED
|
@@ -22,6 +22,143 @@ class BulkHookManager(models.Manager):
|
|
|
22
22
|
def get_queryset(self):
|
|
23
23
|
return HookQuerySet(self.model, using=self._db)
|
|
24
24
|
|
|
25
|
+
def _has_multi_table_inheritance(self, model_cls):
|
|
26
|
+
"""
|
|
27
|
+
Check if this model uses multi-table inheritance.
|
|
28
|
+
"""
|
|
29
|
+
return (
|
|
30
|
+
model_cls._meta.parents and
|
|
31
|
+
not all(parent._meta.abstract for parent in model_cls._meta.parents.values())
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
def _get_base_model(self, model_cls):
|
|
35
|
+
"""
|
|
36
|
+
Get the base model (first non-abstract parent or self).
|
|
37
|
+
"""
|
|
38
|
+
base_model = model_cls
|
|
39
|
+
while base_model._meta.parents:
|
|
40
|
+
parent = next(iter(base_model._meta.parents.values()))
|
|
41
|
+
if not parent._meta.abstract:
|
|
42
|
+
base_model = parent
|
|
43
|
+
else:
|
|
44
|
+
break
|
|
45
|
+
return base_model
|
|
46
|
+
|
|
47
|
+
def _extract_base_objects(self, objs, model_cls):
|
|
48
|
+
"""
|
|
49
|
+
Extract base model objects from inherited objects.
|
|
50
|
+
"""
|
|
51
|
+
base_model = self._get_base_model(model_cls)
|
|
52
|
+
base_objects = []
|
|
53
|
+
|
|
54
|
+
for obj in objs:
|
|
55
|
+
base_obj = base_model()
|
|
56
|
+
for field in base_model._meta.fields:
|
|
57
|
+
# Skip ID field
|
|
58
|
+
if field.name == 'id':
|
|
59
|
+
continue
|
|
60
|
+
|
|
61
|
+
# Safely copy field values
|
|
62
|
+
try:
|
|
63
|
+
if hasattr(obj, field.name):
|
|
64
|
+
setattr(base_obj, field.name, getattr(obj, field.name))
|
|
65
|
+
except (AttributeError, ValueError):
|
|
66
|
+
# Skip fields that can't be copied
|
|
67
|
+
continue
|
|
68
|
+
|
|
69
|
+
base_objects.append(base_obj)
|
|
70
|
+
|
|
71
|
+
return base_objects
|
|
72
|
+
|
|
73
|
+
def _extract_child_objects(self, objs, model_cls):
|
|
74
|
+
"""
|
|
75
|
+
Extract child model objects from inherited objects.
|
|
76
|
+
"""
|
|
77
|
+
child_objects = []
|
|
78
|
+
|
|
79
|
+
for obj in objs:
|
|
80
|
+
child_obj = model_cls()
|
|
81
|
+
child_obj.pk = obj.pk # Set the same PK as base
|
|
82
|
+
|
|
83
|
+
# Copy only fields specific to this model
|
|
84
|
+
for field in model_cls._meta.fields:
|
|
85
|
+
# Skip ID field and fields that don't belong to this model
|
|
86
|
+
if field.name == 'id':
|
|
87
|
+
continue
|
|
88
|
+
|
|
89
|
+
# Check if this field belongs to the current model
|
|
90
|
+
# Use a safer way to check field ownership
|
|
91
|
+
try:
|
|
92
|
+
if hasattr(field, 'model') and field.model == model_cls:
|
|
93
|
+
# This field belongs to the current model
|
|
94
|
+
if hasattr(obj, field.name):
|
|
95
|
+
setattr(child_obj, field.name, getattr(obj, field.name))
|
|
96
|
+
except AttributeError:
|
|
97
|
+
# Skip fields that don't have proper model reference
|
|
98
|
+
continue
|
|
99
|
+
|
|
100
|
+
child_objects.append(child_obj)
|
|
101
|
+
|
|
102
|
+
return child_objects
|
|
103
|
+
|
|
104
|
+
def _bulk_create_inherited(self, objs, **kwargs):
|
|
105
|
+
"""
|
|
106
|
+
Handle bulk create for inherited models by handling each table separately.
|
|
107
|
+
"""
|
|
108
|
+
if not objs:
|
|
109
|
+
return []
|
|
110
|
+
|
|
111
|
+
model_cls = self.model
|
|
112
|
+
result = []
|
|
113
|
+
|
|
114
|
+
# Group objects by their actual class
|
|
115
|
+
objects_by_class = {}
|
|
116
|
+
for obj in objs:
|
|
117
|
+
obj_class = obj.__class__
|
|
118
|
+
if obj_class not in objects_by_class:
|
|
119
|
+
objects_by_class[obj_class] = []
|
|
120
|
+
objects_by_class[obj_class].append(obj)
|
|
121
|
+
|
|
122
|
+
for obj_class, class_objects in objects_by_class.items():
|
|
123
|
+
try:
|
|
124
|
+
# Check if this class has multi-table inheritance
|
|
125
|
+
parent_models = [p for p in obj_class._meta.get_parent_list()
|
|
126
|
+
if not p._meta.abstract]
|
|
127
|
+
|
|
128
|
+
if not parent_models:
|
|
129
|
+
# No inheritance, use standard bulk_create
|
|
130
|
+
chunk_result = super(models.Manager, self).bulk_create(class_objects, **kwargs)
|
|
131
|
+
result.extend(chunk_result)
|
|
132
|
+
continue
|
|
133
|
+
|
|
134
|
+
# Handle multi-table inheritance
|
|
135
|
+
# Step 1: Bulk create base objects without hooks
|
|
136
|
+
base_objects = self._extract_base_objects(class_objects, obj_class)
|
|
137
|
+
created_base = super(models.Manager, self).bulk_create(base_objects, **kwargs)
|
|
138
|
+
|
|
139
|
+
# Step 2: Update original objects with base IDs
|
|
140
|
+
for obj, base_obj in zip(class_objects, created_base):
|
|
141
|
+
obj.pk = base_obj.pk
|
|
142
|
+
obj._state.adding = False
|
|
143
|
+
|
|
144
|
+
# Step 3: Bulk create child objects without hooks
|
|
145
|
+
child_objects = self._extract_child_objects(class_objects, obj_class)
|
|
146
|
+
if child_objects:
|
|
147
|
+
# Use _base_manager to avoid recursion
|
|
148
|
+
obj_class._base_manager.bulk_create(child_objects, **kwargs)
|
|
149
|
+
|
|
150
|
+
result.extend(class_objects)
|
|
151
|
+
|
|
152
|
+
except Exception as e:
|
|
153
|
+
# Add debugging information
|
|
154
|
+
import logging
|
|
155
|
+
logger = logging.getLogger(__name__)
|
|
156
|
+
logger.error(f"Error in _bulk_create_inherited for {obj_class}: {e}")
|
|
157
|
+
logger.error(f"Model fields: {[f.name for f in obj_class._meta.fields]}")
|
|
158
|
+
raise
|
|
159
|
+
|
|
160
|
+
return result
|
|
161
|
+
|
|
25
162
|
@transaction.atomic
|
|
26
163
|
def bulk_update(
|
|
27
164
|
self, objs, fields, bypass_hooks=False, bypass_validation=False, **kwargs
|
|
@@ -116,6 +253,9 @@ class BulkHookManager(models.Manager):
|
|
|
116
253
|
f"bulk_create expected instances of {model_cls.__name__}, but got {set(type(obj).__name__ for obj in objs)}"
|
|
117
254
|
)
|
|
118
255
|
|
|
256
|
+
# Check if this model uses multi-table inheritance
|
|
257
|
+
has_multi_table_inheritance = self._has_multi_table_inheritance(model_cls)
|
|
258
|
+
|
|
119
259
|
result = []
|
|
120
260
|
|
|
121
261
|
if not bypass_hooks:
|
|
@@ -128,9 +268,18 @@ class BulkHookManager(models.Manager):
|
|
|
128
268
|
# Then run business logic hooks
|
|
129
269
|
engine.run(model_cls, BEFORE_CREATE, objs, ctx=ctx)
|
|
130
270
|
|
|
271
|
+
# Perform bulk create in chunks
|
|
131
272
|
for i in range(0, len(objs), self.CHUNK_SIZE):
|
|
132
273
|
chunk = objs[i : i + self.CHUNK_SIZE]
|
|
133
|
-
|
|
274
|
+
|
|
275
|
+
if has_multi_table_inheritance:
|
|
276
|
+
# Use our multi-table bulk create
|
|
277
|
+
created_chunk = self._bulk_create_inherited(chunk, **kwargs)
|
|
278
|
+
else:
|
|
279
|
+
# Use Django's standard bulk create
|
|
280
|
+
created_chunk = super(models.Manager, self).bulk_create(chunk, **kwargs)
|
|
281
|
+
|
|
282
|
+
result.extend(created_chunk)
|
|
134
283
|
|
|
135
284
|
if not bypass_hooks:
|
|
136
285
|
engine.run(model_cls, AFTER_CREATE, result, ctx=ctx)
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
2
|
Name: django-bulk-hooks
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.105
|
|
4
4
|
Summary: Hook-style hooks for Django bulk operations like bulk_create and bulk_update.
|
|
5
|
-
Home-page: https://github.com/AugendLimited/django-bulk-hooks
|
|
6
5
|
License: MIT
|
|
7
6
|
Keywords: django,bulk,hooks
|
|
8
7
|
Author: Konrad Beck
|
|
@@ -14,6 +13,7 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
14
13
|
Classifier: Programming Language :: Python :: 3.12
|
|
15
14
|
Classifier: Programming Language :: Python :: 3.13
|
|
16
15
|
Requires-Dist: Django (>=4.0)
|
|
16
|
+
Project-URL: Homepage, https://github.com/AugendLimited/django-bulk-hooks
|
|
17
17
|
Project-URL: Repository, https://github.com/AugendLimited/django-bulk-hooks
|
|
18
18
|
Description-Content-Type: text/markdown
|
|
19
19
|
|
|
@@ -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=Q9Oss6kWCgyL-08t3XYplLSQSLILMAX7Qv9eg7-VgKA,13147
|
|
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.105.dist-info/LICENSE,sha256=dguKIcbDGeZD-vXWdLyErPUALYOvtX_fO4Zjhq481uk,1088
|
|
15
|
+
django_bulk_hooks-0.1.105.dist-info/METADATA,sha256=PVNvKCg4CciKQTGbe7p4L38tsF9kuum22H7zaPu8oNs,6951
|
|
16
|
+
django_bulk_hooks-0.1.105.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
17
|
+
django_bulk_hooks-0.1.105.dist-info/RECORD,,
|
|
File without changes
|