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
« 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"""
5from sqlalchemy.orm import Session
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
16@http
17def get_project_notes(session, user, project_id) -> serial.ReadNotes:
18 """
19 Get a list of ProjectNotes for the given project_id.
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 )
34def issue_if_target_set(project, note):
35 if not note.target_org_id:
36 return None
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
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
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.
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 |
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).
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)
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)
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)
104 return serial.Id(id=note.id)
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
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)
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 )
139 audited_patch(note, note_doc, evt, ["note_text", "private", "target_org_id"])
141 session.add(evt)
144@http
145def delete_project_note(session: Session, user, project_id, note_id):
146 """
147 Delete a Project Note
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)
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)