Install & Compatibility
Where this runs
tested against v3.0.1 · 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.910 runs
installs and imports cleanly · install 0.0s · import 0.000s · 198.5MB
glibcpy 3.10–3.910 runs
installs and imports cleanly · install 3.2s · import 0.000s · 270MB
231MB installed
● package 231MB
Code
Verified usage
Verified import paths — ran on the pinned version, not inferred.
FSMField
✓ from django_fsm import FSMField
✗ from django_fsm import FSMField
This quickstart demonstrates defining states with `FSMField` and transitions with the `@transition` decorator for a `BlogPost` model. It highlights basic state transitions and the use of `protected=True` to enforce transitions via methods only. Note that for versions 3.0.0 and above, the functionality has migrated to `viewflow.fsm`.
import os
import django
from enum import Enum
from django.db import models
from django_fsm import FSMField, transition
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'your_project_name.settings') # Replace with actual project settings if needed
django.setup()
class BlogPost(models.Model):
class BlogState(models.TextChoices):
DRAFT = "draft", "Draft"
REVIEW = "review", "Under Review"
PUBLISHED = "published", "Published"
ARCHIVED = "archived", "Archived"
state = FSMField(choices=BlogState.choices, default=BlogState.DRAFT, protected=True)
title = models.CharField(max_length=255)
content = models.TextField()
@transition(field=state, source=BlogState.DRAFT, target=BlogState.REVIEW)
def submit_for_review(self):
print(f"Blog post '{self.title}' submitted for review.")
@transition(field=state, source=BlogState.REVIEW, target=BlogState.PUBLISHED)
def publish(self):
print(f"Blog post '{self.title}' published!")
@transition(field=state, source=[BlogState.DRAFT, BlogState.REVIEW, BlogState.PUBLISHED], target=BlogState.ARCHIVED)
def archive(self):
print(f"Blog post '{self.title}' archived.")
def __str__(self):
return f"{self.title} ({self.get_state_display()})"
# Example Usage (assuming a Django environment is set up and models are synced)
# For a real application, replace this with actual model creation/retrieval.
# try:
# blog_post = BlogPost.objects.create(title="My First Post", content="Lorem ipsum...")
# print(blog_post)
# if can_proceed(blog_post.submit_for_review):
# blog_post.submit_for_review()
# blog_post.save()
# print(blog_post)
# if can_proceed(blog_post.publish):
# blog_post.publish()
# blog_post.save()
# print(blog_post)
# if can_proceed(blog_post.archive):
# blog_post.archive()
# blog_post.save()
# print(blog_post)
# # This would fail if protected=True and not using a transition method
# # blog_post.state = BlogPost.BlogState.DRAFT
# # blog_post.save()
# except Exception as e:
# print(f"An error occurred: {e}")
Upgrade
Version history
3.0.1latest on PyPI · released Oct 7, 2025
Audit
Dependencies
DjangorequiredCore framework dependency for integration.
viewflowoptionalThe successor library for `django-fsm` functionality (from v3.0.0 onwards).
django-fsm-logoptionalCommonly used for logging FSM transitions in Django Admin.
pygraphvizoptionalRequired for graph_transition command to visualize state machines.