Coverage for rfpy/adaptors.py: 100%
154 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"""
2Functions that accept http request objects (webob)
3and return values to be consumed by api functions
5Purpose is to abstract api functions from http and
6to perform common validation and marshalling jobs
8Request is expected to be HttpRequest, webob subclass defined in rfy.web.request
10ORM model instances returned by functions here should be assumed to be validated as
11being visible to the calling user. Primitives (e.g. ID values like section_id) are not
12validated.
13"""
15import re
17from rfpy.api import fetch
18from rfpy.model.questionnaire import NumberString
19from rfpy.web import serial, Pager
20from rfpy.web.serial import SchemaDocArg
21from rfpy.model.audit import evt_types
24from rfpy.suxint import ArgExtractor, PathArg, GetArg, GetArgSet, PostFileArg, PostArg
27class PagerArg(GetArg):
28 page_number = GetArg(
29 "page",
30 default=1,
31 validator=lambda x: x > 0,
32 doc="Page number of records to show, beginning from 1",
33 )
34 page_size = GetArg(
35 "page_size",
36 default=20,
37 validator=lambda x: 0 < x < 201,
38 doc="The number of records to return in a single page, maximum 200, minimum 1",
39 )
41 def extract(self, request):
42 _pagenum = PagerArg.page_number.extract(request)
43 PagerArg.page_number.validate(_pagenum)
45 _pagesize = PagerArg.page_size.extract(request)
46 PagerArg.page_size.validate(_pagesize)
48 if (_pagenum * _pagesize) > 2**64:
49 # MySQL Limit is a 64 bit integer
50 raise ValueError("Either page number or page size is too large")
52 return Pager(page=_pagenum, page_size=_pagesize)
54 def update_openapi_path_object(self, path_object: dict):
55 ArgExtractor.update_openapi_path_object(PagerArg.page_size, path_object)
56 ArgExtractor.update_openapi_path_object(PagerArg.page_number, path_object)
59def check_node_number(val):
60 bare = val.strip(". ")
61 if not bare:
62 # root is an empty string
63 return True
64 if re.match(r"^(\d+\.?)+$", bare) is None:
65 return False
66 for el in bare.split("."):
67 # base 36 maximum for converting into two letters
68 if int(el) >= 1295:
69 return False
71 return True
74# PATH ARGUMENTS
77section_id = PathArg("section")
79issue_id = PathArg("issue")
81question_id = PathArg("question")
83question_number = PathArg("question", converter=NumberString.from_dotted)
85score_id = PathArg("score")
87project_id = PathArg("project")
89attachment_id = PathArg("attachment")
91answer_id = PathArg("answer")
93element_id = PathArg("element")
95watch_id = PathArg("watch")
97event_id = PathArg("event")
99category_id = PathArg("category")
101reltype_id = PathArg("reltype")
103tag_id = PathArg("tag")
105note_id = PathArg("note")
107weightset_path_id = PathArg("weightset")
109# GET ARGUMENTS
111role_id = GetArg("roleId", arg_type="str")
113q_org_id = GetArg("orgId", arg_type="str", default=None)
115q_participant_id = GetArg("participantId", arg_type="str")
117q_debug = GetArg("debug", arg_type="str")
119with_ancestors = GetArg("ancestors", arg_type="boolean", default=False)
121search_doc = "See Path documentation for details of search special characters"
123org_type = GetArg(
124 "orgType", arg_type="str", default="vendors", enum_values=("clients", "vendors")
125)
128def search_val(val):
129 if val is None or len(val) < 2:
130 raise ValueError("Search term must be at least 2 characters long")
131 return True
134search_term = GetArg(
135 "searchTerm", arg_type="str", validator=search_val, doc=search_doc, required=True
136)
138search_options = GetArgSet(
139 "options",
140 arg_type="str",
141 enum_values=("answers", "choices", "notes", "questions", "scoreComments"),
142 doc="Defines which object types should be searched",
143)
145project_statuses = GetArgSet(
146 "projectStatus",
147 required=False,
148 arg_type="str",
149 enum_values=("Draft", "Live", "Closed"),
150 doc="Filter by provided Project Status values",
151)
153project_sort = GetArg(
154 "projectSort",
155 arg_type="str",
156 enum_values=("date_created", "date_published", "deadline", "title"),
157 doc="Sort projects by dates or title",
158 default="date_created",
159)
161q_project_title = GetArg(
162 "pTitle", arg_type="str", doc="Search for string inside project title"
163)
165issue_sort = GetArg(
166 "issueSort",
167 arg_type="str",
168 enum_values=("deadline", "submitted_date", "issue_date"),
169 doc="Issue list sort order",
170 default="issue_date",
171)
173issue_status = GetArg(
174 "projectStatus",
175 arg_type="str",
176 enum_values=(
177 "Not Sent",
178 "Opportunity",
179 "Accepted",
180 "Updateable",
181 "Declined",
182 "Submitted",
183 "Retracted",
184 ),
185 doc="Filter by provided Project Status values",
186)
188sort_order = GetArg("sort", arg_type="str", enum_values=("asc", "desc"), default="desc")
191offset = GetArg("offset", arg_type="int", doc="For paging results")
193q_question_id = GetArg("questionId")
195q_section_id = GetArg("sectionId")
197scoreset_docs = (
198 "Set to the user ID of a scoring user if Multiple Score Sets are "
199 "enabled for this project"
200)
202"""
203scoreset_id is the ID of the user that created a score set. __default__ is used when returning
204data to indicate the default user. For db queries an empty string is used.
205"""
208def default_scoreset(v):
209 if v in (None, "__default__"):
210 return ""
211 return v
214scoreset_id = GetArg(
215 "scoresetId",
216 arg_type="str",
217 default="__default__",
218 doc=scoreset_docs,
219 converter=default_scoreset,
220)
223weightset_id = GetArg("weightsetId", arg_type="int")
225q_project_id = GetArg("projectId", arg_type="int", doc="ID of the Project")
227q_category_id = GetArg("categoryId", arg_type="int", doc="ID of the Project")
229q_issue_id = GetArg("issueId")
231user_id = GetArg("userId", arg_type="str", required=True)
233node_doc = (
234 "Dotted String number e.g. 3.12.5 giving position of the question"
235 " or section within the questionnaire"
236)
238node_number = GetArg(
239 "nodeNumber",
240 arg_type="str",
241 converter=NumberString.from_dotted,
242 validator=check_node_number,
243 doc=node_doc,
244 default="",
245)
247q_question_number = GetArg(
248 "questionNumber",
249 arg_type="str",
250 converter=NumberString.from_dotted,
251 doc='e.g. "5.2.9"',
252 required=True,
253)
256def val_evt_type(val):
257 if val is not None and not hasattr(evt_types, val):
258 raise ValueError(f"{val} is not a valid event type")
259 return True
262event_type = GetArg("eventType", arg_type="str", validator=val_evt_type)
264restricted_users = GetArg("restricted", arg_type="boolean", default=False)
266pager = PagerArg("page", default=1)
268q_with_questions = GetArg("withQuestions", arg_type="boolean", default=True)
270issue_ids = GetArgSet(
271 "issueIds",
272 array_items_type="int",
273 min_items=1,
274 max_items=10,
275 doc="ID values of Issues to query against",
276)
278project_ids = GetArgSet(
279 "issueId",
280 array_items_type="int",
281 min_items=1,
282 max_items=100,
283 doc="ID values of Projects to query against",
284)
285# POST Args
287attachment_description = PostArg("description", arg_type="str")
288el_id = PostArg("el_id", arg_type="int")
290# POST File Uploads
291attachment_upload = PostFileArg("attachment_upload")
293data_upload = PostFileArg("data_upload", required=True)
295# VALIDATED JSON REQUEST BODY ARGUMENTS
297allocation_doc = SchemaDocArg(serial.AllocatedToList, as_dict=False)
299answers_doc = SchemaDocArg(serial.ElementAnswerList, as_dict=False)
301category_doc = SchemaDocArg(serial.NewCategory, as_dict=False)
303element_doc = SchemaDocArg(serial.qmodels.QElement)
305id_doc = SchemaDocArg(serial.Id)
307ids_doc = SchemaDocArg(serial.IdList, as_dict=False)
309name_doc = SchemaDocArg(serial.ShortName, as_dict=False)
311import_answers_doc = SchemaDocArg(serial.ImportAnswers, as_dict=False)
313import_section_doc = SchemaDocArg(serial.SectionImportDoc, as_dict=False)
315issue_doc = SchemaDocArg(serial.UpdateableIssue, exclude_unset=True)
317issue_status_doc = SchemaDocArg(serial.IssueStatus, as_dict=False)
319issue_workflow_doc = SchemaDocArg(serial.IssueUseWorkflow, as_dict=False)
321move_section_doc = SchemaDocArg(serial.MoveSection, as_dict=False)
323new_client_doc = SchemaDocArg(serial.NewClient, as_dict=False)
325new_issue_doc = SchemaDocArg(serial.NewIssue, as_dict=False)
327new_project_doc = SchemaDocArg(serial.NewProject)
329note_doc = SchemaDocArg(serial.ProjectNote, as_dict=False)
331org_doc = SchemaDocArg(serial.Organisation, as_dict=False)
333participants_list = SchemaDocArg(serial.UpdateParticipantList)
335perm_doc = SchemaDocArg(serial.ProjectPermission, as_dict=False)
337publish_doc = SchemaDocArg(serial.PublishProject)
339qdef_doc = SchemaDocArg(serial.QuestionDef, as_dict=False)
341relationship_doc = SchemaDocArg(serial.Relationship, as_dict=False)
343reltype_doc = SchemaDocArg(serial.RelationshipType, as_dict=False)
345replace_doc = SchemaDocArg(serial.TextReplace, as_dict=False)
347respondent_note_doc = SchemaDocArg(serial.RespondentNote, as_dict=False)
349score_doc = SchemaDocArg(serial.Score, as_dict=False)
351section_doc = SchemaDocArg(serial.Section)
353section_score_docs = SchemaDocArg(serial.SectionScoreDocs)
355edit_sec_doc = SchemaDocArg(serial.EditableSection, as_dict=False)
357tag_assigns_doc = SchemaDocArg(serial.TagAssigns, as_dict=False)
359tag_doc = SchemaDocArg(serial.NewTag, as_dict=False)
361update_project_doc = SchemaDocArg(serial.UpdateableProject)
363user_doc = SchemaDocArg(serial.EditableUser, as_dict=False)
365user_id_doc = SchemaDocArg(serial.UserId, as_dict=False)
367watch_doc = SchemaDocArg(serial.TargetUser, as_dict=False)
369watchers_doc = SchemaDocArg(serial.TargetUserList)
371webhook_doc = SchemaDocArg(serial.NewWebhook, as_dict=False)
373weights_doc = SchemaDocArg(serial.WeightingsDoc, as_dict=False)
375weightset_doc = SchemaDocArg(serial.NewWeightSet, as_dict=False)
377child_nodes_doc = SchemaDocArg(serial.SectionChildNodes, as_dict=False)
380class UserObject(ArgExtractor):
381 swagger_in = "query"
382 swagger_type = "string"
384 def extract(self, request):
385 user_id = request.GET.get("targetUser", None)
386 if not user_id:
387 return None
388 return fetch.user(request.session, user_id)
391target_user = UserObject("targetUser")
394# NON USER DEFINED VALUES ASSOCIATED WITH THE REQUEST
397def session(request):
398 return request.session
401def user(request):
402 return request.user
405def effective_user(request):
406 return request.user