========= 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 :py:class:`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 :py:class:`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.""" List of resources ================= * :py:class:`debusine.db.models.Scope` * :py:class:`debusine.db.models.Workspace` * :py:class:`debusine.db.models.Collection` * :py:class:`debusine.db.models.Artifact` * :py:class:`debusine.db.models.Asset` * :py:class:`debusine.db.models.AssetUsage` * :py:class:`debusine.db.models.Group` * :py:class:`debusine.db.models.WorkRequest` * :py:class:`debusine.db.models.WorkflowTemplate`