django-bulk-hooks 0.1.78__tar.gz → 0.1.79__tar.gz
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-0.1.78 → django_bulk_hooks-0.1.79}/PKG-INFO +3 -106
- django_bulk_hooks-0.1.79/README.md +73 -0
- {django_bulk_hooks-0.1.78 → django_bulk_hooks-0.1.79}/django_bulk_hooks/__init__.py +2 -0
- {django_bulk_hooks-0.1.78 → django_bulk_hooks-0.1.79}/django_bulk_hooks/conditions.py +29 -1
- {django_bulk_hooks-0.1.78 → django_bulk_hooks-0.1.79}/django_bulk_hooks/engine.py +1 -28
- {django_bulk_hooks-0.1.78 → django_bulk_hooks-0.1.79}/django_bulk_hooks/handler.py +1 -1
- {django_bulk_hooks-0.1.78 → django_bulk_hooks-0.1.79}/django_bulk_hooks/registry.py +2 -3
- {django_bulk_hooks-0.1.78 → django_bulk_hooks-0.1.79}/pyproject.toml +1 -1
- django_bulk_hooks-0.1.78/README.md +0 -175
- django_bulk_hooks-0.1.78/django_bulk_hooks/priority.py +0 -16
- {django_bulk_hooks-0.1.78 → django_bulk_hooks-0.1.79}/LICENSE +0 -0
- {django_bulk_hooks-0.1.78 → django_bulk_hooks-0.1.79}/django_bulk_hooks/constants.py +0 -0
- {django_bulk_hooks-0.1.78 → django_bulk_hooks-0.1.79}/django_bulk_hooks/context.py +0 -0
- {django_bulk_hooks-0.1.78 → django_bulk_hooks-0.1.79}/django_bulk_hooks/decorators.py +0 -0
- {django_bulk_hooks-0.1.78 → django_bulk_hooks-0.1.79}/django_bulk_hooks/enums.py +0 -0
- {django_bulk_hooks-0.1.78 → django_bulk_hooks-0.1.79}/django_bulk_hooks/manager.py +0 -0
- {django_bulk_hooks-0.1.78 → django_bulk_hooks-0.1.79}/django_bulk_hooks/models.py +0 -0
- {django_bulk_hooks-0.1.78 → django_bulk_hooks-0.1.79}/django_bulk_hooks/queryset.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: django-bulk-hooks
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.79
|
|
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
|
|
@@ -58,7 +58,7 @@ from django_bulk_hooks import hook, AFTER_UPDATE, Hook
|
|
|
58
58
|
from django_bulk_hooks.conditions import WhenFieldHasChanged
|
|
59
59
|
from .models import Account
|
|
60
60
|
|
|
61
|
-
class AccountHooks(
|
|
61
|
+
class AccountHooks(HookHandler):
|
|
62
62
|
@hook(AFTER_UPDATE, model=Account, condition=WhenFieldHasChanged("balance"))
|
|
63
63
|
def log_balance_change(self, new_records, old_records):
|
|
64
64
|
print("Accounts updated:", [a.pk for a in new_records])
|
|
@@ -74,99 +74,10 @@ class AccountHooks(Hook):
|
|
|
74
74
|
print("Accounts deleted:", [a.pk for a in old_records])
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
-
## 🛠 Supported Hook Events
|
|
78
|
-
|
|
79
|
-
- `BEFORE_CREATE`, `AFTER_CREATE`
|
|
80
|
-
- `BEFORE_UPDATE`, `AFTER_UPDATE`
|
|
81
|
-
- `BEFORE_DELETE`, `AFTER_DELETE`
|
|
82
|
-
|
|
83
|
-
## 🔄 Lifecycle Events
|
|
84
|
-
|
|
85
|
-
### Individual Model Operations
|
|
86
|
-
|
|
87
|
-
The `HookModelMixin` automatically triggers hooks for individual model operations:
|
|
88
|
-
|
|
89
|
-
```python
|
|
90
|
-
# These will trigger BEFORE_CREATE and AFTER_CREATE hooks
|
|
91
|
-
account = Account.objects.create(balance=100.00)
|
|
92
|
-
account.save() # for new instances
|
|
93
|
-
|
|
94
|
-
# These will trigger BEFORE_UPDATE and AFTER_UPDATE hooks
|
|
95
|
-
account.balance = 200.00
|
|
96
|
-
account.save() # for existing instances
|
|
97
|
-
|
|
98
|
-
# This will trigger BEFORE_DELETE and AFTER_DELETE hooks
|
|
99
|
-
account.delete()
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
### Bulk Operations
|
|
103
|
-
|
|
104
|
-
Bulk operations also trigger the same hooks:
|
|
105
|
-
|
|
106
|
-
```python
|
|
107
|
-
# Bulk create - triggers BEFORE_CREATE and AFTER_CREATE hooks
|
|
108
|
-
accounts = [
|
|
109
|
-
Account(balance=100.00),
|
|
110
|
-
Account(balance=200.00),
|
|
111
|
-
]
|
|
112
|
-
Account.objects.bulk_create(accounts)
|
|
113
|
-
|
|
114
|
-
# Bulk update - triggers BEFORE_UPDATE and AFTER_UPDATE hooks
|
|
115
|
-
for account in accounts:
|
|
116
|
-
account.balance *= 1.1
|
|
117
|
-
Account.objects.bulk_update(accounts, ['balance'])
|
|
118
|
-
|
|
119
|
-
# Bulk delete - triggers BEFORE_DELETE and AFTER_DELETE hooks
|
|
120
|
-
Account.objects.bulk_delete(accounts)
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
### Queryset Operations
|
|
124
|
-
|
|
125
|
-
Queryset operations are also supported:
|
|
126
|
-
|
|
127
|
-
```python
|
|
128
|
-
# Queryset update - triggers BEFORE_UPDATE and AFTER_UPDATE hooks
|
|
129
|
-
Account.objects.update(balance=0.00)
|
|
130
|
-
|
|
131
|
-
# Queryset delete - triggers BEFORE_DELETE and AFTER_DELETE hooks
|
|
132
|
-
Account.objects.delete()
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
## 🧠 Why?
|
|
136
|
-
|
|
137
|
-
Django's `bulk_` methods bypass signals and `save()`. This package fills that gap with:
|
|
138
|
-
|
|
139
|
-
- Hooks that behave consistently across creates/updates/deletes
|
|
140
|
-
- **NEW**: Individual model lifecycle hooks that work with `save()` and `delete()`
|
|
141
|
-
- Scalable performance via chunking (default 200)
|
|
142
|
-
- Support for `@hook` decorators and centralized hook classes
|
|
143
|
-
- **NEW**: Automatic hook triggering for admin operations and other Django features
|
|
144
|
-
|
|
145
|
-
## 📦 Usage Examples
|
|
146
|
-
|
|
147
|
-
### Individual Model Operations
|
|
148
|
-
|
|
149
|
-
```python
|
|
150
|
-
# These automatically trigger hooks
|
|
151
|
-
account = Account.objects.create(balance=100.00)
|
|
152
|
-
account.balance = 200.00
|
|
153
|
-
account.save()
|
|
154
|
-
account.delete()
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
### Bulk Operations
|
|
158
|
-
|
|
159
|
-
```python
|
|
160
|
-
# These also trigger hooks
|
|
161
|
-
Account.objects.bulk_create(accounts)
|
|
162
|
-
Account.objects.bulk_update(accounts, ['balance'])
|
|
163
|
-
Account.objects.bulk_delete(accounts)
|
|
164
|
-
```
|
|
165
|
-
|
|
166
77
|
### Advanced Hook Usage
|
|
167
78
|
|
|
168
79
|
```python
|
|
169
|
-
class AdvancedAccountHooks(
|
|
80
|
+
class AdvancedAccountHooks(HookHandler):
|
|
170
81
|
@hook(BEFORE_UPDATE, model=Account, condition=WhenFieldHasChanged("balance"))
|
|
171
82
|
def validate_balance_change(self, new_records, old_records):
|
|
172
83
|
for new_account, old_account in zip(new_records, old_records):
|
|
@@ -179,17 +90,3 @@ class AdvancedAccountHooks(Hook):
|
|
|
179
90
|
# Send welcome email logic here
|
|
180
91
|
pass
|
|
181
92
|
```
|
|
182
|
-
|
|
183
|
-
## 🧩 Integration with Queryable Properties
|
|
184
|
-
|
|
185
|
-
You can extend from `BulkHookManager` to support formula fields or property querying.
|
|
186
|
-
|
|
187
|
-
```python
|
|
188
|
-
class MyManager(BulkHookManager, QueryablePropertiesManager):
|
|
189
|
-
pass
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
## 📝 License
|
|
193
|
-
|
|
194
|
-
MIT © 2024 Augend / Konrad Beck
|
|
195
|
-
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
|
|
2
|
+
# django-bulk-hooks
|
|
3
|
+
|
|
4
|
+
⚡ Bulk hooks for Django bulk operations and individual model lifecycle events.
|
|
5
|
+
|
|
6
|
+
`django-bulk-hooks` brings a declarative, hook-like experience to Django's `bulk_create`, `bulk_update`, and `bulk_delete` — including support for `BEFORE_` and `AFTER_` hooks, conditions, batching, and transactional safety. It also provides comprehensive lifecycle hooks for individual model operations.
|
|
7
|
+
|
|
8
|
+
## ✨ Features
|
|
9
|
+
|
|
10
|
+
- Declarative hook system: `@hook(AFTER_UPDATE, condition=...)`
|
|
11
|
+
- BEFORE/AFTER hooks for create, update, delete
|
|
12
|
+
- Hook-aware manager that wraps Django's `bulk_` operations
|
|
13
|
+
- **NEW**: `HookModelMixin` for individual model lifecycle events
|
|
14
|
+
- Hook chaining, hook deduplication, and atomicity
|
|
15
|
+
- Class-based hook handlers with DI support
|
|
16
|
+
- Support for both bulk and individual model operations
|
|
17
|
+
|
|
18
|
+
## 🚀 Quickstart
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pip install django-bulk-hooks
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Define Your Model
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
from django.db import models
|
|
28
|
+
from django_bulk_hooks.models import HookModelMixin
|
|
29
|
+
|
|
30
|
+
class Account(HookModelMixin):
|
|
31
|
+
balance = models.DecimalField(max_digits=10, decimal_places=2)
|
|
32
|
+
# The HookModelMixin automatically provides BulkHookManager
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Create a Hook Handler
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
from django_bulk_hooks import hook, AFTER_UPDATE, Hook
|
|
39
|
+
from django_bulk_hooks.conditions import WhenFieldHasChanged
|
|
40
|
+
from .models import Account
|
|
41
|
+
|
|
42
|
+
class AccountHooks(HookHandler):
|
|
43
|
+
@hook(AFTER_UPDATE, model=Account, condition=WhenFieldHasChanged("balance"))
|
|
44
|
+
def log_balance_change(self, new_records, old_records):
|
|
45
|
+
print("Accounts updated:", [a.pk for a in new_records])
|
|
46
|
+
|
|
47
|
+
@hook(BEFORE_CREATE, model=Account)
|
|
48
|
+
def before_create(self, new_records, old_records):
|
|
49
|
+
for account in new_records:
|
|
50
|
+
if account.balance < 0:
|
|
51
|
+
raise ValueError("Account cannot have negative balance")
|
|
52
|
+
|
|
53
|
+
@hook(AFTER_DELETE, model=Account)
|
|
54
|
+
def after_delete(self, new_records, old_records):
|
|
55
|
+
print("Accounts deleted:", [a.pk for a in old_records])
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Advanced Hook Usage
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
class AdvancedAccountHooks(HookHandler):
|
|
62
|
+
@hook(BEFORE_UPDATE, model=Account, condition=WhenFieldHasChanged("balance"))
|
|
63
|
+
def validate_balance_change(self, new_records, old_records):
|
|
64
|
+
for new_account, old_account in zip(new_records, old_records):
|
|
65
|
+
if new_account.balance < 0 and old_account.balance >= 0:
|
|
66
|
+
raise ValueError("Cannot set negative balance")
|
|
67
|
+
|
|
68
|
+
@hook(AFTER_CREATE, model=Account)
|
|
69
|
+
def send_welcome_email(self, new_records, old_records):
|
|
70
|
+
for account in new_records:
|
|
71
|
+
# Send welcome email logic here
|
|
72
|
+
pass
|
|
73
|
+
```
|
|
@@ -12,6 +12,7 @@ from django_bulk_hooks.constants import (
|
|
|
12
12
|
from django_bulk_hooks.engine import safe_get_related_object, safe_get_related_attr
|
|
13
13
|
from django_bulk_hooks.handler import HookHandler
|
|
14
14
|
from django_bulk_hooks.models import HookModelMixin
|
|
15
|
+
from django_bulk_hooks.enums import Priority
|
|
15
16
|
|
|
16
17
|
__all__ = [
|
|
17
18
|
"HookHandler",
|
|
@@ -27,4 +28,5 @@ __all__ = [
|
|
|
27
28
|
"VALIDATE_DELETE",
|
|
28
29
|
"safe_get_related_object",
|
|
29
30
|
"safe_get_related_attr",
|
|
31
|
+
"Priority",
|
|
30
32
|
]
|
|
@@ -1,4 +1,32 @@
|
|
|
1
|
-
from
|
|
1
|
+
from django.db import models
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def safe_get_related_object(instance, field_name):
|
|
5
|
+
"""
|
|
6
|
+
Safely get a related object without raising RelatedObjectDoesNotExist.
|
|
7
|
+
Returns None if the foreign key field is None or the related object doesn't exist.
|
|
8
|
+
"""
|
|
9
|
+
if not hasattr(instance, field_name):
|
|
10
|
+
return None
|
|
11
|
+
|
|
12
|
+
# Get the foreign key field
|
|
13
|
+
try:
|
|
14
|
+
field = instance._meta.get_field(field_name)
|
|
15
|
+
if not field.is_relation or field.many_to_many or field.one_to_many:
|
|
16
|
+
return getattr(instance, field_name, None)
|
|
17
|
+
except models.FieldDoesNotExist:
|
|
18
|
+
return getattr(instance, field_name, None)
|
|
19
|
+
|
|
20
|
+
# Check if the foreign key field is None
|
|
21
|
+
fk_field_name = f"{field_name}_id"
|
|
22
|
+
if hasattr(instance, fk_field_name) and getattr(instance, fk_field_name) is None:
|
|
23
|
+
return None
|
|
24
|
+
|
|
25
|
+
# Try to get the related object, but catch RelatedObjectDoesNotExist
|
|
26
|
+
try:
|
|
27
|
+
return getattr(instance, field_name)
|
|
28
|
+
except field.related_model.RelatedObjectDoesNotExist:
|
|
29
|
+
return None
|
|
2
30
|
|
|
3
31
|
|
|
4
32
|
def resolve_dotted_attr(instance, dotted_path):
|
|
@@ -3,38 +3,11 @@ import logging
|
|
|
3
3
|
from django.core.exceptions import ValidationError
|
|
4
4
|
from django.db import models
|
|
5
5
|
from django_bulk_hooks.registry import get_hooks
|
|
6
|
+
from django_bulk_hooks.conditions import safe_get_related_object
|
|
6
7
|
|
|
7
8
|
logger = logging.getLogger(__name__)
|
|
8
9
|
|
|
9
10
|
|
|
10
|
-
def safe_get_related_object(instance, field_name):
|
|
11
|
-
"""
|
|
12
|
-
Safely get a related object without raising RelatedObjectDoesNotExist.
|
|
13
|
-
Returns None if the foreign key field is None or the related object doesn't exist.
|
|
14
|
-
"""
|
|
15
|
-
if not hasattr(instance, field_name):
|
|
16
|
-
return None
|
|
17
|
-
|
|
18
|
-
# Get the foreign key field
|
|
19
|
-
try:
|
|
20
|
-
field = instance._meta.get_field(field_name)
|
|
21
|
-
if not field.is_relation or field.many_to_many or field.one_to_many:
|
|
22
|
-
return getattr(instance, field_name, None)
|
|
23
|
-
except models.FieldDoesNotExist:
|
|
24
|
-
return getattr(instance, field_name, None)
|
|
25
|
-
|
|
26
|
-
# Check if the foreign key field is None
|
|
27
|
-
fk_field_name = f"{field_name}_id"
|
|
28
|
-
if hasattr(instance, fk_field_name) and getattr(instance, fk_field_name) is None:
|
|
29
|
-
return None
|
|
30
|
-
|
|
31
|
-
# Try to get the related object, but catch RelatedObjectDoesNotExist
|
|
32
|
-
try:
|
|
33
|
-
return getattr(instance, field_name)
|
|
34
|
-
except field.related_model.RelatedObjectDoesNotExist:
|
|
35
|
-
return None
|
|
36
|
-
|
|
37
|
-
|
|
38
11
|
def safe_get_related_attr(instance, field_name, attr_name=None):
|
|
39
12
|
"""
|
|
40
13
|
Safely get a related object or its attribute without raising RelatedObjectDoesNotExist.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from collections.abc import Callable
|
|
2
2
|
from typing import Union
|
|
3
3
|
|
|
4
|
-
from django_bulk_hooks.
|
|
4
|
+
from django_bulk_hooks.enums import Priority
|
|
5
5
|
|
|
6
6
|
_hooks: dict[tuple[type, str], list[tuple[type, str, Callable, int]]] = {}
|
|
7
7
|
|
|
@@ -17,8 +17,7 @@ def register_hook(
|
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
def get_hooks(model, event):
|
|
20
|
-
|
|
21
|
-
return hooks
|
|
20
|
+
return _hooks.get((model, event), [])
|
|
22
21
|
|
|
23
22
|
|
|
24
23
|
def list_all_hooks():
|
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
# django-bulk-hooks
|
|
3
|
-
|
|
4
|
-
⚡ Bulk hooks for Django bulk operations and individual model lifecycle events.
|
|
5
|
-
|
|
6
|
-
`django-bulk-hooks` brings a declarative, hook-like experience to Django's `bulk_create`, `bulk_update`, and `bulk_delete` — including support for `BEFORE_` and `AFTER_` hooks, conditions, batching, and transactional safety. It also provides comprehensive lifecycle hooks for individual model operations.
|
|
7
|
-
|
|
8
|
-
## ✨ Features
|
|
9
|
-
|
|
10
|
-
- Declarative hook system: `@hook(AFTER_UPDATE, condition=...)`
|
|
11
|
-
- BEFORE/AFTER hooks for create, update, delete
|
|
12
|
-
- Hook-aware manager that wraps Django's `bulk_` operations
|
|
13
|
-
- **NEW**: `HookModelMixin` for individual model lifecycle events
|
|
14
|
-
- Hook chaining, hook deduplication, and atomicity
|
|
15
|
-
- Class-based hook handlers with DI support
|
|
16
|
-
- Support for both bulk and individual model operations
|
|
17
|
-
|
|
18
|
-
## 🚀 Quickstart
|
|
19
|
-
|
|
20
|
-
```bash
|
|
21
|
-
pip install django-bulk-hooks
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
### Define Your Model
|
|
25
|
-
|
|
26
|
-
```python
|
|
27
|
-
from django.db import models
|
|
28
|
-
from django_bulk_hooks.models import HookModelMixin
|
|
29
|
-
|
|
30
|
-
class Account(HookModelMixin):
|
|
31
|
-
balance = models.DecimalField(max_digits=10, decimal_places=2)
|
|
32
|
-
# The HookModelMixin automatically provides BulkHookManager
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
### Create a Hook Handler
|
|
36
|
-
|
|
37
|
-
```python
|
|
38
|
-
from django_bulk_hooks import hook, AFTER_UPDATE, Hook
|
|
39
|
-
from django_bulk_hooks.conditions import WhenFieldHasChanged
|
|
40
|
-
from .models import Account
|
|
41
|
-
|
|
42
|
-
class AccountHooks(Hook):
|
|
43
|
-
@hook(AFTER_UPDATE, model=Account, condition=WhenFieldHasChanged("balance"))
|
|
44
|
-
def log_balance_change(self, new_records, old_records):
|
|
45
|
-
print("Accounts updated:", [a.pk for a in new_records])
|
|
46
|
-
|
|
47
|
-
@hook(BEFORE_CREATE, model=Account)
|
|
48
|
-
def before_create(self, new_records, old_records):
|
|
49
|
-
for account in new_records:
|
|
50
|
-
if account.balance < 0:
|
|
51
|
-
raise ValueError("Account cannot have negative balance")
|
|
52
|
-
|
|
53
|
-
@hook(AFTER_DELETE, model=Account)
|
|
54
|
-
def after_delete(self, new_records, old_records):
|
|
55
|
-
print("Accounts deleted:", [a.pk for a in old_records])
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
## 🛠 Supported Hook Events
|
|
59
|
-
|
|
60
|
-
- `BEFORE_CREATE`, `AFTER_CREATE`
|
|
61
|
-
- `BEFORE_UPDATE`, `AFTER_UPDATE`
|
|
62
|
-
- `BEFORE_DELETE`, `AFTER_DELETE`
|
|
63
|
-
|
|
64
|
-
## 🔄 Lifecycle Events
|
|
65
|
-
|
|
66
|
-
### Individual Model Operations
|
|
67
|
-
|
|
68
|
-
The `HookModelMixin` automatically triggers hooks for individual model operations:
|
|
69
|
-
|
|
70
|
-
```python
|
|
71
|
-
# These will trigger BEFORE_CREATE and AFTER_CREATE hooks
|
|
72
|
-
account = Account.objects.create(balance=100.00)
|
|
73
|
-
account.save() # for new instances
|
|
74
|
-
|
|
75
|
-
# These will trigger BEFORE_UPDATE and AFTER_UPDATE hooks
|
|
76
|
-
account.balance = 200.00
|
|
77
|
-
account.save() # for existing instances
|
|
78
|
-
|
|
79
|
-
# This will trigger BEFORE_DELETE and AFTER_DELETE hooks
|
|
80
|
-
account.delete()
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
### Bulk Operations
|
|
84
|
-
|
|
85
|
-
Bulk operations also trigger the same hooks:
|
|
86
|
-
|
|
87
|
-
```python
|
|
88
|
-
# Bulk create - triggers BEFORE_CREATE and AFTER_CREATE hooks
|
|
89
|
-
accounts = [
|
|
90
|
-
Account(balance=100.00),
|
|
91
|
-
Account(balance=200.00),
|
|
92
|
-
]
|
|
93
|
-
Account.objects.bulk_create(accounts)
|
|
94
|
-
|
|
95
|
-
# Bulk update - triggers BEFORE_UPDATE and AFTER_UPDATE hooks
|
|
96
|
-
for account in accounts:
|
|
97
|
-
account.balance *= 1.1
|
|
98
|
-
Account.objects.bulk_update(accounts, ['balance'])
|
|
99
|
-
|
|
100
|
-
# Bulk delete - triggers BEFORE_DELETE and AFTER_DELETE hooks
|
|
101
|
-
Account.objects.bulk_delete(accounts)
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
### Queryset Operations
|
|
105
|
-
|
|
106
|
-
Queryset operations are also supported:
|
|
107
|
-
|
|
108
|
-
```python
|
|
109
|
-
# Queryset update - triggers BEFORE_UPDATE and AFTER_UPDATE hooks
|
|
110
|
-
Account.objects.update(balance=0.00)
|
|
111
|
-
|
|
112
|
-
# Queryset delete - triggers BEFORE_DELETE and AFTER_DELETE hooks
|
|
113
|
-
Account.objects.delete()
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
## 🧠 Why?
|
|
117
|
-
|
|
118
|
-
Django's `bulk_` methods bypass signals and `save()`. This package fills that gap with:
|
|
119
|
-
|
|
120
|
-
- Hooks that behave consistently across creates/updates/deletes
|
|
121
|
-
- **NEW**: Individual model lifecycle hooks that work with `save()` and `delete()`
|
|
122
|
-
- Scalable performance via chunking (default 200)
|
|
123
|
-
- Support for `@hook` decorators and centralized hook classes
|
|
124
|
-
- **NEW**: Automatic hook triggering for admin operations and other Django features
|
|
125
|
-
|
|
126
|
-
## 📦 Usage Examples
|
|
127
|
-
|
|
128
|
-
### Individual Model Operations
|
|
129
|
-
|
|
130
|
-
```python
|
|
131
|
-
# These automatically trigger hooks
|
|
132
|
-
account = Account.objects.create(balance=100.00)
|
|
133
|
-
account.balance = 200.00
|
|
134
|
-
account.save()
|
|
135
|
-
account.delete()
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
### Bulk Operations
|
|
139
|
-
|
|
140
|
-
```python
|
|
141
|
-
# These also trigger hooks
|
|
142
|
-
Account.objects.bulk_create(accounts)
|
|
143
|
-
Account.objects.bulk_update(accounts, ['balance'])
|
|
144
|
-
Account.objects.bulk_delete(accounts)
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
### Advanced Hook Usage
|
|
148
|
-
|
|
149
|
-
```python
|
|
150
|
-
class AdvancedAccountHooks(Hook):
|
|
151
|
-
@hook(BEFORE_UPDATE, model=Account, condition=WhenFieldHasChanged("balance"))
|
|
152
|
-
def validate_balance_change(self, new_records, old_records):
|
|
153
|
-
for new_account, old_account in zip(new_records, old_records):
|
|
154
|
-
if new_account.balance < 0 and old_account.balance >= 0:
|
|
155
|
-
raise ValueError("Cannot set negative balance")
|
|
156
|
-
|
|
157
|
-
@hook(AFTER_CREATE, model=Account)
|
|
158
|
-
def send_welcome_email(self, new_records, old_records):
|
|
159
|
-
for account in new_records:
|
|
160
|
-
# Send welcome email logic here
|
|
161
|
-
pass
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
## 🧩 Integration with Queryable Properties
|
|
165
|
-
|
|
166
|
-
You can extend from `BulkHookManager` to support formula fields or property querying.
|
|
167
|
-
|
|
168
|
-
```python
|
|
169
|
-
class MyManager(BulkHookManager, QueryablePropertiesManager):
|
|
170
|
-
pass
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
## 📝 License
|
|
174
|
-
|
|
175
|
-
MIT © 2024 Augend / Konrad Beck
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
from enum import IntEnum
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
class Priority(IntEnum):
|
|
5
|
-
"""
|
|
6
|
-
Named priorities for django-bulk-hooks hooks.
|
|
7
|
-
|
|
8
|
-
Lower values run earlier (higher priority).
|
|
9
|
-
Hooks are sorted in ascending order.
|
|
10
|
-
"""
|
|
11
|
-
|
|
12
|
-
HIGHEST = 0 # runs first
|
|
13
|
-
HIGH = 25 # runs early
|
|
14
|
-
NORMAL = 50 # default ordering
|
|
15
|
-
LOW = 75 # runs later
|
|
16
|
-
LOWEST = 100 # runs last
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|