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
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-23 02:22 +0000
1"""
2Endpoints related to external reviewers.
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"""
11import json
12import sys
14import tator
15import requests
16from flask import current_app, flash, redirect, render_template, request, session
18from . import external_review_bp
19from application.vars.annosaurus import Annosaurus
20from application.image_review.external_review.comment_processor import CommentProcessor
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)
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
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
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'))
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')
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