django-bulk-hooks 0.2.9__py3-none-any.whl → 0.2.93__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.
- django_bulk_hooks/__init__.py +20 -27
- django_bulk_hooks/changeset.py +214 -230
- django_bulk_hooks/conditions.py +12 -12
- django_bulk_hooks/decorators.py +68 -26
- django_bulk_hooks/dispatcher.py +369 -58
- django_bulk_hooks/factory.py +541 -565
- django_bulk_hooks/handler.py +106 -115
- django_bulk_hooks/helpers.py +258 -99
- django_bulk_hooks/manager.py +134 -130
- django_bulk_hooks/models.py +89 -76
- django_bulk_hooks/operations/__init__.py +5 -5
- django_bulk_hooks/operations/analyzer.py +299 -172
- django_bulk_hooks/operations/bulk_executor.py +742 -437
- django_bulk_hooks/operations/coordinator.py +928 -472
- django_bulk_hooks/operations/field_utils.py +335 -0
- django_bulk_hooks/operations/mti_handler.py +696 -473
- django_bulk_hooks/operations/mti_plans.py +103 -87
- django_bulk_hooks/operations/record_classifier.py +196 -0
- django_bulk_hooks/queryset.py +233 -189
- django_bulk_hooks/registry.py +276 -288
- {django_bulk_hooks-0.2.9.dist-info → django_bulk_hooks-0.2.93.dist-info}/METADATA +55 -4
- django_bulk_hooks-0.2.93.dist-info/RECORD +27 -0
- django_bulk_hooks/debug_utils.py +0 -145
- django_bulk_hooks-0.2.9.dist-info/RECORD +0 -26
- {django_bulk_hooks-0.2.9.dist-info → django_bulk_hooks-0.2.93.dist-info}/LICENSE +0 -0
- {django_bulk_hooks-0.2.9.dist-info → django_bulk_hooks-0.2.93.dist-info}/WHEEL +0 -0
django_bulk_hooks/registry.py
CHANGED
|
@@ -1,288 +1,276 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Central registry for hook handlers.
|
|
3
|
-
|
|
4
|
-
Provides thread-safe registration and lookup of hooks with
|
|
5
|
-
deterministic priority ordering.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
import logging
|
|
9
|
-
import threading
|
|
10
|
-
from collections.abc import Callable
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
self.
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
hook_info
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
self._hooks
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
)
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
"""
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
"""
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
self
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
_registry
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
"""
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
registry = get_registry()
|
|
278
|
-
registry.clear()
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
def list_all_hooks() -> Dict[Tuple[Type, str], List[HookInfo]]:
|
|
282
|
-
"""
|
|
283
|
-
List all registered hooks (backward-compatible function).
|
|
284
|
-
|
|
285
|
-
Delegates to the global registry instance.
|
|
286
|
-
"""
|
|
287
|
-
registry = get_registry()
|
|
288
|
-
return registry.list_all()
|
|
1
|
+
"""
|
|
2
|
+
Central registry for hook handlers.
|
|
3
|
+
|
|
4
|
+
Provides thread-safe registration and lookup of hooks with
|
|
5
|
+
deterministic priority ordering.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
import threading
|
|
10
|
+
from collections.abc import Callable
|
|
11
|
+
|
|
12
|
+
from django_bulk_hooks.enums import Priority
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
# Type alias for hook info tuple
|
|
17
|
+
HookInfo = tuple[type, str, Callable | None, int]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class HookRegistry:
|
|
21
|
+
"""
|
|
22
|
+
Central registry for all hook handlers.
|
|
23
|
+
|
|
24
|
+
Manages registration, lookup, and lifecycle of hooks with
|
|
25
|
+
thread-safe operations and deterministic ordering by priority.
|
|
26
|
+
|
|
27
|
+
This is a singleton - use get_registry() to access the instance.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(self):
|
|
31
|
+
"""Initialize an empty registry with thread-safe storage."""
|
|
32
|
+
self._hooks: dict[tuple[type, str], list[HookInfo]] = {}
|
|
33
|
+
self._lock = threading.RLock()
|
|
34
|
+
|
|
35
|
+
def register(
|
|
36
|
+
self,
|
|
37
|
+
model: type,
|
|
38
|
+
event: str,
|
|
39
|
+
handler_cls: type,
|
|
40
|
+
method_name: str,
|
|
41
|
+
condition: Callable | None,
|
|
42
|
+
priority: int | Priority,
|
|
43
|
+
) -> None:
|
|
44
|
+
"""
|
|
45
|
+
Register a hook handler for a model and event.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
model: Django model class
|
|
49
|
+
event: Event name (e.g., 'after_update', 'before_create')
|
|
50
|
+
handler_cls: Hook handler class
|
|
51
|
+
method_name: Name of the method to call on handler
|
|
52
|
+
condition: Optional condition to filter records
|
|
53
|
+
priority: Execution priority (lower values execute first)
|
|
54
|
+
"""
|
|
55
|
+
with self._lock:
|
|
56
|
+
key = (model, event)
|
|
57
|
+
hooks = self._hooks.setdefault(key, [])
|
|
58
|
+
|
|
59
|
+
# Check for duplicates before adding
|
|
60
|
+
hook_info = (handler_cls, method_name, condition, priority)
|
|
61
|
+
if hook_info not in hooks:
|
|
62
|
+
hooks.append(hook_info)
|
|
63
|
+
# Sort by priority (lower values first)
|
|
64
|
+
hooks.sort(key=lambda x: x[3])
|
|
65
|
+
else:
|
|
66
|
+
pass # Hook already registered
|
|
67
|
+
|
|
68
|
+
def get_hooks(self, model: type, event: str) -> list[HookInfo]:
|
|
69
|
+
"""
|
|
70
|
+
Get all hooks for a model and event.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
model: Django model class
|
|
74
|
+
event: Event name
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
List of hook info tuples (handler_cls, method_name, condition, priority)
|
|
78
|
+
sorted by priority (lower values first)
|
|
79
|
+
"""
|
|
80
|
+
with self._lock:
|
|
81
|
+
key = (model, event)
|
|
82
|
+
hooks = self._hooks.get(key, [])
|
|
83
|
+
logger.debug(f"Retrieved {len(hooks)} hooks for {model.__name__}.{event}")
|
|
84
|
+
return hooks
|
|
85
|
+
|
|
86
|
+
def unregister(
|
|
87
|
+
self,
|
|
88
|
+
model: type,
|
|
89
|
+
event: str,
|
|
90
|
+
handler_cls: type,
|
|
91
|
+
method_name: str,
|
|
92
|
+
) -> None:
|
|
93
|
+
"""
|
|
94
|
+
Unregister a specific hook handler.
|
|
95
|
+
|
|
96
|
+
Used when child classes override parent hook methods.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
model: Django model class
|
|
100
|
+
event: Event name
|
|
101
|
+
handler_cls: Hook handler class to remove
|
|
102
|
+
method_name: Method name to remove
|
|
103
|
+
"""
|
|
104
|
+
with self._lock:
|
|
105
|
+
key = (model, event)
|
|
106
|
+
if key not in self._hooks:
|
|
107
|
+
return
|
|
108
|
+
|
|
109
|
+
hooks = self._hooks[key]
|
|
110
|
+
# Filter out the specific hook
|
|
111
|
+
self._hooks[key] = [
|
|
112
|
+
(h_cls, m_name, cond, pri) for h_cls, m_name, cond, pri in hooks if not (h_cls == handler_cls and m_name == method_name)
|
|
113
|
+
]
|
|
114
|
+
|
|
115
|
+
# Clean up empty hook lists
|
|
116
|
+
if not self._hooks[key]:
|
|
117
|
+
del self._hooks[key]
|
|
118
|
+
|
|
119
|
+
def clear(self) -> None:
|
|
120
|
+
"""
|
|
121
|
+
Clear all registered hooks.
|
|
122
|
+
|
|
123
|
+
Useful for testing to ensure clean state between tests.
|
|
124
|
+
"""
|
|
125
|
+
with self._lock:
|
|
126
|
+
self._hooks.clear()
|
|
127
|
+
|
|
128
|
+
# Also clear HookMeta state to ensure complete reset
|
|
129
|
+
from django_bulk_hooks.handler import HookMeta
|
|
130
|
+
|
|
131
|
+
HookMeta._registered.clear()
|
|
132
|
+
HookMeta._class_hook_map.clear()
|
|
133
|
+
|
|
134
|
+
def list_all(self) -> dict[tuple[type, str], list[HookInfo]]:
|
|
135
|
+
"""
|
|
136
|
+
Get all registered hooks for debugging.
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
Dictionary mapping (model, event) tuples to lists of hook info
|
|
140
|
+
"""
|
|
141
|
+
with self._lock:
|
|
142
|
+
return dict(self._hooks)
|
|
143
|
+
|
|
144
|
+
@property
|
|
145
|
+
def hooks(self) -> dict[tuple[type, str], list[HookInfo]]:
|
|
146
|
+
"""
|
|
147
|
+
Expose internal hooks dictionary for testing purposes.
|
|
148
|
+
|
|
149
|
+
This property provides direct access to the internal hooks storage
|
|
150
|
+
to allow tests to clear the registry state between test runs.
|
|
151
|
+
"""
|
|
152
|
+
return self._hooks
|
|
153
|
+
|
|
154
|
+
def count_hooks(
|
|
155
|
+
self,
|
|
156
|
+
model: type | None = None,
|
|
157
|
+
event: str | None = None,
|
|
158
|
+
) -> int:
|
|
159
|
+
"""
|
|
160
|
+
Count registered hooks, optionally filtered by model and/or event.
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
model: Optional model class to filter by
|
|
164
|
+
event: Optional event name to filter by
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
Number of matching hooks
|
|
168
|
+
"""
|
|
169
|
+
with self._lock:
|
|
170
|
+
if model is None and event is None:
|
|
171
|
+
# Count all hooks
|
|
172
|
+
return sum(len(hooks) for hooks in self._hooks.values())
|
|
173
|
+
if model is not None and event is not None:
|
|
174
|
+
# Count hooks for specific model and event
|
|
175
|
+
return len(self._hooks.get((model, event), []))
|
|
176
|
+
if model is not None:
|
|
177
|
+
# Count all hooks for a model
|
|
178
|
+
return sum(len(hooks) for (m, _), hooks in self._hooks.items() if m == model)
|
|
179
|
+
# event is not None
|
|
180
|
+
# Count all hooks for an event
|
|
181
|
+
return sum(len(hooks) for (_, e), hooks in self._hooks.items() if e == event)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
# Global singleton registry
|
|
185
|
+
_registry: HookRegistry | None = None
|
|
186
|
+
_registry_lock = threading.Lock()
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def get_registry() -> HookRegistry:
|
|
190
|
+
"""
|
|
191
|
+
Get the global hook registry instance.
|
|
192
|
+
|
|
193
|
+
Creates the registry on first access (singleton pattern).
|
|
194
|
+
Thread-safe initialization.
|
|
195
|
+
|
|
196
|
+
Returns:
|
|
197
|
+
HookRegistry singleton instance
|
|
198
|
+
"""
|
|
199
|
+
global _registry
|
|
200
|
+
|
|
201
|
+
if _registry is None:
|
|
202
|
+
with _registry_lock:
|
|
203
|
+
# Double-checked locking
|
|
204
|
+
if _registry is None:
|
|
205
|
+
_registry = HookRegistry()
|
|
206
|
+
|
|
207
|
+
return _registry
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
# Backward-compatible module-level functions
|
|
211
|
+
def register_hook(
|
|
212
|
+
model: type,
|
|
213
|
+
event: str,
|
|
214
|
+
handler_cls: type,
|
|
215
|
+
method_name: str,
|
|
216
|
+
condition: Callable | None,
|
|
217
|
+
priority: int | Priority,
|
|
218
|
+
) -> None:
|
|
219
|
+
"""
|
|
220
|
+
Register a hook handler (backward-compatible function).
|
|
221
|
+
|
|
222
|
+
Delegates to the global registry instance.
|
|
223
|
+
"""
|
|
224
|
+
registry = get_registry()
|
|
225
|
+
registry.register(model, event, handler_cls, method_name, condition, priority)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def get_hooks(model: type, event: str) -> list[HookInfo]:
|
|
229
|
+
"""
|
|
230
|
+
Get hooks for a model and event (backward-compatible function).
|
|
231
|
+
|
|
232
|
+
Delegates to the global registry instance.
|
|
233
|
+
"""
|
|
234
|
+
registry = get_registry()
|
|
235
|
+
return registry.get_hooks(model, event)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def unregister_hook(
|
|
239
|
+
model: type,
|
|
240
|
+
event: str,
|
|
241
|
+
handler_cls: type,
|
|
242
|
+
method_name: str,
|
|
243
|
+
) -> None:
|
|
244
|
+
"""
|
|
245
|
+
Unregister a hook handler (backward-compatible function).
|
|
246
|
+
|
|
247
|
+
Delegates to the global registry instance.
|
|
248
|
+
"""
|
|
249
|
+
registry = get_registry()
|
|
250
|
+
registry.unregister(model, event, handler_cls, method_name)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def clear_hooks() -> None:
|
|
254
|
+
"""
|
|
255
|
+
Clear all registered hooks (backward-compatible function).
|
|
256
|
+
|
|
257
|
+
Delegates to the global registry instance.
|
|
258
|
+
Useful for testing.
|
|
259
|
+
"""
|
|
260
|
+
registry = get_registry()
|
|
261
|
+
registry.clear()
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def list_all_hooks() -> dict[tuple[type, str], list[HookInfo]]:
|
|
265
|
+
"""
|
|
266
|
+
List all registered hooks (backward-compatible function).
|
|
267
|
+
|
|
268
|
+
Delegates to the global registry instance.
|
|
269
|
+
"""
|
|
270
|
+
registry = get_registry()
|
|
271
|
+
return registry.list_all()
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
# Expose hooks dictionary for testing purposes
|
|
275
|
+
# This provides backward compatibility with tests that expect to access _hooks directly
|
|
276
|
+
_hooks = get_registry().hooks
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: django-bulk-hooks
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.93
|
|
4
4
|
Summary: Hook-style hooks for Django bulk operations like bulk_create and bulk_update.
|
|
5
5
|
License: MIT
|
|
6
6
|
Keywords: django,bulk,hooks
|
|
@@ -244,17 +244,68 @@ LoanAccount.objects.bulk_update(reordered) # fields are auto-detected
|
|
|
244
244
|
|
|
245
245
|
## 🧩 Integration with Other Managers
|
|
246
246
|
|
|
247
|
-
|
|
247
|
+
### Recommended: QuerySet-based Composition (New Approach)
|
|
248
|
+
|
|
249
|
+
For the best compatibility and to avoid inheritance conflicts, use the queryset-based composition approach:
|
|
250
|
+
|
|
251
|
+
```python
|
|
252
|
+
from django_bulk_hooks.queryset import HookQuerySet
|
|
253
|
+
from queryable_properties.managers import QueryablePropertiesManager
|
|
254
|
+
|
|
255
|
+
class MyManager(QueryablePropertiesManager):
|
|
256
|
+
"""Manager that combines queryable properties with hooks"""
|
|
257
|
+
|
|
258
|
+
def get_queryset(self):
|
|
259
|
+
# Get the QueryableProperties QuerySet
|
|
260
|
+
qs = super().get_queryset()
|
|
261
|
+
# Apply hooks on top of it
|
|
262
|
+
return HookQuerySet.with_hooks(qs)
|
|
263
|
+
|
|
264
|
+
class Article(models.Model):
|
|
265
|
+
title = models.CharField(max_length=100)
|
|
266
|
+
published = models.BooleanField(default=False)
|
|
267
|
+
|
|
268
|
+
objects = MyManager()
|
|
269
|
+
|
|
270
|
+
# This gives you both queryable properties AND hooks
|
|
271
|
+
# No inheritance conflicts, no MRO issues!
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Alternative: Explicit Hook Application
|
|
275
|
+
|
|
276
|
+
For more control, you can apply hooks explicitly:
|
|
277
|
+
|
|
278
|
+
```python
|
|
279
|
+
class MyManager(QueryablePropertiesManager):
|
|
280
|
+
def get_queryset(self):
|
|
281
|
+
return super().get_queryset()
|
|
282
|
+
|
|
283
|
+
def with_hooks(self):
|
|
284
|
+
"""Apply hooks to this queryset"""
|
|
285
|
+
return HookQuerySet.with_hooks(self.get_queryset())
|
|
286
|
+
|
|
287
|
+
# Usage:
|
|
288
|
+
Article.objects.with_hooks().filter(published=True).update(title="Updated")
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### Legacy: Manager Inheritance (Not Recommended)
|
|
292
|
+
|
|
293
|
+
The old inheritance approach still works but is not recommended due to potential MRO conflicts:
|
|
248
294
|
|
|
249
295
|
```python
|
|
250
296
|
from django_bulk_hooks.manager import BulkHookManager
|
|
251
297
|
from queryable_properties.managers import QueryablePropertiesManager
|
|
252
298
|
|
|
253
299
|
class MyManager(BulkHookManager, QueryablePropertiesManager):
|
|
254
|
-
pass
|
|
300
|
+
pass # ⚠️ Can cause inheritance conflicts
|
|
255
301
|
```
|
|
256
302
|
|
|
257
|
-
|
|
303
|
+
**Why the new approach is better:**
|
|
304
|
+
- ✅ No inheritance conflicts
|
|
305
|
+
- ✅ No MRO (Method Resolution Order) issues
|
|
306
|
+
- ✅ Works with any manager combination
|
|
307
|
+
- ✅ Cleaner and more maintainable
|
|
308
|
+
- ✅ Follows Django's queryset enhancement patterns
|
|
258
309
|
|
|
259
310
|
Framework needs to:
|
|
260
311
|
Register these methods
|