@@ -551,43 +551,44 @@ def has_permission(self, request, view):
551551
552552 return False
553553
554- def has_object_permission (self , request , view , obj ):
555- """Check if user has permission for this specific object"""
554+ def _get_target_class_path_for_restricted_edit (self , obj ):
555+ """Resolve project class path for restricted programmer object checks. """
556556 _type = obj ._meta .model .__name__
557+ if _type == "Project" :
558+ return obj .projectClass .path if obj .projectClass else None
559+ if _type == "Note" :
560+ target_project = obj .project
561+ if target_project and target_project .projectClass :
562+ return target_project .projectClass .path
563+ return None
564+ if _type == "ProjectGroup" :
565+ return obj .classRelation .path if obj .classRelation else None
566+ return None
567+
568+ @staticmethod
569+ def _target_path_matches_assigned_paths (target_class_path , assigned_paths ):
570+ """True if target equals or is a child of an assigned path (paths use '/')."""
571+ for path in assigned_paths :
572+ if target_class_path == path or target_class_path .startswith (path + "/" ):
573+ return True
574+ return False
557575
558- # Coordinators and admins bypass restrictions
576+ def has_object_permission (self , request , view , obj ):
577+ """Check if user has permission for this specific object"""
559578 if self .user_is_coordinator_or_admin (request ):
560579 return True
561580
562- # Allow read actions for all objects (lists/retrievals)
563581 if view .action in DJANGO_BASE_READ_ONLY_ACTIONS :
564582 return True
565583
566- # For edit actions, identify the relevant class path
567- target_class_path = None
568-
569- if _type == "Project" :
570- target_class_path = obj .projectClass .path if obj .projectClass else None
571- elif _type == "Note" :
572- target_project = obj .project
573- target_class_path = target_project .projectClass .path if target_project and target_project .projectClass else None
574- elif _type == "ProjectGroup" :
575- # For groups, we check the classRelation
576- target_class_path = obj .classRelation .path if obj .classRelation else None
577-
578- # If we couldn't find a target class path to validate against, deny edit
584+ target_class_path = self ._get_target_class_path_for_restricted_edit (obj )
579585 if not target_class_path :
580586 return False
581587
582- # Get all class paths assigned to user
583588 assigned_paths = self .get_user_assigned_classes_paths (request )
584589 if not assigned_paths :
585590 return False
586591
587- # Check if target class matches or is a child of any assigned path.
588- # Child paths are separated by "/", so we check exact match or prefix + "/".
589- for path in assigned_paths :
590- if target_class_path == path or target_class_path .startswith (path + "/" ):
591- return True
592-
593- return False
592+ return self ._target_path_matches_assigned_paths (
593+ target_class_path , assigned_paths
594+ )
0 commit comments