Coverage for postrfp/shared/fetch/projq.py: 100%
41 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
1"""
2Project related queries
3"""
5import logging
6from typing import (
7 List,
8 Optional,
9 Dict,
10 Any,
11)
13from sqlalchemy import (
14 select,
15 or_,
16 and_,
17)
18from sqlalchemy.orm import (
19 lazyload,
20 Bundle,
21 Query,
22 Session,
23 undefer,
24)
25from sqlalchemy.orm.session import object_session
27from postrfp.model import (
28 Participant,
29 ProjectPermission,
30 ProjectWatchList,
31 ProjectNote,
32 Organisation,
33 Project,
34 User,
35 Issue,
36 Category,
37 Status,
38)
40log = logging.getLogger(__name__)
43def project(
44 session: Session,
45 project_id: int,
46 with_description: bool = False,
47) -> Project:
48 """
49 Fetch a Project from the database
51 Parameters
52 ----------
53 session : Session
55 project_id : int
57 Raises
58 ------
59 NoResultFound if no Project found for the given project_id
60 """
61 q = (
62 session.query(Project)
63 .filter(Project.id == project_id)
64 .options(lazyload(Project.owner_org))
65 )
66 if with_description:
67 q.options(undefer(Project.description))
68 return q.one()
71pw_cols: Bundle = Bundle(
72 "listed_project",
73 Project.id,
74 Project.workflow_id,
75 Project.title,
76 Project.deadline,
77 Organisation.name.label("owner_org_name"),
78 Status.name.label("status"),
79 Organisation.id.label("owner_org_id"),
80 Project.date_created,
81 ProjectWatchList.id.isnot(None).label("is_watched"),
82 ProjectWatchList.date_created.label("watching_since"),
83 single_entity=True,
84)
87def projects_with_watched(
88 session: Session, user: User, participant_id: Optional[str] = None
89) -> Query:
90 org_id = user.org_id
91 if participant_id:
92 org_id = participant_id
93 pq = (
94 session.query(pw_cols)
95 .join(Participant)
96 .join(Status, Project.current_status_id == Status.id)
97 .filter(Participant.org_id == org_id)
98 .outerjoin(
99 ProjectWatchList,
100 and_(
101 ProjectWatchList.user == user, ProjectWatchList.project_id == Project.id
102 ),
103 )
104 .outerjoin(Organisation, Project.org_id == Organisation.id)
105 )
106 if user.is_restricted:
107 pq = pq.join(ProjectPermission).filter(ProjectPermission.user == user)
108 return pq
111def participant_notes_query(project: Project) -> Query:
112 session = object_session(project)
113 assert session is not None
114 pq = session.query(Participant.org_id).filter(Participant.project == project)
115 pn = project.notes_query.filter(
116 or_(ProjectNote.private == 0, ProjectNote.org_id.in_(pq))
117 ).order_by(ProjectNote.note_time.desc())
118 return pn
121def vendor_notes(issue: Issue, user: User) -> Query:
122 nq = issue.project.notes_query
123 own_org_id = user.org_id
124 return nq.filter(
125 or_(
126 and_(
127 ProjectNote.private == True, # noqa
128 ProjectNote.org_id == own_org_id,
129 ProjectNote.kind
130 == "RespondentNote", # Posted by this vendor to himself
131 ),
132 and_(
133 ProjectNote.private == False, # noqa: E712
134 ProjectNote.kind == "IssuerNote",
135 or_(
136 ProjectNote.target_org_id == None, # noqa: E711 # Sent by issuer to all vendors
137 ProjectNote.target_org_id == own_org_id, # Sent to just this vendor
138 ),
139 ),
140 and_(
141 ProjectNote.private == False, # noqa: E712
142 ProjectNote.org_id == own_org_id,
143 ProjectNote.kind
144 == "RespondentNote", # Posted by this vendor to the issue
145 ),
146 )
147 ).order_by(ProjectNote.note_time.desc())
150def note(project: Project, note_id: int) -> ProjectNote:
151 """
152 Fetch a ProjectNote by id checking it belongs to the given project but
153 without checking user visibility
155 @raises NoResultFound
156 """
157 return project.notes_query.filter(ProjectNote.id == note_id).one()
160def issues_for_respondent(
161 session: Session, buyer_user: User, respondent_id: str
162) -> List[Dict[str, Any]]:
163 """
164 Fetch a list of Issue/Project records for the given respondent_id
165 which are visible to the given user
166 """
167 cols = (
168 Issue.id.label("issue_id"),
169 Issue.id.label("id"),
170 Issue.status,
171 Issue.issue_date,
172 Issue.accepted_date,
173 Issue.submitted_date,
174 Issue.deadline,
175 Issue.winloss_exposed,
176 Issue.winloss_expiry,
177 Project.id.label("project_id"),
178 Project.title.label("project_title"),
179 Project.org_id.label("project_owner"),
180 )
181 q = (
182 session.query(*cols)
183 .join(Project)
184 .join(Participant)
185 .filter(
186 Participant.organisation == buyer_user.organisation,
187 Issue.respondent_id == respondent_id,
188 )
189 .order_by(Issue.issue_date.desc())
190 )
191 return q.all()
194def category_for_user(session: Session, user: User, category_id: int) -> Category:
195 """
196 Fetch the category with given id checking that it belongs to the user's organisation
197 Returns NoResultFound if category id or user's org_id are wrong
198 """
199 assert session is not None
200 q = (
201 select(Category)
202 .where(Category.id == category_id)
203 .where(Category.organisation == user.organisation)
204 )
205 return session.execute(q).scalar_one()