Coverage for postrfp/model/acl.py: 92%
49 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-22 21:34 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-22 21:34 +0000
1import logging
2from typing import TYPE_CHECKING, Optional
4from sqlalchemy import (
5 text,
6 ForeignKey,
7 UniqueConstraint,
8)
9from sqlalchemy.types import VARCHAR, INTEGER
10from sqlalchemy.orm import (
11 relationship,
12 backref,
13 foreign,
14 Mapped,
15 mapped_column,
16 DynamicMapped,
17)
19from .meta import Base
20from postrfp.authorisation.roles import ROLES
21from postrfp.model.humans import CustomRole
23if TYPE_CHECKING:
24 from postrfp.model.humans import User
25 from postrfp.model.project import Project
26 from postrfp.model.humans import Organisation
27 from postrfp.model.questionnaire.nodes import Section
30log = logging.getLogger(__name__)
33class Participant(Base):
34 __tablename__ = "project_org_roles"
35 __table_args__ = (UniqueConstraint("project_id", "org_id"),)
37 project_id: Mapped[int] = mapped_column(
38 INTEGER, ForeignKey("projects.id", ondelete="CASCADE"), nullable=False
39 )
41 org_id: Mapped[str] = mapped_column(
42 VARCHAR(length=50),
43 ForeignKey("organisations.id", onupdate="CASCADE"),
44 nullable=False,
45 server_default=text("''"),
46 )
47 role: Mapped[Optional[str]] = mapped_column(
48 VARCHAR(length=255), default=None, nullable=True
49 )
50 organisation: Mapped["Organisation"] = relationship(
51 "Organisation", innerjoin=True, backref="participants"
52 )
53 project: Mapped["Project"] = relationship(
54 "Project", back_populates="participants_query"
55 )
57 permissions: DynamicMapped["ProjectPermission"] = relationship(
58 "ProjectPermission",
59 lazy="dynamic",
60 back_populates="participant",
61 cascade="all, delete",
62 passive_deletes=True,
63 )
64 custom_role: Mapped[CustomRole] = relationship(
65 CustomRole, foreign_keys=[role], primaryjoin=CustomRole.id == foreign(role)
66 )
68 def __repr__(self) -> str:
69 args = (self.org_id, self.project_id, self.role)
70 return "%s participant in project %s with role %s" % args
72 @property
73 def role_permissions(self):
74 return ROLES[self.role]
76 @property
77 def role_name(self):
78 if self.role in ROLES:
79 return self.role
80 elif self.custom_role:
81 return self.custom_role.name
82 else:
83 logging.error("Participant # %s has no role configured", self.id)
85 def as_dict(self) -> dict:
86 org_dict = self.organisation.as_dict()
87 return {"organisation": org_dict, "role": self.role_name}
90class ProjectPermission(Base):
91 __tablename__ = "project_permissions"
92 __table_args__ = (UniqueConstraint("project_org_role_id", "user_id"),)
94 project_org_role_id: Mapped[Optional[int]] = mapped_column(
95 INTEGER,
96 ForeignKey("project_org_roles.id", ondelete="CASCADE"),
97 nullable=True,
98 )
100 user_id: Mapped[Optional[str]] = mapped_column(
101 VARCHAR(length=50),
102 ForeignKey("users.id", ondelete="CASCADE"),
103 nullable=True,
104 )
106 user: Mapped["User"] = relationship("User", back_populates="project_permissions")
108 participant: Mapped["Participant"] = relationship(
109 "Participant", back_populates="permissions"
110 )
112 project: Mapped["Project"] = relationship(
113 "Project",
114 secondary="project_org_roles",
115 viewonly=True,
116 )
118 def __repr__(self) -> str:
119 return "Permission for user %s on project_org_role %s" % (
120 self.user_id,
121 self.project_org_role_id,
122 )
125class SectionPermission(Base):
126 __tablename__ = "section_permissions"
127 __table_args__ = (
128 UniqueConstraint("project_permissions_id", "user_id", "section_id"),
129 )
131 section_id: Mapped[Optional[int]] = mapped_column(
132 INTEGER, ForeignKey("sections.id", ondelete="CASCADE"), nullable=True
133 )
134 user_id: Mapped[Optional[str]] = mapped_column(
135 VARCHAR(length=50),
136 ForeignKey("users.id", ondelete="CASCADE"),
137 nullable=True,
138 )
139 project_permissions_id: Mapped[int] = mapped_column(
140 INTEGER,
141 ForeignKey("project_permissions.id", ondelete="CASCADE"),
142 nullable=False,
143 )
145 section: Mapped["Section"] = relationship(
146 "Section", backref=backref("permissions"), viewonly=True
147 )
148 user: Mapped["User"] = relationship("User", back_populates="section_permissions")
149 project_permission: Mapped["ProjectPermission"] = relationship(
150 "ProjectPermission", backref=backref("section_permissions")
151 )
153 def __repr__(self) -> str:
154 return "Permission for user %s on section %s" % (self.user_id, self.section_id)