django-fsm-dynamic 1.0.0__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.
- django_fsm_dynamic-1.0.0/LICENSE +21 -0
- django_fsm_dynamic-1.0.0/PKG-INFO +237 -0
- django_fsm_dynamic-1.0.0/README.md +202 -0
- django_fsm_dynamic-1.0.0/django_fsm_dynamic/__init__.py +30 -0
- django_fsm_dynamic-1.0.0/django_fsm_dynamic/__version__.py +3 -0
- django_fsm_dynamic-1.0.0/django_fsm_dynamic/core.py +614 -0
- django_fsm_dynamic-1.0.0/django_fsm_dynamic.egg-info/PKG-INFO +237 -0
- django_fsm_dynamic-1.0.0/django_fsm_dynamic.egg-info/SOURCES.txt +13 -0
- django_fsm_dynamic-1.0.0/django_fsm_dynamic.egg-info/dependency_links.txt +1 -0
- django_fsm_dynamic-1.0.0/django_fsm_dynamic.egg-info/requires.txt +9 -0
- django_fsm_dynamic-1.0.0/django_fsm_dynamic.egg-info/top_level.txt +1 -0
- django_fsm_dynamic-1.0.0/pyproject.toml +102 -0
- django_fsm_dynamic-1.0.0/setup.cfg +4 -0
- django_fsm_dynamic-1.0.0/tests/test_dynamic_utilities.py +524 -0
- django_fsm_dynamic-1.0.0/tests/test_dynamic_workflows.py +466 -0
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2025 LevIT
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
@@ -0,0 +1,237 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: django-fsm-dynamic
|
3
|
+
Version: 1.0.0
|
4
|
+
Summary: Dynamic workflow extensions for django-fsm-2
|
5
|
+
Author-email: LevIT <info@levit.be>
|
6
|
+
License-Expression: MIT
|
7
|
+
Project-URL: Homepage, https://gitlab.levitnet.be/levit/django-fsm-dynamic
|
8
|
+
Project-URL: Repository, https://gitlab.levitnet.be/levit/django-fsm-dynamic.git
|
9
|
+
Keywords: django,fsm,workflow,state-machine,dynamic
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
11
|
+
Classifier: Framework :: Django
|
12
|
+
Classifier: Framework :: Django :: 4.2
|
13
|
+
Classifier: Framework :: Django :: 5.0
|
14
|
+
Classifier: Framework :: Django :: 5.1
|
15
|
+
Classifier: Framework :: Django :: 5.2
|
16
|
+
Classifier: Intended Audience :: Developers
|
17
|
+
Classifier: Programming Language :: Python :: 3.8
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
23
|
+
Requires-Python: >=3.8
|
24
|
+
Description-Content-Type: text/markdown
|
25
|
+
License-File: LICENSE
|
26
|
+
Requires-Dist: django>=4.2
|
27
|
+
Requires-Dist: django-fsm-2>=4.0.0
|
28
|
+
Provides-Extra: dev
|
29
|
+
Requires-Dist: build>=1.2.2.post1; extra == "dev"
|
30
|
+
Requires-Dist: coverage; extra == "dev"
|
31
|
+
Requires-Dist: ruff; extra == "dev"
|
32
|
+
Requires-Dist: setuptools>=75.3.2; extra == "dev"
|
33
|
+
Requires-Dist: twine>=6.1.0; extra == "dev"
|
34
|
+
Dynamic: license-file
|
35
|
+
|
36
|
+
# Django FSM Dynamic
|
37
|
+
|
38
|
+
Dynamic workflow extensions for [django-fsm-2](https://github.com/django-commons/django-fsm-2) that allow optional Django apps to modify FSM state machines without creating database migrations.
|
39
|
+
|
40
|
+
[](https://badge.fury.io/py/django-fsm-dynamic)
|
41
|
+
[](https://pypi.org/project/django-fsm-dynamic/)
|
42
|
+
[](https://docs.djangoproject.com/en/stable/releases/)
|
43
|
+
|
44
|
+
## Features
|
45
|
+
|
46
|
+
- **Dynamic State Enums**: Extend state enums at runtime without migrations
|
47
|
+
- **Callable Choices**: Prevent Django from generating migrations when choices change
|
48
|
+
- **Transition Builder**: Programmatically create FSM transitions
|
49
|
+
- **Workflow Extensions**: Structured app-based workflow modifications
|
50
|
+
- **Migration-Free**: All extensions work without requiring database migrations
|
51
|
+
|
52
|
+
## Installation
|
53
|
+
|
54
|
+
```bash
|
55
|
+
pip install django-fsm-dynamic
|
56
|
+
```
|
57
|
+
|
58
|
+
**Requirements:**
|
59
|
+
- Python 3.8+
|
60
|
+
- Django 4.2+
|
61
|
+
- django-fsm-2 4.0+
|
62
|
+
|
63
|
+
## Quick Start
|
64
|
+
|
65
|
+
### 1. Create a Dynamic State Enum
|
66
|
+
|
67
|
+
```python
|
68
|
+
from django_fsm_dynamic import DynamicStateEnum
|
69
|
+
from django_fsm import FSMIntegerField
|
70
|
+
from django.db import models
|
71
|
+
|
72
|
+
class BlogPostStateEnum(DynamicStateEnum):
|
73
|
+
NEW = 10
|
74
|
+
PUBLISHED = 20
|
75
|
+
HIDDEN = 30
|
76
|
+
|
77
|
+
class BlogPost(models.Model):
|
78
|
+
title = models.CharField(max_length=200)
|
79
|
+
state = FSMIntegerField(
|
80
|
+
default=BlogPostStateEnum.NEW,
|
81
|
+
choices=BlogPostStateEnum.get_choices # Prevents migrations!
|
82
|
+
)
|
83
|
+
```
|
84
|
+
|
85
|
+
### 2. Extend from Another App
|
86
|
+
|
87
|
+
```python
|
88
|
+
# In your review app's apps.py
|
89
|
+
from django.apps import AppConfig
|
90
|
+
from django_fsm_dynamic import WorkflowExtension, TransitionBuilder
|
91
|
+
|
92
|
+
class ReviewWorkflowExtension(WorkflowExtension):
|
93
|
+
target_model = 'blog.BlogPost'
|
94
|
+
target_enum = 'blog.models.BlogPostStateEnum'
|
95
|
+
|
96
|
+
def extend_states(self, enum_class):
|
97
|
+
enum_class.add_state('IN_REVIEW', 15)
|
98
|
+
enum_class.add_state('APPROVED', 17)
|
99
|
+
|
100
|
+
def extend_transitions(self, model_class, enum_class):
|
101
|
+
builder = TransitionBuilder(model_class)
|
102
|
+
builder.add_transition('send_to_review', enum_class.NEW, enum_class.IN_REVIEW)
|
103
|
+
builder.add_transition('approve', enum_class.IN_REVIEW, enum_class.APPROVED)
|
104
|
+
builder.build_and_attach()
|
105
|
+
|
106
|
+
class ReviewConfig(AppConfig):
|
107
|
+
name = 'review'
|
108
|
+
|
109
|
+
def ready(self):
|
110
|
+
ReviewWorkflowExtension(self).apply()
|
111
|
+
```
|
112
|
+
|
113
|
+
### 3. Use the Extended Workflow
|
114
|
+
|
115
|
+
```python
|
116
|
+
# Create a blog post
|
117
|
+
post = BlogPost.objects.create(title="My Post", state=BlogPostStateEnum.NEW)
|
118
|
+
|
119
|
+
# Use dynamically added transitions
|
120
|
+
post.send_to_review() # NEW -> IN_REVIEW
|
121
|
+
post.approve() # IN_REVIEW -> APPROVED
|
122
|
+
```
|
123
|
+
|
124
|
+
## Core Components
|
125
|
+
|
126
|
+
### DynamicStateEnum
|
127
|
+
|
128
|
+
Base class for extensible state enums:
|
129
|
+
|
130
|
+
```python
|
131
|
+
from django_fsm_dynamic import DynamicStateEnum
|
132
|
+
|
133
|
+
class MyStateEnum(DynamicStateEnum):
|
134
|
+
NEW = 10
|
135
|
+
PUBLISHED = 20
|
136
|
+
|
137
|
+
# Other apps can extend:
|
138
|
+
MyStateEnum.add_state('IN_REVIEW', 15)
|
139
|
+
|
140
|
+
# Get all choices including dynamic ones:
|
141
|
+
choices = MyStateEnum.get_choices() # [(10, 'New'), (15, 'In Review'), (20, 'Published')]
|
142
|
+
```
|
143
|
+
|
144
|
+
### Dynamic Choices
|
145
|
+
|
146
|
+
Use the `get_choices` method directly to prevent Django migrations:
|
147
|
+
|
148
|
+
```python
|
149
|
+
class MyModel(models.Model):
|
150
|
+
state = FSMIntegerField(
|
151
|
+
default=MyStateEnum.NEW,
|
152
|
+
choices=MyStateEnum.get_choices # No migrations when enum changes!
|
153
|
+
)
|
154
|
+
```
|
155
|
+
|
156
|
+
### TransitionBuilder
|
157
|
+
|
158
|
+
Programmatically create FSM transitions:
|
159
|
+
|
160
|
+
```python
|
161
|
+
from django_fsm_dynamic import TransitionBuilder
|
162
|
+
|
163
|
+
builder = TransitionBuilder(MyModel)
|
164
|
+
builder.add_transition(
|
165
|
+
'approve',
|
166
|
+
source=MyStateEnum.IN_REVIEW,
|
167
|
+
target=MyStateEnum.APPROVED,
|
168
|
+
conditions=[lambda instance: instance.is_valid()],
|
169
|
+
permission='myapp.can_approve'
|
170
|
+
).build_and_attach()
|
171
|
+
```
|
172
|
+
|
173
|
+
### WorkflowExtension
|
174
|
+
|
175
|
+
Structured approach to extending workflows:
|
176
|
+
|
177
|
+
```python
|
178
|
+
from django_fsm_dynamic import WorkflowExtension
|
179
|
+
|
180
|
+
class MyExtension(WorkflowExtension):
|
181
|
+
target_model = 'app.Model'
|
182
|
+
target_enum = 'app.models.StateEnum'
|
183
|
+
|
184
|
+
def extend_states(self, enum_class):
|
185
|
+
enum_class.add_state('NEW_STATE', 99)
|
186
|
+
|
187
|
+
def extend_transitions(self, model_class, enum_class):
|
188
|
+
# Add new transitions
|
189
|
+
pass
|
190
|
+
|
191
|
+
def modify_existing_transitions(self, model_class, enum_class):
|
192
|
+
# Modify existing transitions
|
193
|
+
pass
|
194
|
+
```
|
195
|
+
|
196
|
+
## Migration from django-fsm-2
|
197
|
+
|
198
|
+
If you were using the dynamic utilities from django-fsm-2, simply update your imports:
|
199
|
+
|
200
|
+
```python
|
201
|
+
# Old (django-fsm-2 < 4.1.0)
|
202
|
+
from django_fsm.dynamic import DynamicStateEnum, TransitionBuilder
|
203
|
+
|
204
|
+
# New (with django-fsm-dynamic)
|
205
|
+
from django_fsm_dynamic import DynamicStateEnum, TransitionBuilder
|
206
|
+
```
|
207
|
+
|
208
|
+
**Note**: If you were using `make_callable_choices` from django-fsm-2, simply use `MyStateEnum.get_choices` directly instead - Django 5.0+ accepts callables for the choices parameter.
|
209
|
+
|
210
|
+
All functionality remains the same, just in a separate package.
|
211
|
+
|
212
|
+
## Documentation
|
213
|
+
|
214
|
+
- [Complete Documentation](docs/dynamic_workflows.md)
|
215
|
+
- [API Reference](docs/api.md)
|
216
|
+
- [Examples](examples/)
|
217
|
+
|
218
|
+
## Why Separate Package?
|
219
|
+
|
220
|
+
Dynamic workflows are a powerful but specialized feature. By extracting them into a separate package:
|
221
|
+
|
222
|
+
1. **Focused Development**: Each package has a clear, focused scope
|
223
|
+
2. **Optional Dependency**: Only install if you need dynamic workflows
|
224
|
+
3. **Independent Versioning**: Features can evolve independently
|
225
|
+
4. **Cleaner Core**: django-fsm-2 stays focused on core FSM functionality
|
226
|
+
|
227
|
+
## Contributing
|
228
|
+
|
229
|
+
We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
|
230
|
+
|
231
|
+
## License
|
232
|
+
|
233
|
+
MIT License. See [LICENSE](LICENSE) for details.
|
234
|
+
|
235
|
+
## Changelog
|
236
|
+
|
237
|
+
See [CHANGELOG.md](CHANGELOG.md) for version history.
|
@@ -0,0 +1,202 @@
|
|
1
|
+
# Django FSM Dynamic
|
2
|
+
|
3
|
+
Dynamic workflow extensions for [django-fsm-2](https://github.com/django-commons/django-fsm-2) that allow optional Django apps to modify FSM state machines without creating database migrations.
|
4
|
+
|
5
|
+
[](https://badge.fury.io/py/django-fsm-dynamic)
|
6
|
+
[](https://pypi.org/project/django-fsm-dynamic/)
|
7
|
+
[](https://docs.djangoproject.com/en/stable/releases/)
|
8
|
+
|
9
|
+
## Features
|
10
|
+
|
11
|
+
- **Dynamic State Enums**: Extend state enums at runtime without migrations
|
12
|
+
- **Callable Choices**: Prevent Django from generating migrations when choices change
|
13
|
+
- **Transition Builder**: Programmatically create FSM transitions
|
14
|
+
- **Workflow Extensions**: Structured app-based workflow modifications
|
15
|
+
- **Migration-Free**: All extensions work without requiring database migrations
|
16
|
+
|
17
|
+
## Installation
|
18
|
+
|
19
|
+
```bash
|
20
|
+
pip install django-fsm-dynamic
|
21
|
+
```
|
22
|
+
|
23
|
+
**Requirements:**
|
24
|
+
- Python 3.8+
|
25
|
+
- Django 4.2+
|
26
|
+
- django-fsm-2 4.0+
|
27
|
+
|
28
|
+
## Quick Start
|
29
|
+
|
30
|
+
### 1. Create a Dynamic State Enum
|
31
|
+
|
32
|
+
```python
|
33
|
+
from django_fsm_dynamic import DynamicStateEnum
|
34
|
+
from django_fsm import FSMIntegerField
|
35
|
+
from django.db import models
|
36
|
+
|
37
|
+
class BlogPostStateEnum(DynamicStateEnum):
|
38
|
+
NEW = 10
|
39
|
+
PUBLISHED = 20
|
40
|
+
HIDDEN = 30
|
41
|
+
|
42
|
+
class BlogPost(models.Model):
|
43
|
+
title = models.CharField(max_length=200)
|
44
|
+
state = FSMIntegerField(
|
45
|
+
default=BlogPostStateEnum.NEW,
|
46
|
+
choices=BlogPostStateEnum.get_choices # Prevents migrations!
|
47
|
+
)
|
48
|
+
```
|
49
|
+
|
50
|
+
### 2. Extend from Another App
|
51
|
+
|
52
|
+
```python
|
53
|
+
# In your review app's apps.py
|
54
|
+
from django.apps import AppConfig
|
55
|
+
from django_fsm_dynamic import WorkflowExtension, TransitionBuilder
|
56
|
+
|
57
|
+
class ReviewWorkflowExtension(WorkflowExtension):
|
58
|
+
target_model = 'blog.BlogPost'
|
59
|
+
target_enum = 'blog.models.BlogPostStateEnum'
|
60
|
+
|
61
|
+
def extend_states(self, enum_class):
|
62
|
+
enum_class.add_state('IN_REVIEW', 15)
|
63
|
+
enum_class.add_state('APPROVED', 17)
|
64
|
+
|
65
|
+
def extend_transitions(self, model_class, enum_class):
|
66
|
+
builder = TransitionBuilder(model_class)
|
67
|
+
builder.add_transition('send_to_review', enum_class.NEW, enum_class.IN_REVIEW)
|
68
|
+
builder.add_transition('approve', enum_class.IN_REVIEW, enum_class.APPROVED)
|
69
|
+
builder.build_and_attach()
|
70
|
+
|
71
|
+
class ReviewConfig(AppConfig):
|
72
|
+
name = 'review'
|
73
|
+
|
74
|
+
def ready(self):
|
75
|
+
ReviewWorkflowExtension(self).apply()
|
76
|
+
```
|
77
|
+
|
78
|
+
### 3. Use the Extended Workflow
|
79
|
+
|
80
|
+
```python
|
81
|
+
# Create a blog post
|
82
|
+
post = BlogPost.objects.create(title="My Post", state=BlogPostStateEnum.NEW)
|
83
|
+
|
84
|
+
# Use dynamically added transitions
|
85
|
+
post.send_to_review() # NEW -> IN_REVIEW
|
86
|
+
post.approve() # IN_REVIEW -> APPROVED
|
87
|
+
```
|
88
|
+
|
89
|
+
## Core Components
|
90
|
+
|
91
|
+
### DynamicStateEnum
|
92
|
+
|
93
|
+
Base class for extensible state enums:
|
94
|
+
|
95
|
+
```python
|
96
|
+
from django_fsm_dynamic import DynamicStateEnum
|
97
|
+
|
98
|
+
class MyStateEnum(DynamicStateEnum):
|
99
|
+
NEW = 10
|
100
|
+
PUBLISHED = 20
|
101
|
+
|
102
|
+
# Other apps can extend:
|
103
|
+
MyStateEnum.add_state('IN_REVIEW', 15)
|
104
|
+
|
105
|
+
# Get all choices including dynamic ones:
|
106
|
+
choices = MyStateEnum.get_choices() # [(10, 'New'), (15, 'In Review'), (20, 'Published')]
|
107
|
+
```
|
108
|
+
|
109
|
+
### Dynamic Choices
|
110
|
+
|
111
|
+
Use the `get_choices` method directly to prevent Django migrations:
|
112
|
+
|
113
|
+
```python
|
114
|
+
class MyModel(models.Model):
|
115
|
+
state = FSMIntegerField(
|
116
|
+
default=MyStateEnum.NEW,
|
117
|
+
choices=MyStateEnum.get_choices # No migrations when enum changes!
|
118
|
+
)
|
119
|
+
```
|
120
|
+
|
121
|
+
### TransitionBuilder
|
122
|
+
|
123
|
+
Programmatically create FSM transitions:
|
124
|
+
|
125
|
+
```python
|
126
|
+
from django_fsm_dynamic import TransitionBuilder
|
127
|
+
|
128
|
+
builder = TransitionBuilder(MyModel)
|
129
|
+
builder.add_transition(
|
130
|
+
'approve',
|
131
|
+
source=MyStateEnum.IN_REVIEW,
|
132
|
+
target=MyStateEnum.APPROVED,
|
133
|
+
conditions=[lambda instance: instance.is_valid()],
|
134
|
+
permission='myapp.can_approve'
|
135
|
+
).build_and_attach()
|
136
|
+
```
|
137
|
+
|
138
|
+
### WorkflowExtension
|
139
|
+
|
140
|
+
Structured approach to extending workflows:
|
141
|
+
|
142
|
+
```python
|
143
|
+
from django_fsm_dynamic import WorkflowExtension
|
144
|
+
|
145
|
+
class MyExtension(WorkflowExtension):
|
146
|
+
target_model = 'app.Model'
|
147
|
+
target_enum = 'app.models.StateEnum'
|
148
|
+
|
149
|
+
def extend_states(self, enum_class):
|
150
|
+
enum_class.add_state('NEW_STATE', 99)
|
151
|
+
|
152
|
+
def extend_transitions(self, model_class, enum_class):
|
153
|
+
# Add new transitions
|
154
|
+
pass
|
155
|
+
|
156
|
+
def modify_existing_transitions(self, model_class, enum_class):
|
157
|
+
# Modify existing transitions
|
158
|
+
pass
|
159
|
+
```
|
160
|
+
|
161
|
+
## Migration from django-fsm-2
|
162
|
+
|
163
|
+
If you were using the dynamic utilities from django-fsm-2, simply update your imports:
|
164
|
+
|
165
|
+
```python
|
166
|
+
# Old (django-fsm-2 < 4.1.0)
|
167
|
+
from django_fsm.dynamic import DynamicStateEnum, TransitionBuilder
|
168
|
+
|
169
|
+
# New (with django-fsm-dynamic)
|
170
|
+
from django_fsm_dynamic import DynamicStateEnum, TransitionBuilder
|
171
|
+
```
|
172
|
+
|
173
|
+
**Note**: If you were using `make_callable_choices` from django-fsm-2, simply use `MyStateEnum.get_choices` directly instead - Django 5.0+ accepts callables for the choices parameter.
|
174
|
+
|
175
|
+
All functionality remains the same, just in a separate package.
|
176
|
+
|
177
|
+
## Documentation
|
178
|
+
|
179
|
+
- [Complete Documentation](docs/dynamic_workflows.md)
|
180
|
+
- [API Reference](docs/api.md)
|
181
|
+
- [Examples](examples/)
|
182
|
+
|
183
|
+
## Why Separate Package?
|
184
|
+
|
185
|
+
Dynamic workflows are a powerful but specialized feature. By extracting them into a separate package:
|
186
|
+
|
187
|
+
1. **Focused Development**: Each package has a clear, focused scope
|
188
|
+
2. **Optional Dependency**: Only install if you need dynamic workflows
|
189
|
+
3. **Independent Versioning**: Features can evolve independently
|
190
|
+
4. **Cleaner Core**: django-fsm-2 stays focused on core FSM functionality
|
191
|
+
|
192
|
+
## Contributing
|
193
|
+
|
194
|
+
We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
|
195
|
+
|
196
|
+
## License
|
197
|
+
|
198
|
+
MIT License. See [LICENSE](LICENSE) for details.
|
199
|
+
|
200
|
+
## Changelog
|
201
|
+
|
202
|
+
See [CHANGELOG.md](CHANGELOG.md) for version history.
|
@@ -0,0 +1,30 @@
|
|
1
|
+
"""
|
2
|
+
Django FSM Dynamic Workflows
|
3
|
+
|
4
|
+
Dynamic workflow extensions for django-fsm-2 that allow optional Django apps
|
5
|
+
to modify FSM state machines without creating database migrations.
|
6
|
+
|
7
|
+
Features:
|
8
|
+
- Dynamic state enums that can be extended at runtime
|
9
|
+
- Callable choices that prevent Django migrations
|
10
|
+
- Workflow extension framework for app-based extensions
|
11
|
+
- Transition builder utilities for programmatic transition creation
|
12
|
+
"""
|
13
|
+
|
14
|
+
from __future__ import annotations
|
15
|
+
|
16
|
+
from .__version__ import __version__
|
17
|
+
from .core import DynamicStateEnum
|
18
|
+
from .core import TransitionBuilder
|
19
|
+
from .core import TransitionModifier
|
20
|
+
from .core import WorkflowExtension
|
21
|
+
from .core import create_simple_workflow_extension
|
22
|
+
|
23
|
+
__all__ = [
|
24
|
+
"DynamicStateEnum",
|
25
|
+
"TransitionBuilder",
|
26
|
+
"TransitionModifier",
|
27
|
+
"WorkflowExtension",
|
28
|
+
"__version__",
|
29
|
+
"create_simple_workflow_extension",
|
30
|
+
]
|