Coverage for cc_modules/cc_membership.py: 85%

34 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2022-11-08 23:14 +0000

1#!/usr/bin/env python 

2 

3""" 

4camcops_server/cc_modules/cc_membership.py 

5 

6=============================================================================== 

7 

8 Copyright (C) 2012, University of Cambridge, Department of Psychiatry. 

9 Created by Rudolf Cardinal (rnc1001@cam.ac.uk). 

10 

11 This file is part of CamCOPS. 

12 

13 CamCOPS is free software: you can redistribute it and/or modify 

14 it under the terms of the GNU General Public License as published by 

15 the Free Software Foundation, either version 3 of the License, or 

16 (at your option) any later version. 

17 

18 CamCOPS is distributed in the hope that it will be useful, 

19 but WITHOUT ANY WARRANTY; without even the implied warranty of 

20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

21 GNU General Public License for more details. 

22 

23 You should have received a copy of the GNU General Public License 

24 along with CamCOPS. If not, see <https://www.gnu.org/licenses/>. 

25 

26=============================================================================== 

27 

28**Represents a user's membership of a group.** 

29 

30""" 

31 

32import logging 

33from typing import Optional 

34 

35from cardinal_pythonlib.logs import BraceStyleAdapter 

36from sqlalchemy.orm import relationship, Session as SqlASession 

37from sqlalchemy.sql.schema import Column, ForeignKey 

38from sqlalchemy.sql.sqltypes import Boolean, Integer 

39 

40from camcops_server.cc_modules.cc_sqlalchemy import Base 

41 

42log = BraceStyleAdapter(logging.getLogger(__name__)) 

43 

44 

45# ============================================================================= 

46# User-to-group association table 

47# ============================================================================= 

48# This is many-to-many: 

49# A user can [be in] many groups. 

50# A group can [contain] many users. 

51 

52# ----------------------------------------------------------------------------- 

53# First version: 

54# ----------------------------------------------------------------------------- 

55# https://docs.sqlalchemy.org/en/latest/orm/basic_relationships.html#many-to-many 

56# user_group_table = Table( 

57# "_security_user_group", 

58# Base.metadata, 

59# Column("user_id", Integer, ForeignKey("_security_users.id"), 

60# primary_key=True), 

61# Column("group_id", Integer, ForeignKey("_security_groups.id"), 

62# primary_key=True) 

63# ) 

64 

65 

66# ----------------------------------------------------------------------------- 

67# Second version, when we want more information in the relationship: 

68# ----------------------------------------------------------------------------- 

69# https://stackoverflow.com/questions/7417906/sqlalchemy-manytomany-secondary-table-with-additional-fields # noqa: E501 

70# ... no, association_proxy isn't quite what we want 

71# ... https://docs.sqlalchemy.org/en/latest/orm/extensions/associationproxy.html # noqa: E501 

72# https://docs.sqlalchemy.org/en/latest/orm/basic_relationships.html#association-object # noqa: E501 

73# ... yes 

74# ... ah, but that AND association_proxy: 

75# https://docs.sqlalchemy.org/en/latest/orm/extensions/associationproxy.html#simplifying-association-objects # noqa: E501 

76# ... no, not association_proxy! 

77 

78 

79class UserGroupMembership(Base): 

80 """ 

81 Represents a user's membership of a group, and associated per-group 

82 permissions. 

83 """ 

84 

85 __tablename__ = "_security_user_group" 

86 

87 # PK, so we can use this object easily on its own via the ORM. 

88 id = Column("id", Integer, primary_key=True, autoincrement=True) 

89 

90 # Many-to-many mapping between User and Group 

91 user_id = Column("user_id", Integer, ForeignKey("_security_users.id")) 

92 group_id = Column("group_id", Integer, ForeignKey("_security_groups.id")) 

93 

94 # User attributes that are specific to their group membership 

95 groupadmin = Column( 

96 "groupadmin", 

97 Boolean, 

98 default=False, 

99 comment="Is the user a privileged administrator for this group?", 

100 ) 

101 may_upload = Column( 

102 "may_upload", 

103 Boolean, 

104 default=False, 

105 comment="May the user upload data from a tablet device?", 

106 ) 

107 may_register_devices = Column( 

108 "may_register_devices", 

109 Boolean, 

110 default=False, 

111 comment="May the user register tablet devices?", 

112 ) 

113 may_use_webviewer = Column( 

114 "may_use_webviewer", 

115 Boolean, 

116 default=False, 

117 comment="May the user use the web front end to view " "CamCOPS data?", 

118 ) 

119 view_all_patients_when_unfiltered = Column( 

120 "view_all_patients_when_unfiltered", 

121 Boolean, 

122 default=False, 

123 comment="When no record filters are applied, can the user see " 

124 "all records? (If not, then none are shown.)", 

125 ) 

126 may_dump_data = Column( 

127 "may_dump_data", 

128 Boolean, 

129 default=False, 

130 comment="May the user run database data dumps via the web interface?", 

131 ) 

132 may_run_reports = Column( 

133 "may_run_reports", 

134 Boolean, 

135 default=False, 

136 comment="May the user run reports via the web interface? " 

137 "(Overrides other view restrictions.)", 

138 ) 

139 may_add_notes = Column( 

140 "may_add_notes", 

141 Boolean, 

142 default=False, 

143 comment="May the user add special notes to tasks?", 

144 ) 

145 may_manage_patients = Column( 

146 "may_manage_patients", 

147 Boolean, 

148 default=False, 

149 comment="May the user add/edit/delete patients?", 

150 ) 

151 may_email_patients = Column( 

152 "may_email_patients", 

153 Boolean, 

154 default=False, 

155 comment="May the user send emails to patients?", 

156 ) 

157 

158 group = relationship("Group", back_populates="user_group_memberships") 

159 user = relationship("User", back_populates="user_group_memberships") 

160 

161 def __init__(self, user_id: int, group_id: int): 

162 self.user_id = user_id 

163 self.group_id = group_id 

164 

165 @classmethod 

166 def get_ugm_by_id( 

167 cls, dbsession: SqlASession, ugm_id: Optional[int] 

168 ) -> Optional["UserGroupMembership"]: 

169 """ 

170 Fetches a :class:`UserGroupMembership` by its ID. 

171 """ 

172 if ugm_id is None: 

173 return None 

174 return dbsession.query(cls).filter(cls.id == ugm_id).first()