Skip to content

Commit be8f775

Browse files
committed
Deprecate StateLog model, to instead bring your own model.
This is an implementation that support the idea exposed in #133 It's still a Draft, I'm open for any feedback more specifically around the names. Not tested yet with a real project. It's meant to be completely backward compatible.
1 parent d29ed3f commit be8f775

18 files changed

+225
-91
lines changed

README.md

Lines changed: 43 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ by enabling a cached backend. See [Advanced Usage](#advanced-usage)
1717
## 4.0.0 (not released)
1818

1919
- remove support for django 2.2 & 4.0
20+
- Bring your own PersistedTransition model:
21+
From this relase, django-fsm-log is deprecating StateLog and instead encourages you
22+
to define for your own application the concrete model that will persist the transition
2023

2124
## 3.1.0 (2023-03-23)
2225

@@ -99,14 +102,29 @@ python manage.py migrate django_fsm_log
99102

100103
## Usage
101104

105+
### Define you own model
106+
107+
```python
108+
from django_fsm_log.models import PersistedTransitionMixin
109+
110+
111+
class TransitionLog(PersistedTransitionMixin):
112+
pass
113+
```
114+
115+
### Register the model
116+
117+
```python
118+
DJANGO_FSM_LOG_CONCRETE_MODEL = 'poll.models.TransitionLog' # This model must inherit from django_fsm_log.models.PersistedTransition
119+
```
120+
102121
The app listens for the `django_fsm.signals.post_transition` signal and
103122
creates a new record for each transition.
104123

105124
To query the log:
106125

107126
```python
108-
from django_fsm_log.models import StateLog
109-
StateLog.objects.all()
127+
TransitionLog.objects.all()
110128
# ...all recorded logs...
111129
```
112130

@@ -125,11 +143,10 @@ For convenience there is a custom `for_` manager method to easily filter on the
125143

126144
```python
127145
from my_app.models import Article
128-
from django_fsm_log.models import StateLog
129146

130147
article = Article.objects.all()[0]
131148

132-
StateLog.objects.for_(article)
149+
TransitionLog.objects.for_(article)
133150
# ...logs for article...
134151
```
135152

@@ -157,7 +174,7 @@ With this the transition gets logged when the `by` kwarg is present.
157174

158175
```python
159176
article = Article.objects.create()
160-
article.submit(by=some_user) # StateLog.by will be some_user
177+
article.submit(by=some_user) # TransitionLog.by will be some_user
161178
```
162179

163180
### `description` Decorator
@@ -210,21 +227,31 @@ article.submit() # logged with "Article submitted" description
210227

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

213-
To use it expand your own `AdminModel` by adding `StateLogInline` to its inlines:
230+
To use it expand your own `AdminModel` by adding `PersistedTransitionInline` to its inlines:
214231

215232
```python
216233
from django.contrib import admin
217-
from django_fsm_log.admin import StateLogInline
234+
from django_fsm_log.admin import PersistedTransitionInline
218235

219236

220237
@admin.register(FSMModel)
221238
class FSMModelAdmin(admin.ModelAdmin):
222-
inlines = [StateLogInline]
239+
inlines = [PersistedTransitionInline]
240+
```
241+
242+
### Migration to Abstract model PersistedTransitionMixin
243+
244+
Once you defined your own model, you'll have to create the relevant migration to create the table.
245+
246+
```sh
247+
python manage.py makemigrations
223248
```
224249

250+
Additionally you'd want to migrate the data from django_fsm_log.models.StateLog to your new table.
251+
225252
### Advanced Usage
226253

227-
You can change the behaviour of this app by turning on caching for StateLog records.
254+
You can change the behaviour of this app by turning on caching for PersistedTransition records.
228255
Simply add `DJANGO_FSM_LOG_STORAGE_METHOD = 'django_fsm_log.backends.CachedBackend'` to your project's settings file.
229256
It will use your project's default cache backend by default. If you wish to use a specific cache backend, you can add to
230257
your project's settings:
@@ -233,22 +260,23 @@ your project's settings:
233260
DJANGO_FSM_LOG_CACHE_BACKEND = 'some_other_cache_backend'
234261
```
235262

236-
The StateLog object is now available after the `django_fsm.signals.pre_transition`
263+
The PersistedTransition object is now available after the `django_fsm.signals.pre_transition`
237264
signal is fired, but is deleted from the cache and persisted to the database after `django_fsm.signals.post_transition`
238265
is fired.
239266

240267
This is useful if:
241268

242-
- you need immediate access to StateLog details, and cannot wait until `django_fsm.signals.post_transition`
269+
- you need immediate access to PersistedTransition details, and cannot wait until `django_fsm.signals.post_transition`
243270
has been fired
244-
- at any stage, you need to verify whether or not the StateLog has been written to the database
271+
- at any stage, you need to verify whether or not the PersistedTransition has been written to the database
245272

246-
Access to the pending StateLog record is available via the `pending_objects` manager
273+
Access to the pending PersistedTransition record is available via the `pending_objects` manager
247274

248275
```python
249-
from django_fsm_log.models import StateLog
276+
from my_app.models import TransitionLog
277+
250278
article = Article.objects.get(...)
251-
pending_state_log = StateLog.pending_objects.get_for_object(article)
279+
pending_transition_logs = TransitionLog.pending_objects.get_for_object(article)
252280
```
253281

254282
## Contributing

django_fsm_log/admin.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
1+
from warnings import warn
2+
13
from django.contrib.contenttypes.admin import GenericTabularInline
24
from django.db.models import F
35

4-
from .models import StateLog
6+
from .backends import _get_concrete_model
57

6-
__all__ = ("StateLogInline",)
8+
__all__ = ("PersistedTransitionInline",)
79

810

9-
class StateLogInline(GenericTabularInline):
10-
model = StateLog
11+
class PersistedTransitionInline(GenericTabularInline):
12+
model = _get_concrete_model()
1113
can_delete = False
1214

1315
def has_add_permission(self, request, obj=None):
@@ -30,3 +32,12 @@ def get_readonly_fields(self, request, obj=None):
3032

3133
def get_queryset(self, request):
3234
return super().get_queryset(request).order_by(F("timestamp").desc())
35+
36+
37+
def StateLogInline(*args, **kwargs):
38+
warn(
39+
"StateLogInLine has been deprecated by PersistedTransitionInline.",
40+
DeprecationWarning,
41+
stacklevel=2,
42+
)
43+
return PersistedTransitionInline(*args, **kwargs)

django_fsm_log/apps.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ class DjangoFSMLogAppConfig(AppConfig):
1010

1111
def ready(self):
1212
backend = import_string(settings.DJANGO_FSM_LOG_STORAGE_METHOD)
13-
StateLog = self.get_model("StateLog")
13+
ConcreteModel = import_string(settings.DJANGO_FSM_LOG_CONCRETE_MODEL)
1414

15-
backend.setup_model(StateLog)
15+
backend.setup_model(ConcreteModel)
1616

1717
pre_transition.connect(backend.pre_transition_callback)
1818
post_transition.connect(backend.post_transition_callback)

django_fsm_log/backends.py

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,18 @@
1+
import typing
2+
3+
from django.utils.module_loading import import_string
4+
15
from django_fsm_log.conf import settings
26

37
from .helpers import FSMLogDescriptor
48

9+
if typing.TYPE_CHECKING:
10+
import django_fsm_log.models
11+
12+
13+
def _get_concrete_model() -> typing.Type["django_fsm_log.models.PersistedTransitionMixin"]:
14+
return import_string(settings.DJANGO_FSM_LOG_CONCRETE_MODEL)
15+
516

617
def _pre_transition_callback(sender, instance, name, source, target, manager, **kwargs):
718
if BaseBackend._get_model_qualified_name__(sender) in settings.DJANGO_FSM_LOG_IGNORED_MODELS:
@@ -49,21 +60,21 @@ def _get_model_qualified_name__(sender):
4960
class CachedBackend(BaseBackend):
5061
@staticmethod
5162
def setup_model(model):
52-
from .managers import PendingStateLogManager
63+
from .managers import PendingPersistedTransitionManager
5364

54-
model.add_to_class("pending_objects", PendingStateLogManager())
65+
model.add_to_class("pending_objects", PendingPersistedTransitionManager())
5566

5667
@staticmethod
5768
def pre_transition_callback(sender, instance, name, source, target, **kwargs):
58-
from .models import StateLog
69+
klass = _get_concrete_model()
5970

60-
return _pre_transition_callback(sender, instance, name, source, target, StateLog.pending_objects, **kwargs)
71+
return _pre_transition_callback(sender, instance, name, source, target, klass.pending_objects, **kwargs)
6172

6273
@staticmethod
6374
def post_transition_callback(sender, instance, name, source, target, **kwargs):
64-
from .models import StateLog
75+
klass = _get_concrete_model()
6576

66-
StateLog.pending_objects.commit_for_object(instance)
77+
klass.pending_objects.commit_for_object(instance)
6778

6879

6980
class SimpleBackend(BaseBackend):
@@ -77,9 +88,9 @@ def pre_transition_callback(sender, **kwargs):
7788

7889
@staticmethod
7990
def post_transition_callback(sender, instance, name, source, target, **kwargs):
80-
from .models import StateLog
91+
klass = _get_concrete_model()
8192

82-
return _pre_transition_callback(sender, instance, name, source, target, StateLog.objects, **kwargs)
93+
return _pre_transition_callback(sender, instance, name, source, target, klass.objects, **kwargs)
8394

8495

8596
if settings.DJANGO_FSM_LOG_STORAGE_METHOD == "django_fsm_log.backends.CachedBackend":

django_fsm_log/conf.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
1+
from typing import List
2+
13
from appconf import AppConf
24
from django.conf import settings # noqa: F401
35

46

57
class DjangoFSMLogConf(AppConf):
68
STORAGE_METHOD = "django_fsm_log.backends.SimpleBackend"
79
CACHE_BACKEND = "default"
8-
IGNORED_MODELS = []
10+
IGNORED_MODELS: List[str] = []
11+
CONCRETE_MODEL: str = "django_fsm_log.models.StateLog"
12+
13+
class Meta:
14+
prefix = "django_fsm_log"
15+
holder = "django_fsm_log.conf.settings"

django_fsm_log/managers.py

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,30 @@
1+
from warnings import warn
2+
13
from django.contrib.contenttypes.models import ContentType
24
from django.db import models
35
from django.db.models.query import QuerySet
46

57
from django_fsm_log.backends import cache
68

79

8-
class StateLogQuerySet(QuerySet):
10+
class PersistedTransitionQuerySet(QuerySet):
911
def _get_content_type(self, obj):
1012
return ContentType.objects.get_for_model(obj)
1113

1214
def for_(self, obj):
1315
return self.filter(content_type=self._get_content_type(obj), object_id=obj.pk)
1416

1517

16-
class StateLogManager(models.Manager):
18+
def StateLogQuerySet(*args, **kwargs):
19+
warn("StateLogQuerySet has been renamed PersistedTransitionQuerySet", DeprecationWarning, stacklevel=2)
20+
return PersistedTransitionQuerySet(*args, **kwargs)
21+
22+
23+
class PersistedTransitionManager(models.Manager):
1724
use_in_migrations = True
1825

1926
def get_queryset(self):
20-
return StateLogQuerySet(self.model)
27+
return PersistedTransitionQuerySet(self.model)
2128

2229
def __getattr__(self, attr, *args):
2330
# see https://code.djangoproject.com/ticket/15062 for details
@@ -26,7 +33,12 @@ def __getattr__(self, attr, *args):
2633
return getattr(self.get_queryset(), attr, *args)
2734

2835

29-
class PendingStateLogManager(models.Manager):
36+
def StateLogManager(*args, **kwargs):
37+
warn("StateLogManager has been renamed PersistedTransitionManager", DeprecationWarning, stacklevel=2)
38+
return PersistedTransitionManager(*args, **kwargs)
39+
40+
41+
class PendingPersistedTransitionManager(models.Manager):
3042
def _get_cache_key_for_object(self, obj):
3143
return f"StateLog:{obj.__class__.__name__}:{obj.pk}"
3244

@@ -46,3 +58,8 @@ def commit_for_object(self, obj):
4658
def get_for_object(self, obj):
4759
key = self._get_cache_key_for_object(obj)
4860
return cache.get(key)
61+
62+
63+
def PendingStateLogManager(*args, **kwargs):
64+
warn("PendingStateLogManager has been renamed PendingPersistedTransitionManager", DeprecationWarning, stacklevel=2)
65+
return PendingPersistedTransitionManager(*args, **kwargs)

django_fsm_log/migrations/0001_initial.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
from django.db import models, migrations
21
import django.utils.timezone
32
from django.conf import settings
3+
from django.db import migrations, models
44

55

66
class Migration(migrations.Migration):

django_fsm_log/migrations/0004_auto_20190131_0341.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Generated by Django 2.1.5 on 2019-01-31 03:41
22

33
from django.db import migrations
4+
45
import django_fsm_log.managers
56

67

django_fsm_log/models.py

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,22 @@
1+
from warnings import warn
2+
3+
from django.conf import settings
14
from django.contrib.contenttypes.fields import GenericForeignKey
25
from django.contrib.contenttypes.models import ContentType
36
from django.db import models
47
from django.utils.timezone import now
58
from django_fsm import FSMFieldMixin, FSMIntegerField
69

7-
from .conf import settings
8-
from .managers import StateLogManager
10+
from .managers import PersistedTransitionManager
11+
912

13+
class PersistedTransitionMixin(models.Model):
14+
"""
15+
Abstract Class that should be subclassed by the host application.
16+
Host projects should own the migrations and
17+
can decide on their own primary key type.
18+
"""
1019

11-
class StateLog(models.Model):
1220
timestamp = models.DateTimeField(default=now)
1321
by = models.ForeignKey(
1422
getattr(settings, "AUTH_USER_MODEL", "auth.User"),
@@ -26,9 +34,10 @@ class StateLog(models.Model):
2634

2735
description = models.TextField(blank=True, null=True)
2836

29-
objects = StateLogManager()
37+
objects = PersistedTransitionManager()
3038

3139
class Meta:
40+
abstract = True
3241
get_latest_by = "timestamp"
3342

3443
def __str__(self):
@@ -47,3 +56,15 @@ def get_state_display(self, field_name="state"):
4756

4857
def get_source_state_display(self):
4958
return self.get_state_display("source_state")
59+
60+
61+
class StateLog(PersistedTransitionMixin):
62+
def __init__(self, *args, **kwargs):
63+
warn(
64+
"StateLog model has been deprecated, you should now bring your own Model."
65+
"\nPlease check the documentation at https://django-fsm-log.readthedocs.io/en/latest/"
66+
"\nto know how to.",
67+
DeprecationWarning,
68+
stacklevel=2,
69+
)
70+
return super().__init__(*args, **kwargs)

tests/admin.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
from django.contrib import admin
22

3-
from django_fsm_log.admin import StateLogInline
3+
from django_fsm_log.admin import PersistedTransitionInline
44

55
from .models import Article
66

77

88
class ArticleAdmin(admin.ModelAdmin):
9-
inlines = [StateLogInline]
9+
inlines = [PersistedTransitionInline]
1010

1111

1212
admin.site.register(Article, ArticleAdmin)

0 commit comments

Comments
 (0)