CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-django-fsm

Django friendly finite state machine support for models through FSMField and the @transition decorator.

Pending
Overview
Eval results
Files

transitions.mddocs/

Transition Management

Decorator for marking methods as state transitions with comprehensive configuration options and utility functions for checking transition availability and permissions.

Capabilities

Transition Decorator

The core decorator that marks model methods as state transitions, enabling declarative finite state machine behavior.

def transition(field, 
               source="*", 
               target=None, 
               on_error=None, 
               conditions=[], 
               permission=None, 
               custom={}):
    """
    Method decorator to mark allowed state transitions.
    
    Parameters:
    - field: FSM field instance or field name string
    - source: Source state(s) - string, list, tuple, set, "*" (any), or "+" (any except target)
    - target: Target state, State instance (GET_STATE, RETURN_VALUE), or None for validation-only
    - on_error: State to transition to if method raises exception
    - conditions: List of functions that must return True for transition to proceed
    - permission: Permission string or callable for authorization checking
    - custom: Dictionary of custom metadata for the transition
    
    Returns:
    Decorated method that triggers state transitions when called
    """

Basic Transition Usage

Simple state transitions with single source and target states:

from django.db import models
from django_fsm import FSMField, transition

class Order(models.Model):
    state = FSMField(default='pending')
    
    @transition(field=state, source='pending', target='confirmed')
    def confirm(self):
        # Business logic here
        self.confirmed_at = timezone.now()
    
    @transition(field=state, source='confirmed', target='shipped')
    def ship(self):
        # Generate tracking number, notify customer, etc.
        pass

Multiple Source States

Transitions can be triggered from multiple source states:

class BlogPost(models.Model):
    state = FSMField(default='draft')
    
    @transition(field=state, source=['draft', 'review'], target='published')
    def publish(self):
        self.published_at = timezone.now()
    
    @transition(field=state, source=['draft', 'published'], target='archived')
    def archive(self):
        pass

Wildcard Source States

Use special source values for flexible transitions:

class Document(models.Model):
    state = FSMField(default='new')
    
    # Can be called from any state
    @transition(field=state, source='*', target='error')
    def mark_error(self):
        pass
    
    # Can be called from any state except 'deleted'
    @transition(field=state, source='+', target='deleted')
    def delete(self):
        pass

Conditional Transitions

Add conditions that must be met before transition can proceed:

def can_publish(instance):
    return instance.content and instance.title

def has_approval(instance):
    return instance.approved_by is not None

class Article(models.Model):
    state = FSMField(default='draft')
    content = models.TextField()
    title = models.CharField(max_length=200)
    approved_by = models.ForeignKey(User, null=True, blank=True, on_delete=models.SET_NULL)
    
    @transition(
        field=state, 
        source='draft', 
        target='published',
        conditions=[can_publish, has_approval]
    )
    def publish(self):
        self.published_at = timezone.now()

Permission-Based Transitions

Control access to transitions using Django's permission system:

class Document(models.Model):
    state = FSMField(default='draft')
    
    # String permission
    @transition(
        field=state, 
        source='review', 
        target='approved',
        permission='myapp.can_approve_document'
    )
    def approve(self):
        pass
    
    # Callable permission
    def can_reject(instance, user):
        return user.is_staff or user == instance.author
    
    @transition(
        field=state,
        source='review',
        target='rejected', 
        permission=can_reject
    )
    def reject(self):
        pass

Error Handling in Transitions

Specify fallback states when transitions raise exceptions:

class PaymentOrder(models.Model):
    state = FSMField(default='pending')
    
    @transition(
        field=state,
        source='pending',
        target='paid',
        on_error='failed'
    )
    def process_payment(self):
        # If this raises an exception, state becomes 'failed'
        result = payment_gateway.charge(self.amount)
        if not result.success:
            raise PaymentException("Payment failed")

Validation-Only Transitions

Set target to None to validate state without changing it:

class Contract(models.Model):
    state = FSMField(default='draft')
    
    @transition(field=state, source='draft', target=None)
    def validate_draft(self):
        # Perform validation logic
        # State remains 'draft' if successful
        if not self.is_valid():
            raise ValidationError("Contract is invalid")

Custom Transition Metadata

Store additional metadata with transitions:

class Workflow(models.Model):
    state = FSMField(default='new')
    
    @transition(
        field=state,
        source='new',
        target='processing',
        custom={
            'priority': 'high',
            'requires_notification': True,
            'estimated_time': 300
        }
    )
    def start_processing(self):
        pass

Transition Objects

Transition Class

The Transition class represents individual state transitions and provides metadata and permission checking capabilities.

class Transition(object):
    def __init__(self, method, source, target, on_error, conditions, permission, custom):
        """
        Represents a single state machine transition.
        
        Parameters:
        - method: The transition method
        - source: Source state for the transition
        - target: Target state for the transition
        - on_error: Error state (optional)
        - conditions: List of condition functions
        - permission: Permission string or callable
        - custom: Custom metadata dictionary
        
        Attributes:
        - name: Name of the transition method
        - source: Source state
        - target: Target state
        - on_error: Error fallback state
        - conditions: Transition conditions
        - permission: Permission configuration
        - custom: Custom transition metadata
        """
    
    @property
    def name(self):
        """Return the name of the transition method."""
    
    def has_perm(self, instance, user):
        """
        Check if user has permission to execute this transition.
        
        Parameters:
        - instance: Model instance
        - user: User to check permissions for
        
        Returns:
        bool: True if user has permission
        """

Transition objects are created automatically by the @transition decorator and can be accessed through various utility functions:

from django_fsm import FSMField, transition

class Order(models.Model):
    state = FSMField(default='pending')
    
    @transition(field=state, source='pending', target='confirmed')
    def confirm(self):
        pass

# Access transition objects
order = Order()
transitions = order.get_available_state_transitions()
for transition in transitions:
    print(f"Transition: {transition.name}")
    print(f"From {transition.source} to {transition.target}")
    print(f"Custom metadata: {transition.custom}")

FSMMeta Class

The FSMMeta class manages transition metadata for individual methods, storing all configured transitions and their conditions.

class FSMMeta(object):
    def __init__(self, field, method):
        """
        Initialize FSM metadata for a transition method.
        
        Parameters:
        - field: FSM field instance
        - method: Transition method
        
        Attributes:
        - field: Associated FSM field
        - transitions: Dictionary mapping source states to Transition objects
        """
    
    def add_transition(self, method, source, target, on_error=None, conditions=[], permission=None, custom={}):
        """
        Add a transition configuration to this method.
        
        Parameters:
        - method: Transition method
        - source: Source state
        - target: Target state
        - on_error: Error state (optional)
        - conditions: List of condition functions
        - permission: Permission string or callable
        - custom: Custom metadata dictionary
        """
    
    def has_transition(self, state):
        """
        Check if any transition exists from the given state.
        
        Parameters:
        - state: Current state to check
        
        Returns:
        bool: True if transition is possible
        """
    
    def conditions_met(self, instance, state):
        """
        Check if all transition conditions are satisfied.
        
        Parameters:
        - instance: Model instance
        - state: Current state
        
        Returns:
        bool: True if all conditions are met
        """
    
    def has_transition_perm(self, instance, state, user):
        """
        Check if user has permission for transition from given state.
        
        Parameters:
        - instance: Model instance
        - state: Current state
        - user: User to check permissions for
        
        Returns:
        bool: True if user has permission
        """

Access FSMMeta through transition methods:

# Access FSM metadata
order = Order()
meta = order.confirm._django_fsm  # FSMMeta instance

# Check transition availability
if meta.has_transition(order.state):
    print("Confirm transition is available")

# Check conditions
if meta.conditions_met(order, order.state):
    print("All conditions are satisfied")

Transition Utility Functions

can_proceed

Check if a transition method can be called from the current state:

def can_proceed(bound_method, check_conditions=True):
    """
    Returns True if model state allows calling the bound transition method.
    
    Parameters:
    - bound_method: Bound method with @transition decorator
    - check_conditions: Whether to check transition conditions (default: True)
    
    Returns:
    bool: True if transition is allowed
    
    Raises:
    TypeError: If method is not a transition
    """

Usage example:

from django_fsm import can_proceed

order = Order.objects.get(pk=1)

# Check without conditions
if can_proceed(order.ship, check_conditions=False):
    print("Ship transition is possible from current state")

# Check with conditions
if can_proceed(order.ship):
    order.ship()
    order.save()
else:
    print("Cannot ship order yet")

has_transition_perm

Check if a user has permission to execute a transition:

def has_transition_perm(bound_method, user):
    """
    Returns True if model state allows calling method and user has permission.
    
    Parameters:
    - bound_method: Bound method with @transition decorator
    - user: User instance to check permissions for
    
    Returns:
    bool: True if user can execute transition
    
    Raises:
    TypeError: If method is not a transition
    """

Usage example:

from django_fsm import has_transition_perm

def process_approval(request, document_id):
    document = Document.objects.get(pk=document_id)
    
    if has_transition_perm(document.approve, request.user):
        document.approve()
        document.save()
        return redirect('success')
    else:
        return HttpResponseForbidden("You don't have permission to approve")

Advanced Transition Patterns

Multi-Field State Machines

Different fields can have their own independent state machines:

class Order(models.Model):
    payment_state = FSMField(default='unpaid')
    fulfillment_state = FSMField(default='pending')
    
    @transition(field=payment_state, source='unpaid', target='paid')
    def process_payment(self):
        pass
    
    @transition(field=fulfillment_state, source='pending', target='shipped')
    def ship_order(self):
        pass

Cascading Transitions

Transitions can trigger other transitions:

class Order(models.Model):
    state = FSMField(default='new')
    
    @transition(field=state, source='new', target='processing')
    def start_processing(self):
        pass
    
    @transition(field=state, source='processing', target='completed')
    def complete(self):
        # Automatically trigger another action
        self.notify_customer()
    
    def notify_customer(self):
        # Send notification logic
        pass

State-Dependent Method Behavior

Use current state to determine method behavior:

class Document(models.Model):
    state = FSMField(default='draft')
    
    def save(self, *args, **kwargs):
        if self.state == 'published':
            # Extra validation for published documents
            self.full_clean()
        super().save(*args, **kwargs)
    
    @transition(field=state, source='draft', target='published')
    def publish(self):
        self.published_at = timezone.now()

Install with Tessl CLI

npx tessl i tessl/pypi-django-fsm

docs

dynamic-states.md

exceptions.md

field-types.md

index.md

model-mixins.md

signals.md

transitions.md

visualization.md

tile.json