Skip to content

Commit 80a8246

Browse files
committed
Make it compatible with fsm-log (#51)
1 parent 4c59cdd commit 80a8246

File tree

10 files changed

+148
-15
lines changed

10 files changed

+148
-15
lines changed

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -427,31 +427,31 @@ from django_fsm.admin import FSMAdminMixin
427427

428428
@admin.register(AdminBlogPost)
429429
class MyAdmin(FSMAdminMixin, admin.ModelAdmin):
430-
fsm_field = ['my_fsm_field',]
430+
fsm_field = ['my_fsm_field']
431431
...
432432
```
433433

434-
3. You can customize the label by adding ``custom={"label"="My awesome transition"}`` to the transition decorator
434+
3. You can customize the label by adding ``custom={"label": "My awesome transition"}`` to the transition decorator
435435

436436
``` python
437437
@transition(
438438
field='state',
439439
source=['startstate'],
440440
target='finalstate',
441-
custom={"label"=False},
441+
custom={"label": False},
442442
)
443443
def do_something(self, param):
444444
...
445445
```
446446

447-
4. By adding ``custom={"admin"=False}`` to the transition decorator, one can disallow a transition to show up in the admin interface.
447+
4. By adding ``custom={"admin": False}`` to the transition decorator, one can disallow a transition to show up in the admin interface.
448448

449449
``` python
450450
@transition(
451451
field='state',
452452
source=['startstate'],
453453
target='finalstate',
454-
custom={"admin"=False},
454+
custom={"admin": False},
455455
)
456456
def do_something(self, param):
457457
# will not add a button "Do Something" to your admin model interface

django_fsm/admin.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations
22

33
from dataclasses import dataclass
4+
from functools import partial
45
from typing import Any
56

67
from django.conf import settings
@@ -15,6 +16,13 @@
1516

1617
import django_fsm as fsm
1718

19+
try:
20+
import django_fsm_log # noqa: F401
21+
except ModuleNotFoundError:
22+
FSM_LOG_ENABLED = False
23+
else:
24+
FSM_LOG_ENABLED = True
25+
1826

1927
@dataclass
2028
class FSMObjectTransition:
@@ -127,7 +135,20 @@ def response_change(self, request: HttpRequest, obj: Any) -> HttpResponse:
127135
)
128136

129137
try:
130-
transition_func()
138+
if FSM_LOG_ENABLED:
139+
for fn in [
140+
partial(transition_func, request=request, by=request.user),
141+
partial(transition_func, by=request.user),
142+
transition_func,
143+
]:
144+
try:
145+
fn()
146+
except TypeError: # noqa: PERF203
147+
pass
148+
else:
149+
break
150+
else:
151+
transition_func()
131152
except fsm.TransitionNotAllowed:
132153
self.message_user(
133154
request=request,

poetry.lock

Lines changed: 49 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ pre-commit = "*"
4848
pytest = "*"
4949
pytest-cov = "*"
5050
pytest-django = "*"
51+
django_fsm_log = "*"
5152

5253
[tool.pytest.ini_options]
5354
DJANGO_SETTINGS_MODULE = "tests.settings"

tests/settings.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
"django.contrib.sessions",
4444
"django.contrib.messages",
4545
"django.contrib.staticfiles",
46+
"django_fsm_log",
4647
"guardian",
4748
*PROJECT_APPS,
4849
]
@@ -135,3 +136,35 @@
135136
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
136137

137138
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
139+
140+
141+
# Django FSM-log settings
142+
DJANGO_FSM_LOG_IGNORED_MODELS = (
143+
# "tests.testapp.models.AdminBlogPost",
144+
"tests.testapp.models.Application",
145+
"tests.testapp.models.BlogPost",
146+
"tests.testapp.models.DbState",
147+
"tests.testapp.models.FKApplication",
148+
"tests.testapp.tests.SimpleBlogPost",
149+
"tests.testapp.tests.test_abstract_inheritance.BaseAbstractModel",
150+
"tests.testapp.tests.test_abstract_inheritance.InheritedFromAbstractModel",
151+
"tests.testapp.tests.test_access_deferred_fsm_field.DeferrableModel",
152+
"tests.testapp.tests.test_basic_transitions.SimpleBlogPost",
153+
"tests.testapp.tests.test_conditions.BlogPostWithConditions",
154+
"tests.testapp.tests.test_custom_data.BlogPostWithCustomData",
155+
"tests.testapp.tests.test_exception_transitions.ExceptionalBlogPost",
156+
"tests.testapp.tests.test_graph_transitions.VisualBlogPost",
157+
"tests.testapp.tests.test_integer_field.BlogPostWithIntegerField",
158+
"tests.testapp.tests.test_lock_mixin.ExtendedBlogPost",
159+
"tests.testapp.tests.test_lock_mixin.LockedBlogPost",
160+
"tests.testapp.tests.test_mixin_support.MixinSupportTestModel",
161+
"tests.testapp.tests.test_multi_resultstate.MultiResultTest",
162+
"tests.testapp.tests.test_multidecorators.MultiDecoratedModel",
163+
"tests.testapp.tests.test_protected_field.ProtectedAccessModel",
164+
"tests.testapp.tests.test_protected_fields.RefreshableProtectedAccessModel",
165+
"tests.testapp.tests.test_proxy_inheritance.InheritedModel",
166+
"tests.testapp.tests.test_state_transitions.Caterpillar",
167+
"tests.testapp.tests.test_string_field_parameter.BlogPostWithStringField",
168+
"tests.testapp.tests.test_transition_all_except_target.ExceptTargetTransitionShortcut",
169+
"tests.testapp.tests.test_key_field.FKBlogPost",
170+
)

tests/testapp/admin.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations
22

33
from django.contrib import admin
4+
from django_fsm_log.admin import StateLogInline
45

56
from django_fsm.admin import FSMAdminMixin
67

@@ -20,3 +21,5 @@ class AdminBlogPostAdmin(FSMAdminMixin, admin.ModelAdmin):
2021
"state",
2122
"step",
2223
]
24+
25+
inlines = [StateLogInline]

tests/testapp/models.py

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
from __future__ import annotations
22

33
from django.db import models
4+
from django_fsm_log.decorators import fsm_log_by
5+
from django_fsm_log.decorators import fsm_log_description
46

57
from django_fsm import GET_STATE
68
from django_fsm import RETURN_VALUE
@@ -279,6 +281,8 @@ class AdminBlogPost(models.Model):
279281

280282
# state transitions
281283

284+
@fsm_log_by
285+
@fsm_log_description
282286
@transition(
283287
field=state,
284288
source="*",
@@ -287,17 +291,21 @@ class AdminBlogPost(models.Model):
287291
"admin": False,
288292
},
289293
)
290-
def secret_transition(self):
294+
def secret_transition(self, by=None, description=None):
291295
pass
292296

297+
@fsm_log_by
298+
@fsm_log_description
293299
@transition(
294300
field=state,
295301
source=[AdminBlogPostState.CREATED],
296302
target=AdminBlogPostState.REVIEWED,
297303
)
298-
def moderate(self):
304+
def moderate(self, by=None, description=None):
299305
pass
300306

307+
@fsm_log_by
308+
@fsm_log_description
301309
@transition(
302310
field=state,
303311
source=[
@@ -306,9 +314,11 @@ def moderate(self):
306314
],
307315
target=AdminBlogPostState.PUBLISHED,
308316
)
309-
def publish(self):
317+
def publish(self, by=None, description=None):
310318
pass
311319

320+
@fsm_log_by
321+
@fsm_log_description
312322
@transition(
313323
field=state,
314324
source=[
@@ -317,11 +327,13 @@ def publish(self):
317327
],
318328
target=AdminBlogPostState.HIDDEN,
319329
)
320-
def hide(self):
330+
def hide(self, by=None, description=None):
321331
pass
322332

323333
# step transitions
324334

335+
@fsm_log_by
336+
@fsm_log_description
325337
@transition(
326338
field=step,
327339
source=[AdminBlogPostStep.STEP_1],
@@ -330,17 +342,21 @@ def hide(self):
330342
"label": "Go to Step 2",
331343
},
332344
)
333-
def step_two(self):
345+
def step_two(self, by=None, description=None):
334346
pass
335347

348+
@fsm_log_by
349+
@fsm_log_description
336350
@transition(
337351
field=step,
338352
source=[AdminBlogPostStep.STEP_2],
339353
target=AdminBlogPostStep.STEP_3,
340354
)
341-
def step_three(self):
355+
def step_three(self, by=None, description=None):
342356
pass
343357

358+
@fsm_log_by
359+
@fsm_log_description
344360
@transition(
345361
field=step,
346362
source=[
@@ -349,5 +365,5 @@ def step_three(self):
349365
],
350366
target=AdminBlogPostStep.STEP_1,
351367
)
352-
def step_reset(self):
368+
def step_reset(self, by=None, description=None):
353369
pass

tests/testapp/tests/test_admin.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from django.contrib.auth import get_user_model
88
from django.test import TestCase
99
from django.test.client import RequestFactory
10+
from django_fsm_log.models import StateLog
1011

1112
from django_fsm import ConcurrentTransition
1213
from django_fsm import FSMField
@@ -105,6 +106,7 @@ def setUpTestData(cls):
105106
cls.user = get_user_model().objects.create_user(username="jacob", password="password", is_staff=True) # noqa: S106
106107

107108
def test_unknown_transition(self, mock_message_user):
109+
assert StateLog.objects.count() == 0
108110
request = RequestFactory().post(
109111
path="/",
110112
data={"_fsm_transition_to": "unknown_transition"},
@@ -126,8 +128,10 @@ def test_unknown_transition(self, mock_message_user):
126128

127129
updated_blog_post = AdminBlogPost.objects.get(pk=blog_post.pk)
128130
assert updated_blog_post.state == AdminBlogPostState.CREATED
131+
assert StateLog.objects.count() == 0
129132

130133
def test_transition_applied(self, mock_message_user):
134+
assert StateLog.objects.count() == 0
131135
request = RequestFactory().post(
132136
path="/",
133137
data={"_fsm_transition_to": "moderate"},
@@ -150,8 +154,11 @@ def test_transition_applied(self, mock_message_user):
150154

151155
updated_blog_post = AdminBlogPost.objects.get(pk=blog_post.pk)
152156
assert updated_blog_post.state == AdminBlogPostState.REVIEWED
157+
assert StateLog.objects.count() == 1
158+
assert StateLog.objects.get().by == self.user
153159

154160
def test_transition_not_allowed_exception(self, mock_message_user):
161+
assert StateLog.objects.count() == 0
155162
request = RequestFactory().post(
156163
path="/",
157164
data={"_fsm_transition_to": "publish"},
@@ -174,8 +181,10 @@ def test_transition_not_allowed_exception(self, mock_message_user):
174181

175182
updated_blog_post = AdminBlogPost.objects.get(pk=blog_post.pk)
176183
assert updated_blog_post.state == AdminBlogPostState.CREATED
184+
assert StateLog.objects.count() == 0
177185

178186
def test_concurrent_transition_exception(self, mock_message_user):
187+
assert StateLog.objects.count() == 0
179188
request = RequestFactory().post(
180189
path="/",
181190
data={"_fsm_transition_to": "moderate"},
@@ -202,3 +211,4 @@ def test_concurrent_transition_exception(self, mock_message_user):
202211

203212
updated_blog_post = AdminBlogPost.objects.get(pk=blog_post.pk)
204213
assert updated_blog_post.state == AdminBlogPostState.CREATED
214+
assert StateLog.objects.count() == 0

tests/testapp/tests/test_transition_all_except_target.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ def remove(self):
2020
pass
2121

2222

23-
class Test(TestCase):
23+
class TestExceptTargetTransitionShortcut(TestCase):
2424
def setUp(self):
2525
self.model = ExceptTargetTransition()
2626

tox.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ deps =
1616
dj52: Django==5.2
1717
djmain: https://github.com/django/django/tarball/main
1818

19+
django-fsm-log
1920
django-guardian
2021
graphviz
2122
pep8

0 commit comments

Comments
 (0)