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.
@@ -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
+ [![PyPI version](https://badge.fury.io/py/django-fsm-dynamic.svg)](https://badge.fury.io/py/django-fsm-dynamic)
41
+ [![Python Support](https://img.shields.io/pypi/pyversions/django-fsm-dynamic.svg)](https://pypi.org/project/django-fsm-dynamic/)
42
+ [![Django Support](https://img.shields.io/badge/django-4.2%2B-blue)](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
+ [![PyPI version](https://badge.fury.io/py/django-fsm-dynamic.svg)](https://badge.fury.io/py/django-fsm-dynamic)
6
+ [![Python Support](https://img.shields.io/pypi/pyversions/django-fsm-dynamic.svg)](https://pypi.org/project/django-fsm-dynamic/)
7
+ [![Django Support](https://img.shields.io/badge/django-4.2%2B-blue)](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
+ ]
@@ -0,0 +1,3 @@
1
+ from __future__ import annotations
2
+
3
+ __version__ = "1.0.0"