11import typing
22
33from django .core .files .uploadedfile import UploadedFile
4+ from django .db .models import Count , Q
5+ from drf_spectacular .utils import extend_schema_field
46from rest_framework import serializers
57
68from cohorts .constants import COHORT_CSV_MAX_FILE_SIZE_BYTES
79from cohorts .exceptions import CsvFileTooLargeError
8- from cohorts .models import Cohort , CohortSyncKey
10+ from cohorts .models import Cohort , CohortMembershipState , CohortSyncKey
911from cohorts .services import create_cohort
1012from environments .models import Environment
1113from metadata .serializers import MetadataSerializer , MetadataSerializerMixin
@@ -19,12 +21,19 @@ class Meta:
1921 model = Segment
2022
2123
24+ class CohortMembershipCountsSerializer (serializers .Serializer ): # type: ignore[type-arg]
25+ applied = serializers .IntegerField (min_value = 0 )
26+ pending_add = serializers .IntegerField (min_value = 0 )
27+ pending_remove = serializers .IntegerField (min_value = 0 )
28+
29+
2230class CohortSerializer (serializers .ModelSerializer [Cohort ]):
2331 name = serializers .CharField (max_length = 2000 , source = "segment.name" )
2432 description = serializers .CharField (
2533 source = "segment.description" , required = False , allow_null = True
2634 )
2735 metadata = MetadataSerializer (required = False , many = True , write_only = True )
36+ membership_counts = serializers .SerializerMethodField ()
2837
2938 class Meta :
3039 model = Cohort
@@ -38,11 +47,41 @@ class Meta:
3847 "source_type" ,
3948 "version" ,
4049 "created_at" ,
50+ "last_synced_at" ,
51+ "membership_counts" ,
52+ )
53+ read_only_fields = (
54+ "segment" ,
55+ "source_type" ,
56+ "version" ,
57+ "created_at" ,
58+ "last_synced_at" ,
59+ )
60+
61+ @extend_schema_field (CohortMembershipCountsSerializer )
62+ def get_membership_counts (self , cohort : Cohort ) -> dict [str , int ]:
63+ # Clients derive sync status and progress from these. The viewset
64+ # annotates the counts; a freshly created cohort isn't annotated.
65+ if (applied := getattr (cohort , "applied_count" , None )) is not None :
66+ return {
67+ "applied" : applied ,
68+ "pending_add" : getattr (cohort , "pending_add_count" , 0 ),
69+ "pending_remove" : getattr (cohort , "pending_remove_count" , 0 ),
70+ }
71+ return cohort .memberships .aggregate (
72+ applied = Count ("id" , filter = Q (state = CohortMembershipState .APPLIED )),
73+ pending_add = Count ("id" , filter = Q (state = CohortMembershipState .PENDING_ADD )),
74+ pending_remove = Count (
75+ "id" , filter = Q (state = CohortMembershipState .PENDING_REMOVE )
76+ ),
4177 )
42- read_only_fields = ("segment" , "source_type" , "version" , "created_at" )
4378
4479 def validate (self , attrs : dict [str , typing .Any ]) -> dict [str , typing .Any ]:
4580 attrs = super ().validate (attrs )
81+ if self .instance is not None and "metadata" not in attrs :
82+ # A partial update without metadata must not fail the
83+ # required-metadata check.
84+ return attrs
4685 environment = Environment .objects .get (
4786 api_key = self .context ["view" ].kwargs ["environment_api_key" ]
4887 )
@@ -64,6 +103,19 @@ def create(self, validated_data: dict[str, typing.Any]) -> Cohort:
64103 _SegmentMetadataHandler ()._update_metadata (cohort .segment , metadata_data )
65104 return cohort
66105
106+ def update (self , instance : Cohort , validated_data : dict [str , typing .Any ]) -> Cohort :
107+ # Only the managed segment's fields are updatable.
108+ metadata_data = validated_data .pop ("metadata" , None )
109+ segment_data = validated_data .pop ("segment" , {})
110+ if segment_data :
111+ segment = instance .segment
112+ for field , value in segment_data .items ():
113+ setattr (segment , field , value )
114+ segment .save (update_fields = list (segment_data ))
115+ if metadata_data is not None :
116+ _SegmentMetadataHandler ()._update_metadata (instance .segment , metadata_data )
117+ return instance
118+
67119
68120class CohortSyncKeySerializer (serializers .ModelSerializer [CohortSyncKey ]):
69121 key = serializers .SerializerMethodField ()
0 commit comments