1+ import logging
2+
13from django .apps import apps
24from django .core .exceptions import ObjectDoesNotExist
35from django .db import transaction
1820
1921from ..models import DABContentType , DABPermission
2022from ..remote import RemoteObject
23+ from .fields import ActorAnsibleIdField
2124from .queries import assignment_qs_user_to_obj , assignment_qs_user_to_obj_perm
2225
26+ logger = logging .getLogger (__name__ )
27+
2328
2429class RoleDefinitionSerializer (CommonModelSerializer ):
2530 permissions = serializers .SlugRelatedField (
@@ -112,17 +117,19 @@ def get_by_ansible_id(self, ansible_id, requesting_user, for_field):
112117 raise ValidationError ({for_field : msg .format (pk_value = ansible_id )})
113118 return resource .content_object
114119
115- def get_actor_from_data (self , validated_data , requesting_user ):
120+ def validate (self , attrs ):
121+ """Validate that exactly one of actor or actor_ansible_id is provided"""
116122 actor_aid_field = f'{ self .actor_field } _ansible_id'
117- if validated_data .get (self .actor_field ) and validated_data .get (actor_aid_field ):
123+
124+ # Check what was actually provided in the request
125+ has_actor_in_request = self .actor_field in self .initial_data
126+ has_actor_aid_in_request = actor_aid_field in self .initial_data
127+
128+ # If both actor and actor_ansible_id are present or both not present than we error out
129+ if has_actor_in_request == has_actor_aid_in_request :
118130 self .raise_id_fields_error (self .actor_field , actor_aid_field )
119- elif validated_data .get (self .actor_field ):
120- actor = validated_data [self .actor_field ]
121- elif ansible_id := validated_data .get (actor_aid_field ):
122- actor = self .get_by_ansible_id (ansible_id , requesting_user , for_field = actor_aid_field )
123- else :
124- self .raise_id_fields_error (self .actor_field , f'{ self .actor_field } _ansible_id' )
125- return actor
131+
132+ return super ().validate (attrs )
126133
127134 def get_object_from_data (self , validated_data , role_definition , requesting_user ):
128135 obj = None
@@ -145,10 +152,11 @@ def get_object_from_data(self, validated_data, role_definition, requesting_user)
145152 elif validated_data .get ('object_ansible_id' ):
146153 obj = self .get_by_ansible_id (validated_data .get ('object_ansible_id' ), requesting_user , for_field = 'object_ansible_id' )
147154 if permission_registry .content_type_model .objects .get_for_model (obj ) != role_definition .content_type :
155+ model_name = getattr (role_definition .content_type , 'model' , 'global' )
148156 raise ValidationError (
149157 {
150158 'object_ansible_id' : _ ('Object type of %(model_name)s does not match role type of %(role_definition)s' )
151- % {'model_name' : obj ._meta .model_name , 'role_definition' : role_definition . content_type . model }
159+ % {'model_name' : obj ._meta .model_name , 'role_definition' : model_name }
152160 }
153161 )
154162 return obj
@@ -158,7 +166,7 @@ def create(self, validated_data):
158166 requesting_user = self .context ['view' ].request .user
159167
160168 # Resolve actor - team or user
161- actor = self .get_actor_from_data ( validated_data , requesting_user )
169+ actor = validated_data [ self .actor_field ]
162170
163171 # Resolve object
164172 obj = self .get_object_from_data (validated_data , rd , requesting_user )
@@ -216,7 +224,8 @@ def _get_summary_fields(self, obj) -> dict[str, dict]:
216224
217225class RoleUserAssignmentSerializer (BaseAssignmentSerializer ):
218226 actor_field = 'user'
219- user_ansible_id = serializers .UUIDField (
227+ user_ansible_id = ActorAnsibleIdField (
228+ source = 'user' ,
220229 required = False ,
221230 help_text = _ ('The resource ID of the user who will receive permissions from this assignment. An alternative to user field.' ),
222231 allow_null = True , # for ease of use of the browseable API
@@ -232,7 +241,8 @@ def get_actor_queryset(self, requesting_user):
232241
233242class RoleTeamAssignmentSerializer (BaseAssignmentSerializer ):
234243 actor_field = 'team'
235- team_ansible_id = serializers .UUIDField (
244+ team_ansible_id = ActorAnsibleIdField (
245+ source = 'team' ,
236246 required = False ,
237247 help_text = _ ('The resource ID of the team who will receive permissions from this assignment. An alternative to team field.' ),
238248 allow_null = True ,
@@ -257,9 +267,21 @@ def _get_related(self, obj) -> dict[str, str]:
257267 return {}
258268 related_fields = {}
259269 actor_cls = self .Meta .model
270+
271+ # Use ansible_id if available, otherwise fall back to pk
272+ actor_identifier = obj .pk
273+ try :
274+ if hasattr (obj , 'resource' ) and obj .resource :
275+ actor_identifier = str (obj .resource .ansible_id )
276+ except ObjectDoesNotExist :
277+ # Resource doesn't exist, stick with pk
278+ logger .warning (
279+ f"No resource for { self .Meta .model } { obj .pk } due to internal error. Linking role-{ actor_cls ._meta .model_name } -access-assignments as pk."
280+ )
281+
260282 related_fields ['details' ] = get_relative_url (
261283 f'role-{ actor_cls ._meta .model_name } -access-assignments' ,
262- kwargs = {'model_name' : self .context .get ("content_type" ).api_slug , 'pk' : self .context .get ("related_object" ).pk , 'actor_pk' : obj . pk },
284+ kwargs = {'model_name' : self .context .get ("content_type" ).api_slug , 'pk' : self .context .get ("related_object" ).pk , 'actor_pk' : actor_identifier },
263285 )
264286 return related_fields
265287
0 commit comments