django-bulk-hooks 0.1.83__py3-none-any.whl → 0.1.85__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.

@@ -14,6 +14,35 @@ from django_bulk_hooks.constants import (
14
14
  from django_bulk_hooks.context import HookContext
15
15
  from django_bulk_hooks.engine import run
16
16
  from django_bulk_hooks.manager import BulkHookManager
17
+ from django.db.models.fields.related_descriptors import ForwardManyToOneDescriptor
18
+ from functools import wraps
19
+ import contextlib
20
+
21
+
22
+ @contextlib.contextmanager
23
+ def patch_foreign_key_behavior():
24
+ """
25
+ Temporarily patches Django's foreign key descriptor to return None instead of raising
26
+ RelatedObjectDoesNotExist when accessing an unset foreign key field.
27
+ """
28
+ original_get = ForwardManyToOneDescriptor.__get__
29
+
30
+ @wraps(original_get)
31
+ def safe_get(self, instance, cls=None):
32
+ if instance is None:
33
+ return self
34
+ try:
35
+ return original_get(self, instance, cls)
36
+ except self.RelatedObjectDoesNotExist:
37
+ return None
38
+
39
+ # Patch the descriptor
40
+ ForwardManyToOneDescriptor.__get__ = safe_get
41
+ try:
42
+ yield
43
+ finally:
44
+ # Restore original behavior
45
+ ForwardManyToOneDescriptor.__get__ = original_get
17
46
 
18
47
 
19
48
  class HookModelMixin(models.Model):
@@ -43,46 +72,60 @@ class HookModelMixin(models.Model):
43
72
  if is_create:
44
73
  # For create operations, run VALIDATE_CREATE hooks for validation
45
74
  ctx = HookContext(self.__class__)
46
- run(self.__class__, VALIDATE_CREATE, [self], ctx=ctx)
75
+ with patch_foreign_key_behavior():
76
+ run(self.__class__, VALIDATE_CREATE, [self], ctx=ctx)
47
77
  else:
48
78
  # For update operations, run VALIDATE_UPDATE hooks for validation
49
79
  try:
50
80
  old_instance = self.__class__.objects.get(pk=self.pk)
51
81
  ctx = HookContext(self.__class__)
52
- run(self.__class__, VALIDATE_UPDATE, [self], [old_instance], ctx=ctx)
82
+ with patch_foreign_key_behavior():
83
+ run(self.__class__, VALIDATE_UPDATE, [self], [old_instance], ctx=ctx)
53
84
  except self.__class__.DoesNotExist:
54
85
  # If the old instance doesn't exist, treat as create
55
86
  ctx = HookContext(self.__class__)
56
- run(self.__class__, VALIDATE_CREATE, [self], ctx=ctx)
87
+ with patch_foreign_key_behavior():
88
+ run(self.__class__, VALIDATE_CREATE, [self], ctx=ctx)
57
89
 
58
90
  def save(self, *args, **kwargs):
59
91
  is_create = self.pk is None
60
92
  ctx = HookContext(self.__class__)
61
93
 
62
94
  if is_create:
63
- # For create operations, let Django save first so relationships are set up
95
+ # For create operations, run BEFORE hooks first
96
+ with patch_foreign_key_behavior():
97
+ run(self.__class__, BEFORE_CREATE, [self], ctx=ctx)
98
+
99
+ # Then let Django save
64
100
  super().save(*args, **kwargs)
65
101
 
66
- # Now run hooks after Django has set up the object
67
- run(self.__class__, BEFORE_CREATE, [self], ctx=ctx)
68
- run(self.__class__, AFTER_CREATE, [self], ctx=ctx)
102
+ # Then run AFTER hooks
103
+ with patch_foreign_key_behavior():
104
+ run(self.__class__, AFTER_CREATE, [self], ctx=ctx)
69
105
  else:
70
106
  # For update operations, we need to get the old record
71
107
  try:
72
108
  old_instance = self.__class__.objects.get(pk=self.pk)
73
109
 
74
- # Let Django save first
110
+ # Run BEFORE hooks first
111
+ with patch_foreign_key_behavior():
112
+ run(self.__class__, BEFORE_UPDATE, [self], [old_instance], ctx=ctx)
113
+
114
+ # Then let Django save
75
115
  super().save(*args, **kwargs)
76
116
 
77
- # Now run hooks after Django has set up the object
78
- run(self.__class__, BEFORE_UPDATE, [self], [old_instance], ctx=ctx)
79
- run(self.__class__, AFTER_UPDATE, [self], [old_instance], ctx=ctx)
117
+ # Then run AFTER hooks
118
+ with patch_foreign_key_behavior():
119
+ run(self.__class__, AFTER_UPDATE, [self], [old_instance], ctx=ctx)
80
120
  except self.__class__.DoesNotExist:
81
121
  # If the old instance doesn't exist, treat as create
122
+ with patch_foreign_key_behavior():
123
+ run(self.__class__, BEFORE_CREATE, [self], ctx=ctx)
124
+
82
125
  super().save(*args, **kwargs)
83
126
 
84
- run(self.__class__, BEFORE_CREATE, [self], ctx=ctx)
85
- run(self.__class__, AFTER_CREATE, [self], ctx=ctx)
127
+ with patch_foreign_key_behavior():
128
+ run(self.__class__, AFTER_CREATE, [self], ctx=ctx)
86
129
 
87
130
  return self
88
131
 
@@ -90,12 +133,15 @@ class HookModelMixin(models.Model):
90
133
  ctx = HookContext(self.__class__)
91
134
 
92
135
  # Run validation hooks first
93
- run(self.__class__, VALIDATE_DELETE, [self], ctx=ctx)
136
+ with patch_foreign_key_behavior():
137
+ run(self.__class__, VALIDATE_DELETE, [self], ctx=ctx)
94
138
 
95
139
  # Then run business logic hooks
96
- run(self.__class__, BEFORE_DELETE, [self], ctx=ctx)
140
+ with patch_foreign_key_behavior():
141
+ run(self.__class__, BEFORE_DELETE, [self], ctx=ctx)
97
142
 
98
143
  result = super().delete(*args, **kwargs)
99
144
 
100
- run(self.__class__, AFTER_DELETE, [self], ctx=ctx)
145
+ with patch_foreign_key_behavior():
146
+ run(self.__class__, AFTER_DELETE, [self], ctx=ctx)
101
147
  return result
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: django-bulk-hooks
3
- Version: 0.1.83
3
+ Version: 0.1.85
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
@@ -7,10 +7,10 @@ django_bulk_hooks/engine.py,sha256=b1AO8qyl2VMt1CHyefn1Gt1_ARaV80yBZ3mGXsJ9_eA,2
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
9
  django_bulk_hooks/manager.py,sha256=-V128ACxPAz82ua4jQRFUkjAKtKW4MN5ppz0bHcv5s4,7138
10
- django_bulk_hooks/models.py,sha256=m_dsCcKX8euGU-aDvlkY-vi5QTsRaXhyT-6EZotYuXM,3813
10
+ django_bulk_hooks/models.py,sha256=CD-v8JAZCOnGAHsAxOrlJz5HiOOXNVhGi7p8n3m4oLk,5423
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.83.dist-info/LICENSE,sha256=dguKIcbDGeZD-vXWdLyErPUALYOvtX_fO4Zjhq481uk,1088
14
- django_bulk_hooks-0.1.83.dist-info/METADATA,sha256=G6_6NsM-MqUem0Y5fmZz0pktXtmsaIVpglFeBDIv7JM,9051
15
- django_bulk_hooks-0.1.83.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
16
- django_bulk_hooks-0.1.83.dist-info/RECORD,,
13
+ django_bulk_hooks-0.1.85.dist-info/LICENSE,sha256=dguKIcbDGeZD-vXWdLyErPUALYOvtX_fO4Zjhq481uk,1088
14
+ django_bulk_hooks-0.1.85.dist-info/METADATA,sha256=JJ0uZaiHpf5An0kqNQySULj4hSSpn0JHEppz-xUwnMg,9051
15
+ django_bulk_hooks-0.1.85.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
16
+ django_bulk_hooks-0.1.85.dist-info/RECORD,,