Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
65f4c56
feat: added create project permission
mohamedelabbas1996 Oct 17, 2025
77e74d2
fat: handled permission check for model level permissions
mohamedelabbas1996 Oct 17, 2025
0213718
feat: return the create model level permission to the frontend with t…
mohamedelabbas1996 Oct 17, 2025
9197c50
migration: added the create project permission migration
mohamedelabbas1996 Oct 17, 2025
dc0bc64
refactor: separate object-level and model-level permission checks
mohamedelabbas1996 Oct 21, 2025
6d6e355
feat: handle project create case using model level permissions
mohamedelabbas1996 Oct 21, 2025
f8dfe0a
feat: add AuthenticatedUsers global role and auto-assign on user signup
mohamedelabbas1996 Oct 21, 2025
2932300
tests: added tests for model level permissions
mohamedelabbas1996 Oct 21, 2025
77cd1a6
feat: return model and object level permissions to the frontend
mohamedelabbas1996 Oct 24, 2025
66fe481
refactor: delegate collection-level permission handling to model
mohamedelabbas1996 Oct 27, 2025
de17278
feat: added model level permissions for the taxon and processing serv…
mohamedelabbas1996 Oct 27, 2025
13fc473
migration: added a migration to add all users to the AuthenticatedUs…
mohamedelabbas1996 Oct 27, 2025
10cb65b
tests: added tests for model level permissions for Project, Taxon and…
mohamedelabbas1996 Oct 27, 2025
88db935
refactor: get_collection_level_permissions signature to match with ge…
mohamedelabbas1996 Oct 29, 2025
b5d837e
refactor tests: extract shared permission helpers into BasePermission…
mohamedelabbas1996 Oct 29, 2025
4a068ec
feat: added custom model level permissions for taxon and processing s…
mohamedelabbas1996 Oct 29, 2025
ecf81f6
fix: assign project manager role to the project owner when creating a…
mohamedelabbas1996 Oct 29, 2025
b93ebec
feat: return custom model level permissions to frontend
mohamedelabbas1996 Oct 30, 2025
bf5a04d
fix: support multi-word custom actions
mohamedelabbas1996 Oct 30, 2025
7e081a6
docs: added a doc string for Project.check_permission
mohamedelabbas1996 Oct 30, 2025
2189a78
refactor: rename AuthenticatedUsers to AuthorizedUser
mohamedelabbas1996 Oct 30, 2025
c2ee8b5
migration: create model-level permissions in a single migration
mohamedelabbas1996 Oct 30, 2025
abc3356
test: added tests for custom model-level permissions
mohamedelabbas1996 Oct 30, 2025
67e3666
test: fix processing service endpoint url and assign_tags request pay…
mohamedelabbas1996 Oct 30, 2025
f7ab979
chore: rename m2m permission descriptions to indicate global scope
mohamedelabbas1996 Nov 4, 2025
58a352e
feat: grant AuthorizedUsers role default global model-level permissio…
mohamedelabbas1996 Nov 4, 2025
53e9b73
test: fixed tests
mohamedelabbas1996 Nov 6, 2025
eca0cba
chore: move permission methods to PermissionsMixin
mohamedelabbas1996 Nov 6, 2025
a4f8535
chore: changed chore: rename signal handler to assign_authorized_user…
mohamedelabbas1996 Nov 6, 2025
32dd4fc
feat: add signal to synchronize global role group permissions
mohamedelabbas1996 Nov 6, 2025
319a5bc
test: add test for automatic AuthorizedUser group assignment
mohamedelabbas1996 Nov 6, 2025
2841549
feat: include "globally" in model-level permission names to clarify g…
mohamedelabbas1996 Nov 6, 2025
269b646
refactor: Refactored the _get_or_update_permission method to stop ove…
mohamedelabbas1996 Nov 7, 2025
cd5c17c
chore: add merge migration for main branch integration
mihow Jan 31, 2026
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