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

1import logging 

2from typing import TYPE_CHECKING, Optional 

3 

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) 

18 

19from .meta import Base 

20from postrfp.authorisation.roles import ROLES 

21from postrfp.model.humans import CustomRole 

22 

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 

28 

29 

30log = logging.getLogger(__name__) 

31 

32 

33class Participant(Base): 

34 __tablename__ = "project_org_roles" 

35 __table_args__ = (UniqueConstraint("project_id", "org_id"),) 

36 

37 project_id: Mapped[int] = mapped_column( 

38 INTEGER, ForeignKey("projects.id", ondelete="CASCADE"), nullable=False 

39 ) 

40 

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 ) 

56 

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 ) 

67 

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 

71 

72 @property 

73 def role_permissions(self): 

74 return ROLES[self.role] 

75 

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) 

84 

85 def as_dict(self) -> dict: 

86 org_dict = self.organisation.as_dict() 

87 return {"organisation": org_dict, "role": self.role_name} 

88 

89 

90class ProjectPermission(Base): 

91 __tablename__ = "project_permissions" 

92 __table_args__ = (UniqueConstraint("project_org_role_id", "user_id"),) 

93 

94 project_org_role_id: Mapped[Optional[int]] = mapped_column( 

95 INTEGER, 

96 ForeignKey("project_org_roles.id", ondelete="CASCADE"), 

97 nullable=True, 

98 ) 

99 

100 user_id: Mapped[Optional[str]] = mapped_column( 

101 VARCHAR(length=50), 

102 ForeignKey("users.id", ondelete="CASCADE"), 

103 nullable=True, 

104 ) 

105 

106 user: Mapped["User"] = relationship("User", back_populates="project_permissions") 

107 

108 participant: Mapped["Participant"] = relationship( 

109 "Participant", back_populates="permissions" 

110 ) 

111 

112 project: Mapped["Project"] = relationship( 

113 "Project", 

114 secondary="project_org_roles", 

115 viewonly=True, 

116 ) 

117 

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 ) 

123 

124 

125class SectionPermission(Base): 

126 __tablename__ = "section_permissions" 

127 __table_args__ = ( 

128 UniqueConstraint("project_permissions_id", "user_id", "section_id"), 

129 ) 

130 

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 ) 

144 

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 ) 

152 

153 def __repr__(self) -> str: 

154 return "Permission for user %s on section %s" % (self.user_id, self.section_id)