Bin
2025-12-16 9e0b2ba2c317b1a86212f24cbae3195ad1f3dbfa
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
"""
FSM QuerySet Mixins for annotating entities with their current state.
 
Provides reusable Django QuerySet mixins that efficiently annotate entities
with their current FSM state using optimized subqueries to prevent N+1 queries.
 
Usage:
    class TaskQuerySet(FSMStateQuerySetMixin, models.QuerySet):
        pass
 
    class TaskManager(models.Manager):
        def get_queryset(self):
            return TaskQuerySet(self.model, using=self._db)
 
Note:
    State annotation is guarded by 'fflag_feat_fit_568_finite_state_management' only.
    This allows background FSM processes to annotate current_state for internal use.
 
    UI/API consumption of state fields is separately controlled by serializers
    that check BOTH 'fflag_feat_fit_568_finite_state_management' AND
    'fflag_feat_fit_710_fsm_state_fields' before exposing state data.
 
    When the flag is disabled, no annotation is performed and there is zero performance impact.
"""
 
import logging
 
from core.current_request import CurrentContext
from core.feature_flags import flag_set
from django.db.models import OuterRef, Subquery
from fsm.registry import get_state_model
 
logger = logging.getLogger(__name__)
 
 
class FSMStateQuerySetMixin:
    """
    Mixin for Django QuerySets to efficiently annotate FSM state.
 
    Provides the `with_state()` method that adds a `current_state`
    annotation to the queryset using an optimized subquery.
 
    This approach:
    - Prevents N+1 queries by using a single JOIN/subquery
    - Handles missing states gracefully (returns None)
    - Uses UUID7 natural ordering for optimal performance
    - Works with any FSM entity that has a registered state model
 
    Example:
        # In your model manager
        class TaskManager(models.Manager):
            def get_queryset(self):
                return TaskQuerySet(self.model, using=self._db)
 
            def with_state(self):
                return self.get_queryset().with_state()
 
        # Usage - both approaches work identically
        tasks = Task.objects.with_state().filter(project=project)
        # Or chain it after filters
        tasks = Task.objects.filter(project=project).with_state()
 
        for task in tasks:
            print(f"Task {task.id}: {task.current_state}")  # No additional queries!
    """
 
    def with_state(self):
        """
        Annotate the queryset with the current FSM state.
 
        Adds a `current_state` field to each object containing the current
        state string value. This is done using an efficient subquery that
        leverages UUID7 natural ordering to prevent N+1 queries.
 
        Returns:
            QuerySet: The annotated queryset with `current_state` field
 
        Example:
            # Chain after filters
            tasks = Task.objects.filter(project=project).with_state()
 
            # Or use from manager
            tasks = Task.objects.with_state().filter(project=project)
 
            # Multiple chaining
            tasks = Task.objects.filter(is_labeled=True).with_state().order_by('-created_at')
 
        Note:
            - If FSM feature flag is disabled, returns queryset unchanged (zero impact)
            - If no state exists for an entity, `current_state` will be None
            - The state is read-only and should not be modified directly
        """
        # Check only fflag_feat_fit_568_finite_state_management for background FSM processes.
        # This allows background processes to annotate current_state for internal use.
        # UI/API serializers separately check both fflag_feat_fit_568 AND fflag_feat_fit_710
        # before exposing state data to consumers.
        user = CurrentContext.get_user()
        if not flag_set('fflag_feat_fit_568_finite_state_management', user=user):
            logger.debug('FSM feature flag disabled, skipping state annotation')
            return self
 
        # Get the entity name from the model
        entity_name = self.model._meta.model_name
 
        # Get the state model for this entity
        state_model = get_state_model(entity_name)
 
        if not state_model:
            # No state model registered, return queryset as-is
            logger.debug(f'No state model registered for {entity_name}, skipping annotation')
            return self
 
        # Get the foreign key field name on the state model
        # e.g., 'task_id' for TaskState
        entity_field_name = state_model._get_entity_field_name()
        fk_field = f'{entity_field_name}_id'
 
        # Create subquery to get current state using UUID7 natural ordering
        # This is extremely efficient because:
        # 1. UUID7 provides natural time ordering (latest = highest ID)
        # 2. We only fetch the state column, not the entire record
        # 3. Django optimizes this into a single JOIN or lateral subquery
        current_state_subquery = Subquery(
            state_model.objects.filter(**{fk_field: OuterRef('pk')}).order_by('-id').values('state')[:1]
        )
 
        # Annotate the queryset with the current state
        return self.annotate(current_state=current_state_subquery)