Install & Compatibility
Where this runs
tested against v4.2.4 · pip install
no network on importno background threads
Install × environment matrix
Each cell = how many times install + import succeeded across repeated harness runs. Partial = flaky.
glibc = Debian/Ubuntu slim · musl = Alpine Linux
muslpy 3.10–3.920 runs
installs and imports cleanly · install 0.0s · import 0.614s · 66.6MB
glibcpy 3.10–3.920 runs
installs and imports cleanly · install 3.5s · import 0.544s · 67MB
66MB installed
● package 66MB
Code
Verified usage
Verified import paths — ran on the pinned version, not inferred.
FSMField
✓ from django_fsm import FSMField
✗ from django_fsm.db.fields import FSMField
The field types are directly available under `django_fsm` in modern versions, not `django_fsm.db.fields`.
FSMModelMixin
✓ from django_fsm import FSMModelMixin
Required for `FSMField` to function correctly on models.
transition
✓ from django_fsm import transition
Decorator for defining state transitions on model methods.
can_proceed
✓ from django_fsm import can_proceed
Utility function to check if a transition is currently allowed.
FSMAdminMixin
✓ from django_fsm.admin import FSMAdminMixin
✗ from fsm_admin.mixins import FSMTransitionMixin
While `FSMTransitionMixin` exists in `django-fsm-2-admin`, `FSMAdminMixin` is the built-in option if `django-fsm-2-admin` is not used. Import paths differ between the two projects.
This quickstart demonstrates defining a Django model with an FSMField and several state transitions. The `FSMModelMixin` is inherited, and `transition` decorators specify valid state changes. The example also shows how to check if a transition is allowed using `can_proceed` before executing it and saving the model instance to persist the state change to the database.
from django.db import models
from django_fsm import FSMField, transition, FSMModelMixin
class BlogPost(FSMModelMixin, models.Model):
class State(models.TextChoices):
NEW = "new", "New"
DRAFT = "draft", "Draft"
PUBLISHED = "published", "Published"
ARCHIVED = "archived", "Archived"
title = models.CharField(max_length=255)
state = FSMField(default=State.NEW, protected=True)
@transition(field=state, source=State.NEW, target=State.DRAFT)
def create_draft(self):
print(f"Transitioning from {self.state} to {self.State.DRAFT}")
@transition(field=state, source=State.DRAFT, target=State.PUBLISHED)
def publish(self):
print(f"Transitioning from {self.state} to {self.State.PUBLISHED}")
@transition(field=state, source='*', target=State.ARCHIVED)
def archive(self):
print(f"Transitioning from {self.state} to {self.State.ARCHIVED}")
def __str__(self):
return f"{self.title} ({self.state})"
# Example Usage (assuming a Django environment and database):
# from .models import BlogPost, can_proceed # assuming this is in app.models
#
# post = BlogPost.objects.create(title='My First Post')
# print(post) # My First Post (new)
#
# if can_proceed(post.create_draft):
# post.create_draft()
# post.save()
# print(post) # My First Post (draft)
#
# if can_proceed(post.publish):
# post.publish()
# post.save()
# print(post) # My First Post (published)
#
# # Attempting an invalid transition
# if can_proceed(post.create_draft):
# print("Should not be able to draft from published state")
# else:
# print(f"Cannot transition from {post.state} to draft")
#
# if can_proceed(post.archive):
# post.archive()
# post.save()
# print(post) # My First Post (archived)
Debug
Known issues
breakingVersion 4.0.0 of `django-fsm-2` removed support for Django 3.2, 4.0, and 4.1. It added support for Django 5.1. Projects on these older Django versions should stick to `django-fsm-2 < 4.0.0` or upgrade their Django version.fixUpgrade Django to a supported version (e.g., 5.1+) or pin `django-fsm-2` to a version prior to 4.0.0 (e.g., `django-fsm-2<4.0.0`).
affects: >=4.0.0
gotchaAttempting to directly assign a new value to an `FSMField` (e.g., `instance.state = 'new_state'`) will often fail with an `AttributeError` if the field is `protected=True` (which is often the default or desired behavior). State changes *must* occur via methods decorated with `@transition`.fixAlways use the methods decorated with `@transition` to change the state of an FSM-managed field, e.g., `instance.transition_method()`.
affects: All
gotchaAfter a successful transition method call, the model's state is updated in memory, but it is *not* automatically persisted to the database. You must explicitly call `instance.save()` to commit the state change.fixEnsure `instance.save()` is called immediately after a successful transition method execution to persist the state change. For concurrent environments, consider using `django_fsm.ConcurrentTransitionMixin` within `django.db.transaction.atomic()` blocks.
affects: All
breakingThe original `django-fsm` project was archived and later revived as `viewflow.fsm` (version 3.0.0+), introducing an entirely new and incompatible API. While `django-fsm-2` aims to be a drop-in replacement for *older* `django-fsm` versions (pre-archival), migrating from `viewflow.fsm` (i.e., `django-fsm >= 3.0.0`) to `django-fsm-2` is not a simple switch.fixIf migrating from `django-fsm` before its archival, `django-fsm-2` is a drop-in replacement. If migrating from the *new* `viewflow.fsm` (aka `django-fsm` 3.0.0+), expect significant API refactoring, as `viewflow.fsm` uses a different `State` class and transition paradigm.
affects: Users of `django-fsm >= 3.0.0` (which is `viewflow.fsm`)
Errors
Common errors & fixes
AttributeError: can't set attribute 'state' (or similar with your FSMField name)
Attempting to directly assign a new value to an FSMField, which is protected against direct modification.
fixUse a method decorated with `@transition` to change the state. For example, if your field is `state` and you have a `publish()` method: `my_model_instance.publish()`.
TransitionNotAllowed
An attempt was made to execute a transition that is not permitted by the defined state machine rules (e.g., source state does not match, or target state is unreachable from the current state).
fixEnsure the current state of the model instance is a valid `source` for the desired transition. Check the `@transition` decorator's `source` and `target` parameters. Use `can_proceed(instance.transition_method)` to check validity before calling.
NameError: name 'my_state_constant' is not defined
This usually happens when state names in `source` or `target` parameters of the `@transition` decorator are used as unquoted variables instead of string literals or properly imported constants (e.g., from `models.TextChoices`).
fixEnsure state names are either enclosed in quotes (e.g., `source='new'`) or correctly reference an imported/defined constant (e.g., `source=BlogPost.State.NEW`).
Upgrade
Version history
4.2.4latest on PyPI · released Mar 16, 2026
Audit
Dependencies
DjangorequiredCore framework dependency, specific versions supported.
django-fsm-logoptionalProvides persistence and logging of FSM transitions.
django-fsm-2-adminoptionalIntegrates FSM transitions into the Django Admin interface.
graphvizoptionalRequired for drawing state transition diagrams.