1010 CreateSegmentOverrideFeatureStateSerializer ,
1111 FeatureStateValueSerializer ,
1212)
13+ from common .projects .permissions import VIEW_PROJECT
14+ from django .db import models
1315from drf_spectacular .utils import extend_schema_field
1416from drf_writable_nested import ( # type: ignore[attr-defined]
1517 WritableNestedModelSerializer ,
2931 FeatureFlagCodeReferencesRepositoryCountSerializer ,
3032)
3133from projects .models import Project
34+ from users .models import FFAdminUser , UserPermissionGroup
3235from users .serializers import (
3336 UserIdsSerializer ,
3437 UserListSerializer ,
@@ -161,12 +164,28 @@ def validate_tags(self, tags: str) -> list[int]:
161164 raise serializers .ValidationError ("Tag IDs must be integers." )
162165
163166
167+ class _FeatureOwnersField (serializers .PrimaryKeyRelatedField [FFAdminUser ]):
168+ def get_queryset (self ) -> models .QuerySet [FFAdminUser ]:
169+ return FFAdminUser .objects .all ()
170+
171+ def to_representation (self , value : FFAdminUser ) -> dict [str , Any ]:
172+ return UserListSerializer (value ).data
173+
174+
175+ class _FeatureGroupOwnersField (serializers .PrimaryKeyRelatedField [UserPermissionGroup ]):
176+ def get_queryset (self ) -> models .QuerySet [UserPermissionGroup ]:
177+ return UserPermissionGroup .objects .all ()
178+
179+ def to_representation (self , value : UserPermissionGroup ) -> dict [str , Any ]:
180+ return UserPermissionGroupSummarySerializer (value ).data
181+
182+
164183class CreateFeatureSerializer (DeleteBeforeUpdateWritableNestedModelSerializer ):
165184 multivariate_options = NestedMultivariateFeatureOptionSerializer (
166185 many = True , required = False
167186 )
168- owners = UserListSerializer (many = True , read_only = True )
169- group_owners = UserPermissionGroupSummarySerializer (many = True , read_only = True )
187+ owners = _FeatureOwnersField (many = True , required = False )
188+ group_owners = _FeatureGroupOwnersField (many = True , required = False )
170189
171190 environment_feature_state = serializers .SerializerMethodField ()
172191 segment_feature_state = serializers .SerializerMethodField ()
@@ -240,12 +259,22 @@ def create(self, validated_data: dict) -> Feature: # type: ignore[type-arg]
240259 project = self .context ["project" ]
241260 self .validate_project_features_limit (project )
242261
243- # Add the default(User creating the feature) owner of the feature
244- # NOTE: pop the user before passing the data to create
262+ # Pop M2M fields before creating the instance (can't pass to Model.objects.create)
263+ owners : list [FFAdminUser ] = validated_data .pop ("owners" , [])
264+ group_owners : list [UserPermissionGroup ] = validated_data .pop ("group_owners" , [])
265+
245266 user = validated_data .pop ("user" , None )
246267 instance = super (CreateFeatureSerializer , self ).create (validated_data ) # type: ignore[no-untyped-call]
247- if user and getattr (user , "is_master_api_key_user" , False ) is False :
268+
269+ if owners :
270+ instance .owners .add (* owners )
271+ elif user and getattr (user , "is_master_api_key_user" , False ) is False :
272+ # Auto-add the creating user as owner only when no explicit owners provided
248273 instance .owners .add (user )
274+
275+ if group_owners :
276+ instance .group_owners .add (* group_owners )
277+
249278 return instance # type: ignore[no-any-return]
250279
251280 def validate_project_features_limit (self , project : Project ) -> None :
@@ -275,6 +304,26 @@ def validate_multivariate_options(self, multivariate_options): # type: ignore[n
275304 raise serializers .ValidationError ("Invalid percentage allocation" )
276305 return multivariate_options
277306
307+ def validate_owners (self , owners : list [FFAdminUser ]) -> list [FFAdminUser ]:
308+ project : Project = self .context ["project" ]
309+ for user in owners :
310+ if not user .has_project_permission (VIEW_PROJECT , project ):
311+ raise serializers .ValidationError (
312+ "Some users do not have access to this project."
313+ )
314+ return owners
315+
316+ def validate_group_owners (
317+ self , group_owners : list [UserPermissionGroup ]
318+ ) -> list [UserPermissionGroup ]:
319+ project : Project = self .context ["project" ]
320+ for group in group_owners :
321+ if group .organisation_id != project .organisation_id :
322+ raise serializers .ValidationError (
323+ "Some groups do not belong to this project's organisation."
324+ )
325+ return group_owners
326+
278327 def validate_name (self , name : str ): # type: ignore[no-untyped-def]
279328 view = self .context ["view" ]
280329
@@ -317,8 +366,23 @@ def validate(self, attrs: dict[str, Any]) -> dict[str, Any]:
317366 "Selected Tags must be from the same Project as current Feature"
318367 )
319368
369+ self ._validate_enforce_feature_owners (attrs )
370+
320371 return attrs
321372
373+ def _validate_enforce_feature_owners (self , attrs : dict [str , Any ]) -> None :
374+ project : Project = self .context ["project" ]
375+ if (
376+ not self .instance
377+ and project .enforce_feature_owners
378+ and not attrs .get ("owners" )
379+ and not attrs .get ("group_owners" )
380+ ):
381+ raise serializers .ValidationError (
382+ "This project requires at least one owner or group owner "
383+ "when creating a feature."
384+ )
385+
322386 @extend_schema_field (FeatureStateSerializerSmall (allow_null = True ))
323387 def get_environment_feature_state ( # type: ignore[return]
324388 self , instance : Feature
@@ -399,6 +463,9 @@ def update(self, feature: Feature, validated_data: dict[str, Any]) -> Feature:
399463class UpdateFeatureSerializerWithMetadata (FeatureSerializerWithMetadata ):
400464 """prevent users from changing certain values after creation"""
401465
466+ owners = _FeatureOwnersField (many = True , read_only = True )
467+ group_owners = _FeatureGroupOwnersField (many = True , read_only = True )
468+
402469 class Meta (FeatureSerializerWithMetadata .Meta ):
403470 read_only_fields = FeatureSerializerWithMetadata .Meta .read_only_fields + ( # type: ignore[assignment]
404471 "default_enabled" ,
@@ -416,6 +483,9 @@ class ListFeatureSerializer(FeatureSerializerWithMetadata):
416483class UpdateFeatureSerializer (ListFeatureSerializer ):
417484 """prevent users from changing certain values after creation"""
418485
486+ owners = _FeatureOwnersField (many = True , read_only = True )
487+ group_owners = _FeatureGroupOwnersField (many = True , read_only = True )
488+
419489 class Meta (ListFeatureSerializer .Meta ):
420490 read_only_fields = ListFeatureSerializer .Meta .read_only_fields + ( # type: ignore[assignment]
421491 "default_enabled" ,
0 commit comments