Resources
Resources are a subset of database models in Debusine which have a common structure around user display and permission management.
Resource metadata
When a resource can have a user-provided description, it is in a
description TextField whose contents can use Markdown formatting.
Resource roles
Groups can have roles on resources, which are used for permission management.
The set of roles for a resource is defined as an enumerated type derived from
permissions.RoleBase, and made available as a Roles attribute
in the model class. For example:
class WorkspaceRoles(permissions.Roles, WorkspaceRoleBase, enum.ReprEnum):
"""Available roles for a Workspace."""
OWNER = ...
CONTRIBUTOR = ...
VIEWER = ...
WorkspaceRoles.setup()
class Workspace(models.Model):
"""Workspace model."""
Roles: TypeAlias = WorkspaceRoles
Roles can be entirely implied by roles on other resources, as is currently the
case with debusine.db.models.Collection, or explicitly assigned to
groups.
When they are explicitly assigned to groups, it is done via a model with
resource, group, and role members. For example:
class WorkspaceRole(models.Model):
"""Role assignments for workspaces."""
Roles: TypeAlias = WorkspaceRoles
resource = models.ForeignKey(
Workspace,
on_delete=models.CASCADE,
related_name="roles",
)
group = models.ForeignKey(
"Group",
on_delete=models.CASCADE,
related_name="workspace_roles",
)
role = models.CharField(max_length=16, choices=Roles.choices)
class Meta(TypedModelMeta):
constraints = [
UniqueConstraint(
fields=["resource", "group", "role"],
name="%(app_label)s_%(class)s_unique_resource_group_role",
),
]
The role assignment model is made accessible via the get_roles_model method
in the manager of the resource model:
class WorkspaceManager(models.Manager["Workspace"]):
...
def get_roles_model(self) -> type["WorkspaceRole"]:
"""Get the model used for role assignment."""
return WorkspaceRole
Consistency is important in order to make use of structural typing to maintain code that can operate on any type of resource.
Managers for resource models have at least these well defined methods:
class WorkspaceManager(models.Manager["Workspace"]):
...
def has_role(self, pc: PermissionContext, role: WorkspaceRoles) -> bool:
"""Check if the user has the given role on this Workspace."""
def get_roles(self, pc: PermissionContext) -> frozenset[WorkspaceRoles]:
"""Get the effective roles of the user on this workspace."""
Querysets for resource roles have at least these well defined methods:
class WorkspaceQuerySet[A](QuerySet["Workspace", A]):
...
def with_role(self, pc: PermissionContext, role: WorkspaceRoles) -> Self:
"""Keep only resources where the user has the given role."""
The resource model itself has at least these well defined methods:
class Workspace(models.Model):
"""Workspace model."""
Roles: TypeAlias = WorkspaceRoles
def has_role(self, pc: PermissionContext, role: WorkspaceRoles) -> bool:
"""Check if the user has the given role on this Workspace."""
def get_roles(self, pc: PermissionContext) -> frozenset[WorkspaceRoles]:
"""Get the effective roles of the user on this workspace."""