django-bulk-hooks 0.1.93__py3-none-any.whl → 0.1.94__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 +70 -215
- {django_bulk_hooks-0.1.93.dist-info → django_bulk_hooks-0.1.94.dist-info}/METADATA +1 -1
- {django_bulk_hooks-0.1.93.dist-info → django_bulk_hooks-0.1.94.dist-info}/RECORD +5 -5
- {django_bulk_hooks-0.1.93.dist-info → django_bulk_hooks-0.1.94.dist-info}/LICENSE +0 -0
- {django_bulk_hooks-0.1.93.dist-info → django_bulk_hooks-0.1.94.dist-info}/WHEEL +0 -0
django_bulk_hooks/conditions.py
CHANGED
|
@@ -29,30 +29,6 @@ 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
|
-
|
|
56
32
|
def safe_get_related_attr(instance, field_name, attr_name=None):
|
|
57
33
|
"""
|
|
58
34
|
Safely get a related object or its attribute without raising RelatedObjectDoesNotExist.
|
|
@@ -99,84 +75,19 @@ def safe_get_related_attr(instance, field_name, attr_name=None):
|
|
|
99
75
|
related_obj = safe_get_related_object(instance, field_name)
|
|
100
76
|
if related_obj is None:
|
|
101
77
|
return None
|
|
102
|
-
|
|
103
78
|
if attr_name is None:
|
|
104
79
|
return related_obj
|
|
105
|
-
|
|
106
80
|
return getattr(related_obj, attr_name, None)
|
|
107
81
|
|
|
108
82
|
|
|
109
|
-
def
|
|
83
|
+
def is_field_set(instance, field_name):
|
|
110
84
|
"""
|
|
111
|
-
|
|
85
|
+
Check if a field has been set on a model instance.
|
|
112
86
|
|
|
113
|
-
This
|
|
114
|
-
|
|
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
|
|
87
|
+
This is useful for checking if a field has been explicitly set,
|
|
88
|
+
even if it's been set to None.
|
|
124
89
|
"""
|
|
125
|
-
|
|
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
|
-
|
|
149
|
-
|
|
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.
|
|
154
|
-
"""
|
|
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
|
|
90
|
+
return hasattr(instance, field_name)
|
|
180
91
|
|
|
181
92
|
|
|
182
93
|
class HookCondition:
|
|
@@ -203,179 +114,123 @@ class HookCondition:
|
|
|
203
114
|
return set()
|
|
204
115
|
|
|
205
116
|
|
|
206
|
-
class
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
117
|
+
class IsBlank(HookCondition):
|
|
118
|
+
"""
|
|
119
|
+
Condition that checks if a field is blank (None or empty string).
|
|
120
|
+
"""
|
|
121
|
+
def __init__(self, field_name):
|
|
122
|
+
self.field_name = field_name
|
|
210
123
|
|
|
211
124
|
def check(self, instance, original_instance=None):
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
current_value = getattr(instance, self.field, None)
|
|
215
|
-
return current_value == self.value
|
|
216
|
-
except Exception:
|
|
217
|
-
# If there's any error accessing the field, treat it as None
|
|
218
|
-
return self.value is None
|
|
125
|
+
value = getattr(instance, self.field_name, None)
|
|
126
|
+
return value is None or value == ""
|
|
219
127
|
|
|
220
128
|
def get_required_fields(self):
|
|
221
|
-
return {self.
|
|
129
|
+
return {self.field_name}
|
|
222
130
|
|
|
223
131
|
|
|
224
|
-
class
|
|
225
|
-
def __init__(self,
|
|
226
|
-
self.
|
|
227
|
-
self.value = value
|
|
132
|
+
class AndCondition(HookCondition):
|
|
133
|
+
def __init__(self, *conditions):
|
|
134
|
+
self.conditions = conditions
|
|
228
135
|
|
|
229
136
|
def check(self, instance, original_instance=None):
|
|
230
|
-
|
|
231
|
-
return current_value != self.value
|
|
137
|
+
return all(c.check(instance, original_instance) for c in self.conditions)
|
|
232
138
|
|
|
233
139
|
def get_required_fields(self):
|
|
234
|
-
|
|
140
|
+
fields = set()
|
|
141
|
+
for condition in self.conditions:
|
|
142
|
+
fields.update(condition.get_required_fields())
|
|
143
|
+
return fields
|
|
235
144
|
|
|
236
145
|
|
|
237
|
-
class
|
|
238
|
-
def __init__(self,
|
|
239
|
-
self.
|
|
240
|
-
self.value = value
|
|
146
|
+
class OrCondition(HookCondition):
|
|
147
|
+
def __init__(self, *conditions):
|
|
148
|
+
self.conditions = conditions
|
|
241
149
|
|
|
242
150
|
def check(self, instance, original_instance=None):
|
|
243
|
-
|
|
244
|
-
return False
|
|
245
|
-
original_value = resolve_dotted_attr(original_instance, self.field)
|
|
246
|
-
return original_value == self.value
|
|
151
|
+
return any(c.check(instance, original_instance) for c in self.conditions)
|
|
247
152
|
|
|
248
153
|
def get_required_fields(self):
|
|
249
|
-
|
|
154
|
+
fields = set()
|
|
155
|
+
for condition in self.conditions:
|
|
156
|
+
fields.update(condition.get_required_fields())
|
|
157
|
+
return fields
|
|
250
158
|
|
|
251
159
|
|
|
252
|
-
class
|
|
253
|
-
def __init__(self,
|
|
254
|
-
|
|
255
|
-
Check if a field's value has changed or remained the same.
|
|
256
|
-
|
|
257
|
-
Args:
|
|
258
|
-
field: The field name to check
|
|
259
|
-
has_changed: If True (default), condition passes when field has changed.
|
|
260
|
-
If False, condition passes when field has remained the same.
|
|
261
|
-
This is useful for:
|
|
262
|
-
- Detecting stable/unchanged fields
|
|
263
|
-
- Validating field immutability
|
|
264
|
-
- Ensuring critical fields remain constant
|
|
265
|
-
- State machine validations
|
|
266
|
-
"""
|
|
267
|
-
self.field = field
|
|
268
|
-
self.has_changed = has_changed
|
|
160
|
+
class NotCondition(HookCondition):
|
|
161
|
+
def __init__(self, condition):
|
|
162
|
+
self.condition = condition
|
|
269
163
|
|
|
270
164
|
def check(self, instance, original_instance=None):
|
|
271
|
-
|
|
272
|
-
# For new instances:
|
|
273
|
-
# - If we're checking for changes (has_changed=True), return False since there's no change yet
|
|
274
|
-
# - If we're checking for stability (has_changed=False), return True since it's technically unchanged
|
|
275
|
-
return not self.has_changed
|
|
276
|
-
|
|
277
|
-
current_value = resolve_dotted_attr(instance, self.field)
|
|
278
|
-
original_value = resolve_dotted_attr(original_instance, self.field)
|
|
279
|
-
return (current_value != original_value) == self.has_changed
|
|
165
|
+
return not self.condition.check(instance, original_instance)
|
|
280
166
|
|
|
281
167
|
def get_required_fields(self):
|
|
282
|
-
return
|
|
168
|
+
return self.condition.get_required_fields()
|
|
283
169
|
|
|
284
170
|
|
|
285
|
-
class
|
|
286
|
-
def __init__(self,
|
|
287
|
-
self.
|
|
171
|
+
class IsEqual(HookCondition):
|
|
172
|
+
def __init__(self, field_name, value):
|
|
173
|
+
self.field_name = field_name
|
|
288
174
|
self.value = value
|
|
289
175
|
|
|
290
176
|
def check(self, instance, original_instance=None):
|
|
291
|
-
|
|
292
|
-
current_value = resolve_dotted_attr(instance, self.field)
|
|
293
|
-
return current_value == self.value
|
|
294
|
-
|
|
295
|
-
current_value = resolve_dotted_attr(instance, self.field)
|
|
296
|
-
original_value = resolve_dotted_attr(original_instance, self.field)
|
|
297
|
-
return current_value == self.value and current_value != original_value
|
|
177
|
+
return getattr(instance, self.field_name, None) == self.value
|
|
298
178
|
|
|
299
179
|
def get_required_fields(self):
|
|
300
|
-
return {self.
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
class IsGreaterThan(HookCondition):
|
|
304
|
-
def __init__(self, field, value):
|
|
305
|
-
self.field = field
|
|
306
|
-
self.value = value
|
|
307
|
-
|
|
308
|
-
def check(self, instance, original_instance=None):
|
|
309
|
-
current = resolve_dotted_attr(instance, self.field)
|
|
310
|
-
return current is not None and current > self.value
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
class IsGreaterThanOrEqual(HookCondition):
|
|
314
|
-
def __init__(self, field, value):
|
|
315
|
-
self.field = field
|
|
316
|
-
self.value = value
|
|
317
|
-
|
|
318
|
-
def check(self, instance, original_instance=None):
|
|
319
|
-
current = resolve_dotted_attr(instance, self.field)
|
|
320
|
-
return current is not None and current >= self.value
|
|
180
|
+
return {self.field_name}
|
|
321
181
|
|
|
322
182
|
|
|
323
|
-
class
|
|
324
|
-
def __init__(self,
|
|
325
|
-
self.
|
|
183
|
+
class WasEqual(HookCondition):
|
|
184
|
+
def __init__(self, field_name, value):
|
|
185
|
+
self.field_name = field_name
|
|
326
186
|
self.value = value
|
|
327
187
|
|
|
328
188
|
def check(self, instance, original_instance=None):
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
class IsLessThanOrEqual(HookCondition):
|
|
334
|
-
def __init__(self, field, value):
|
|
335
|
-
self.field = field
|
|
336
|
-
self.value = value
|
|
189
|
+
if original_instance is None:
|
|
190
|
+
return False
|
|
191
|
+
return getattr(original_instance, self.field_name, None) == self.value
|
|
337
192
|
|
|
338
|
-
def
|
|
339
|
-
|
|
340
|
-
return current is not None and current <= self.value
|
|
193
|
+
def get_required_fields(self):
|
|
194
|
+
return {self.field_name}
|
|
341
195
|
|
|
342
196
|
|
|
343
|
-
class
|
|
344
|
-
def __init__(self,
|
|
345
|
-
self.
|
|
346
|
-
self.condition2 = condition2
|
|
197
|
+
class HasChanged(HookCondition):
|
|
198
|
+
def __init__(self, field_name):
|
|
199
|
+
self.field_name = field_name
|
|
347
200
|
|
|
348
201
|
def check(self, instance, original_instance=None):
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
)
|
|
202
|
+
if original_instance is None:
|
|
203
|
+
return True
|
|
204
|
+
return getattr(instance, self.field_name, None) != getattr(original_instance, self.field_name, None)
|
|
353
205
|
|
|
354
206
|
def get_required_fields(self):
|
|
355
|
-
return self.
|
|
207
|
+
return {self.field_name}
|
|
356
208
|
|
|
357
209
|
|
|
358
|
-
class
|
|
359
|
-
def __init__(self,
|
|
360
|
-
self.
|
|
361
|
-
self.
|
|
210
|
+
class ChangesTo(HookCondition):
|
|
211
|
+
def __init__(self, field_name, value):
|
|
212
|
+
self.field_name = field_name
|
|
213
|
+
self.value = value
|
|
362
214
|
|
|
363
215
|
def check(self, instance, original_instance=None):
|
|
216
|
+
if original_instance is None:
|
|
217
|
+
return getattr(instance, self.field_name, None) == self.value
|
|
364
218
|
return (
|
|
365
|
-
|
|
366
|
-
|
|
219
|
+
getattr(instance, self.field_name, None) == self.value
|
|
220
|
+
and getattr(instance, self.field_name, None) != getattr(original_instance, self.field_name, None)
|
|
367
221
|
)
|
|
368
222
|
|
|
369
223
|
def get_required_fields(self):
|
|
370
|
-
return self.
|
|
224
|
+
return {self.field_name}
|
|
371
225
|
|
|
372
226
|
|
|
373
|
-
class
|
|
374
|
-
def __init__(self,
|
|
375
|
-
self.
|
|
227
|
+
class IsNotEqual(HookCondition):
|
|
228
|
+
def __init__(self, field_name, value):
|
|
229
|
+
self.field_name = field_name
|
|
230
|
+
self.value = value
|
|
376
231
|
|
|
377
232
|
def check(self, instance, original_instance=None):
|
|
378
|
-
return
|
|
233
|
+
return getattr(instance, self.field_name, None) != self.value
|
|
379
234
|
|
|
380
235
|
def get_required_fields(self):
|
|
381
|
-
return self.
|
|
236
|
+
return {self.field_name}
|
|
@@ -1,5 +1,5 @@
|
|
|
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=HTavJ6K6xJtk2pZS9YBcgAW495myDCNxpLo60YiXeDY,7747
|
|
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
|
|
@@ -10,7 +10,7 @@ django_bulk_hooks/manager.py,sha256=DcVosEA4RS79KSYgw3Z14_a9Sd8CfxNNc5F3eSb8xc0,
|
|
|
10
10
|
django_bulk_hooks/models.py,sha256=a9XoGgIG4Sfi_kvGnPBbG2DlvgZDz6Qck4VG-DGqFT0,4981
|
|
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.94.dist-info/LICENSE,sha256=dguKIcbDGeZD-vXWdLyErPUALYOvtX_fO4Zjhq481uk,1088
|
|
14
|
+
django_bulk_hooks-0.1.94.dist-info/METADATA,sha256=33bEnBka7zaJnRj3ov8NcZRU_CCTe-7GNlsDPU0Yjng,9051
|
|
15
|
+
django_bulk_hooks-0.1.94.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
16
|
+
django_bulk_hooks-0.1.94.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|