Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
933fbd8
feat: added create project permission
mohamedelabbas1996 Oct 17, 2025
ab39902
fat: handled permission check for model level permissions
mohamedelabbas1996 Oct 17, 2025
45ad448
feat: return the create model level permission to the frontend with t…
mohamedelabbas1996 Oct 17, 2025
3326955
migration: added the create project permission migration
mohamedelabbas1996 Oct 17, 2025
fc6c137
refactor: separate object-level and model-level permission checks
mohamedelabbas1996 Oct 21, 2025
d8692b0
feat: handle project create case using model level permissions
mohamedelabbas1996 Oct 21, 2025
f8cad51
feat: add AuthenticatedUsers global role and auto-assign on user signup
mohamedelabbas1996 Oct 21, 2025
ebdd2e9
tests: added tests for model level permissions
mohamedelabbas1996 Oct 21, 2025
5a99c21
feat: return model and object level permissions to the frontend
mohamedelabbas1996 Oct 24, 2025
18332ef
refactor: delegate collection-level permission handling to model
mohamedelabbas1996 Oct 27, 2025
4ce2ea4
feat: added model level permissions for the taxon and processing serv…
mohamedelabbas1996 Oct 27, 2025
b07d01f
migration: added a migration to add all users to the AuthenticatedUs…
mohamedelabbas1996 Oct 27, 2025
f3f6fe7
tests: added tests for model level permissions for Project, Taxon and…
mohamedelabbas1996 Oct 27, 2025
cb40ad8
deleted migration file
mohamedelabbas1996 Oct 27, 2025
814ecba
Merge branch 'main' into feat/model-level-permissions
mohamedelabbas1996 Oct 27, 2025
331cc83
Merge branch 'main' into feat/model-level-permissions
mohamedelabbas1996 Oct 29, 2025
38f55e4
refactor: get_collection_level_permissions signature to match with ge…
mohamedelabbas1996 Oct 29, 2025
53a6434
refactor tests: extract shared permission helpers into BasePermission…
mohamedelabbas1996 Oct 29, 2025
bec698c
feat: added custom model level permissions for taxon and processing s…
mohamedelabbas1996 Oct 29, 2025
9f78153
fix: assign project manager role to the project owner when creating a…
mohamedelabbas1996 Oct 29, 2025
051c315
feat: return custom model level permissions to frontend
mohamedelabbas1996 Oct 30, 2025
c4d84ba
fix: support multi-word custom actions
mohamedelabbas1996 Oct 30, 2025
990a4f9
docs: added a doc string for Project.check_permission
mohamedelabbas1996 Oct 30, 2025
aba1839
refactor: rename AuthenticatedUsers to AuthorizedUser
mohamedelabbas1996 Oct 30, 2025
819bf77
migration: create model-level permissions in a single migration
mohamedelabbas1996 Oct 30, 2025
7917378
test: added tests for custom model-level permissions
mohamedelabbas1996 Oct 30, 2025
c64bd72
test: fix processing service endpoint url and assign_tags request pay…
mohamedelabbas1996 Oct 30, 2025
03a514f
chore: rename m2m permission descriptions to indicate global scope
mohamedelabbas1996 Nov 4, 2025
f6e4897
feat: grant AuthorizedUsers role default global model-level permissio…
mohamedelabbas1996 Nov 4, 2025
93c1253
test: fixed tests
mohamedelabbas1996 Nov 6, 2025
f4dd262
chore: move permission methods to PermissionsMixin
mohamedelabbas1996 Nov 6, 2025
700520b
chore: changed chore: rename signal handler to assign_authorized_user…
mohamedelabbas1996 Nov 6, 2025
86b8566
Merge branch 'main' into feat/model-level-permissions
mohamedelabbas1996 Nov 6, 2025
5fa61f3
feat: add signal to synchronize global role group permissions
mohamedelabbas1996 Nov 6, 2025
22b83f2
test: add test for automatic AuthorizedUser group assignment
mohamedelabbas1996 Nov 6, 2025
10a3bfa
Merge branch 'feat/model-level-permissions' of https://github.com/Rol…
mohamedelabbas1996 Nov 6, 2025
a7b6d87
feat: include "globally" in model-level permission names to clarify g…
mohamedelabbas1996 Nov 6, 2025
2a21c02
refactor: Refactored the _get_or_update_permission method to stop ove…
mohamedelabbas1996 Nov 7, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 3 additions & 95 deletions ami/base/models.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from django.contrib.auth.models import AbstractUser, AnonymousUser
from django.contrib.auth.models import AnonymousUser
from django.db import models
from django.db.models import Q, QuerySet
from guardian.shortcuts import get_perms

import ami.tasks
from ami.base.permissions import PermissionsMixin
from ami.users.models import User


Expand Down Expand Up @@ -88,7 +88,7 @@ def visible_for_user(self, user: User | AnonymousUser) -> QuerySet:
return self.filter(filter_condition).distinct()


class BaseModel(models.Model):
class BaseModel(PermissionsMixin, models.Model):
""" """

created_at = models.DateTimeField(auto_now_add=True)
Expand Down Expand Up @@ -166,97 +166,5 @@ def update_calculated_fields(self, *args, **kwargs):
"""Update calculated fields specific to each model."""
pass

def _get_object_perms(self, user):
"""
Get the object-level permissions for the user on this instance.
This method retrieves permissions like `update_modelname`, `create_modelname`, etc.
"""
project = self.get_project()
if not project:
return []

model_name = self._meta.model_name
all_perms = get_perms(user, project)
object_perms = [perm for perm in all_perms if perm.endswith(f"_{model_name}")]
return object_perms

def check_permission(self, user: AbstractUser | AnonymousUser, action: str) -> bool:
"""
Check if the user has permission to perform the action
on this instance.
This method is used to determine if the user can perform
CRUD operations or custom actions on the model instance.
"""
from ami.users.roles import BasicMember

project = self.get_project() if hasattr(self, "get_project") else None
if not project:
return False
if action == "retrieve":
if project.draft:
# Allow view permission for members and owners of draft projects
return BasicMember.has_role(user, project) or user == project.owner or user.is_superuser
return True
model = self._meta.model_name
crud_map = {
"create": f"create_{model}",
"update": f"update_{model}",
"partial_update": f"update_{model}",
"destroy": f"delete_{model}",
}

if action in crud_map:
return user.has_perm(crud_map[action], project)

# Delegate to model-specific logic
return self.check_custom_permission(user, action)

def check_custom_permission(self, user: AbstractUser | AnonymousUser, action: str) -> bool:
"""Check custom permissions for the user on this instance.
This is used for actions that are not standard CRUD operations.
"""
assert self._meta.model_name is not None, "Model must have a model_name defined in Meta class."
model_name = self._meta.model_name.lower()
permission_codename = f"{action}_{model_name}"
project = self.get_project() if hasattr(self, "get_project") else None

return user.has_perm(permission_codename, project)

def get_user_object_permissions(self, user) -> list[str]:
"""
Returns a list of object-level permissions the user has on this instance.
This is used by frontend to determine what actions the user can perform.
"""
# Return all permissions for superusers
if user.is_superuser:
allowed_custom_actions = self.get_custom_user_permissions(user)
return ["update", "delete"] + allowed_custom_actions

object_perms = self._get_object_perms(user)
# Check for update and delete permissions
allowed_actions = set()
for perm in object_perms:
action = perm.split("_", 1)[0]
if action in {"update", "delete"}:
allowed_actions.add(action)

allowed_custom_actions = self.get_custom_user_permissions(user)
allowed_actions.update(set(allowed_custom_actions))
return list(allowed_actions)

def get_custom_user_permissions(self, user: AbstractUser | AnonymousUser) -> list[str]:
"""
Returns a list of custom permissions (not standard CRUD actions) that the user has on this instance.
"""
object_perms = self._get_object_perms(user)
custom_perms = set()
# Extract custom permissions that are not standard CRUD actions
for perm in object_perms:
action = perm.split("_", 1)[0]
# Make sure to exclude standard CRUD actions
if action not in ["view", "create", "update", "delete"]:
custom_perms.add(action)
return list(custom_perms)

class Meta:
abstract = True
Loading