33from uuid import UUID
44
55import django .core .exceptions
6+ from django .db import models
67from common .features .multivariate .serializers import (
78 MultivariateFeatureStateValueSerializer ,
89)
910from common .features .serializers import (
1011 CreateSegmentOverrideFeatureStateSerializer ,
1112 FeatureStateValueSerializer ,
1213)
14+ from common .projects .permissions import VIEW_PROJECT
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,30 @@ 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+
169+ def get_queryset (self ) -> models .QuerySet [FFAdminUser ]:
170+ return FFAdminUser .objects .all ()
171+
172+ def to_representation (self , value : FFAdminUser ) -> dict [str , Any ]:
173+ return UserListSerializer (value ).data
174+
175+
176+ class _FeatureGroupOwnersField (serializers .PrimaryKeyRelatedField [UserPermissionGroup ]):
177+
178+ def get_queryset (self ) -> models .QuerySet [UserPermissionGroup ]:
179+ return UserPermissionGroup .objects .all ()
180+
181+ def to_representation (self , value : UserPermissionGroup ) -> dict [str , Any ]:
182+ return UserPermissionGroupSummarySerializer (value ).data
183+
184+
164185class CreateFeatureSerializer (DeleteBeforeUpdateWritableNestedModelSerializer ):
165186 multivariate_options = NestedMultivariateFeatureOptionSerializer (
166187 many = True , required = False
167188 )
168- owners = UserListSerializer (many = True , read_only = True )
169- group_owners = UserPermissionGroupSummarySerializer (many = True , read_only = True )
189+ owners = _FeatureOwnersField (many = True , required = False )
190+ group_owners = _FeatureGroupOwnersField (many = True , required = False )
170191
171192 environment_feature_state = serializers .SerializerMethodField ()
172193 segment_feature_state = serializers .SerializerMethodField ()
@@ -240,12 +261,22 @@ def create(self, validated_data: dict) -> Feature: # type: ignore[type-arg]
240261 project = self .context ["project" ]
241262 self .validate_project_features_limit (project )
242263
264+ # Pop M2M fields before creating the instance (can't pass to Model.objects.create)
265+ owners : list [FFAdminUser ] = validated_data .pop ("owners" , [])
266+ group_owners : list [UserPermissionGroup ] = validated_data .pop ("group_owners" , [])
267+
243268 # Add the default(User creating the feature) owner of the feature
244269 # NOTE: pop the user before passing the data to create
245270 user = validated_data .pop ("user" , None )
246271 instance = super (CreateFeatureSerializer , self ).create (validated_data ) # type: ignore[no-untyped-call]
247272 if user and getattr (user , "is_master_api_key_user" , False ) is False :
248273 instance .owners .add (user )
274+
275+ if owners :
276+ instance .owners .add (* owners )
277+ if group_owners :
278+ instance .group_owners .add (* group_owners )
279+
249280 return instance # type: ignore[no-any-return]
250281
251282 def validate_project_features_limit (self , project : Project ) -> None :
@@ -275,6 +306,26 @@ def validate_multivariate_options(self, multivariate_options): # type: ignore[n
275306 raise serializers .ValidationError ("Invalid percentage allocation" )
276307 return multivariate_options
277308
309+ def validate_owners (self , owners : list [FFAdminUser ]) -> list [FFAdminUser ]:
310+ project : Project = self .context ["project" ]
311+ for user in owners :
312+ if not user .has_project_permission (VIEW_PROJECT , project ):
313+ raise serializers .ValidationError (
314+ "Some users do not have access to this project."
315+ )
316+ return owners
317+
318+ def validate_group_owners (
319+ self , group_owners : list [UserPermissionGroup ]
320+ ) -> list [UserPermissionGroup ]:
321+ project : Project = self .context ["project" ]
322+ for group in group_owners :
323+ if group .organisation_id != project .organisation_id :
324+ raise serializers .ValidationError (
325+ "Some groups do not belong to this project's organisation."
326+ )
327+ return group_owners
328+
278329 def validate_name (self , name : str ): # type: ignore[no-untyped-def]
279330 view = self .context ["view" ]
280331
@@ -317,8 +368,23 @@ def validate(self, attrs: dict[str, Any]) -> dict[str, Any]:
317368 "Selected Tags must be from the same Project as current Feature"
318369 )
319370
371+ self ._validate_enforce_feature_owners (attrs )
372+
320373 return attrs
321374
375+ def _validate_enforce_feature_owners (self , attrs : dict [str , Any ]) -> None :
376+ project : Project = self .context ["project" ]
377+ if (
378+ not self .instance
379+ and project .enforce_feature_owners
380+ and not attrs .get ("owners" )
381+ and not attrs .get ("group_owners" )
382+ ):
383+ raise serializers .ValidationError (
384+ "This project requires at least one owner or group owner "
385+ "when creating a feature."
386+ )
387+
322388 @extend_schema_field (FeatureStateSerializerSmall (allow_null = True ))
323389 def get_environment_feature_state ( # type: ignore[return]
324390 self , instance : Feature
@@ -399,6 +465,9 @@ def update(self, feature: Feature, validated_data: dict[str, Any]) -> Feature:
399465class UpdateFeatureSerializerWithMetadata (FeatureSerializerWithMetadata ):
400466 """prevent users from changing certain values after creation"""
401467
468+ owners = _FeatureOwnersField (many = True , read_only = True )
469+ group_owners = _FeatureGroupOwnersField (many = True , read_only = True )
470+
402471 class Meta (FeatureSerializerWithMetadata .Meta ):
403472 read_only_fields = FeatureSerializerWithMetadata .Meta .read_only_fields + ( # type: ignore[assignment]
404473 "default_enabled" ,
@@ -416,6 +485,9 @@ class ListFeatureSerializer(FeatureSerializerWithMetadata):
416485class UpdateFeatureSerializer (ListFeatureSerializer ):
417486 """prevent users from changing certain values after creation"""
418487
488+ owners = _FeatureOwnersField (many = True , read_only = True )
489+ group_owners = _FeatureGroupOwnersField (many = True , read_only = True )
490+
419491 class Meta (ListFeatureSerializer .Meta ):
420492 read_only_fields = ListFeatureSerializer .Meta .read_only_fields + ( # type: ignore[assignment]
421493 "default_enabled" ,
0 commit comments