{"id":8100,"library":"django-fsm-2","title":"Django FSM-2","description":"Django FSM-2 is an active, maintained fork of the original `django-fsm` library, providing declarative finite state machine support for Django models. It enables developers to define state fields and transitions using decorators, centralizing an object's lifecycle logic. Currently at version 4.2.4, it aims for regular updates and planned typing support.","status":"active","version":"4.2.4","language":"en","source_language":"en","source_url":"https://github.com/django-commons/django-fsm-2","tags":["Django","FSM","State Machine","Workflow","Model","State Management"],"install":[{"cmd":"pip install django-fsm-2","lang":"bash","label":"Install stable version"}],"dependencies":[{"reason":"Core framework dependency, specific versions supported.","package":"Django","optional":false},{"reason":"Provides persistence and logging of FSM transitions.","package":"django-fsm-log","optional":true},{"reason":"Integrates FSM transitions into the Django Admin interface.","package":"django-fsm-2-admin","optional":true},{"reason":"Required for drawing state transition diagrams.","package":"graphviz","optional":true}],"imports":[{"note":"The field types are directly available under `django_fsm` in modern versions, not `django_fsm.db.fields`.","wrong":"from django_fsm.db.fields import FSMField","symbol":"FSMField","correct":"from django_fsm import FSMField"},{"note":"Required for `FSMField` to function correctly on models.","symbol":"FSMModelMixin","correct":"from django_fsm import FSMModelMixin"},{"note":"Decorator for defining state transitions on model methods.","symbol":"transition","correct":"from django_fsm import transition"},{"note":"Utility function to check if a transition is currently allowed.","symbol":"can_proceed","correct":"from django_fsm import can_proceed"},{"note":"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.","wrong":"from fsm_admin.mixins import FSMTransitionMixin","symbol":"FSMAdminMixin","correct":"from django_fsm.admin import FSMAdminMixin"}],"quickstart":{"code":"from django.db import models\nfrom django_fsm import FSMField, transition, FSMModelMixin\n\nclass BlogPost(FSMModelMixin, models.Model):\n    class State(models.TextChoices):\n        NEW = \"new\", \"New\"\n        DRAFT = \"draft\", \"Draft\"\n        PUBLISHED = \"published\", \"Published\"\n        ARCHIVED = \"archived\", \"Archived\"\n\n    title = models.CharField(max_length=255)\n    state = FSMField(default=State.NEW, protected=True)\n\n    @transition(field=state, source=State.NEW, target=State.DRAFT)\n    def create_draft(self):\n        print(f\"Transitioning from {self.state} to {self.State.DRAFT}\")\n\n    @transition(field=state, source=State.DRAFT, target=State.PUBLISHED)\n    def publish(self):\n        print(f\"Transitioning from {self.state} to {self.State.PUBLISHED}\")\n\n    @transition(field=state, source='*', target=State.ARCHIVED)\n    def archive(self):\n        print(f\"Transitioning from {self.state} to {self.State.ARCHIVED}\")\n\n    def __str__(self):\n        return f\"{self.title} ({self.state})\"\n\n# Example Usage (assuming a Django environment and database):\n# from .models import BlogPost, can_proceed # assuming this is in app.models\n#\n# post = BlogPost.objects.create(title='My First Post')\n# print(post) # My First Post (new)\n#\n# if can_proceed(post.create_draft):\n#     post.create_draft()\n#     post.save()\n# print(post) # My First Post (draft)\n#\n# if can_proceed(post.publish):\n#     post.publish()\n#     post.save()\n# print(post) # My First Post (published)\n#\n# # Attempting an invalid transition\n# if can_proceed(post.create_draft):\n#     print(\"Should not be able to draft from published state\")\n# else:\n#     print(f\"Cannot transition from {post.state} to draft\")\n#\n# if can_proceed(post.archive):\n#     post.archive()\n#     post.save()\n# print(post) # My First Post (archived)","lang":"python","description":"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."},"warnings":[{"fix":"Upgrade 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`).","message":"Version 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.","severity":"breaking","affected_versions":">=4.0.0"},{"fix":"Always use the methods decorated with `@transition` to change the state of an FSM-managed field, e.g., `instance.transition_method()`.","message":"Attempting 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`.","severity":"gotcha","affected_versions":"All"},{"fix":"Ensure `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.","message":"After 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.","severity":"gotcha","affected_versions":"All"},{"fix":"If 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.","message":"The 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.","severity":"breaking","affected_versions":"Users of `django-fsm >= 3.0.0` (which is `viewflow.fsm`)"}],"env_vars":null,"last_verified":"2026-04-16T00:00:00.000Z","next_check":"2026-07-15T00:00:00.000Z","problems":[{"fix":"Use 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()`.","cause":"Attempting to directly assign a new value to an FSMField, which is protected against direct modification.","error":"AttributeError: can't set attribute 'state' (or similar with your FSMField name)"},{"fix":"Ensure 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.","cause":"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).","error":"TransitionNotAllowed"},{"fix":"Ensure state names are either enclosed in quotes (e.g., `source='new'`) or correctly reference an imported/defined constant (e.g., `source=BlogPost.State.NEW`).","cause":"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`).","error":"NameError: name 'my_state_constant' is not defined"}]}