Coverage for application/image_review/external_review/routes.py: 15%

133 statements  

« prev     ^ index     » next       coverage.py v7.9.1, created at 2025-06-23 02:22 +0000

1""" 

2Endpoints related to external reviewers. 

3 

4/image-review/external-review [GET] 

5/image-review/external-review/annotation [POST, DELETE] 

6/image-review/external-review/reviewer-list [GET] 

7/image-review/external-review/reviewer [POST] 

8/image-review/external-review/reviewer/<name> [DELETE] 

9""" 

10 

11import json 

12import sys 

13 

14import tator 

15import requests 

16from flask import current_app, flash, redirect, render_template, request, session 

17 

18from . import external_review_bp 

19from application.vars.annosaurus import Annosaurus 

20from application.image_review.external_review.comment_processor import CommentProcessor 

21 

22 

23# displays comments in the external review db 

24@external_review_bp.get('') 

25def get_external_review(): 

26 comments = [] 

27 unread_comments = 0 

28 read_comments = 0 

29 total_comments = 0 

30 if 'tator_token' in session.keys(): 

31 try: 

32 api = tator.get_api( 

33 host=current_app.config.get('TATOR_URL'), 

34 token=session['tator_token'], 

35 ) 

36 except tator.openapi.tator_openapi.exceptions.ApiException: 

37 flash('Error connecting to Tator', 'danger') 

38 return redirect('/') 

39 try: 

40 print('Fetching external comments...', end='') 

41 sys.stdout.flush() 

42 with requests.get( 

43 url=f'{current_app.config.get("DARC_REVIEW_URL")}/stats', 

44 headers=current_app.config.get('DARC_REVIEW_HEADERS'), 

45 ) as stats_res: 

46 stats_json = stats_res.json() 

47 unread_comments = stats_json['unread_comments'] 

48 read_comments = stats_json['read_comments'] 

49 total_comments = stats_json['total_comments'] 

50 # get a list of comments from external review db 

51 if request.args.get('reviewer'): 

52 query = '' 

53 if request.args.get('read'): 

54 query += '?read=true' 

55 elif request.args.get('unread'): 

56 query += '?unread=true' 

57 comments_res = requests.get( 

58 url=f'{current_app.config.get("DARC_REVIEW_URL")}/comment/reviewer/{request.args.get("reviewer")}{query}', 

59 headers=current_app.config.get('DARC_REVIEW_HEADERS'), 

60 ) 

61 comments_json = comments_res.json() 

62 comments = comments_json['comments'] 

63 unread_comments = comments_json['unread_comments'] 

64 read_comments = comments_json['read_comments'] 

65 total_comments = comments_json['total_comments'] 

66 elif request.args.get('unread'): 

67 unread_comments_res = requests.get( 

68 url=f'{current_app.config.get("DARC_REVIEW_URL")}/comment/unread', 

69 headers=current_app.config.get('DARC_REVIEW_HEADERS'), 

70 ) 

71 comments = unread_comments_res.json() 

72 elif request.args.get('read'): 

73 read_comments_res = requests.get( 

74 url=f'{current_app.config.get("DARC_REVIEW_URL")}/comment/read', 

75 headers=current_app.config.get('DARC_REVIEW_HEADERS'), 

76 ) 

77 comments = read_comments_res.json() 

78 else: 

79 all_comments_res = requests.get( 

80 url=f'{current_app.config.get("DARC_REVIEW_URL")}/comment/all', 

81 headers=current_app.config.get('DARC_REVIEW_HEADERS'), 

82 ) 

83 comments = all_comments_res.json() 

84 print('fetched!') 

85 image_ref_res = requests.get(f'{current_app.config.get("DARC_REVIEW_URL")}/image-reference/quick') 

86 if image_ref_res.status_code != 200: 

87 raise requests.exceptions.ConnectionError 

88 image_refs = image_ref_res.json() 

89 except requests.exceptions.ConnectionError: 

90 _reviewers = [] 

91 print('\nERROR: unable to connect to external review server\n') 

92 comment_loader = CommentProcessor( 

93 comments=comments, 

94 annosaurus_url=current_app.config.get('ANNOSAURUS_URL'), 

95 vars_phylogeny_url=current_app.config.get('VARS_PHYLOGENY_URL'), 

96 tator_localizations_url=f'{current_app.config.get("TATOR_URL")}/rest/Localizations', 

97 ) 

98 if len(comment_loader.distilled_records) < 1: 

99 if request.args.get('unread'): 

100 return render_template('errors/404.html', err='unread'), 404 

101 return render_template('errors/404.html', err='comments'), 404 

102 data = { 

103 'annotations': comment_loader.distilled_records, 

104 'title': f'External Review {"(" + request.args.get("reviewer") + ")" if request.args.get("reviewer") else ""}', 

105 'tab_title': f'External Review {"(" + request.args.get("reviewer") + ")" if request.args.get("reviewer") else ""}', 

106 'concepts': session.get('vars_concepts', []), 

107 'reviewers': session.get('reviewers', []), 

108 'comments': comments, 

109 'missing_records': comment_loader.missing_records, 

110 'unread_comment_count': unread_comments, 

111 'read_comment_count': read_comments, 

112 'total_comment_count': total_comments, 

113 'image_refs': image_refs, 

114 } 

115 return render_template('/image_review/image-review.html', data=data) 

116 

117 

118# adds an annotation for review/updates the reviewer for an annotation 

119@external_review_bp.post('/annotation') 

120def add_external_review(): 

121 def add_vars_or_tator_comment(status_code): 

122 if not request.values.get('all_localizations'): # VARS annotation, update VARS comment 

123 annosaurus = Annosaurus(current_app.config.get('ANNOSAURUS_URL')) 

124 annosaurus.update_annotation_comment( 

125 observation_uuid=request.values.get('observation_uuid'), 

126 reviewers=json.loads(request.values.get('reviewers')), 

127 client_secret=current_app.config.get('ANNOSAURUS_CLIENT_SECRET') 

128 ) 

129 else: # Tator localization, update Tator notes 

130 api = tator.get_api( 

131 host=current_app.config.get('TATOR_URL'), 

132 token=session.get('tator_token'), 

133 ) 

134 current_notes = api.get_localization_by_elemental_id( 

135 version=json.loads(request.values.get('all_localizations'))[0].get('version', 45), 

136 elemental_id=request.values.get('observation_uuid'), 

137 ).attributes.get('Notes', '').split('|') 

138 current_notes = [note for note in current_notes if 'send to' not in note.lower()] # get rid of 'send to expert' notes 

139 current_notes = [note for note in current_notes if 'added for review' not in note.lower()] # get rid of old 'added for review' notes 

140 current_notes = '|'.join(current_notes) 

141 new_notes = f'{current_notes + "|" if current_notes else ""}Added for review: {", ".join(json.loads(request.values.get("reviewers")))}' 

142 api.update_localization_by_elemental_id( 

143 version=json.loads(request.values.get('all_localizations'))[0].get('version', 45), 

144 elemental_id=request.values.get('observation_uuid'), 

145 localization_update=tator.models.LocalizationUpdate( 

146 attributes={'Notes': new_notes}, 

147 ) 

148 ) 

149 return {}, status_code 

150 data = { 

151 'uuid': request.values.get('observation_uuid'), 

152 'all_localizations': request.values.get('all_localizations'), 

153 'section_id': request.values.get('section_id'), 

154 'sequence': request.values.get('sequence'), 

155 'timestamp': request.values.get('timestamp'), 

156 'reviewers': request.values.get('reviewers'), 

157 'image_url': request.values.get('image_url'), 

158 'video_url': request.values.get('video_url'), 

159 'annotator': request.values.get('annotator'), 

160 'depth': request.values.get('depth'), 

161 'lat': request.values.get('lat'), 

162 'long': request.values.get('long'), 

163 'temperature': request.values.get('temperature'), 

164 'oxygen_ml_l': request.values.get('oxygen_ml_l'), 

165 } 

166 with requests.post( 

167 url=f'{current_app.config.get("DARC_REVIEW_URL")}/comment', 

168 headers=current_app.config.get('DARC_REVIEW_HEADERS'), 

169 data=data, 

170 ) as post_comment_res: 

171 if post_comment_res.status_code == 409: # comment already exists in the db, update record 

172 put_comment_res = requests.put( 

173 url=f'{current_app.config.get("DARC_REVIEW_URL")}/comment/reviewers/{data["uuid"]}', 

174 headers=current_app.config.get('DARC_REVIEW_HEADERS'), 

175 data=data, 

176 ) 

177 if put_comment_res.status_code == 200: 

178 return add_vars_or_tator_comment(200) 

179 elif post_comment_res.status_code == 201: # comment added to db, update VARS "comment" field 

180 return add_vars_or_tator_comment(201) 

181 return {}, 500 

182 

183 

184# deletes an item from the external review db 

185@external_review_bp.delete('/annotation') 

186def delete_external_review(): 

187 delete_comment_res = requests.delete( 

188 url=f'{current_app.config.get("DARC_REVIEW_URL")}/comment/{request.values.get("uuid")}', 

189 headers=current_app.config.get('DARC_REVIEW_HEADERS'), 

190 ) 

191 if delete_comment_res.status_code == 200: 

192 if request.values.get('tator') and request.values.get('tator') == 'true': # tator localization 

193 api = tator.get_api( 

194 host=current_app.config.get('TATOR_URL'), 

195 token=session.get('tator_token'), 

196 ) 

197 current_notes = api.get_localization_by_elemental_id( 

198 version=request.values.get('tator_version'), 

199 elemental_id=request.values.get('uuid'), 

200 ).attributes.get('Notes', '').split('|') 

201 current_notes = [note for note in current_notes if 'send to' not in note.lower()] # get rid of 'send to expert' notes 

202 current_notes = [note for note in current_notes if 'added for review' not in note.lower()] # get rid of old 'added for review' notes 

203 api.update_localization_by_elemental_id( 

204 version=request.values.get('tator_version'), 

205 elemental_id=request.values.get('uuid'), 

206 localization_update=tator.models.LocalizationUpdate( 

207 attributes={'Notes': '|'.join(current_notes)}, 

208 ) 

209 ) 

210 else: # VARS annotation 

211 annosaurus = Annosaurus(current_app.config.get('ANNOSAURUS_URL')) 

212 annosaurus.update_annotation_comment( 

213 observation_uuid=request.values.get('uuid'), 

214 reviewers=[], 

215 client_secret=current_app.config.get('ANNOSAURUS_CLIENT_SECRET') 

216 ) 

217 return {}, 200 

218 return {}, 500 

219 

220 

221# displays information about all the reviewers in the hurl db 

222@external_review_bp.get('/reviewer-list') 

223def reviewers(): 

224 return render_template('image_review/external-reviewers.html', reviewers=session.get('reviewers')) 

225 

226 

227# create or update a reviewer's information 

228@external_review_bp.post('/reviewer') 

229def update_reviewer_info(): 

230 success = False 

231 name = request.values.get('ogReviewerName') or 'nobody' 

232 data = { 

233 'new_name': request.values.get('editReviewerName'), 

234 'phylum': request.values.get('editPhylum'), 

235 'focus': request.values.get('editFocus'), 

236 'organization': request.values.get('editOrganization'), 

237 'email': request.values.get('editEmail') 

238 } 

239 patch_reviewer_res = requests.patch( 

240 url=f'{current_app.config.get("DARC_REVIEW_URL")}/reviewer/{name}', 

241 headers=current_app.config.get('DARC_REVIEW_HEADERS'), 

242 data=data, 

243 ) 

244 if patch_reviewer_res.status_code == 404: 

245 data['name'] = data['new_name'] 

246 post_reviewer_res = requests.post( 

247 url=f'{current_app.config.get("DARC_REVIEW_URL")}/reviewer', 

248 headers=current_app.config.get('DARC_REVIEW_HEADERS'), 

249 data=data, 

250 ) 

251 if post_reviewer_res.status_code == 201: 

252 success = True 

253 flash('Successfully added reviewer', 'success') 

254 else: 

255 flash(post_reviewer_res.json(), 'danger') 

256 elif patch_reviewer_res.status_code == 200: 

257 success = True 

258 flash('Successfully updated reviewer', 'success') 

259 else: 

260 flash(patch_reviewer_res.json(), 'danger') 

261 if success: 

262 with requests.get( 

263 url=f'{current_app.config.get("DARC_REVIEW_URL")}/reviewer/all', 

264 headers=current_app.config.get('DARC_REVIEW_HEADERS'), 

265 ) as all_reviewers_res: 

266 session['reviewers'] = all_reviewers_res.json() 

267 return redirect('/image-review/external-review/reviewer-list') 

268 

269 

270# delete a reviewer 

271@external_review_bp.delete('/reviewer/<name>') 

272def delete_reviewer(name): 

273 delete_reviewer_res = requests.delete( 

274 url=f'{current_app.config.get("DARC_REVIEW_URL")}/reviewer/{name}', 

275 headers=current_app.config.get('DARC_REVIEW_HEADERS'), 

276 ) 

277 if delete_reviewer_res.status_code == 200: 

278 flash('Successfully deleted reviewer', 'success') 

279 with requests.get( 

280 url=f'{current_app.config.get("DARC_REVIEW_URL")}/reviewer/all', 

281 headers=current_app.config.get('DARC_REVIEW_HEADERS'), 

282 ) as res: 

283 session['reviewers'] = res.json() 

284 else: 

285 flash('Error deleting reviewer', 'danger') 

286 return {}, delete_reviewer_res.status_code