django-bulk-hooks 0.1.101__py3-none-any.whl → 0.1.102__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 +11 -23
- django_bulk_hooks/conditions.py +185 -167
- django_bulk_hooks/decorators.py +112 -7
- django_bulk_hooks/engine.py +15 -82
- django_bulk_hooks/handler.py +3 -28
- django_bulk_hooks/manager.py +69 -31
- django_bulk_hooks/models.py +14 -33
- django_bulk_hooks/registry.py +3 -3
- django_bulk_hooks-0.1.102.dist-info/METADATA +228 -0
- django_bulk_hooks-0.1.102.dist-info/RECORD +16 -0
- django_bulk_hooks-0.1.101.dist-info/METADATA +0 -295
- django_bulk_hooks-0.1.101.dist-info/RECORD +0 -16
- {django_bulk_hooks-0.1.101.dist-info → django_bulk_hooks-0.1.102.dist-info}/LICENSE +0 -0
- {django_bulk_hooks-0.1.101.dist-info → django_bulk_hooks-0.1.102.dist-info}/WHEEL +0 -0
django_bulk_hooks/__init__.py
CHANGED
|
@@ -1,19 +1,3 @@
|
|
|
1
|
-
from django_bulk_hooks.conditions import (
|
|
2
|
-
ChangesTo,
|
|
3
|
-
HasChanged,
|
|
4
|
-
IsBlank,
|
|
5
|
-
IsEqual,
|
|
6
|
-
IsGreaterThan,
|
|
7
|
-
IsGreaterThanOrEqual,
|
|
8
|
-
IsLessThan,
|
|
9
|
-
IsLessThanOrEqual,
|
|
10
|
-
IsNotEqual,
|
|
11
|
-
LambdaCondition,
|
|
12
|
-
WasEqual,
|
|
13
|
-
is_field_set,
|
|
14
|
-
safe_get_related_attr,
|
|
15
|
-
safe_get_related_object,
|
|
16
|
-
)
|
|
17
1
|
from django_bulk_hooks.constants import (
|
|
18
2
|
AFTER_CREATE,
|
|
19
3
|
AFTER_DELETE,
|
|
@@ -25,10 +9,20 @@ from django_bulk_hooks.constants import (
|
|
|
25
9
|
VALIDATE_DELETE,
|
|
26
10
|
VALIDATE_UPDATE,
|
|
27
11
|
)
|
|
12
|
+
from django_bulk_hooks.conditions import (
|
|
13
|
+
ChangesTo,
|
|
14
|
+
HasChanged,
|
|
15
|
+
IsEqual,
|
|
16
|
+
IsNotEqual,
|
|
17
|
+
WasEqual,
|
|
18
|
+
safe_get_related_object,
|
|
19
|
+
safe_get_related_attr,
|
|
20
|
+
is_field_set,
|
|
21
|
+
)
|
|
28
22
|
from django_bulk_hooks.decorators import hook, select_related
|
|
29
|
-
from django_bulk_hooks.enums import Priority
|
|
30
23
|
from django_bulk_hooks.handler import HookHandler
|
|
31
24
|
from django_bulk_hooks.models import HookModelMixin
|
|
25
|
+
from django_bulk_hooks.enums import Priority
|
|
32
26
|
|
|
33
27
|
__all__ = [
|
|
34
28
|
"HookHandler",
|
|
@@ -53,10 +47,4 @@ __all__ = [
|
|
|
53
47
|
"IsEqual",
|
|
54
48
|
"IsNotEqual",
|
|
55
49
|
"WasEqual",
|
|
56
|
-
"IsBlank",
|
|
57
|
-
"IsGreaterThan",
|
|
58
|
-
"IsLessThan",
|
|
59
|
-
"IsGreaterThanOrEqual",
|
|
60
|
-
"IsLessThanOrEqual",
|
|
61
|
-
"LambdaCondition",
|
|
62
50
|
]
|
django_bulk_hooks/conditions.py
CHANGED
|
@@ -8,7 +8,7 @@ def safe_get_related_object(instance, field_name):
|
|
|
8
8
|
"""
|
|
9
9
|
if not hasattr(instance, field_name):
|
|
10
10
|
return None
|
|
11
|
-
|
|
11
|
+
|
|
12
12
|
# Get the foreign key field
|
|
13
13
|
try:
|
|
14
14
|
field = instance._meta.get_field(field_name)
|
|
@@ -16,12 +16,12 @@ def safe_get_related_object(instance, field_name):
|
|
|
16
16
|
return getattr(instance, field_name, None)
|
|
17
17
|
except models.FieldDoesNotExist:
|
|
18
18
|
return getattr(instance, field_name, None)
|
|
19
|
-
|
|
19
|
+
|
|
20
20
|
# Check if the foreign key field is None
|
|
21
21
|
fk_field_name = f"{field_name}_id"
|
|
22
22
|
if hasattr(instance, fk_field_name) and getattr(instance, fk_field_name) is None:
|
|
23
23
|
return None
|
|
24
|
-
|
|
24
|
+
|
|
25
25
|
# Try to get the related object, but catch RelatedObjectDoesNotExist
|
|
26
26
|
try:
|
|
27
27
|
return getattr(instance, field_name)
|
|
@@ -29,25 +29,49 @@ def safe_get_related_object(instance, field_name):
|
|
|
29
29
|
return None
|
|
30
30
|
|
|
31
31
|
|
|
32
|
+
def is_field_set(instance, field_name):
|
|
33
|
+
"""
|
|
34
|
+
Check if a foreign key field is set without raising RelatedObjectDoesNotExist.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
instance: The model instance
|
|
38
|
+
field_name: The foreign key field name
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
True if the field is set, False otherwise
|
|
42
|
+
"""
|
|
43
|
+
# Check the foreign key ID field first
|
|
44
|
+
fk_field_name = f"{field_name}_id"
|
|
45
|
+
if hasattr(instance, fk_field_name):
|
|
46
|
+
fk_value = getattr(instance, fk_field_name, None)
|
|
47
|
+
return fk_value is not None
|
|
48
|
+
|
|
49
|
+
# Fallback to checking the field directly
|
|
50
|
+
try:
|
|
51
|
+
return getattr(instance, field_name) is not None
|
|
52
|
+
except Exception:
|
|
53
|
+
return False
|
|
54
|
+
|
|
55
|
+
|
|
32
56
|
def safe_get_related_attr(instance, field_name, attr_name=None):
|
|
33
57
|
"""
|
|
34
58
|
Safely get a related object or its attribute without raising RelatedObjectDoesNotExist.
|
|
35
|
-
|
|
59
|
+
|
|
36
60
|
This is particularly useful in hooks where objects might not have their related
|
|
37
61
|
fields populated yet (e.g., during bulk_create operations or on unsaved objects).
|
|
38
|
-
|
|
62
|
+
|
|
39
63
|
Args:
|
|
40
64
|
instance: The model instance
|
|
41
65
|
field_name: The foreign key field name
|
|
42
66
|
attr_name: Optional attribute name to access on the related object
|
|
43
|
-
|
|
67
|
+
|
|
44
68
|
Returns:
|
|
45
69
|
The related object, the attribute value, or None if not available
|
|
46
|
-
|
|
70
|
+
|
|
47
71
|
Example:
|
|
48
72
|
# Instead of: loan_transaction.status.name (which might fail)
|
|
49
73
|
# Use: safe_get_related_attr(loan_transaction, 'status', 'name')
|
|
50
|
-
|
|
74
|
+
|
|
51
75
|
status_name = safe_get_related_attr(loan_transaction, 'status', 'name')
|
|
52
76
|
if status_name in {Status.COMPLETE.value, Status.FAILED.value}:
|
|
53
77
|
# Process the transaction
|
|
@@ -63,31 +87,96 @@ def safe_get_related_attr(instance, field_name, attr_name=None):
|
|
|
63
87
|
# If we have an ID but the object isn't loaded, try to load it
|
|
64
88
|
try:
|
|
65
89
|
field = instance._meta.get_field(field_name)
|
|
66
|
-
if hasattr(field,
|
|
90
|
+
if hasattr(field, 'related_model'):
|
|
67
91
|
related_obj = field.related_model.objects.get(id=fk_value)
|
|
68
92
|
if attr_name is None:
|
|
69
93
|
return related_obj
|
|
70
94
|
return getattr(related_obj, attr_name, None)
|
|
71
95
|
except (field.related_model.DoesNotExist, AttributeError):
|
|
72
96
|
return None
|
|
73
|
-
|
|
97
|
+
|
|
74
98
|
# For saved objects or when the above doesn't work, use the original method
|
|
75
99
|
related_obj = safe_get_related_object(instance, field_name)
|
|
76
100
|
if related_obj is None:
|
|
77
101
|
return None
|
|
102
|
+
|
|
78
103
|
if attr_name is None:
|
|
79
104
|
return related_obj
|
|
105
|
+
|
|
80
106
|
return getattr(related_obj, attr_name, None)
|
|
81
107
|
|
|
82
108
|
|
|
83
|
-
def
|
|
109
|
+
def safe_get_related_attr_with_fallback(instance, field_name, attr_name=None, fallback_value=None):
|
|
84
110
|
"""
|
|
85
|
-
|
|
111
|
+
Enhanced version of safe_get_related_attr that provides fallback handling.
|
|
112
|
+
|
|
113
|
+
This function is especially useful for bulk operations where related objects
|
|
114
|
+
might not be fully loaded or might not exist yet.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
instance: The model instance
|
|
118
|
+
field_name: The foreign key field name
|
|
119
|
+
attr_name: Optional attribute name to access on the related object
|
|
120
|
+
fallback_value: Value to return if the related object or attribute doesn't exist
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
The related object, the attribute value, or fallback_value if not available
|
|
124
|
+
"""
|
|
125
|
+
# First try the standard safe access
|
|
126
|
+
result = safe_get_related_attr(instance, field_name, attr_name)
|
|
127
|
+
if result is not None:
|
|
128
|
+
return result
|
|
129
|
+
|
|
130
|
+
# If that fails, try to get the foreign key ID and fetch the object directly
|
|
131
|
+
fk_field_name = f"{field_name}_id"
|
|
132
|
+
if hasattr(instance, fk_field_name):
|
|
133
|
+
fk_id = getattr(instance, fk_field_name)
|
|
134
|
+
if fk_id is not None:
|
|
135
|
+
try:
|
|
136
|
+
# Get the field to determine the related model
|
|
137
|
+
field = instance._meta.get_field(field_name)
|
|
138
|
+
if field.is_relation and not field.many_to_many and not field.one_to_many:
|
|
139
|
+
# Try to fetch the related object directly
|
|
140
|
+
related_obj = field.related_model.objects.get(pk=fk_id)
|
|
141
|
+
if attr_name is None:
|
|
142
|
+
return related_obj
|
|
143
|
+
return getattr(related_obj, attr_name, fallback_value)
|
|
144
|
+
except (field.related_model.DoesNotExist, AttributeError):
|
|
145
|
+
pass
|
|
146
|
+
|
|
147
|
+
return fallback_value
|
|
148
|
+
|
|
86
149
|
|
|
87
|
-
|
|
88
|
-
|
|
150
|
+
def resolve_dotted_attr(instance, dotted_path):
|
|
151
|
+
"""
|
|
152
|
+
Recursively resolve a dotted attribute path, e.g., "type.category".
|
|
153
|
+
This function is designed to work with pre-loaded foreign keys to avoid queries.
|
|
89
154
|
"""
|
|
90
|
-
|
|
155
|
+
if instance is None:
|
|
156
|
+
return None
|
|
157
|
+
|
|
158
|
+
current = instance
|
|
159
|
+
for attr in dotted_path.split("."):
|
|
160
|
+
if current is None:
|
|
161
|
+
return None
|
|
162
|
+
|
|
163
|
+
# Check if this is a foreign key that might trigger a query
|
|
164
|
+
if hasattr(current, '_meta') and hasattr(current._meta, 'get_field'):
|
|
165
|
+
try:
|
|
166
|
+
field = current._meta.get_field(attr)
|
|
167
|
+
if field.is_relation and not field.many_to_many and not field.one_to_many:
|
|
168
|
+
# For foreign keys, use safe access to prevent RelatedObjectDoesNotExist
|
|
169
|
+
current = safe_get_related_object(current, attr)
|
|
170
|
+
else:
|
|
171
|
+
current = getattr(current, attr, None)
|
|
172
|
+
except Exception:
|
|
173
|
+
# If field lookup fails, fall back to regular attribute access
|
|
174
|
+
current = getattr(current, attr, None)
|
|
175
|
+
else:
|
|
176
|
+
# Not a model instance, use regular attribute access
|
|
177
|
+
current = getattr(current, attr, None)
|
|
178
|
+
|
|
179
|
+
return current
|
|
91
180
|
|
|
92
181
|
|
|
93
182
|
class HookCondition:
|
|
@@ -114,94 +203,54 @@ class HookCondition:
|
|
|
114
203
|
return set()
|
|
115
204
|
|
|
116
205
|
|
|
117
|
-
class
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
def __init__(self, field_name):
|
|
123
|
-
self.field_name = field_name
|
|
124
|
-
|
|
125
|
-
def check(self, instance, original_instance=None):
|
|
126
|
-
value = getattr(instance, self.field_name, None)
|
|
127
|
-
return value is None or value == ""
|
|
128
|
-
|
|
129
|
-
def get_required_fields(self):
|
|
130
|
-
return {self.field_name}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
class AndCondition(HookCondition):
|
|
134
|
-
def __init__(self, *conditions):
|
|
135
|
-
self.conditions = conditions
|
|
136
|
-
|
|
137
|
-
def check(self, instance, original_instance=None):
|
|
138
|
-
return all(c.check(instance, original_instance) for c in self.conditions)
|
|
139
|
-
|
|
140
|
-
def get_required_fields(self):
|
|
141
|
-
fields = set()
|
|
142
|
-
for condition in self.conditions:
|
|
143
|
-
fields.update(condition.get_required_fields())
|
|
144
|
-
return fields
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
class OrCondition(HookCondition):
|
|
148
|
-
def __init__(self, *conditions):
|
|
149
|
-
self.conditions = conditions
|
|
150
|
-
|
|
151
|
-
def check(self, instance, original_instance=None):
|
|
152
|
-
return any(c.check(instance, original_instance) for c in self.conditions)
|
|
153
|
-
|
|
154
|
-
def get_required_fields(self):
|
|
155
|
-
fields = set()
|
|
156
|
-
for condition in self.conditions:
|
|
157
|
-
fields.update(condition.get_required_fields())
|
|
158
|
-
return fields
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
class NotCondition(HookCondition):
|
|
162
|
-
def __init__(self, condition):
|
|
163
|
-
self.condition = condition
|
|
206
|
+
class IsEqual(HookCondition):
|
|
207
|
+
def __init__(self, field, value):
|
|
208
|
+
self.field = field
|
|
209
|
+
self.value = value
|
|
164
210
|
|
|
165
211
|
def check(self, instance, original_instance=None):
|
|
166
|
-
|
|
212
|
+
current_value = resolve_dotted_attr(instance, self.field)
|
|
213
|
+
return current_value == self.value
|
|
167
214
|
|
|
168
215
|
def get_required_fields(self):
|
|
169
|
-
return self.
|
|
216
|
+
return {self.field.split('.')[0]}
|
|
170
217
|
|
|
171
218
|
|
|
172
|
-
class
|
|
173
|
-
def __init__(self,
|
|
174
|
-
self.
|
|
219
|
+
class IsNotEqual(HookCondition):
|
|
220
|
+
def __init__(self, field, value):
|
|
221
|
+
self.field = field
|
|
175
222
|
self.value = value
|
|
176
223
|
|
|
177
224
|
def check(self, instance, original_instance=None):
|
|
178
|
-
|
|
225
|
+
current_value = resolve_dotted_attr(instance, self.field)
|
|
226
|
+
return current_value != self.value
|
|
179
227
|
|
|
180
228
|
def get_required_fields(self):
|
|
181
|
-
return {self.
|
|
229
|
+
return {self.field.split('.')[0]}
|
|
182
230
|
|
|
183
231
|
|
|
184
232
|
class WasEqual(HookCondition):
|
|
185
|
-
def __init__(self,
|
|
186
|
-
self.
|
|
233
|
+
def __init__(self, field, value):
|
|
234
|
+
self.field = field
|
|
187
235
|
self.value = value
|
|
188
236
|
|
|
189
237
|
def check(self, instance, original_instance=None):
|
|
190
238
|
if original_instance is None:
|
|
191
239
|
return False
|
|
192
|
-
|
|
240
|
+
original_value = resolve_dotted_attr(original_instance, self.field)
|
|
241
|
+
return original_value == self.value
|
|
193
242
|
|
|
194
243
|
def get_required_fields(self):
|
|
195
|
-
return {self.
|
|
244
|
+
return {self.field.split('.')[0]}
|
|
196
245
|
|
|
197
246
|
|
|
198
247
|
class HasChanged(HookCondition):
|
|
199
|
-
def __init__(self,
|
|
248
|
+
def __init__(self, field, has_changed=True):
|
|
200
249
|
"""
|
|
201
250
|
Check if a field's value has changed or remained the same.
|
|
202
|
-
|
|
251
|
+
|
|
203
252
|
Args:
|
|
204
|
-
|
|
253
|
+
field: The field name to check
|
|
205
254
|
has_changed: If True (default), condition passes when field has changed.
|
|
206
255
|
If False, condition passes when field has remained the same.
|
|
207
256
|
This is useful for:
|
|
@@ -210,149 +259,118 @@ class HasChanged(HookCondition):
|
|
|
210
259
|
- Ensuring critical fields remain constant
|
|
211
260
|
- State machine validations
|
|
212
261
|
"""
|
|
213
|
-
self.
|
|
262
|
+
self.field = field
|
|
214
263
|
self.has_changed = has_changed
|
|
215
264
|
|
|
216
265
|
def check(self, instance, original_instance=None):
|
|
217
266
|
if original_instance is None:
|
|
218
267
|
# For new instances:
|
|
219
|
-
# - If we're checking for changes (has_changed=True), return
|
|
220
|
-
# - If we're checking for stability (has_changed=False), return
|
|
221
|
-
return self.has_changed
|
|
222
|
-
|
|
223
|
-
current_value =
|
|
224
|
-
original_value =
|
|
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)
|
|
225
274
|
return (current_value != original_value) == self.has_changed
|
|
226
275
|
|
|
227
276
|
def get_required_fields(self):
|
|
228
|
-
return {self.
|
|
277
|
+
return {self.field.split('.')[0]}
|
|
229
278
|
|
|
230
279
|
|
|
231
280
|
class ChangesTo(HookCondition):
|
|
232
|
-
def __init__(self,
|
|
233
|
-
self.
|
|
281
|
+
def __init__(self, field, value):
|
|
282
|
+
self.field = field
|
|
234
283
|
self.value = value
|
|
235
284
|
|
|
236
285
|
def check(self, instance, original_instance=None):
|
|
237
286
|
if original_instance is None:
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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
|
|
242
293
|
|
|
243
294
|
def get_required_fields(self):
|
|
244
|
-
return {self.
|
|
295
|
+
return {self.field.split('.')[0]}
|
|
245
296
|
|
|
246
297
|
|
|
247
|
-
class
|
|
248
|
-
def __init__(self,
|
|
249
|
-
self.
|
|
298
|
+
class IsGreaterThan(HookCondition):
|
|
299
|
+
def __init__(self, field, value):
|
|
300
|
+
self.field = field
|
|
250
301
|
self.value = value
|
|
251
302
|
|
|
252
303
|
def check(self, instance, original_instance=None):
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
def get_required_fields(self):
|
|
256
|
-
return {self.field_name}
|
|
304
|
+
current = resolve_dotted_attr(instance, self.field)
|
|
305
|
+
return current is not None and current > self.value
|
|
257
306
|
|
|
258
307
|
|
|
259
|
-
class
|
|
260
|
-
def __init__(self,
|
|
261
|
-
self.
|
|
308
|
+
class IsGreaterThanOrEqual(HookCondition):
|
|
309
|
+
def __init__(self, field, value):
|
|
310
|
+
self.field = field
|
|
262
311
|
self.value = value
|
|
263
312
|
|
|
264
313
|
def check(self, instance, original_instance=None):
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
return False
|
|
268
|
-
return field_value > self.value
|
|
269
|
-
|
|
270
|
-
def get_required_fields(self):
|
|
271
|
-
return {self.field_name}
|
|
314
|
+
current = resolve_dotted_attr(instance, self.field)
|
|
315
|
+
return current is not None and current >= self.value
|
|
272
316
|
|
|
273
317
|
|
|
274
318
|
class IsLessThan(HookCondition):
|
|
275
|
-
def __init__(self,
|
|
276
|
-
self.
|
|
319
|
+
def __init__(self, field, value):
|
|
320
|
+
self.field = field
|
|
277
321
|
self.value = value
|
|
278
322
|
|
|
279
323
|
def check(self, instance, original_instance=None):
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
return False
|
|
283
|
-
return field_value < self.value
|
|
284
|
-
|
|
285
|
-
def get_required_fields(self):
|
|
286
|
-
return {self.field_name}
|
|
324
|
+
current = resolve_dotted_attr(instance, self.field)
|
|
325
|
+
return current is not None and current < self.value
|
|
287
326
|
|
|
288
327
|
|
|
289
|
-
class
|
|
290
|
-
def __init__(self,
|
|
291
|
-
self.
|
|
328
|
+
class IsLessThanOrEqual(HookCondition):
|
|
329
|
+
def __init__(self, field, value):
|
|
330
|
+
self.field = field
|
|
292
331
|
self.value = value
|
|
293
332
|
|
|
294
333
|
def check(self, instance, original_instance=None):
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
return False
|
|
298
|
-
return field_value >= self.value
|
|
334
|
+
current = resolve_dotted_attr(instance, self.field)
|
|
335
|
+
return current is not None and current <= self.value
|
|
299
336
|
|
|
300
|
-
def get_required_fields(self):
|
|
301
|
-
return {self.field_name}
|
|
302
337
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
self.
|
|
307
|
-
self.value = value
|
|
338
|
+
class AndCondition(HookCondition):
|
|
339
|
+
def __init__(self, condition1, condition2):
|
|
340
|
+
self.condition1 = condition1
|
|
341
|
+
self.condition2 = condition2
|
|
308
342
|
|
|
309
343
|
def check(self, instance, original_instance=None):
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
344
|
+
return (
|
|
345
|
+
self.condition1.check(instance, original_instance)
|
|
346
|
+
and self.condition2.check(instance, original_instance)
|
|
347
|
+
)
|
|
314
348
|
|
|
315
349
|
def get_required_fields(self):
|
|
316
|
-
return
|
|
350
|
+
return self.condition1.get_required_fields() | self.condition2.get_required_fields()
|
|
317
351
|
|
|
318
352
|
|
|
319
|
-
class
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
This makes it easy to create custom conditions inline without defining
|
|
324
|
-
a full class.
|
|
325
|
-
|
|
326
|
-
Example:
|
|
327
|
-
# Simple lambda condition
|
|
328
|
-
condition = LambdaCondition(lambda instance: instance.price > 100)
|
|
353
|
+
class OrCondition(HookCondition):
|
|
354
|
+
def __init__(self, condition1, condition2):
|
|
355
|
+
self.condition1 = condition1
|
|
356
|
+
self.condition2 = condition2
|
|
329
357
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
358
|
+
def check(self, instance, original_instance=None):
|
|
359
|
+
return (
|
|
360
|
+
self.condition1.check(instance, original_instance)
|
|
361
|
+
or self.condition2.check(instance, original_instance)
|
|
333
362
|
)
|
|
334
363
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
lambda instance: instance.price > 100 and instance.is_active
|
|
338
|
-
))
|
|
339
|
-
def handle_expensive_active_products(self, new_records, old_records):
|
|
340
|
-
pass
|
|
341
|
-
"""
|
|
364
|
+
def get_required_fields(self):
|
|
365
|
+
return self.condition1.get_required_fields() | self.condition2.get_required_fields()
|
|
342
366
|
|
|
343
|
-
def __init__(self, func, required_fields=None):
|
|
344
|
-
"""
|
|
345
|
-
Initialize with a callable function.
|
|
346
367
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
"""
|
|
351
|
-
self.func = func
|
|
352
|
-
self._required_fields = required_fields or set()
|
|
368
|
+
class NotCondition(HookCondition):
|
|
369
|
+
def __init__(self, condition):
|
|
370
|
+
self.condition = condition
|
|
353
371
|
|
|
354
372
|
def check(self, instance, original_instance=None):
|
|
355
|
-
return self.
|
|
373
|
+
return not self.condition.check(instance, original_instance)
|
|
356
374
|
|
|
357
375
|
def get_required_fields(self):
|
|
358
|
-
return self.
|
|
376
|
+
return self.condition.get_required_fields()
|