To store state in a database, a class with viewflow.fsm.State
slot
can be composed with a Django model. This approach allows you to leverage
Django’s ORM for state persistence while utilizing Viewflow’s FSM capabilities.
First, define your model with a state field:
# models.py
from django.db import models
from django.db.models import TextChoices
from django.utils.translation import gettext_lazy as _
class ReportState(TextChoices):
NEW = 'NEW', _('New')
APPROVED = 'APPROVED', _('Approved')
REJECTED = 'REJECTED', _('Rejected')
PUBLISHED = 'PUBLISHED', _('Published')
class Report(models.Model):
text = models.TextField()
state_field = models.CharField(
max_length=150,
choices=ReportState.choices,
default=ReportState.NEW
)
Next, create a flow class that connects the FSM to your model. Use the State.setter and State.getter decorators to specify how the state is stored and retrieved.
# flow.py
from viewflow import fsm
from .models import Report, ReportState
class ReportFlow(object):
state_field = fsm.State(ReportState, default=ReportState.NEW)
def __init__(self, report):
self.report = report
@state_field.setter()
def _set_report_state(self, value):
self.report.state_field = value
@state_field.getter()
def _get_report_state(self):
return self.report.state_field
@state_field.transition(source=ReportState.NEW, target=ReportState.APPROVED)
def approve(self):
# Additional business logic can be added here
pass
@state_field.transition(source=ReportState.NEW, target=ReportState.REJECTED)
def reject(self):
# Additional business logic can be added here
pass
@state_field.transition(source=ReportState.APPROVED, target=ReportState.PUBLISHED)
def publish(self):
# Additional business logic can be added here
pass
@state_field.on_success()
def _on_transition_success(self, descriptor, source, target):
self.report.save()
The State.on_success decorator allows you to specify actions to be performed at the end of a transition, such as saving the model or triggering additional operations.
In Django views, you can handle state transitions with proper permission checks and form validation. Here’s an example of a view that handles an approval action:
# views.py
from django.shortcuts import get_object_or_404, redirect, render
from django.core.exceptions import PermissionDenied
from .models import Report
from .forms import ApproveForm
from .flow import ReportFlow
def approve(request, report_pk):
report = get_object_or_404(Report, pk=report_pk)
flow = ReportFlow(report)
# Check if user has permission to perform this transition
if not flow.approve.has_permission(request.user):
raise PermissionDenied
form = ApproveForm(request.POST or None, instance=report)
if form.is_valid():
form.save(commit=False) # Don't save yet, let the flow handle it
flow.approve() # Perform the state transition
return redirect('../')
return render(request, 'approve.html', {
'report': report,
'flow': flow,
'form': form
})
You can track state changes by creating a dedicated model to log transitions. This is useful for auditing and maintaining a history of state changes.
First, define a model to log the changes:
# models.py
from django.db import models
from django.utils import timezone
class ReportChangeLog(models.Model):
report = models.ForeignKey(Report, on_delete=models.CASCADE)
changed = models.DateTimeField(default=timezone.now)
source = models.CharField(max_length=150)
target = models.CharField(max_length=150)
class Meta:
ordering = ['-changed']
Then add an on_success handler to your flow class to record changes:
# flow.py
from django.db import transaction
from .models import Report, ReportState, ReportChangeLog
class ReportFlow(object):
state_field = fsm.State(ReportState, default=ReportState.NEW)
# ... other methods ...
@state_field.on_success()
def _log_state_change(self, descriptor, source, target, **kwargs):
with transaction.atomic():
self.report.save()
ReportChangeLog.objects.create(
report=self.report,
source=source.value,
target=target.value
)