Coverage for toardb/stationmeta/crud.py: 82%

464 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-11-03 20:32 +0000

1# SPDX-FileCopyrightText: 2021 Forschungszentrum Jülich GmbH 

2# SPDX-License-Identifier: MIT 

3 

4""" 

5Create, Read, Update, Delete functionality 

6 

7""" 

8 

9import json 

10import requests 

11 

12from typing import List 

13from pydantic import ValidationError 

14from geoalchemy2.shape import to_shape 

15from geoalchemy2.types import Geometry 

16from geoalchemy2.elements import WKBElement, WKTElement 

17from sqlalchemy import cast, Text, insert, update, delete, select, and_, String, text 

18from sqlalchemy.orm import Session 

19from sqlalchemy.engine import Engine 

20from sqlalchemy.dialects.postgresql import JSONB, ARRAY 

21from sqlalchemy.inspection import inspect 

22from fastapi import File, UploadFile 

23from fastapi.responses import JSONResponse 

24 

25from . import models 

26from .models import StationmetaCore, StationmetaGlobal, StationmetaChangelog, \ 

27 stationmeta_core_stationmeta_roles_table, stationmeta_core_stationmeta_annotations_table 

28from .schemas import get_coordinates_from_geom, get_geom_from_coordinates, get_abbreviation_from_code_description, get_code_description_from_abbreviation, \ 

29 Stationmeta, StationmetaCreate, StationmetaPatch, StationmetaGlobalFields, Coordinates, get_coordinates_from_string 

30from toardb.utils.utils import get_value_from_str, get_str_from_value, get_hr_value, create_filter 

31from toardb.utils.settings import base_geopeas_url 

32import toardb 

33 

34 

35def get_stationmeta_core(db: Session, station_code: str): 

36 db_object = db.query(models.StationmetaCore).filter(models.StationmetaCore.codes.any(station_code.strip())).first() 

37 # there is a mismatch with coordinates and additional_metadata 

38 if db_object: 

39 if isinstance(db_object.coordinates, (WKBElement, WKTElement)): 

40 db_object.coordinates = get_coordinates_from_geom(db_object.coordinates) 

41 db_object.additional_metadata = str(db_object.additional_metadata).replace("'",'"') 

42 return db_object 

43 

44 

45def get_stationmeta(db: Session, station_code: str, fields: str = None): 

46 if fields: 

47 global_params = {column.name for column in inspect(StationmetaGlobal).c if column.name not in ['id','station_id']} 

48 lconstr_glob = any(field in global_params for field in fields.split(',')) 

49 if fields.find("globalmeta") >= 0: 

50 fields = fields.replace("globalmeta","") 

51 fields += ','.join(global_params) 

52 lconstr_glob = True 

53 fields2 = [] 

54 for field in fields.split(','): 

55 if field == "id": 

56 fields2.append("stationmeta_core.id") 

57 elif field == "coordinates": 

58 fields2.append("ST_AsText(coordinates)") 

59 else: 

60 fields2.append(field) 

61 tmp_object = db.query(*map(text,fields2)). \ 

62 select_from(StationmetaCore).filter(models.StationmetaCore.codes.any(station_code.strip())). \ 

63 join(StationmetaGlobal).filter(models.StationmetaCore.id == models.StationmetaGlobal.station_id). \ 

64 first() 

65 if tmp_object: 

66 db_object = dict(zip(fields.split(','),tmp_object)) 

67 else: 

68 return tmp_object 

69 # there is a mismatch with coordinates and additional_metadata 

70 if lconstr_glob: 

71 db_object_cp = db_object.copy() 

72 global_atts = {} 

73 for key in db_object: 

74 if key in global_params: 

75 global_atts[key] = db_object[key] 

76 del db_object_cp[key] 

77 db_object = db_object_cp 

78 db_object["globalmeta"] = StationmetaGlobalFields(**global_atts) 

79 try: 

80 db_object['coordinates'] = get_coordinates_from_string(db_object['coordinates']) 

81 except: 

82 pass 

83 try: 

84 db_object['additional_metadata'] = str(db_object['additional_metadata']).replace("'",'"') 

85 except: 

86 pass 

87 else: 

88 db_object = db.query(models.StationmetaCore).filter(models.StationmetaCore.codes.any(station_code.strip())).first() 

89 # there is a mismatch with coordinates and additional_metadata 

90 if db_object: 

91 try: 

92 if isinstance(db_object.coordinates, (WKBElement, WKTElement)): 

93 db_object.coordinates = get_coordinates_from_geom(db_object.coordinates) 

94 except: 

95 pass 

96 try: 

97 db_object.additional_metadata = str(db_object.additional_metadata).replace("'",'"') 

98 except: 

99 pass 

100 return db_object 

101 

102 

103def get_stationmeta_by_id(db: Session, station_id: int): 

104 db_object = db.query(models.StationmetaCore).filter(models.StationmetaCore.id == station_id).first() 

105 # there is a mismatch with coordinates and additional_metadata 

106 if db_object: 

107 if isinstance(db_object.coordinates, (WKBElement, WKTElement)): 

108 db_object.coordinates = get_coordinates_from_geom(db_object.coordinates) 

109 db_object.additional_metadata = str(db_object.additional_metadata).replace("'",'"') 

110 return db_object 

111 

112 

113def station_id_exists(db: Session, station_id: int): 

114 db_object = db.query(models.StationmetaCore).filter(models.StationmetaCore.id == station_id).first() 

115 return (db_object != None) 

116 

117 

118def get_all_stationmeta(db: Session, path_params, query_params): 

119 try: 

120 limit, offset, fields, format, filters = create_filter(query_params, "stationmeta") 

121 s_c_filter = filters["s_c_filter"] 

122 s_g_filter = filters["s_g_filter"] 

123 except (KeyError, ValueError) as e: 

124 status_code=400 

125 return JSONResponse(status_code=status_code, content=str(e)) 

126 global_params = {column.name for column in inspect(StationmetaGlobal).c if column.name not in ['id','station_id']} 

127 

128 if fields: 

129 fields2 = [] 

130 for field in fields.split(','): 

131 if field == "id": 

132 fields2.append("stationmeta_core.id") 

133 elif field == "coordinates": 

134 fields2.append("ST_AsText(coordinates)") 

135 else: 

136 fields2.append(field) 

137 db_objects_l = db.query(*map(text,fields2)).select_from(StationmetaCore,StationmetaGlobal).filter(text(s_c_filter)). \ 

138 join(StationmetaGlobal).filter(and_(StationmetaCore.id == StationmetaGlobal.station_id, text(s_g_filter))). \ 

139 distinct().order_by(models.StationmetaCore.id). \ 

140 limit(limit).offset(offset) 

141 db_objects = [] 

142 for db_object_immut in db_objects_l: 

143 db_object = dict(zip(fields.split(','),db_object_immut)) 

144 # there is a mismatch with coordinates and additional_metadata 

145 try: 

146 db_object['coordinates'] = get_coordinates_from_string(db_object['coordinates']) 

147 except: 

148 pass 

149 try: 

150 db_object['additional_metadata'] = str(db_object['additional_metadata']).replace("'",'"') 

151 except: 

152 pass 

153 db_object_cp = db_object.copy() 

154 global_atts = {} 

155 for key in db_object: 

156 if key in global_params: 

157 global_atts[key] = db_object[key] 

158 del db_object_cp[key] 

159 db_object = db_object_cp 

160 if global_atts: 

161 db_object["globalmeta"] = StationmetaGlobalFields(**global_atts) 

162 db_objects.append(db_object) 

163 else: 

164 db_objects = db.query(models.StationmetaCore).filter(text(s_c_filter)). \ 

165 join(StationmetaGlobal).filter(and_(StationmetaCore.id == StationmetaGlobal.station_id, text(s_g_filter))). \ 

166 order_by(models.StationmetaCore.id). \ 

167 limit(limit).offset(offset).all() 

168 

169 for db_object in db_objects: 

170 # there is a mismatch with coordinates and additional_metadata 

171 if isinstance(db_object.coordinates, (WKBElement, WKTElement)): 

172 db_object.coordinates = get_coordinates_from_geom(db_object.coordinates) 

173 db_object.additional_metadata = str(db_object.additional_metadata).replace("'",'"') 

174 

175 if limit: 

176 return db_objects[:limit] 

177 else: 

178 return db_objects 

179 

180 

181# is this internal, or should this also go to public REST api? 

182# - get_stationmeta_roles 

183# - get_stationmeta_changelog 

184# - get_unique_stationmeta_role 

185# - get_stationmeta_role_by_id 

186# - get_unique_stationmeta_annotation 

187def get_stationmeta_roles(db: Session, station_id: int): 

188 return db.execute(select([stationmeta_core_stationmeta_roles_table]).where(stationmeta_core_stationmeta_roles_table.c.station_id == station_id)) 

189 

190 

191def get_stationmeta_changelog(db: Session, station_id: int): 

192 return db.query(models.StationmetaChangelog).filter(models.StationmetaChangelog.station_id == station_id).order_by(models.StationmetaChangelog.datetime).all() 

193 

194 

195def get_unique_stationmeta_role(db: Session, role: int, contact_id: int, status: int): 

196 db_object = db.query(models.StationmetaRole).filter(models.StationmetaRole.role == role) \ 

197 .filter(models.StationmetaRole.contact_id == contact_id) \ 

198 .filter(models.StationmetaRole.status == status) \ 

199 .first() 

200 return db_object 

201 

202def get_stationmeta_role_by_id(db: Session, role_id): 

203 return db.query(models.StationmetaRole).filter(models.StationmetaRole.id == role_id).first() 

204 

205 

206def get_unique_stationmeta_annotation(db: Session, text: str, contributor_id: int): 

207 db_object = db.query(models.StationmetaAnnotation).filter(models.StationmetaAnnotation.text == text) \ 

208 .filter(models.StationmetaAnnotation.contributor_id == contributor_id) \ 

209 .first() 

210 return db_object 

211 

212 

213# is this internal, or should this also go to public REST api? 

214def get_stationmeta_annotations(db: Session, station_id: int): 

215 return db.execute(select([stationmeta_core_stationmeta_annotations_table]).where(stationmeta_core_stationmeta_annotations_table.c.station_id == station_id)) 

216 

217 

218def get_stationmeta_global(db: Session, station_id: int): 

219 db_object = db.query(models.StationmetaGlobal).filter(models.StationmetaGlobal.station_id == station_id) \ 

220 .first() 

221 return db_object 

222 

223 

224def determine_stationmeta_global(db, tmp_coordinates, country): 

225 # GEO-PEAS expects country to be a two-letter-string 

226 country = get_str_from_value(toardb.toardb.CN_vocabulary,country) 

227 db_globalservices = db.query(models.StationmetaGlobalService).all() 

228 globalmeta_dict = {} 

229 for db_service in db_globalservices: 

230 request=db_service.service_url.format(base_url=f'{base_geopeas_url}',lat=tmp_coordinates["lat"],lon=tmp_coordinates["lng"], 

231 country=country) 

232 try: 

233 result = requests.get(request).json() 

234 # htap_region_tier1 (also major_road) does not return "agg_function"! 

235 try: 

236 key = result["properties"]["agg_function"] 

237 except: 

238 if db_service.variable_name != 'distance_to_major_road_year2020': 

239 key = "value" 

240 else: 

241 key = 'distance' 

242 try: 

243 value = result["properties"][key] 

244 except: 

245 value = result[key] 

246 except: 

247 continue 

248 globalmeta_dict[db_service.variable_name] = value 

249 return globalmeta_dict 

250 

251 

252def create_stationmeta(db: Session, engine: Engine, stationmeta: StationmetaCreate, 

253 author_id: int, force: bool): 

254 stationmeta_dict = stationmeta.dict() 

255 roles_data = stationmeta_dict.pop('roles', None) 

256 annotations_data = stationmeta_dict.pop('annotations', None) 

257 aux_images_data = stationmeta_dict.pop('aux_images', None) 

258 aux_docs_data = stationmeta_dict.pop('aux_docs', None) 

259 aux_urls_data = stationmeta_dict.pop('aux_urls', None) 

260 db_stationmeta = models.StationmetaCore(**stationmeta_dict) 

261 # there's a mismatch with coordinates --> how to automatically switch back and forth?! 

262 tmp_coordinates = db_stationmeta.coordinates 

263 db_stationmeta.coordinates = get_geom_from_coordinates(Coordinates(**db_stationmeta.coordinates)) 

264 # there's also a mismatch with additional_metadata --> BUT: this should not be switched back! 

265 # in upload command, we have now: "additional_metadata": "{}" 

266 # but return from this method gives: "additional_metadata": {} 

267 # ==> there is a mismatch between model(JSONB) and schema(JSON) 

268 db_stationmeta.additional_metadata = json.loads(str(db_stationmeta.additional_metadata).replace("'",'"')) 

269 num_records = 0 

270 if not force: 

271 with engine.begin() as connection: 

272 radius = 10 

273 db_cmd = f"select id,codes,name,coordinates,state,country from stationmeta_core where ST_DistanceSphere(stationmeta_core.coordinates, '{db_stationmeta.coordinates}') < {radius}" 

274 records = connection.execute(db_cmd) 

275 num_records = records.rowcount 

276 if num_records > 0: 

277 status_code=444 

278 if num_records == 1: 

279 message={"message": f'already in database --> patch record (add station code)'} 

280 else: 

281 message = {"message": 'more than one station falls within the given radius!\n' + \ 

282 'choose which station record to patch (add station code)'} 

283 station_ids = [] 

284 station_records = [] 

285 for record in records: 

286 station_ids.append(record[0]) 

287 station_records.append((*record[1:2],str(get_coordinates_from_geom(WKBElement(record[3]))),*record[4:])) 

288 message["station_ids"] = station_ids 

289 message["station_records"] = station_records 

290 engine.dispose() 

291 if num_records == 0: 

292 status_code=200 

293 db_stationmeta.country = get_value_from_str(toardb.toardb.CN_vocabulary,db_stationmeta.country) 

294 db_stationmeta.timezone = get_value_from_str(toardb.toardb.TZ_vocabulary,db_stationmeta.timezone) 

295 db_stationmeta.type = get_value_from_str(toardb.toardb.ST_vocabulary,db_stationmeta.type) 

296 db_stationmeta.type_of_area = get_value_from_str(toardb.toardb.TA_vocabulary,db_stationmeta.type_of_area) 

297 db_stationmeta.coordinate_validation_status = get_value_from_str(toardb.toardb.CV_vocabulary,db_stationmeta.coordinate_validation_status) 

298 db.add(db_stationmeta) 

299 result = db.commit() 

300 db.refresh(db_stationmeta) 

301 # get stationmeta_core_id 

302 stationmeta_core_id = db_stationmeta.id 

303 message={"message":f'new station: {db_stationmeta.codes},{db_stationmeta.name},{tmp_coordinates}', 

304 "station_id": stationmeta_core_id} 

305 # store roles and update association table 

306 if roles_data: 

307 for r in roles_data: 

308 db_role = models.StationmetaRole(**r) 

309 db_role.role = get_value_from_str(toardb.toardb.RC_vocabulary,db_role.role) 

310 db_role.status = get_value_from_str(toardb.toardb.RS_vocabulary,db_role.status) 

311 # check whether role is already present in database 

312 db_object = get_unique_stationmeta_role(db, db_role.role, db_role.contact_id, db_role.status) 

313 if db_object: 

314 role_id = db_object.id 

315 else: 

316 # Something is going wrong here! 

317 # Is the model StationmetaRole correctly defined?! 

318 del db_role.contact 

319 db.add(db_role) 

320 db.commit() 

321 db.refresh(db_role) 

322 role_id = db_role.id 

323 db.execute(insert(stationmeta_core_stationmeta_roles_table).values(station_id=stationmeta_core_id, role_id=role_id)) 

324 db.commit() 

325 # store annotations and update association table 

326 if annotations_data: 

327 for a in annotations_data: 

328 db_annotation = models.StationmetaAnnotation(**a) 

329 db_annotation.kind = get_value_from_str(toardb.toardb.AK_vocabulary,db_annotation.kind) 

330 # check whether annotation is already present in database 

331 db_object = get_unique_stationmeta_annotation(db, db_annotation.text, db_annotation.contributor_id) 

332 if db_object: 

333 annotation_id = db_object.id 

334 else: 

335 db.add(db_annotation) 

336 db.commit() 

337 db.refresh(db_annotation) 

338 annotation_id = db_annotation.id 

339 db.execute(insert(stationmeta_core_stationmeta_annotations_table).values(station_id=stationmeta_core_id, annotation_id=annotation_id)) 

340 db.commit() 

341 # store aux_images 

342 if aux_images_data: 

343 for i in aux_images_data: 

344 db_aux_image = models.StationmetaAuxImage(**i) 

345 db_aux_image.station_id = stationmeta_core_id 

346 db.add(db_aux_image) 

347 db.commit() 

348 db.refresh(db_aux_image) 

349 # store aux_docs 

350 if aux_docs_data: 

351 for d in aux_docs_data: 

352 db_aux_doc = models.StationmetaAuxDoc(**d) 

353 db_aux_doc.station_id = stationmeta_core_id 

354 db.add(db_aux_doc) 

355 db.commit() 

356 db.refresh(db_aux_doc) 

357 # store aux_urls 

358 if aux_urls_data: 

359 for u in aux_urls_data: 

360 db_aux_url = models.StationmetaAuxUrl(**u) 

361 db_aux_url.station_id = stationmeta_core_id 

362 db.add(db_aux_url) 

363 db.commit() 

364 db.refresh(db_aux_url) 

365 # determine stationmeta_global 

366 globalmeta_dict = determine_stationmeta_global(db, tmp_coordinates,db_stationmeta.country) 

367 db_globalmeta = models.StationmetaGlobal(**globalmeta_dict) 

368 db_globalmeta.station_id = stationmeta_core_id 

369 db.add(db_globalmeta) 

370 db.commit() 

371 db.refresh(db_globalmeta) 

372 # create changelog entry 

373 type_of_change = get_value_from_str(toardb.toardb.CL_vocabulary,"Created") 

374 description="station created" 

375 db_changelog = StationmetaChangelog(description=description, station_id=stationmeta_core_id, author_id=author_id, type_of_change=type_of_change, 

376 old_value='', new_value='') 

377 db.add(db_changelog) 

378 db.commit() 

379 # there's a mismatch with coordinates --> how to automatically switch back and forth?! 

380 db_stationmeta.coordinates = tmp_coordinates 

381 res ={} 

382 res['detail'] = message 

383 return JSONResponse(status_code=status_code, content=res) 

384 

385 

386def get_field_from_record(db, station_id, field, db_stationmeta): 

387 value = str(getattr(db_stationmeta,field)) 

388 # if needed, convert this to human readable format (controlled vocabulary) 

389 value = get_hr_value('stationmeta',field,value) 

390 if isinstance(value, str): 

391 value = f"'{value}'" 

392 # I forgot to add the code to the following if/else-construct?! 

393 if field in ['roles', 'annotations', 'aux_images', 'aux_docs', 'aux_urls', 'globalmeta']: 

394 field = f"'{field}': " + value 

395 else: 

396 field = f"'{field}': " + value 

397 return field 

398 

399 

400def patch_stationmeta(db: Session, description: str, 

401 station_id: int, stationmeta: StationmetaPatch, 

402 author_id: int): 

403 

404 # still to be done: 

405 # collect all old/new_values from different sections! 

406 

407 no_log = (description == 'NOLOG') 

408 stationmeta_dict = stationmeta.dict() 

409 # delete empty fields from stationmeta_dict already at this place, to be able to 

410 # distinguish between "single value correction in metadata" and "comprehensive metadata revision" 

411 # (see controlled vocabulary "CL_vocabulary") 

412 stationmeta_dict2 = {k: v for k, v in stationmeta_dict.items() if v is not None} 

413 number_of_elements = len(stationmeta_dict2) 

414 if not no_log: 

415 if (number_of_elements == 1): 

416 type_of_change = get_value_from_str(toardb.toardb.CL_vocabulary,"SingleValue") 

417 else: 

418 type_of_change = get_value_from_str(toardb.toardb.CL_vocabulary,"Comprehensive") 

419 roles_data = stationmeta_dict.pop('roles', None) 

420 annotations_data = stationmeta_dict.pop('annotations', None) 

421 aux_images_data = stationmeta_dict.pop('aux_images', None) 

422 aux_docs_data = stationmeta_dict.pop('aux_docs', None) 

423 aux_urls_data = stationmeta_dict.pop('aux_urls', None) 

424 globalmeta_data = stationmeta_dict.pop('globalmeta', None) 

425 # there's a mismatch with coordinates --> how to automatically switch back and forth?! 

426 # ==> the following two commands are not working 

427 # ==> workaround 

428 # db.query(models.StationmetaCore).filter(models.StationmetaCore.id == stationm_id).update(stationmeta_dict2) 

429 db_obj = models.StationmetaCore(**stationmeta_dict) 

430 tmp_coordinates = db_obj.coordinates 

431 db_stationmeta = db.query(models.StationmetaCore).get(station_id) 

432 db_stationmeta.coordinates = get_geom_from_coordinates(db_stationmeta.coordinates) 

433 if not no_log: 

434 old_value="{" 

435 number_of_commas = number_of_elements - 1 

436 for k, v in stationmeta_dict.items(): 

437 if v is not None: 

438 # prepare changelog entry 

439 if not no_log: 

440 field = get_field_from_record(db, station_id, k, db_stationmeta) 

441 if (number_of_commas == 0): 

442 old_value=old_value + field + '}' 

443 else: 

444 old_value=old_value + field + ',' 

445 number_of_commas = number_of_commas - 1 

446 setattr(db_stationmeta,k,stationmeta_dict[k]) 

447 # do the following for every entry that uses the controlled vocabulary! 

448 # this should be improved! 

449 if db_obj.country: 

450 db_stationmeta.country = get_value_from_str(toardb.toardb.CN_vocabulary, db_obj.country) 

451 if db_obj.timezone: 

452 db_stationmeta.timezone = get_value_from_str(toardb.toardb.TZ_vocabulary, db_obj.timezone) 

453 if db_obj.type: 

454 db_stationmeta.type = get_value_from_str(toardb.toardb.ST_vocabulary, db_obj.type) 

455 if db_obj.type_of_area: 

456 db_stationmeta.type_of_area = get_value_from_str(toardb.toardb.TA_vocabulary, db_obj.type_of_area) 

457 if db_obj.coordinate_validation_status: 

458 db_stationmeta.coordinate_validation_status = get_value_from_str(toardb.toardb.CV_vocabulary, db_obj.coordinate_validation_status) 

459 result = db.commit() 

460 # store roles and update association table 

461 if roles_data: 

462 # prepare changelog entry/entries 

463 if not no_log: 

464 description = description + f"; add role" 

465 db_old_roles = get_stationmeta_roles(db, station_id) 

466 old_value = old_value + "'roles': {" 

467 for oldr in db_old_roles: 

468 old_role = get_stationmeta_role_by_id(db, oldr.role_id) 

469 old_value=old_value + "{'role': '" + get_str_from_value(toardb.toardb.RC_vocabulary,old_role.role) + \ 

470 "', 'status': '" + get_str_from_value(toardb.toardb.RS_vocabulary,old_role.status) + \ 

471 "', 'contact_id': " + str(old_role.contact_id) + '}' 

472 if (number_of_commas == 0): 

473 old_value=old_value + '}' 

474 else: 

475 old_value=old_value + ',' 

476 number_of_commas = number_of_commas - 1 

477 for r in roles_data: 

478 db_role = models.StationmetaRole(**r) 

479 db_role.role = get_value_from_str(toardb.toardb.RC_vocabulary,db_role.role) 

480 db_role.status = get_value_from_str(toardb.toardb.RS_vocabulary,db_role.status) 

481 # check whether role is already present in database 

482 db_object = get_unique_stationmeta_role(db, db_role.role, db_role.contact_id, db_role.status) 

483 if db_object: 

484 role_id = db_object.id 

485 else: 

486 db.add(db_role) 

487 db.commit() 

488 db.refresh(db_role) 

489 role_id = db_role.id 

490 db.execute(insert(stationmeta_core_stationmeta_roles_table).values(station_id=station_id, role_id=role_id)) 

491 db.commit() 

492 # store annotations and update association table 

493 if annotations_data: 

494 # prepare changelog entry/entries 

495 if not no_log: 

496 description = description + f"; add annotation" 

497 db_old_annotations = get_stationmeta_annotations(db, station_id) 

498 for olda in db_old_annotations: 

499 old_annotation = get_stationmeta_annotation_by_id(db, olda.annotation_id) 

500 old_value=old_value + "{'kind': '" + get_str_from_value(toardb.toardb.RC_vocabulary,old_role.kind) + \ 

501 "', 'text': '" + get_str_from_value(toardb.toardb.RS_vocabulary,old_role.status) + \ 

502 "', 'date_added': '" + get_str_from_value(toardb.toardb.RS_vocabulary,old_role.status) + \ 

503 "', 'approved': '" + get_str_from_value(toardb.toardb.RS_vocabulary,old_role.status) + \ 

504 "', 'contributor_id': " + str(old_role.contact_id) + '}' 

505 if (number_of_commas == 0): 

506 old_value=old_value + '}' 

507 else: 

508 old_value=old_value + ',' 

509 number_of_commas = number_of_commas - 1 

510 for a in annotations_data: 

511 db_annotation = models.StationmetaAnnotation(**a) 

512 db_annotation.kind = get_value_from_str(toardb.toardb.AK_vocabulary,db_annotation.kind) 

513 # check whether annotation is already present in database 

514 db_object = get_unique_stationmeta_annotation(db, db_annotation.text, db_annotation.contributor_id) 

515 if db_object: 

516 annotation_id = db_object.id 

517 else: 

518 db.add(db_annotation) 

519 db.commit() 

520 db.refresh(db_annotation) 

521 annotation_id = db_annotation.id 

522 db.execute(insert(stationmeta_core_stationmeta_annotations_table).values(station_id=station_id, annotation_id=annotation_id)) 

523 db.commit() 

524 # patch aux_images 

525 if aux_images_data: 

526 for i in aux_images_data: 

527 db_aux_image = models.StationmetaAuxImage(**i) 

528 db_aux_image.station_id = station_id 

529 db.add(db_aux_image) 

530 db.commit() 

531 db.refresh(db_aux_image) 

532 # patch aux_docs 

533 if aux_docs_data: 

534 for d in aux_docs_data: 

535 db_aux_doc = models.StationmetaAuxDoc(**d) 

536 db_aux_doc.station_id = station_id 

537 db.add(db_aux_doc) 

538 db.commit() 

539 db.refresh(db_aux_doc) 

540 # patch aux_urls 

541 if aux_urls_data: 

542 for u in aux_urls_data: 

543 db_aux_url = models.StationmetaAuxUrl(**u) 

544 db_aux_url.station_id = station_id 

545 db.add(db_aux_url) 

546 db.commit() 

547 db.refresh(db_aux_url) 

548 # patch globalmeta 

549 if globalmeta_data: 

550 db_globalmeta = get_stationmeta_global(db,station_id) 

551 old_global = {} 

552 if db_globalmeta: 

553 # combine old and new data: 

554 for key, value in globalmeta_data.items(): 

555 # "if value" would also skip a value of 0.0!!! 

556 if value != None: 

557 old_value = getattr(db_globalmeta, key) 

558 # do the following for every entry that uses the controlled vocabulary! 

559 # this should be improved! 

560 if key == "climatic_zone_year2016": 

561 value = get_value_from_str(toardb.toardb.CZ_vocabulary, value) 

562 old_value = get_str_from_value(toardb.toardb.CZ_vocabulary, old_value) 

563 elif key == "toar1_category": 

564 value = get_value_from_str(toardb.toardb.TC_vocabulary, value) 

565 old_value = get_str_from_value(toardb.toardb.TC_vocabulary, old_value) 

566 elif key == "toar2_category": 

567 value = get_value_from_str(toardb.toardb.TA_vocabulary, value) 

568 old_value = get_str_from_value(toardb.toardb.TA_vocabulary, old_value) 

569 elif key == "htap_region_tier1_year2010": 

570 value = get_value_from_str(toardb.toardb.TR_vocabulary, value) 

571 old_value = get_str_from_value(toardb.toardb.TR_vocabulary, old_value) 

572 elif key == "dominant_landcover_year2012": 

573 value = get_value_from_str(toardb.toardb.LC_vocabulary, value) 

574 old_value = get_str_from_value(toardb.toardb.LC_vocabulary, old_value) 

575 elif key == "landcover_description_25km_year2012": 

576 value = get_abbreviation_from_code_description(toardb.toardb.LC_vocabulary, value) 

577 old_value = get_code_description_from_abbreviation(toardb.toardb.LC_vocabulary, old_value) 

578 elif key == "dominant_ecoregion_year2017": 

579 value = get_value_from_str(toardb.toardb.ER_vocabulary, value) 

580 old_value = get_str_from_value(toardb.toardb.ER_vocabulary, old_value) 

581 elif key == "ecoregion_description_25km_year2017": 

582 value = get_abbreviation_from_code_description(toardb.toardb.ER_vocabulary, value) 

583 old_value = get_code_description_from_abbreviation(toardb.toardb.ER_vocabulary, old_value) 

584 else: 

585 pass 

586 old_global[key] = old_value 

587 setattr(db_globalmeta, key, value) 

588 else: 

589 db_globalmeta = models.StationmetaGlobal(**globalmeta_data) 

590 db.add(db_globalmeta) 

591 db.commit() 

592 stationmeta_dict2 = {k: v for k, v in stationmeta_dict2['globalmeta'].items() if v is not None} 

593 old_value = str(old_global) 

594 if not no_log: 

595 db_changelog = StationmetaChangelog(description=description, station_id=station_id, author_id=author_id, type_of_change=type_of_change, 

596 old_value=old_value, new_value=str(stationmeta_dict2)) 

597 db.add(db_changelog) 

598 db.commit() 

599 # there's a mismatch with coordinates --> how to automatically switch back and forth?! 

600 db_stationmeta.coordinates = tmp_coordinates 

601 status_code = 200 

602 message={"message": f'patched stationmeta record for station_id {station_id}'} 

603 message["station_id"] = station_id 

604 return JSONResponse(status_code=status_code, content=message) 

605 

606 

607def delete_stationmeta_field(db: Session, station_id: int, field: str, author_id: int): 

608 # id can never be deleted (and of course also not changed)!!! 

609 # there are mandatory fields (from stationmeta_core), that cannot be deleted! 

610 # --> set these to their default value 

611 new_value = "" 

612 field_table = {'roles': stationmeta_core_stationmeta_roles_table, 

613 'annotations': stationmeta_core_stationmeta_annotations_table} 

614# 'aux_images': stationmeta_core_stationmeta_aux_images_table, 

615# 'aux_docs': stationmeta_core_stationmeta_aux_docs_table, 

616# 'aux_urls': stationmeta_core_stationmeta_aux_urls_table, 

617 if (field in field_table): 

618 db.execute(delete(field_table[field]).where(field_table[field].c.station_id==station_id)) 

619 new_value = f"'{field}': []" 

620 # problem with automatic conversion of coordinates (although not explicitly fetched from database) 

621 # ==> next two lines are a workaround 

622 db_stationmeta = db.query(models.StationmetaCore).get(station_id) 

623 tmp_coordinates = db_stationmeta.coordinates 

624 db_stationmeta.coordinates = get_geom_from_coordinates(db_stationmeta.coordinates) 

625 # also write changelog entry 

626 type_of_change = get_value_from_str(toardb.toardb.CL_vocabulary,"SingleValue") 

627 description=f"delete field {field}" 

628 old_value = get_field_from_record(db, station_id, field, db_stationmeta) 

629 db_changelog = StationmetaChangelog(description=description, station_id=station_id, author_id=author_id, type_of_change=type_of_change, 

630 old_value=old_value, new_value=new_value) 

631 db.add(db_changelog) 

632 db.commit() 

633 # there's a mismatch with coordinates --> how to automatically switch back and forth?! 

634 db_stationmeta.coordinates = tmp_coordinates 

635 # hotfix: db_stationmeta.global needs to be retranslated to the dict that is understood by StationmetaGlobal 

636 db_stationmeta.globalmeta = None 

637 return db_stationmeta