githubEdit

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>StateTransition table

  • Transition 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:

  1. A status CharField on the model with the defined choices

  2. An OrderStateTransition model for recording transition history

  3. A states accessor for all state operations

Transitioning States

Checking Transitions

Viewing History

API Reference

StateMachine Constructor

Parameter
Type
Description

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:

Property
Type
Description

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

Method
Returns
Description

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

Parameter
Type
Default
Description

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:

Field
Type
Description

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:

  • AddField for the status field on the parent model

  • CreateModel for the <ModelName>StateTransition table

Admin Integration

Register the transition model in the admin for visibility:

Last updated