State Machine
This reference documents the StateMachine descriptor for managing model state transitions with audit history.
Overview
The StateMachine descriptor provides:
State validation: Enforces allowed state transitions at runtime
Per-model transition tables: Each model gets its own
<ModelName>StateTransitiontableTransition history: All state changes are recorded with timestamps and metadata
Namespaced API: All state operations accessed via
instance.states.*
Basic Usage
Defining a State Machine
from gyrinx.core.models.state_machine import StateMachine
from gyrinx.models import Base
class Order(Base):
states = StateMachine(
states=[
("PENDING", "Pending"),
("CONFIRMED", "Confirmed"),
("SHIPPED", "Shipped"),
("DELIVERED", "Delivered"),
("CANCELLED", "Cancelled"),
],
initial="PENDING",
transitions={
"PENDING": ["CONFIRMED", "CANCELLED"],
"CONFIRMED": ["SHIPPED", "CANCELLED"],
"SHIPPED": ["DELIVERED"],
},
)This creates:
A
statusCharField on the model with the defined choicesAn
OrderStateTransitionmodel for recording transition historyA
statesaccessor for all state operations
Transitioning States
Checking Transitions
Viewing History
API Reference
StateMachine Constructor
states
list[tuple[str, str]]
List of (value, label) tuples defining valid states
initial
str
The initial state for new instances
transitions
dict[str, list[str]]
Map of from_state to list of allowed to_states
StateMachineAccessor Properties
Accessed via instance.states:
current
str
Current status value
display
str
Human-readable label for current status
is_terminal
bool
True if no valid transitions from current state
history
QuerySet
Transitions for this instance, ordered by most recent first
StateMachineAccessor Methods
can_transition_to(state)
bool
Check if transition is allowed
get_valid_transitions()
list[str]
Get list of valid target states
transition_to(state, metadata=None, save=True)
Transition
Execute transition
transition_to Parameters
new_status
str
Required
Target state to transition to
metadata
dict
None
Optional metadata to store with transition
save
bool
True
Whether to save the model after transitioning
When save=False, the status is updated in memory but not persisted to the model. However, the transition record is still created in the database. This is useful when you need to update multiple fields atomically, but you must wrap the entire operation in transaction.atomic() to ensure consistency (see Patterns section below).
Transition Model Fields
The dynamically created transition model (e.g., OrderStateTransition) has:
id
UUID
Primary key
instance
ForeignKey
Reference to the parent model
from_status
CharField
Previous status value
to_status
CharField
New status value
transitioned_at
DateTimeField
When the transition occurred
metadata
JSONField
Optional metadata (empty dict by default)
Exceptions
InvalidStateTransition
Raised when attempting an invalid transition:
Configuration Validation
The StateMachine validates configuration at creation time:
Patterns
Convenience Methods with Atomic Transactions
For complex state transitions with side effects, create convenience methods wrapped in transaction.atomic() to ensure the transition record and model save succeed or fail together:
The transaction.atomic() ensures that if the save() fails, the transition record is also rolled back, maintaining consistency between the model state and transition history.
Accessing Class-Level Configuration
For introspection, access the descriptor on the class:
Migrations
When adding a StateMachine to a model, run makemigrations. Django automatically detects the dynamically created transition model:
This creates a migration with:
AddFieldfor thestatusfield on the parent modelCreateModelfor the<ModelName>StateTransitiontable
Admin Integration
Register the transition model in the admin for visibility:
Last updated