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

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 

18from datetime import datetime 

19 

20from pyramid.httpexceptions import HTTPBadRequest, HTTPNotFound, HTTPUnauthorized 

21from pyramid.request import Request 

22from pyramid.response import Response 

23from pyramid.view import view_config 

24 

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 

34 

35 

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 

43 

44 URL: available_time_model_version_list_by_solicitation_id_and_facility_id/{{solicitation_id}}/{{facility_id}} 

45 

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 """ 

53 

54 is_tta_member = "tta_member" in request.identity.roles 

55 

56 is_active_tac_member = is_active_tac(request.matchdict["solicitation_id"], request.identity.user_id, request.repo) 

57 

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 ) 

63 

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 ) 

71 

72 return Response(json_body=[atmv.__json__() for atmv in available_time_model_versions]) 

73 

74 

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 

82 

83 URL: available_time_model_version/{{atmv_id}} 

84 

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 """ 

91 

92 is_tta_member = "tta_member" in request.identity.roles 

93 

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") 

97 

98 # retrieve the ATMV 

99 atmv: AvailableTimeModelVersion = request.lookup(request.matchdict["atmv_id"], AvailableTimeModelVersion) 

100 

101 return Response(json_body=atmv.__json__()) 

102 

103 

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 

109 

110 URL: available_time_model_version_create/{{solicitation_id}} with JSON body like 

111 { 

112 facilityId: <int>, 

113 parentAvailableTimeModelVersionId: <int>, 

114 } 

115 

116 parentAvailableTimeModelVersionId is optional and is only used when cloning an existing Available Time Model 

117 Version. 

118 

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)) 

137 

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)) 

146 

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 ) 

160 

161 return Response(json_body=available_time_model_version.__json__()) 

162 

163 

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 

169 

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 

173 

174 

175 URL: available_time_model_version/{available_time_model_version_id}/update with JSON body like 

176 { 

177 comments: <str>, 

178 } 

179 

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 """ 

189 

190 params = request.json_body 

191 

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())) 

197 

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") 

201 

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 ) 

206 

207 # Support only the following to be updated 

208 atmv.comments = params["comments"] 

209 

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 ) 

214 

215 request.repo.available_time_model_version_repo.update(atmv) 

216 

217 return Response(json_body=atmv.__json__())