Coverage for middle_layer/allocate/application_layer/rest_api/views/available_time_model_version.py: 83.61%
61 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/>.
18from datetime import datetime
20from pyramid.httpexceptions import HTTPBadRequest, HTTPNotFound, HTTPUnauthorized
21from pyramid.request import Request
22from pyramid.response import Response
23from pyramid.view import view_config
25from allocate.domain_layer.entities.available_time_model_version import AvailableTimeModelVersion
26from allocate.domain_layer.services.create_available_time_model_version_service import (
27 clone_available_time_model_version,
28 create_available_time_model_version,
29)
30from common.application_layer.rest_api import make_expected_params_message
31from common.application_layer.services.permissions_service import is_active_tac
32from solicit.domain_layer.entities.capability import Facility
33from solicit.domain_layer.entities.solicitation import Solicitation
36@view_config(
37 route_name="available_time_model_version_list_by_solicitation_id_and_facility_id",
38 renderer="json",
39 permission="available_time_model_version_list_by_solicitation_id_and_facility_id",
40)
41def available_time_model_version_list_by_solicitation_id_and_facility_id(request: Request) -> Response:
42 """List all AvailableTimeModelVersions for a given Solicitation and Facility
44 URL: available_time_model_version_list_by_solicitation_id_and_facility_id/{{solicitation_id}}/{{facility_id}}
46 :param request: GET request
47 :return: Response with JSON-formatted list of all Available Time Model Versions on the given Solicitation and
48 Facility
49 or 404 response (HTTPNotFound) if no Solicitation or Facility exists with the given ids,
50 or 400 response (HTTPBadRequest) if solicitation_id or facility_id is not an integer,
51 or 401 response (HTTPUnauthorized) if the requesting user is not a TTA member or active TAC member
52 """
54 is_tta_member = "tta_member" in request.identity.roles
56 is_active_tac_member = is_active_tac(request.matchdict["solicitation_id"], request.identity.user_id, request.repo)
58 # User making update request must be TTA member or active TAC member
59 if not (is_tta_member or is_active_tac_member):
60 raise HTTPUnauthorized(
61 body=f"User {request.identity.user_id} is not a TTA Member or Active Member of a TAC responsible for this proposal"
62 )
64 # Note: if the requesting user is an active TAC member for the solicitation and facility, they can see all
65 # associated ATMVs.
66 available_time_model_versions: list[AvailableTimeModelVersion] = (
67 request.repo.available_time_model_version_repo.list_by_solicitation_id_and_facility_id(
68 request.matchdict["solicitation_id"], request.matchdict["facility_id"]
69 )
70 )
72 return Response(json_body=[atmv.__json__() for atmv in available_time_model_versions])
75@view_config(
76 route_name="available_time_model_version_by_id",
77 renderer="json",
78 permission="available_time_model_version_by_id",
79)
80def available_time_model_version_by_id(request: Request) -> Response:
81 """Return AvailableTimeModelVersion for a given ATMV ID
83 URL: available_time_model_version/{{atmv_id}}
85 :param request: GET request
86 :return: Response with JSON-formatted Available Time Model Version with the given Id
87 or 404 response (HTTPNotFound) if no ATMV exists with the given id,
88 or 400 response (HTTPBadRequest) if atmv_id is not an integer,
89 or 401 response (HTTPUnauthorized) if the requesting user is not a TTA member or active TAC member
90 """
92 is_tta_member = "tta_member" in request.identity.roles
94 # User making update request must be TTA member
95 if not is_tta_member:
96 raise HTTPUnauthorized(body=f"User {request.identity.user_id} is not a TTA Member")
98 # retrieve the ATMV
99 atmv: AvailableTimeModelVersion = request.lookup(request.matchdict["atmv_id"], AvailableTimeModelVersion)
101 return Response(json_body=atmv.__json__())
104@view_config(
105 route_name="available_time_model_version_create", renderer="json", permission="available_time_model_version_create"
106)
107def available_time_model_version_create(request: Request) -> Response:
108 """Create Available Time Model Version for a given Solicitation and Facility
110 URL: available_time_model_version_create/{{solicitation_id}} with JSON body like
111 {
112 facilityId: <int>,
113 parentAvailableTimeModelVersionId: <int>,
114 }
116 parentAvailableTimeModelVersionId is optional and is only used when cloning an existing Available Time Model
117 Version.
119 :param request: PUT Request
120 :return: Response with the list of Available Time Model Versions json.
121 or 404 response (HTTPNotFound) if no Solicitation or Facility exists with the given ids,
122 or 400 response (HTTPBadRequest) if solicitation_id or facility_id is not an integer,
123 or 401 response (HTTPUnauthorized) if the requesting user does not have permission
124 to CREATE an Allocation Version
125 """
126 params = request.json_body
127 if "facilityId" not in params:
128 raise HTTPBadRequest(body=make_expected_params_message(["facilityId"], params.keys()))
129 try:
130 # Verify that a Solicitation exists with ID solicitationId
131 sol = request.lookup(request.matchdict["solicitation_id"], Solicitation)
132 fac = request.lookup(params["facilityId"], Facility)
133 except NameError as e:
134 raise HTTPBadRequest(body=str(e))
135 except ValueError as e:
136 raise HTTPNotFound(body=str(e))
138 if "parentAvailableTimeModelVersionId" in params:
139 # we are cloning now, but first we need to make sure we have a real ATMV
140 try:
141 atmv = request.lookup(params["parentAvailableTimeModelVersionId"], AvailableTimeModelVersion)
142 except NameError as e:
143 raise HTTPBadRequest(body=str(e))
144 except ValueError as e:
145 raise HTTPNotFound(body=str(e))
147 available_time_model_version: AvailableTimeModelVersion = clone_available_time_model_version(
148 atmv, request.identity.user_id, request.identity.first_name, request.identity.last_name, request.repo
149 )
150 else:
151 # call a service to create the necessary Available Time Model Version
152 available_time_model_version: AvailableTimeModelVersion = create_available_time_model_version(
153 sol,
154 request.identity.user_id,
155 request.identity.first_name,
156 request.identity.last_name,
157 fac,
158 request.repo,
159 )
161 return Response(json_body=available_time_model_version.__json__())
164@view_config(
165 route_name="available_time_model_version_update", renderer="json", permission="available_time_model_version_update"
166)
167def available_time_model_version_update(request: Request) -> Response:
168 """Update a specific Allocation Version
170 Note - comments can be updated by TTA members regardless of whether the Available Time Model Version is read-only or not.
171 - any additional parameters supplied in the JSON body beyond comments are ignored
172 - any update to the comments on the ATM Version will also update its version history
175 URL: available_time_model_version/{available_time_model_version_id}/update with JSON body like
176 {
177 comments: <str>,
178 }
180 :param request: PUT Request
181 :return: Response with the Allocation Version json.
182 or 404 response (HTTPNotFound) if no AvailableTimeModelVersion exists with the given id,
183 or 400 response (HTTPBadRequest) if all parameters not passed
184 or comments is empty,
185 or available_time_model_version_id, is not an integer,
186 or 401 response (HTTPUnauthorized) if the requesting user does not have permission
187 to UPDATE an Allocation Version
188 """
190 params = request.json_body
192 expected_params = [
193 "comments",
194 ]
195 if not all([expected in params for expected in expected_params]):
196 raise HTTPBadRequest(body=make_expected_params_message(expected_params, params.keys()))
198 # Comments cannot be empty
199 if not params["comments"]:
200 raise HTTPBadRequest(body=f"Comments cannot be empty when updating an Available Time Model Version")
202 # Verify that Available Time Model Version exists with ID
203 atmv: AvailableTimeModelVersion = request.lookup(
204 request.matchdict["available_time_model_version_id"], AvailableTimeModelVersion
205 )
207 # Support only the following to be updated
208 atmv.comments = params["comments"]
210 atmv.version_history += (
211 f"\n{datetime.now()} - Comments updated by user "
212 f"{request.identity.user_id}, {request.identity.first_name} {request.identity.last_name}"
213 )
215 request.repo.available_time_model_version_repo.update(atmv)
217 return Response(json_body=atmv.__json__())