3030)
3131from django .db .models .fields .json import KeyTextTransform
3232from django .db .models .functions import Cast , Coalesce , Concat
33+ from fsm .queryset_mixins import FSMStateQuerySetMixin
3334from pydantic import BaseModel
3435
3536from label_studio .core .utils .common import load_func
@@ -488,7 +489,16 @@ def apply_filters(queryset, filters, project, request):
488489 return queryset
489490
490491
491- class TaskQuerySet (models .QuerySet ):
492+ class TaskQuerySet (FSMStateQuerySetMixin , models .QuerySet ):
493+ """
494+ QuerySet for Task model with FSM state annotation support.
495+
496+ Extends Django's QuerySet with:
497+ - FSM state annotation (via FSMStateQuerySetMixin)
498+ - Data Manager filters and ordering
499+ - Selected items handling
500+ """
501+
492502 def prepared (self , prepare_params = None ):
493503 """Apply filters, ordering and selected items to queryset
494504
@@ -700,6 +710,29 @@ def dummy(queryset):
700710 return queryset
701711
702712
713+ def annotate_state (queryset ):
714+ """
715+ Annotate queryset with FSM state as 'state' field.
716+
717+ Uses FSMStateQuerySetMixin.annotate_fsm_state() to efficiently annotate
718+ the current state without causing N+1 queries. Aliases 'current_state' to
719+ 'state' to match the Data Manager column name.
720+
721+ Note: Feature flag checks and user context validation are handled by
722+ annotate_fsm_state() itself, so no additional checks are needed here.
723+ """
724+ # Use the mixin's annotate_fsm_state() method which creates 'current_state' annotation
725+ # (includes feature flag and user context checks)
726+ queryset = queryset .annotate_fsm_state ()
727+
728+ # Alias 'current_state' to 'state' for Data Manager column compatibility
729+ # Only add the alias if current_state was actually added (feature flags enabled)
730+ if 'current_state' in queryset .query .annotations :
731+ return queryset .annotate (state = F ('current_state' ))
732+
733+ return queryset
734+
735+
703736settings .DATA_MANAGER_ANNOTATIONS_MAP = {
704737 'avg_lead_time' : annotate_avg_lead_time ,
705738 'completed_at' : annotate_completed_at ,
@@ -712,6 +745,7 @@ def dummy(queryset):
712745 'file_upload' : file_upload ,
713746 'draft_exists' : annotate_draft_exists ,
714747 'storage_filename' : annotate_storage_filename ,
748+ 'state' : annotate_state ,
715749}
716750
717751
@@ -724,6 +758,19 @@ def update_annotation_map(obj):
724758
725759
726760class PreparedTaskManager (models .Manager ):
761+ """
762+ Manager for Task model with Data Manager annotations.
763+
764+ Provides:
765+ - Advanced query annotations for Data Manager
766+ - Filter and ordering support
767+ - FSM state annotation support (via TaskQuerySet)
768+
769+ Note: Overrides the base get_queryset() to return TaskQuerySet. Also has
770+ a custom get_queryset(fields_for_evaluation, prepare_params, ...) method
771+ for Data Manager-specific functionality.
772+ """
773+
727774 @staticmethod
728775 def annotate_queryset (
729776 queryset , fields_for_evaluation = None , all_fields = False , excluded_fields_for_evaluation = None , request = None
@@ -754,13 +801,23 @@ def get_queryset(
754801 self , fields_for_evaluation = None , prepare_params = None , all_fields = False , excluded_fields_for_evaluation = None
755802 ):
756803 """
804+ Get queryset with optional Data Manager annotations and filters.
805+
806+ When called without parameters (Django internal use), returns TaskQuerySet.
807+ When called with parameters (Data Manager use), returns annotated and filtered queryset.
808+
757809 :param fields_for_evaluation: list of annotated fields in task
758810 :param prepare_params: filters, ordering, selected items
759811 :param all_fields: evaluate all fields for task
760812 :param excluded_fields_for_evaluation: list of fields to exclude even when all_fields=True
761813 :param request: request for user extraction
762814 :return: task queryset with annotated fields
763815 """
816+ # If called without parameters, return base TaskQuerySet (for Django internal use)
817+ if prepare_params is None :
818+ return TaskQuerySet (self .model , using = self ._db )
819+
820+ # Otherwise, use Data Manager filtering and annotation
764821 queryset = self .only_filtered (prepare_params = prepare_params )
765822 # Expose view data to annotation functions for column-specific configuration
766823 queryset .view_data = getattr (prepare_params , 'data' , None )
@@ -781,5 +838,24 @@ def only_filtered(self, prepare_params=None):
781838
782839
783840class TaskManager (models .Manager ):
841+ """
842+ Default manager for Task model.
843+
844+ Provides:
845+ - User-scoped filtering
846+ - Custom QuerySet with FSM state support
847+
848+ Note: Overrides get_queryset() to return TaskQuerySet, which includes
849+ FSMStateQuerySetMixin for state annotation support.
850+ """
851+
852+ def get_queryset (self ):
853+ """Return TaskQuerySet which includes FSM state annotation support"""
854+ return TaskQuerySet (self .model , using = self ._db )
855+
784856 def for_user (self , user ):
785- return self .filter (project__organization = user .active_organization )
857+ return self .get_queryset ().filter (project__organization = user .active_organization )
858+
859+ def with_state (self ):
860+ """Return queryset with FSM state annotated."""
861+ return self .get_queryset ().annotate_fsm_state ()
0 commit comments