Coverage for middle_layer/testdata/application_layer/rest_api/views/testdata.py: 69.59%
296 statements
« prev ^ index » next coverage.py v7.10.5, created at 2026-03-09 06:13 +0000
« prev ^ index » next coverage.py v7.10.5, created at 2026-03-09 06:13 +0000
1# Copyright 2024 Associated Universities, Inc.
2#
3# This file is part of Telescope Time Allocation Tools (TTAT).
4#
5# TTAT is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, either version 3 of the License, or
8# any later version.
9#
10# TTAT is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with TTAT. If not, see <https://www.gnu.org/licenses/>.
17#
18import json
19from http import HTTPStatus
21from pyramid.httpexceptions import HTTPBadRequest, HTTPInternalServerError
22from pyramid.request import Request
23from pyramid.response import Response
24from pyramid.view import view_config
25from sqlalchemy.orm.session import Session
27from allocate.domain_layer.entities.proposal_disposition_group import ProposalDispositionGroup
28from allocate.domain_layer.entities.publication_destination import PublicationDestination
29from common import get_middle_layer
30from common.application_layer.rest_api import make_expected_params_message
31from review.domain_layer.entities.science_review_panel import ScienceReviewPanel
32from solicit.domain_layer.entities.solicitation import ProposalProcess, Solicitation
33from testdata.application_layer.services.context import Context
36def get_context(session: Session, solicitation_id: int) -> Context:
37 try:
38 return Context(session, solicitation_id)
39 except AssertionError:
40 raise HTTPBadRequest(f"Requested Solicitation with ID={solicitation_id} is not a Test or Demo Solicitation.")
43@view_config(
44 route_name="read_context",
45 renderer="json",
46 permission="set_context",
47)
48def read_context(request: Request) -> Response:
49 """
50 URL: testdata/{solicitation_id}/context
52 :param request: GET request
53 :return: 200 response (Success) with json_body of requested Context entity
54 or 400 response (HTTPBadRequest) if a Solicitation is not a Test or Demo
55 """
56 sol_id = request.matchdict["solicitation_id"]
57 context = get_context(request.repo.session, sol_id)
58 return Response(json_body=context.context.__json__(), status_code=HTTPStatus.OK)
61@view_config(
62 route_name="set_context",
63 renderer="json",
64 permission="set_context",
65)
66def set_context(request: Request) -> Response:
67 """
68 Sets context's do_notify field to determine if emails should be sent
70 URL: testdata/{solicitation_id}/context
72 :param request: PUT request with a JSON object like:
73 {
74 doNotify: <bool>
75 }
76 :return: 200 response (Success) with json_body of requested Context entity
77 or 400 response (HTTPBadRequest) if a Solicitation is not a Test or Demo
78 or parameters are missing
79 """
80 sol_id = request.matchdict["solicitation_id"]
81 params = request.json_body
82 if not "doNotify" in params:
83 raise HTTPBadRequest(body=make_expected_params_message(["doNotify"], params.keys()))
84 context = get_context(request.repo.session, sol_id)
85 context.context.do_notify = params["doNotify"]
88@view_config(
89 route_name="generate_proposals",
90 renderer="json",
91 permission="generate_proposals",
92)
93def generate_proposals(request: Request) -> Response:
94 """
95 Generate and submit Proposals for a testdata Solicitation
96 URL: solicitations/generate_proposals
98 :param request: POST request with a JSON object like:
99 {
100 solicitationId: <int>
101 proposalCount: <int>
102 }
103 :return: 201 response (Created) with JSON body like:
104 {
105 "proposals": [<proposal-json>,...]
106 }
107 or 400 response (HTTPBadRequest) if a expected parameters aren't present,
108 or the Proposal Process ID isn't an int or the Solicitation is not a Test or Demo
109 or 500 response (HTTPInternalServerError) if a Proposal submission notification fails to send
110 """
111 expected_params = ["solicitationId", "proposalCount"]
112 params = request.json_body
113 if not all([expected in params for expected in expected_params]):
114 # JSON params do not contain all expected params
115 raise HTTPBadRequest(body=make_expected_params_message(expected_params, params.keys()))
116 context = get_context(request.repo.session, params["solicitationId"])
117 try:
118 props = context.make_proposals(params["proposalCount"], do_submit=True)
119 except RuntimeError as e:
120 raise HTTPInternalServerError(body=str(e))
121 return Response(json_body=[prop.__json__() for prop in props], status_code=HTTPStatus.CREATED)
124@view_config(
125 route_name="generate_solicitation",
126 renderer="json",
127 permission="generate_solicitation",
128)
129def generate_solicitation(request: Request) -> Response:
130 """
131 Generate a Solicitation based on the Sem_25A config file
132 URL: solicitations/generate_solicitation
134 :param request: POST request with a JSON object like:
135 {
136 name: <str>,
137 proposalCodePrefix: <str>,
138 proposalProcess: {proposalProcessId: <int>},
139 randomSeed: <int, optional>
140 isDemo: <boolean>
141 }
142 :return: 201 response (Created) with JSON body like:
143 {
144 "solicitation": <solicitation-json>,
145 }
146 or 400 response (HTTPBadRequest) if a expected parameters aren't present,
147 or the Proposal Process ID isn't an int
148 or 404 response (HTTPNotFound) if a ProposalProcess with the given proposalProcessId cannot be found
149 or 500 response (HTTPInternalServerError) if a Proposal submission notification fails to send
150 """
151 expected_params = ["name", "proposalCodePrefix", "proposalProcess", "randomSeed", "isDemo", "doNotify"]
152 params = request.json_body
153 if not all([expected in params for expected in expected_params]):
154 # JSON params do not contain all expected params
155 raise HTTPBadRequest(body=make_expected_params_message(expected_params, params.keys()))
156 if "proposalProcessId" not in params["proposalProcess"]:
157 # JSON params do not contain all expected params
158 raise HTTPBadRequest(body=make_expected_params_message(["proposalProcessId"], params["proposalProcess"].keys()))
160 pp = request.lookup(params["proposalProcess"]["proposalProcessId"], ProposalProcess)
162 with open(get_middle_layer() / "config_files" / "sem26A.json") as file:
163 sem26_config = json.load(file)
165 test_type = "Demo" if params["isDemo"] is True else "Test"
167 try:
168 context = Context(request.repo.session)
169 sol = context.make_solicitation(
170 test_type,
171 sem26_config,
172 params["name"],
173 params["proposalCodePrefix"],
174 pp,
175 params["randomSeed"],
176 params["doNotify"],
177 )
178 except RuntimeError as e:
179 raise HTTPInternalServerError(body=str(e))
180 response = {"solicitation": sol.__json__()}
181 return Response(json_body=response, status_code=HTTPStatus.CREATED)
184@view_config(
185 route_name="generate_science_review_panels",
186 renderer="json",
187 permission="science_review_panel_upsert",
188)
189def generate_science_review_panels(request: Request) -> Response:
190 """
191 Generate a ScienceReviewPanel for the given test/demo solicitation, with reviewers, and assign proposals
192 URL: testdata/generate_science_review_panels
194 :param request: POST request
195 :return: 201 response (Created) Response with a list of JSON-formatted SRPs
196 or 400 response (HTTPBadRequest) if expected solicitation_id is not given,
197 or reference solicitation is not test/demo
198 or there are unvetted proposals
199 or there are insufficient available users to assign to a panel
200 or 404 response (HTTPNotFound) if no solicitation exists with the given solicitation_id
201 or 412 response (HTTPPreconditionFailed) if there's an issue with persistence or data integrity
202 """
203 solicitation = request.lookup(request.matchdict["solicitation_id"], Solicitation)
204 context = get_context(request.repo.session, solicitation_id=solicitation.solicitation_id)
206 try:
207 context.make_srps()
208 except ValueError as e:
209 raise HTTPBadRequest(body=str(e))
210 srps = context.get_srps()
212 return Response(json_body=[srp.__json__() for srp in srps], status_code=HTTPStatus.CREATED)
215@view_config(
216 route_name="vet_all_proposals",
217 renderer="json",
218 permission="vet_all_proposals",
219)
220def vet_all_proposals(request: Request) -> Response:
221 """
222 Vet all proposals on a test/demo Solicitation
223 URL: testdata/vet_all_proposals/{solicitation_id}
224 :return: Response with JSON-formatted vetted proposals
225 or 400 response (HTTPBadRequest) if the ID is invalid
226 or 404 response (HTTPNotFound) if no solicitation is found with the ID
227 """
228 solicitation = request.lookup(request.matchdict["solicitation_id"], Solicitation)
229 context = Context(request.repo.session, solicitation.solicitation_id)
230 context.vet_all_proposals()
231 proposals = context.get_proposals()
232 return Response(status_code=HTTPStatus.OK, json_body=[p.__json__() for p in proposals])
235@view_config(
236 route_name="complete_panel_configuration",
237 renderer="json",
238 permission="complete_panel_configuration",
239)
240def complete_panel_configuration(request: Request) -> Response:
241 """
242 Complete science panel configuration on a test/demo Solicitation
243 URL: testdata/complete_panel_configuration/{solicitation_id}
244 :return: Response with JSON-formatted science review panels
245 or 400 response (HTTPBadRequest) if the ID is invalid
246 or if panels have already been created (determined by the presence of ISRs)
247 or 404 response (HTTPNotFound) if no solicitation is found with the ID
248 """
249 solicitation = request.lookup(request.matchdict["solicitation_id"], Solicitation)
250 context = Context(request.repo.session, solicitation.solicitation_id)
252 try:
253 context.complete_panel_configuration()
254 except ValueError as e:
255 raise HTTPBadRequest(body=str(e))
257 srps = context.get_srps()
258 return Response(status_code=HTTPStatus.OK, json_body=[srp.__json__() for srp in srps])
261@view_config(
262 route_name="set_certify_conflicts",
263 renderer="json",
264 permission="set_certify_conflicts",
265)
266def set_certify_conflicts(request: Request) -> Response:
267 """
268 For the specified ScienceReviewPanel, set all conflict states to Available unless they are AutoConflicted
269 and certify the conflicts
270 URL: testdata/set_certify_conflicts
272 :param request: PUT request
273 :return: 200 response (Success) Response with a list of JSON-formatted ISRs
274 or 400 response (HTTPBadRequest) if expected srp_id is not given, or reference solicitation is not test/demo
275 or 404 response (HTTPNotFound) if no scienceReviewPanel exists with the given srp_id or no solicitation
276 exists for the srp specified
277 """
278 srp = request.lookup(request.matchdict["srp_id"], ScienceReviewPanel)
279 solicitation = request.lookup(srp.solicitation_id, Solicitation)
280 context = get_context(request.repo.session, solicitation_id=solicitation.solicitation_id)
281 context.set_certify_conflicts(srp)
282 isrs = context.get_isrs(srp)
284 return Response(json_body=[isr.__json__() for isr in isrs], status_code=HTTPStatus.OK)
287@view_config(
288 route_name="set_isr_comments_scores",
289 renderer="json",
290 permission="individual_science_review_update",
291)
292def set_isr_comments_scores(request: Request) -> Response:
293 """
294 For the specified ScienceReviewPanel, set isr.comment_for_pi and isr.individual_score unless they are AutoConflicted
295 and update the state to SAved
296 URL: testdata/set_isr_comments_scores
298 :param request: PUT request
299 :return: 200 response (Success) Response with a list of JSON-formatted ISRs
300 or 400 response (HTTPBadRequest) if expected srp_id is not given, or reference solicitation is not test/demo
301 or 404 response (HTTPNotFound) if no scienceReviewPanel exists with the given srp_id or no solicitation
302 exists for the srp specified
303 """
304 srp = request.lookup(request.matchdict["srp_id"], ScienceReviewPanel)
305 solicitation = request.lookup(srp.solicitation_id, Solicitation)
306 context = get_context(request.repo.session, solicitation_id=solicitation.solicitation_id)
307 context.set_isr_types(srp)
308 context.set_isr_comments_and_scores(srp)
309 isrs = context.get_isrs(srp)
311 return Response(json_body=[isr.__json__() for isr in isrs], status_code=HTTPStatus.OK)
314@view_config(
315 route_name="finalize_test_isrs",
316 renderer="json",
317 permission="finalize_test_isrs",
318)
319def finalize_test_isrs(request: Request) -> Response:
320 """
321 For the specified ScienceReviewPanel, set finalize all ISRs as the reviewer for those ISRs (rather than as TTA
322 member since that would normally close that ISR
323 URL: testdata/finalize_test_isrs
325 :param request: PUT request
326 :return: 200 response (Success) Response with a list of JSON-formatted ISRs
327 or 400 response (HTTPBadRequest) if expected srp_id is not given, or reference solicitation is not test/demo
328 or 404 response (HTTPNotFound) if no scienceReviewPanel exists with the given srp_id or no solicitation
329 exists for the srp specified
330 """
331 srp = request.lookup(request.matchdict["srp_id"], ScienceReviewPanel)
332 solicitation = request.lookup(srp.solicitation_id, Solicitation)
333 context = get_context(request.repo.session, solicitation_id=solicitation.solicitation_id)
335 context.finalize_test_isrs(srp)
337 isrs = context.get_isrs(srp)
339 return Response(json_body=[isr.__json__() for isr in isrs], status_code=HTTPStatus.OK)
342@view_config(
343 route_name="launch_consensus",
344 renderer="json",
345 permission="launch_consensus",
346)
347def launch_consensus(request: Request) -> Response:
348 """
349 For the specified ScienceReviewPanel, launch the consensus phase
350 URL: testdata/launch_consensus
352 :param request: PUT request
353 :return: 200 response (Success) Response with a list of JSON-formatted PPRs
354 Note: if consensus has already been initiated for this SRP, the existing PPRs will be returned. Also, no error
355 will be raised.
356 or 400 response (HTTPBadRequest) if expected srp_id is not given, or reference solicitation is not test/demo
357 or 404 response (HTTPNotFound) if no scienceReviewPanel exists with the given srp_id
358 or no solicitation exists for the srp specified
359 """
360 srp = request.lookup(request.matchdict["srp_id"], ScienceReviewPanel)
361 solicitation = request.lookup(srp.solicitation_id, Solicitation)
362 try:
363 context = get_context(request.repo.session, solicitation_id=solicitation.solicitation_id)
364 pprs = context.launch_consensus(srp)
365 except ValueError as e:
366 raise HTTPBadRequest(body=str(e))
368 return Response(json_body=[ppr.__json__() for ppr in pprs], status_code=HTTPStatus.OK)
371@view_config(
372 route_name="set_ppr_comments",
373 renderer="json",
374 permission="set_ppr_comments",
375)
376def set_ppr_comments(request: Request) -> Response:
377 """
378 For the specified ScienceReviewPanel, update each PPR on a SRP with a concatenated list of ISR comments from each
379 proposal
380 URL: testdata/def set_ppr_comments(request: Request) -> Response:
382 :param request: PUT request
383 :return: 200 response (Success) Response with a list of JSON-formatted ISRs
384 or 400 response (HTTPBadRequest) if expected srp_id is not given, or reference solicitation is not test/demo
385 or 404 response (HTTPNotFound) if no scienceReviewPanel exists with the given srp_id or no solicitation
386 exists for the srp specified
387 """
388 srp = request.lookup(request.matchdict["srp_id"], ScienceReviewPanel)
389 solicitation = request.lookup(srp.solicitation_id, Solicitation)
390 context = get_context(request.repo.session, solicitation_id=solicitation.solicitation_id)
391 context.set_ppr_comments(srp)
392 pprs = context.get_pprs(srp)
394 return Response(json_body=[ppr.__json__() for ppr in pprs], status_code=HTTPStatus.OK)
397@view_config(
398 route_name="complete_ppr_comments",
399 renderer="json",
400 permission="complete_ppr_comments",
401)
402def complete_ppr_comments(request: Request) -> Response:
403 """
404 For the specified ScienceReviewPanel, update the state of PPRs with the SRP
405 URL: testdata/complete_ppr_comments
407 :param request: PUT request
408 :return: 200 response (Success) Response with a list of JSON-formatted ISRs
409 or 400 response (HTTPBadRequest) if expected srp_id is not given,
410 or reference solicitation is not test/demo
411 or 404 response (HTTPNotFound) if no scienceReviewPanel exists with the given srp_id or no solicitation
412 exists for the srp specified
413 """
414 srp = request.lookup(request.matchdict["srp_id"], ScienceReviewPanel)
415 solicitation = request.lookup(srp.solicitation_id, Solicitation)
416 context = get_context(request.repo.session, solicitation_id=solicitation.solicitation_id)
418 # Confirm that all PPRs are in a non-Blank state, if one is found to be Blank error
419 pprs = context.get_pprs(srp)
421 for ppr in pprs:
422 if ppr.review_state == "Blank":
423 raise HTTPBadRequest(body="One or more Consensus Reviews found to be in Blank review state")
425 context.set_complete_pprs(srp)
426 pprs = context.get_pprs(srp)
428 return Response(json_body=[ppr.__json__() for ppr in pprs], status_code=HTTPStatus.OK)
431@view_config(
432 route_name="finalize_test_pprs",
433 renderer="json",
434 permission="finalize_test_pprs",
435)
436def finalize_test_pprs(request: Request) -> Response:
437 """
438 For the specified ScienceReviewPanel, finalize the PPRs on the SRP
439 URL: testdata/finalize_test_pprs
441 :param request: PUT request
442 :return: 200 response (Success) Response with a list of JSON-formatted ISRs
443 or 400 response (HTTPBadRequest) if expected srp_id is not given,
444 or reference solicitation is not test/demo
445 or 404 response (HTTPNotFound) if no scienceReviewPanel exists with the given srp_id or no solicitation
446 exists for the srp specified
447 """
448 srp = request.lookup(request.matchdict["srp_id"], ScienceReviewPanel)
449 solicitation = request.lookup(srp.solicitation_id, Solicitation)
450 context = get_context(request.repo.session, solicitation_id=solicitation.solicitation_id)
452 # Confirm that all PPRs are in a non-Blank state, if one is found to be Blank error
453 pprs = context.get_pprs(srp)
455 for ppr in pprs:
456 if ppr.review_state not in ["Completed", "Finalized"]:
457 raise HTTPBadRequest(
458 body="One or more Consensus Reviews found to be in a non-Completed or Finalized " "review state"
459 )
461 try:
462 pprs = context.finalize_pprs(srp)
463 except ValueError as e:
464 raise HTTPBadRequest(body=str(e))
466 return Response(json_body=[ppr.__json__() for ppr in pprs], status_code=HTTPStatus.OK)
469@view_config(
470 route_name="run_all_ppr_steps",
471 renderer="json",
472 permission="run_all_ppr_steps",
473)
474def run_all_ppr_steps(request: Request) -> Response:
475 """
476 For the specified solicitation, run all the steps of the Panel Proposal Review process
477 URL: testdata/run_all_ppr_process_steps/{solicitation_id}
479 :param request: POST request
480 :return: 200 response (Success) Response with a dictionary containing SRPs
481 or 400 response (HTTPBadRequest) if expected solicitation_id is not given,
482 or reference solicitation is not test/demo
483 or associated solicitation is not closed
484 or attempt is made to run all steps against an OSR solicitation (not currently implemented in any form)
485 or 404 response (HTTPNotFound) if no scienceReviewPanel exists with the given srp_id or no solicitation
486 exists for the srp specified
487 """
488 solicitation = request.lookup(request.matchdict["solicitation_id"], Solicitation)
489 context = get_context(request.repo.session, solicitation_id=solicitation.solicitation_id)
491 if solicitation.proposal_process.proposal_process_name == "Panel Proposal Review":
492 try:
493 context.run_all_ppr_process_steps()
494 except ValueError as e:
495 raise HTTPBadRequest(body=str(e))
496 elif solicitation.proposal_process.proposal_process_name == "Observatory Site Review":
497 try:
498 context.run_all_osr_process_steps()
499 except ValueError as e:
500 raise HTTPBadRequest(body=str(e))
502 srps = context.get_srps()
504 return Response(json_body=[srp.__json__() for srp in srps], status_code=HTTPStatus.CREATED)
507@view_config(
508 route_name="set_osr_comments",
509 renderer="json",
510 permission="set_osr_comments",
511)
512def set_osr_comments(request: Request) -> Response:
513 """
514 For any Observatory Site Review proposals submitted and are in a Blank state, set the comments
515 URL: testdata/set_osr_comments/{solicitation_id}
517 :param request: PUT request
518 :return: 200 response (Success) Response with a list of JSON-formatted OSR proposal reviews
519 or 400 response (HTTPBadRequest) if expected solicitation_id is not given,
520 or reference solicitation is not test/demo
521 or 404 response (HTTPNotFound) if the solicitation specified does not exist
522 """
524 solicitation = request.lookup(request.matchdict["solicitation_id"], Solicitation)
525 context = get_context(request.repo.session, solicitation_id=solicitation.solicitation_id)
527 osr_prop_reviews = context.set_osr_comments()
529 # Currently, if none of the OSR proposal reviews are in a Blank state, success is returned
531 return Response(json_body=[osr.__json__() for osr in osr_prop_reviews], status_code=HTTPStatus.OK)
534@view_config(
535 route_name="set_osr_scientific_merit_metrics",
536 renderer="json",
537 permission="set_osr_scientific_merit_metrics",
538)
539def set_osr_scientific_merit_metrics(request: Request) -> Response:
540 """
541 For any Observatory Site Review proposals submitted and are in a Saved state, set the scientific merit metrics
542 URL: testdata/set_osr_scientific_merit_metrics/{solicitation_id}
544 :param request: PUT request
545 :return: 200 response (Success) Response with a list of JSON-formatted OSR proposal reviews
546 or 400 response (HTTPBadRequest) if expected solicitation_id is not given,
547 or reference solicitation is not test/demo
548 or 404 response (HTTPNotFound) if the solicitation specified does not exist
549 """
551 solicitation = request.lookup(request.matchdict["solicitation_id"], Solicitation)
552 context = get_context(request.repo.session, solicitation_id=solicitation.solicitation_id)
554 osr_prop_reviews = context.set_osr_scientific_merit_metrics()
556 # Currently, if none of the OSR proposal reviews are in a Saved state, success is returned
558 return Response(json_body=[osr.__json__() for osr in osr_prop_reviews], status_code=HTTPStatus.OK)
561@view_config(
562 route_name="finalize_osr_reviews",
563 renderer="json",
564 permission="finalize_osr_reviews",
565)
566def finalize_osr_reviews(request: Request) -> Response:
567 """
568 For any Observatory Site Review proposals submitted and are in a Saved state, and the scientific merit metric have been set, finalize the reviews
569 URL: testdata/finalize_osr_reviews/{solicitation_id}
571 :param request: PUT request
572 :return: 200 response (Success) Response with a list of JSON-formatted OSR proposal reviews
573 or 400 response (HTTPBadRequest) if expected solicitation_id is not given,
574 or reference solicitation is not test/demo
575 or 404 response (HTTPNotFound) if the solicitation specified does not exist
576 """
578 solicitation = request.lookup(request.matchdict["solicitation_id"], Solicitation)
579 context = get_context(request.repo.session, solicitation_id=solicitation.solicitation_id)
581 osr_prop_reviews = context.finalize_osr_reviews()
583 # Currently, if none of the OSR proposal reviews are in a Saved state, and the metric set to 0 or 1, success is returned
585 return Response(json_body=[osr.__json__() for osr in osr_prop_reviews], status_code=HTTPStatus.OK)
588@view_config(
589 route_name="run_all_osr_process_steps",
590 renderer="json",
591 permission="run_all_osr_process_steps",
592)
593def run_all_osr_process_steps(request: Request) -> Response:
594 """
595 For any Observatory Site Review proposals submitted and are in a Saved state, and the scientific merit metric have been set, finalize the reviews
596 URL: testdata/run_all_osr_process_steps/{solicitation_id}
598 :param request: PUT request
599 :return: 200 response (Success) Response with a list of JSON-formatted OSR proposal reviews
600 or 400 response (HTTPBadRequest) if expected solicitation_id is not given,
601 or reference solicitation is not test/demo
602 or 404 response (HTTPNotFound) if the solicitation specified does not exist
603 """
605 solicitation = request.lookup(request.matchdict["solicitation_id"], Solicitation)
606 context = get_context(request.repo.session, solicitation_id=solicitation.solicitation_id)
608 context.run_all_osr_process_steps()
610 # Currently, success is returned along with all OSRs associated with the solicitation
611 osr_prop_reviews = request.repo.osr_proposal_review_repo.list_by_solicitation_id(solicitation.solicitation_id)
613 return Response(json_body=[osr.__json__() for osr in osr_prop_reviews], status_code=HTTPStatus.OK)
616@view_config(
617 route_name="configure_tac_members",
618 renderer="json",
619 permission="allocate_publish",
620)
621def configure_tac_members(request: Request) -> Response:
622 """
623 For the specified solicitation, configure TAC members
624 URL: testdata/configure_tac_members/{{solicitation_id}}
626 :param request: POST request
627 :return: 200 response (Success) Response with a dictionary containing SRPs
628 or 400 response (HTTPBadRequest) if expected solicitation_id is not given,
629 or publication destination is not valid
630 or 404 response (HTTPNotFound) if no solicitation exists
631 """
632 solicitation = request.lookup(request.matchdict["solicitation_id"], Solicitation)
633 context = get_context(request.repo.session, solicitation_id=solicitation.solicitation_id)
635 try:
636 context.configure_tac_members()
637 except ValueError as e:
638 raise HTTPBadRequest(body=str(e))
640 tms = context.get_tac_members()
642 return Response(json_body=[tm.__json__() for tm in tms], status_code=HTTPStatus.CREATED)
645@view_config(
646 route_name="allocate_publish",
647 renderer="json",
648 permission="allocate_publish",
649)
650@view_config(
651 route_name="allocate_publish_with_pdg",
652 renderer="json",
653 permission="allocate_publish",
654)
655def allocate_publish(request: Request) -> Response:
656 """
657 For the specified solicitation, run all the steps of allocation and publish to the desired destination
658 URL: testdata/allocate_publish/{{solicitation_id}}/{{publication_destination}} or
659 testdata/allocate_publish/{{solicitation_id}}/{{publication_destination}}/{{proposal_disposition_group_id}}
661 :param request: POST request
662 :return: 200 response (Success) Response with a dictionary containing SRPs
663 or 400 response (HTTPBadRequest) if expected solicitation_id is not given,
664 or publication destination is not valid
665 or 404 response (HTTPNotFound) if no solicitation exists or there is no proposalDispositionGroup associated with
666 the optionally provided proposal_disposition_id
667 """
668 solicitation = request.lookup(request.matchdict["solicitation_id"], Solicitation)
669 context = get_context(request.repo.session, solicitation_id=solicitation.solicitation_id)
671 pdg = None
672 proposal_disposition_group_id = request.matchdict.get("proposal_disposition_group_id")
673 if proposal_disposition_group_id:
674 pdg = request.lookup(proposal_disposition_group_id, ProposalDispositionGroup)
676 # obtain the destination from the URL
677 dname: str = request.matchdict["publication_destination"]
679 # attempt to find it
680 try:
681 destination = PublicationDestination(dname.upper())
682 except ValueError:
683 raise HTTPBadRequest(body=f"Unable to publish allocation version to unknown destination {dname}")
685 try:
686 context.generate_and_publish(destination, pdg)
687 except ValueError as e:
688 raise HTTPBadRequest(body=str(e))
690 pdgs = [pdg] if proposal_disposition_group_id else context.get_proposal_disposition_groups()
692 return Response(json_body=[pdg.__json__() for pdg in pdgs], status_code=HTTPStatus.CREATED)
695@view_config(
696 route_name="generate_proposal_disposition_comments",
697 renderer="json",
698 permission="allocate_publish",
699)
700@view_config(
701 route_name="generate_proposal_disposition_comments_with_pdg",
702 renderer="json",
703 permission="allocate_publish",
704)
705def generate_proposal_disposition_comments(request: Request) -> Response:
706 """
707 For the specified solicitation, generate comments for each proposal disposition
708 URL: testdata/generate_proposal_disposition_comments/{{solicitation_id}} or
709 testdata/generate_proposal_disposition_comments/{{solicitation_id}}/{{proposal_disposition_group_id}}
711 :param request: PUT request
712 :return: 200 response (Success) Response with a dictionary containing PDGs
713 or 400 response (HTTPBadRequest) if expected solicitation_id is not given
714 or 404 response (HTTPNotFound) if no solicitation exists or there is no proposalDispositionGroup associated with
715 the optionally provided proposal_disposition_id
716 """
717 solicitation = request.lookup(request.matchdict["solicitation_id"], Solicitation)
718 context = get_context(request.repo.session, solicitation_id=solicitation.solicitation_id)
720 pdg = None
721 proposal_disposition_group_id = request.matchdict.get("proposal_disposition_group_id")
722 if proposal_disposition_group_id:
723 pdg = request.lookup(proposal_disposition_group_id, ProposalDispositionGroup)
725 try:
726 context.generate_proposal_disposition_comments(pdg)
727 except ValueError as e:
728 raise HTTPBadRequest(body=str(e))
730 pdgs = [pdg] if proposal_disposition_group_id else context.get_proposal_disposition_groups()
732 return Response(json_body=[pdg.__json__() for pdg in pdgs], status_code=HTTPStatus.CREATED)
735@view_config(
736 route_name="allocate_advance_to_end",
737 renderer="json",
738 permission="allocate_publish",
739)
740@view_config(
741 route_name="allocate_advance_to_end_with_pdg",
742 renderer="json",
743 permission="allocate_publish",
744)
745def allocate_advance_to_end(request: Request) -> Response:
746 """
747 For the specified solicitation, run all the steps of allocation and publish to the desired destination
748 URL: testdata/allocate_advance_to_end/{{solicitation_id}} or
749 testdata/allocate_advance_to_end/{{solicitation_id}}/{{proposal_disposition_group_id}}
751 :param request: PUT request where proposal_disposition_group_id parametr is optional
752 :return: 200 response (Success) Response with a dictionary containing SRPs
753 or 400 response (HTTPBadRequest) if expected solicitation_id is not given,
754 or 404 response (HTTPNotFound) if no solicitation exists or there is no proposalDispositionGroup associated with
755 the optionally provided proposal_disposition_id
756 """
757 solicitation = request.lookup(request.matchdict["solicitation_id"], Solicitation)
758 context = get_context(request.repo.session, solicitation_id=solicitation.solicitation_id)
760 pdg = None
761 proposal_disposition_group_id = request.matchdict.get("proposal_disposition_group_id")
762 if proposal_disposition_group_id:
763 pdg = request.lookup(proposal_disposition_group_id, ProposalDispositionGroup)
765 try:
766 context.advance_to_end_allocate_and_accept(pdg)
767 except ValueError as e:
768 raise HTTPBadRequest(body=str(e))
770 pdgs = context.get_proposal_disposition_groups()
772 return Response(json_body=[pdg.__json__() for pdg in pdgs], status_code=HTTPStatus.CREATED)
775@view_config(
776 route_name="closeout_advance_to_end",
777 renderer="json",
778 permission="update_disposition_letter",
779)
780@view_config(
781 route_name="closeout_advance_to_end_with_pdg",
782 renderer="json",
783 permission="update_disposition_letter",
784)
785def closeout_advance_to_end(request: Request) -> Response:
786 """
787 For the specified solicitation, generate and send disposition letters
788 URL: testdata/closeout_advance_to_end/{{solicitation_id}} or
789 testdata/closeout_advance_to_end/{{solicitation_id}}/{{proposal_disposition_group_id}}
791 :param request: PUT request where proposal_disposition_group_id parametr is optional
792 :return: 201 response (Success) Response with list of proposal disposition groups
793 or 400 response (HTTPBadRequest) if expected solicitation_id is not given,
794 or 404 response (HTTPNotFound) if no solicitation exists or there is no proposalDispositionGroup associated with
795 the optionally provided proposal_disposition_id
796 """
797 solicitation = request.lookup(request.matchdict["solicitation_id"], Solicitation)
798 context = get_context(request.repo.session, solicitation_id=solicitation.solicitation_id)
800 pdg = None
801 proposal_disposition_group_id = request.matchdict.get("proposal_disposition_group_id")
802 if proposal_disposition_group_id:
803 pdg = request.lookup(proposal_disposition_group_id, ProposalDispositionGroup)
805 try:
806 context.advance_to_end_closeout(pdg)
807 except ValueError as e:
808 raise HTTPBadRequest(body=str(e))
810 pdgs = context.get_proposal_disposition_groups()
812 return Response(json_body=[pdg.__json__() for pdg in pdgs], status_code=HTTPStatus.CREATED)
815@view_config(
816 route_name="advance_to_project_creation",
817 renderer="json",
818 permission="export_prototype_project",
819)
820@view_config(
821 route_name="advance_to_project_creation_with_pdg",
822 renderer="json",
823 permission="export_prototype_project",
824)
825def advance_to_project_creation(request: Request) -> Response:
826 """
827 For the specified solicitation, export projects
828 URL: testdata/advance_to_project_creation/{{solicitation_id}} or
829 testdata/advance_to_project_creation/{{solicitation_id}}/{{proposal_disposition_group_id}}
831 :param request: PUT request where proposal_disposition_group_id parametr is optional
832 :return: 201 response (HTTPCreated) Response with a list of proposal disposition groups
833 or 400 response (HTTPBadRequest) if expected solicitation_id is not given,
834 or 404 response (HTTPNotFound) if no solicitation exists or there is no proposalDispositionGroup associated with
835 the optionally provided proposal_disposition_id
836 """
837 solicitation = request.lookup(request.matchdict["solicitation_id"], Solicitation)
838 context = get_context(request.repo.session, solicitation_id=solicitation.solicitation_id)
840 pdg = None
841 proposal_disposition_group_id = request.matchdict.get("proposal_disposition_group_id")
842 if proposal_disposition_group_id:
843 pdg = request.lookup(proposal_disposition_group_id, ProposalDispositionGroup)
845 try:
846 context.advance_to_end_project_creation(pdg)
847 except ValueError as e:
848 raise HTTPBadRequest(body=str(e))
850 pdgs = context.get_proposal_disposition_groups()
852 return Response(json_body=[pdg.__json__() for pdg in pdgs], status_code=HTTPStatus.CREATED)
855@view_config(
856 route_name="cleanup",
857 renderer="json",
858 permission="cleanup",
859)
860def cleanup(request: Request) -> Response:
861 """
862 Deletes this solicitation and its children
863 URL: testdata/{solicitation_id}
865 :param request: DELETE request
866 :return: 200 response (Success) Response with json of the deleted solicitation
867 or 404 response (HTTPNotFound) if no solicitation is found
868 """
869 solicitation = request.lookup(request.matchdict["solicitation_id"], Solicitation)
870 context = get_context(request.repo.session, solicitation_id=solicitation.solicitation_id)
871 context.cleanup()
872 return Response(json_body=solicitation.__json__(), status_code=HTTPStatus.OK)