2222
2323
2424def set_permissions_for_obj_to_user (
25- user_val : int | str | " UserModel" ,
25+ user_val : int | str | UserModel ,
2626 instance : django .db .models .Model ,
2727 permissions : list [PermissionTypes ],
2828) -> None :
@@ -167,7 +167,7 @@ def set_permissions_for_obj_to_user(
167167 assign_perm (f"{ app_name } .publish_{ model_name } " , user , instance )
168168
169169
170- def get_users_group_ids (user_instance : " UserModel" ) -> list [str | int ]:
170+ def get_users_group_ids (user_instance : UserModel ) -> list [str | int ]:
171171 """
172172 For a given user, return list of group ids it belongs to.
173173 """
@@ -206,7 +206,7 @@ def get_permission_id_to_name_map_for_model(
206206
207207
208208def get_users_permissions_for_obj (
209- user : " UserModel" ,
209+ user : UserModel ,
210210 instance : django .db .models .Model ,
211211 include_group_permissions : bool = False ,
212212) -> set [str ]:
@@ -304,7 +304,7 @@ def get_users_permissions_for_obj(
304304
305305
306306def user_has_permission_for_obj (
307- user_val : int | str | " UserModel" ,
307+ user_val : int | str | UserModel ,
308308 instance : django .db .models .Model ,
309309 permission : PermissionTypes ,
310310 include_group_permissions : bool = False ,
@@ -383,10 +383,10 @@ def user_has_permission_for_obj(
383383 # For private annotations, permissions are limited by BOTH the source object AND doc+corpus
384384 # We need to check the source object permissions match or exceed what's being requested
385385 if instance .created_by_analysis_id :
386- # Check if user has the requested permission level on the analysis
387- if not user_has_permission_for_obj (
386+ source_analysis = instance . created_by_analysis
387+ if source_analysis is None or not user_has_permission_for_obj (
388388 user ,
389- instance . created_by_analysis ,
389+ source_analysis ,
390390 permission , # Check for the same permission level being requested
391391 include_group_permissions = include_group_permissions ,
392392 ):
@@ -396,10 +396,10 @@ def user_has_permission_for_obj(
396396 )
397397 return False
398398 elif instance .created_by_extract_id :
399- # Check if user has the requested permission level on the extract
400- if not user_has_permission_for_obj (
399+ source_extract = instance . created_by_extract
400+ if source_extract is None or not user_has_permission_for_obj (
401401 user ,
402- instance . created_by_extract ,
402+ source_extract ,
403403 permission , # Check for the same permission level being requested
404404 include_group_permissions = include_group_permissions ,
405405 ):
@@ -409,7 +409,11 @@ def user_has_permission_for_obj(
409409 )
410410 return False
411411
412- # Now check document+corpus permissions using the query optimizer
412+ # Now check document+corpus permissions using the query optimizer.
413+ # An annotation without a parent document has no inheritable scope,
414+ # so non-superuser access is denied (the superuser branch is above).
415+ if instance .document_id is None :
416+ return False
413417 can_read , can_create , can_update , can_delete , can_comment = (
414418 AnnotationQueryOptimizer ._compute_effective_permissions (
415419 user = user ,
@@ -467,8 +471,12 @@ def user_has_permission_for_obj(
467471 )
468472 return False
469473
470- # Relationships inherit permissions from document+corpus
471- # Use the same logic as annotations
474+ # Relationships inherit permissions from document+corpus.
475+ # Relationships without a document have no inheritable scope, so
476+ # we deny all non-superuser access (the superuser short-circuit
477+ # above is the only escape).
478+ if instance .document_id is None :
479+ return False
472480 can_read , can_create , can_update , can_delete , can_comment = (
473481 AnnotationQueryOptimizer ._compute_effective_permissions (
474482 user = user ,
0 commit comments