Having a StateLog on object creation with the default state?
ddahan opened this issue · comments
In my app, I'm trying to show the full life cycles of objects using FSM.
While I love django-fsm-log for that purpose, the object creation itself is missing from these logs, as it's not a transition.
But after all, we could see this first step as a transition from the "None
state" to the "created
(default) state".
That's why I was wondering what is the best way, to create a StateLog instance when my object is created with the default state.
For example:
class SomeObject(models.Model):
state = FSMField(
choices=SomeObjectState.choices,
default="created",
protected=True,
)
some_obj = SomeObject.objects.create()
StateLog.objects.for_(some_obj).count() # I would love to have 1 item here!
Thanks.
django-fsm-log offers persistence of the transition. When we initialize a state machine, there is no transition called, per nature I would say.
What you could do, is to call a transition yourself that can be triggered from a post_create signal handler.
I don't think it's a functionality that needs to live in django-fsm-log, but it could be a documented pattern.
@ticosax Thanks for the reply. I was thinking of this kind of solution too.
Except that since signals are often considered to be a bad practice for code readability, I guess I would override the save()
method for that purpose.
Then, I would put that code in a mixin, sothat it can be used for any model that have a state field.
I'll try this and let you know about the result on this thread.
Yep, it seems to work as expected. I'll detail my solution here to potentially:
- help people browsing the issues and having a similar need
- get some additional tips to improve this piece of code.
The mixin
# Reusable enum choices
INITIALIZED = "INITIALIZED", "initialisé"
CREATED = "CREATED", "créé"
class FSMTransitionOnCreateMixin(models.Model):
"""
Add an automatic new transition (from INITIALIZED to CREATED) for a class
with a `state` attribute.
The purpose is to have a transition log as soon as an object is created.
"""
class Meta:
abstract = True
@fsm_log_by
@transition(
field="state",
source=INITIALIZED[0],
target=CREATED[0],
)
def to_created(self, by=None, description=None):
pass
def save(self, *args, **kwargs):
"""
- super() needs to be run before, as django fsm needs object_id to build the log
- adding state must be recorded before calling super(), as it would become false
anyway after calling super().
"""
adding = self._state.adding # save the state
super().save(*args, **kwargs)
if adding is True:
self.to_created()
Example usage in a class with a state
from core.mixins.fsm import CREATED, INITIALIZED, FSMTransitionOnCreateMixin
class MandateState(models.TextChoices):
INITIALIZED = INITIALIZED # avoid repetition
CREATED = CREATED # avoid repetition
SENT = "SENT", "envoyé au client"
SIGNED = "SIGNED", "signé par le client"
ONGOING_SEARCH = "ONGOING_SEARCH", "recherche en cours"
PAUSED_SEARCH = "PAUSED_SEARCH", "recherche en pause"
ENDED = "ENDED", "recherche terminée"
class Mandate(FSMTransitionOnCreateMixin, models.Model):
# ...
state = FSMField(
"statut",
choices=MandateState.choices,
default=MandateState.INITIALIZED,
protected=True,
)
Or you can define the initial transition in a signal:
from django.db.models.signals import post_save
from django.dispatch import receiver
from django_fsm.signals import post_transition
from project.foobar.models import Entity
@receiver(post_save, sender=Entity)
def trigger_initial_transition(sender, instance, created, **kwargs):
if created:
instance.baz()
Assuming you have a model like so:
from django.db import models
from django_fsm import FSMIntegerField
from django_fsm import transition
class Entity(models.Model):
...
class State(models.IntegerChoices):
_NONE = 0, "-"
FOO = 1, "foo"
BAR = 2, "bar"
...
state = FSMIntegerField(
default=State._NONE,
choices=State.choices,
blank=True,
)
@transition(
state,
source=[State._NONE],
target=State.FOO,
)
def baz(self):
""""""
...