Coverage for rfpy/api/endpoints/notes.py: 100%

60 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-04-24 10:52 +0000

1""" 

2Manage Project Notes - messages between participants and respondents in a project. 

3""" 

4 

5from sqlalchemy.orm import Session 

6 

7from rfpy.model import ProjectNote, AuditEvent 

8from rfpy.model.helpers import audited_patch 

9from rfpy.model.humans import User 

10from rfpy.suxint import http 

11from rfpy.web import serial 

12from rfpy.api import fetch, validate 

13from rfpy.auth import perms 

14 

15 

16@http 

17def get_project_notes(session, user, project_id) -> serial.ReadNotes: 

18 """ 

19 Get a list of ProjectNotes for the given project_id. 

20 

21 @raise AuthorisationFailue - if the user is not a standard user from the buying organisation 

22 @raise NoResultFound - if the specified project cannot be loaded 

23 """ 

24 project = fetch.project(session, project_id) 

25 validate.check(user, perms.PROJECT_ACCESS, project=project) 

26 return serial.ReadNotes( 

27 [ 

28 serial.ReadNote.model_validate(pn) 

29 for pn in fetch.participant_notes_query(project) 

30 ] 

31 ) 

32 

33 

34def issue_if_target_set(project, note): 

35 if not note.target_org_id: 

36 return None 

37 

38 issue = project._issues.filter_by(respondent_id=note.target_org_id).first() 

39 if issue is None: 

40 m = f"Org '{note.target_org_id}'' is not a respondent in project {project.id}" 

41 raise ValueError(m) 

42 return issue 

43 

44 

45@http 

46def post_project_note( 

47 session: Session, user: User, project_id: int, note_doc: serial.ProjectNote 

48) -> serial.Id: 

49 """ 

50 Add a Note to the given Project 

51 

52 The visibility of Notes to different organisation is given by the table below - the last three 

53 columns indicate the visiblity of the message to each group. 

54 

55 |private|target_org_id defined|all respondents? |target respondent?|participants? | 

56 |-------|---------------------|-----------------|------------------|----------------| 

57 |false | true | no | yes | yes | 

58 |false | false | yes | not applicable | yes | 

59 |true | false | yes | not applicable | yes | 

60 |true | true | no | no | yes | 

61 

62 

63 Field 'target_org_id' is the ID of the Organisation to whom a Note is addressed. The 

64 Organisation must be a respondent for the current Project 

65 (i.e assigned as Respondent for an Issue). 

66 

67 Setting 'target_org_id' has no effect if the message is private (in which case only users in 

68 Particpant organisations can view the message) 

69 

70 @permissions PROJECT_ADD_NOTE 

71 """ 

72 project = fetch.project(session, project_id) 

73 validate.check(user, perms.PROJECT_ADD_NOTE, project=project) 

74 note = ProjectNote( 

75 kind="IssuerNote", 

76 project_id=project_id, 

77 note_text=note_doc.note_text, 

78 private=note_doc.private, 

79 user_id=user.id, 

80 org_id=user.org_id, 

81 ) 

82 issue = None 

83 target_id = note_doc.target_org_id 

84 if target_id is not None and target_id.strip() != "": 

85 note.target_org_id = target_id 

86 issue = issue_if_target_set(project, note) 

87 

88 session.add(note) 

89 session.flush() 

90 evt = AuditEvent.create( 

91 session, 

92 "PROJECT_NOTE_ADDED", 

93 project=project, 

94 object_id=note.id, 

95 user_id=user.id, 

96 org_id=user.org_id, 

97 issue=issue, 

98 ) 

99 evt.add_change("note_text", "", note.note_text) 

100 evt.add_change("private", "", note.private) 

101 evt.add_change("target_org_id", "", note.target_org_id) 

102 session.add(evt) 

103 

104 return serial.Id(id=note.id) 

105 

106 

107@http 

108def put_project_note(session: Session, user, project_id, note_id, note_doc): 

109 """ 

110 Update a Project Note. See Post Project Note for details of privacy and visibility 

111 

112 @permissions PROJECT_ADD_NOTE 

113 """ 

114 project = fetch.project(session, project_id) 

115 validate.check(user, perms.PROJECT_ADD_NOTE, project=project) 

116 note = fetch.note(project, note_id) 

117 issue = issue_if_target_set(project, note) 

118 

119 if issue is not None: 

120 evt = AuditEvent.create( 

121 session, 

122 "PROJECT_NOTE_UPDATED", 

123 project=project, 

124 object_id=note.id, 

125 user_id=user.id, 

126 org_id=user.org_id, 

127 issue=issue, 

128 ) 

129 else: 

130 evt = AuditEvent.create( 

131 session, 

132 "PROJECT_NOTE_UPDATED", 

133 project=project, 

134 object_id=note.id, 

135 user_id=user.id, 

136 org_id=user.org_id, 

137 ) 

138 

139 audited_patch(note, note_doc, evt, ["note_text", "private", "target_org_id"]) 

140 

141 session.add(evt) 

142 

143 

144@http 

145def delete_project_note(session: Session, user, project_id, note_id): 

146 """ 

147 Delete a Project Note 

148 

149 @permissions PROJECT_ADD_NOTE 

150 """ 

151 project = fetch.project(session, project_id) 

152 validate.check(user, perms.PROJECT_ADD_NOTE, project=project) 

153 note = fetch.note(project, note_id) 

154 

155 issue = issue_if_target_set(project, note) 

156 evt = AuditEvent.create( 

157 session, 

158 "PROJECT_NOTE_DELETED", 

159 project=project, 

160 object_id=note.id, 

161 user_id=user.id, 

162 org_id=user.org_id, 

163 issue=issue, 

164 ) 

165 session.add(evt) 

166 session.flush() 

167 session.delete(note)