Skip to content

Commit 03cb35e

Browse files
Merge pull request #143 from lorenzomorandini/add-description-arg-to-decorator
Add description argument to fsm_log_description decorator
2 parents d3360be + 8adc148 commit 03cb35e

File tree

4 files changed

+118
-11
lines changed

4 files changed

+118
-11
lines changed

README.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ by enabling a cached backend. See [Advanced Usage](#advanced-usage)
1414

1515
## Changelog
1616

17+
### Unreleased
18+
19+
- `fsm_log_description` now accepts a default description parameter
20+
- Document `fsm_log_description` decorator
21+
1722
### 3.0.0 (2022-01-14)
1823

1924
- Switch to github actions (from travis-ci)
@@ -149,6 +154,52 @@ article = Article.objects.create()
149154
article.submit(by=some_user) # StateLog.by will be some_user
150155
```
151156

157+
### `description` Decorator
158+
159+
Decorator that allows to set a custom description (saved on database) to a transitions.
160+
161+
```python
162+
from django.db import models
163+
from django_fsm import FSMField, transition
164+
from django_fsm_log.decorators import fsm_log_description
165+
166+
class Article(models.Model):
167+
168+
state = FSMField(default='draft', protected=True)
169+
170+
@fsm_log_description(description='Article submitted') # description param is NOT required
171+
@transition(field=state, source='draft', target='submitted')
172+
def submit(self, description=None):
173+
pass
174+
175+
article = Article.objects.create()
176+
article.submit() # logged with "Article submitted" description
177+
article.submit(description="Article reviewed and submitted") # logged with "Article reviewed and submitted" description
178+
```
179+
180+
.. TIP::
181+
The "description" argument passed when calling ".submit" has precedence over the default description set in the decorator
182+
183+
The decorator also accepts a `allow_inline` boolean argument that allows to set the description inside the transition method.
184+
185+
```python
186+
from django.db import models
187+
from django_fsm import FSMField, transition
188+
from django_fsm_log.decorators import fsm_log_description
189+
190+
class Article(models.Model):
191+
192+
state = FSMField(default='draft', protected=True)
193+
194+
@fsm_log_description(allow_inline=True)
195+
@transition(field=state, source='draft', target='submitted')
196+
def submit(self, description=None):
197+
description.set("Article submitted")
198+
199+
article = Article.objects.create()
200+
article.submit() # logged with "Article submitted" description
201+
```
202+
152203
### Admin integration
153204

154205
There is an InlineForm available that can be used to display the history of changes.

django_fsm_log/decorators.py

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@
44

55

66
def fsm_log_by(func):
7+
"""Set the "by" field of a transition.
8+
9+
:param func: transition method
10+
:type func: function
11+
"""
12+
713
@wraps(func)
814
def wrapped(instance, *args, **kwargs):
915
try:
@@ -16,20 +22,28 @@ def wrapped(instance, *args, **kwargs):
1622
return wrapped
1723

1824

19-
def fsm_log_description(func=None, allow_inline=False):
25+
def fsm_log_description(func=None, allow_inline=False, description=None):
26+
"""Set the "description" field of a transition.
27+
28+
:param func: transition method, defaults to None
29+
:type func: function, optional
30+
:param allow_inline: allow to set the description inside the transition method, defaults to False
31+
:type allow_inline: bool, optional
32+
:param description: default description, defaults to None
33+
:type description: str, optional
34+
"""
2035
if func is None:
21-
return partial(fsm_log_description, allow_inline=allow_inline)
36+
return partial(fsm_log_description, allow_inline=allow_inline, description=description)
2237

2338
@wraps(func)
2439
def wrapped(instance, *args, **kwargs):
2540
with FSMLogDescriptor(instance, "description") as descriptor:
26-
try:
27-
description = kwargs["description"]
28-
except KeyError:
29-
if allow_inline:
30-
kwargs["description"] = descriptor
31-
return func(instance, *args, **kwargs)
32-
descriptor.set(description)
41+
if kwargs.get("description"):
42+
descriptor.set(kwargs["description"])
43+
elif allow_inline:
44+
kwargs["description"] = descriptor
45+
else:
46+
descriptor.set(description)
3347
return func(instance, *args, **kwargs)
3448

3549
return wrapped

tests/models.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ class Article(models.Model):
99
("draft", "Draft"),
1010
("submitted", "Article submitted"),
1111
("published", "Article published"),
12+
("temporary", "Article published (temporary)"),
1213
("deleted", "Article deleted"),
1314
)
1415

@@ -35,6 +36,19 @@ def publish(self, by=None):
3536
def delete(self, using=None):
3637
pass
3738

39+
@fsm_log_by
40+
@fsm_log_description(description="Article restored")
41+
@transition(field=state, source="deleted", target="draft")
42+
def restore(self, description=None, by=None):
43+
pass
44+
45+
@fsm_log_by
46+
@fsm_log_description(allow_inline=True, description="Article published as temporary")
47+
@transition(field=state, source="draft", target="temporary")
48+
def publish_as_temporary(self, description=None, by=None):
49+
if not isinstance(description, str):
50+
description.set("Article published (temporary)")
51+
3852
@fsm_log_by
3953
@fsm_log_description(allow_inline=True)
4054
@transition(field=state, source="draft", target="submitted")

tests/test_model.py

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@
77

88

99
def test_get_available_state_transitions(article):
10-
assert len(list(article.get_available_state_transitions())) == 4
10+
assert len(list(article.get_available_state_transitions())) == 5
1111

1212

1313
def test_get_all_state_transitions(article):
14-
assert len(list(article.get_all_state_transitions())) == 6
14+
assert len(list(article.get_all_state_transitions())) == 8
1515

1616

1717
def test_log_created_on_transition(article):
@@ -83,6 +83,34 @@ def test_description_can_be_mutated_by_the_transition(article):
8383
article.__django_fsm_log_attr_description
8484

8585

86+
def test_default_description(article):
87+
article.delete()
88+
article.save()
89+
article.restore()
90+
article.save()
91+
92+
log = StateLog.objects.all()[1]
93+
assert log.description == "Article restored"
94+
95+
96+
def test_default_description_call_priority(article):
97+
article.delete()
98+
article.save()
99+
article.restore(description="Restored because of mistake")
100+
article.save()
101+
102+
log = StateLog.objects.all()[1]
103+
assert log.description == "Restored because of mistake"
104+
105+
106+
def test_default_description_inline_priority(article):
107+
article.publish_as_temporary()
108+
article.save()
109+
110+
log = StateLog.objects.all()[0]
111+
assert log.description == "Article published (temporary)"
112+
113+
86114
def test_logged_state_is_new_state(article):
87115
article.submit()
88116

0 commit comments

Comments
 (0)