django-fsm
Django-friendly finite state machine support.
django-fsm adds simple, declarative state management for Django models and plain Python objects.
Instead of managing states with ad-hoc string fields and writing error-prone assignments across views and models, you define states and transitions explicitly. Transitions validate source and target states, enforce business conditions, check user permissions, and keep side-effects clean.
Installation
python -m pip install django-fsm
For plain Python classes, no other dependencies are required. To use with Django models, make sure Django is installed or specify the extra:
python -m pip install 'django-fsm[django]'
Quick start
Add an FSMField to your Django model and mark transition methods with the @transition decorator:
from django.db import models
from django_fsm import FSMField, transition, can_proceed
class BlogPost(models.Model):
state = FSMField(default='draft', protected=True)
@transition(field=state, source='draft', target='published')
def publish(self):
"""Side-effects when publishing a post (e.g. notify users, update cache)."""
The protected=True option prevents direct state modification:
post = BlogPost()
post.state = 'published' # Raises AttributeError
To invoke a transition and persist the change:
post = BlogPost.objects.get(pk=post_id)
if can_proceed(post.publish):
post.publish()
post.save()
Conditions and Permissions
Conditions
Use the conditions argument to enforce rules before a transition is allowed. Conditions are callables that take the model instance and return a boolean:
def can_publish(instance):
return bool(instance.title and instance.body)
class BlogPost(models.Model):
state = FSMField(default='draft')
@transition(field=state, source='draft', target='published', conditions=[can_publish])
def publish(self):
pass
Permissions
You can guard transitions with permissions and verify them using has_transition_perm:
from django_fsm import transition, has_transition_perm
class BlogPost(models.Model):
state = FSMField(default='draft')
@transition(field=state, source='draft', target='published', permission='blog.can_publish')
def publish(self):
pass
In your view:
if not has_transition_perm(post.publish, request.user):
raise PermissionDenied
post.publish()
post.save()
Dynamic Targets
When the target state depends on runtime logic or method results:
RETURN_VALUE(*allowed_states) selects the target from the method’s return value:
from django_fsm import FSMField, RETURN_VALUE, transition
class Task(models.Model):
status = FSMField(default='pending')
@transition(field=status, source='pending', target=RETURN_VALUE('approved', 'rejected'))
def review(self, score):
return 'approved' if score >= 80 else 'rejected'
GET_STATE(func, states=[...]) calculates the target state before the transition runs, based on the instance and method arguments.
Plain Python State Machines
State also works with plain Python classes without Django database models or settings:
from django_fsm import State
class Review:
state = State(states=None, default='draft')
@state.transition(source='draft', target='published')
def publish(self):
pass
review = Review()
review.publish()
assert review.state == 'published'
Viewflow Pro Integrations
django-fsm integrates out of the box with Viewflow Pro and Django REST Framework:
django_fsm.admin.FlowAdminMixin: Viewflow Admin integration for state workflows.
django_fsm.viewset.FlowViewsMixin: Viewflow Viewset workflows.
django_fsm.rest.FlowRESTMixin: Optional Django REST Framework viewset integration (requires django-fsm[rest]).
Documentation & Contributing
Full documentation: https://docs.viewflow.io/fsm/index.html
Contributing & development instructions: see CONTRIBUTING.rst
License
Django FSM is licensed under AGPL-3.0-or-later with the viewflow-library-exception. See LICENSE and LICENSE_EXCEPTION for details.
Metadata
Release files for django-fsm 5.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| django_fsm-5.0.0.tar.gz | 72.5 kB | Without provenance | Details for django_fsm-5.0.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| django_fsm-5.0.0-py3-none-any.whl | Python 3 | none | any | Details for django_fsm-5.0.0-py3-none-any.whl |
Total release size: 118.3 kB
Release files / django_fsm-5.0.0.tar.gz
| Download URL | django_fsm-5.0.0.tar.gz |
|---|---|
| Size | 72.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
aa6833a6477b1512a98d7e599f3ae527e71028bcc5615a8e842422b05423d9e2
|
|
BLAKE2b-256 checksum How to use checksums |
0542a8407ac3dc5a908673594556eaee721d5f09627ffe9050554fc896ff1d96
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.4
|
Release files / django_fsm-5.0.0-py3-none-any.whl
| Download URL | django_fsm-5.0.0-py3-none-any.whl |
|---|---|
| Size | 45.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
6fd9bc9510d47708e1e5249dcbd893420ec69b4f1b8cb0391fa90d8195436b55
|
|
BLAKE2b-256 checksum How to use checksums |
15ae959287c49eb8fd6cd2ca1b5d1d239e910b5150c28ac7e1243833e142fb0f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.4
|
Security for django-fsm 5.0.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.