From e60c2ce770a25a0b8bf039db3d7988effddb184f Mon Sep 17 00:00:00 2001 From: Andria Capai Date: Fri, 25 Oct 2024 10:30:50 +0200 Subject: [PATCH] style: apply black Reviewed-by: andriacap --- .github/workflows/lint.yml | 2 +- backend/api.py | 333 ++++++++++-------- backend/app.py | 40 ++- backend/config.py | 10 +- backend/env.py | 5 +- backend/migrations/env.py | 44 +-- .../33990994eeae_observatory_images.py | 39 +- ...bd35bb30_observatory_photo_to_thumbnail.py | 21 +- .../6443d436740a_site_main_observatory.py | 25 +- .../versions/ba8ff1826771_some_icons.py | 21 +- .../d7b6052f4dad_photo_id_observatory.py | 25 +- .../versions/d8d449eefa0f_observatory.py | 73 ++-- backend/migrations/versions/f545b345135c_.py | 13 +- .../ffd0e83f3c4c_initial_migration.py | 7 +- backend/models.py | 257 ++++++++------ backend/routes.py | 199 +++++++---- backend/utils.py | 308 +++++++++------- 17 files changed, 855 insertions(+), 567 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 43ed0f91..d597d856 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -22,4 +22,4 @@ jobs: pip install black==24.10.0 - name: Run Black - run: black --check ./backend + run: black --check --exclude "backend/custom_app.py" ./backend diff --git a/backend/api.py b/backend/api.py index 85437816..b60f7946 100644 --- a/backend/api.py +++ b/backend/api.py @@ -1,4 +1,13 @@ -from flask import Flask, request, Blueprint, Response, jsonify, abort, Response, current_app +from flask import ( + Flask, + request, + Blueprint, + Response, + jsonify, + abort, + Response, + current_app, +) from flask_login import login_required, current_user from werkzeug.exceptions import NotFound from werkzeug.wsgi import FileWrapper @@ -16,7 +25,7 @@ from env import db -api = Blueprint('api', __name__) +api = Blueprint("api", __name__) photo_schema = models.TPhotoSchema(many=True) observatory_schema_full = models.ObservatorySchemaFull(many=False) @@ -28,23 +37,28 @@ corThemeStheme_Schema = models.CorThemeSthemeSchema(many=True) themes_sthemes_schema = models.CorSthemeThemeSchema(many=True) -@api.route('/api/thumbor/presets//', methods=['GET']) + +@api.route("/api/thumbor/presets//", methods=["GET"]) def thumborPreset(name, filename): presets = { - 'noxl': 'fit-in/5000x5000/filters:no_upscale():quality(90)', - '50x50': '50x50', - '100x100': '100x100', - '150x150': '150x150', - '200x150': '200x150', - '200x200': '200x200', + "noxl": "fit-in/5000x5000/filters:no_upscale():quality(90)", + "50x50": "50x50", + "100x100": "100x100", + "150x150": "150x150", + "200x150": "200x150", + "200x200": "200x200", } preset = presets.get(name) if not preset: abort(404) - - url = preset + '/' + urllib.parse.quote(f'http://backend/static/upload/images/{filename}', safe='') + + url = ( + preset + + "/" + + urllib.parse.quote(f"http://backend/static/upload/images/{filename}", safe="") + ) signature = utils.getThumborSignature(url) - response = requests.get(f'http://thumbor:8000/{signature}/{url}') + response = requests.get(f"http://thumbor:8000/{signature}/{url}") if response.status_code != 200: abort(response.status_code) @@ -54,22 +68,24 @@ def thumborPreset(name, filename): w = FileWrapper(b) return Response(w, mimetype=type[0]) -@api.route('/api/conf', methods=['GET']) + +@api.route("/api/conf", methods=["GET"]) @fnauth.check_auth(2) def returnDdConf(): dbconf = utils.getDbConf() - + return jsonify(dbconf) -@api.route('/api/observatories', methods=['GET']) + +@api.route("/api/observatories", methods=["GET"]) def returnAllObservatories(): - get_all = models.Observatory.query.order_by('title').all() + get_all = models.Observatory.query.order_by("title").all() items = observatories_schema.dump(get_all) - + return jsonify(items) -@api.route('/api/observatories', methods=['POST']) +@api.route("/api/observatories", methods=["POST"]) @fnauth.check_auth(2) def postObservatory(): try: @@ -80,13 +96,13 @@ def postObservatory(): except Exception as exception: print(exception) return str(exception), 400 - + db.session.refresh(db_obj) resp = observatory_schema_full.dump(db_obj) return jsonify(resp) -@api.route('/api/observatories/', methods=['GET']) +@api.route("/api/observatories/", methods=["GET"]) def returnObservatoryById(id): row = models.Observatory.query.filter_by(id=id).first() if not row: @@ -95,7 +111,7 @@ def returnObservatoryById(id): return jsonify(dict) -@api.route('/api/observatories/', methods=['PATCH']) +@api.route("/api/observatories/", methods=["PATCH"]) @fnauth.check_auth(2) def patchObservatory(id): try: @@ -112,28 +128,26 @@ def patchObservatory(id): return jsonify(dict) -@api.route('/api/observatories//image', methods=['PATCH']) +@api.route("/api/observatories//image", methods=["PATCH"]) @fnauth.check_auth(2) def patchObservatoryImage(id): - field = request.form.get('field') - if field not in ['thumbnail', 'logo']: + field = request.form.get("field") + if field not in ["thumbnail", "logo"]: return "Invalid field value: " + str(field), 400 - image = request.files.get('image') + image = request.files.get("image") if not image: return "Missing field: image", 400 rows = models.Observatory.query.filter_by(id=id) if not rows.count(): abort(404) - + dicts = observatories_schema.dump(rows) - base_path = '/app/static/upload/images/' - + base_path = "/app/static/upload/images/" + _, ext = os.path.splitext(image.filename) - filename = 'observatory-' + str(id) + '-' + field + '-' + utils.getRandStr(4) + ext + filename = "observatory-" + str(id) + "-" + field + "-" + utils.getRandStr(4) + ext image.save(os.path.join(base_path + filename)) - rows.update({ - field: filename - }) + rows.update({field: filename}) db.session.commit() if dicts[0][field]: @@ -142,30 +156,31 @@ def patchObservatoryImage(id): except Exception as exception: pass - return jsonify({ - 'filename': filename - }), 200 + return jsonify({"filename": filename}), 200 -@api.route('/api/sites', methods=['GET']) +@api.route("/api/sites", methods=["GET"]) def returnAllSites(): dbconf = utils.getDbConf() - get_all_sites = models.TSite.query.order_by(dbconf['default_sort_sites']).all() + get_all_sites = models.TSite.query.order_by(dbconf["default_sort_sites"]).all() sites = site_schema.dump(get_all_sites) for site in sites: if len(site.get("t_photos")) > 0: - if site.get('main_photo') == None : + if site.get("main_photo") == None: first_photo = site.get("t_photos") main_photo = models.TPhoto.query.filter_by( id_photo=first_photo[0] ).one_or_none() else: main_photo = models.TPhoto.query.filter_by( - id_photo=site.get('main_photo')).one_or_none() + id_photo=site.get("main_photo") + ).one_or_none() if main_photo: photo_schema = models.TPhotoSchema() main_photo = photo_schema.dump(main_photo) - site['main_photo'] = main_photo.get("path_file_photo") #utils.getThumbnail(main_photo).get('output_name') + site["main_photo"] = main_photo.get( + "path_file_photo" + ) # utils.getThumbnail(main_photo).get('output_name') else: site["main_photo"] = "no_photo" @@ -175,118 +190,123 @@ def returnAllSites(): return jsonify(sites) -@api.route('/api/site/', methods=['GET']) +@api.route("/api/site/", methods=["GET"]) def returnSiteById(id_site): get_site_by_id = models.TSite.query.filter_by(id_site=id_site) site = site_schema.dump(get_site_by_id) - get_photos_by_site = models.TPhoto.query.order_by( - 'filter_date').filter_by(id_site=id_site).all() + get_photos_by_site = ( + models.TPhoto.query.order_by("filter_date").filter_by(id_site=id_site).all() + ) dump_photos = photo_schema.dump(get_photos_by_site) - cor_sthemes_themes = site[0].get('cor_site_stheme_themes') + cor_sthemes_themes = site[0].get("cor_site_stheme_themes") cor_list = [] themes_list = [] subthemes_list = [] for cor in cor_sthemes_themes: - cor_list.append(cor.get('id_stheme_theme')) + cor_list.append(cor.get("id_stheme_theme")) query = models.CorSthemeTheme.query.filter( - models.CorSthemeTheme.id_stheme_theme.in_(cor_list)) + models.CorSthemeTheme.id_stheme_theme.in_(cor_list) + ) themes_sthemes = themes_sthemes_schema.dump(query) for item in themes_sthemes: - if item.get('dico_theme').get('id_theme') not in themes_list: - themes_list.append(item.get('dico_theme').get('id_theme')) - if item.get('dico_stheme').get('id_stheme') not in subthemes_list: - subthemes_list.append(item.get('dico_stheme').get('id_stheme')) + if item.get("dico_theme").get("id_theme") not in themes_list: + themes_list.append(item.get("dico_theme").get("id_theme")) + if item.get("dico_stheme").get("id_stheme") not in subthemes_list: + subthemes_list.append(item.get("dico_stheme").get("id_stheme")) - site[0]['themes'] = themes_list - site[0]['subthemes'] = subthemes_list + site[0]["themes"] = themes_list + site[0]["subthemes"] = subthemes_list photos = dump_photos return jsonify(site=site, photos=photos), 200 -@api.route('/api/gallery', methods=['GET']) +@api.route("/api/gallery", methods=["GET"]) def gallery(): - get_photos = models.TPhoto.query.order_by('id_site').all() + get_photos = models.TPhoto.query.order_by("id_site").all() dump_photos = photo_schema.dump(get_photos) return jsonify(dump_photos), 200 -@api.route('/api/themes', methods=['GET']) +@api.route("/api/themes", methods=["GET"]) def returnAllThemes(): get_all_themes = models.DicoTheme.query.all() themes = themes_schema.dump(get_all_themes) return jsonify(themes), 200 -@api.route('/api/subThemes', methods=['GET']) +@api.route("/api/subThemes", methods=["GET"]) def returnAllSubthemes(): get_all_subthemes = models.DicoStheme.query.all() subthemes = subthemes_schema.dump(get_all_subthemes) for sub in subthemes: themes_of_subthemes = [] - for item in sub.get('cor_stheme_themes'): - themes_of_subthemes.append(item.get('id_theme')) - sub['themes'] = themes_of_subthemes - del sub['cor_stheme_themes'] + for item in sub.get("cor_stheme_themes"): + themes_of_subthemes.append(item.get("id_theme")) + sub["themes"] = themes_of_subthemes + del sub["cor_stheme_themes"] return jsonify(subthemes), 200 -@api.route('/api/licences', methods=['GET']) +@api.route("/api/licences", methods=["GET"]) def returnAllLicences(): get_all_licences = models.DicoLicencePhoto.query.all() licences = licences_schema.dump(get_all_licences) return jsonify(licences), 200 -@api.route('/api/users/', methods=['GET']) + +@api.route("/api/users/", methods=["GET"]) @login_required def returnAllUsers(id_app): - a = Application.query.filter_by(code_application=current_app.config["CODE_APPLICATION"]).one() - all_users = AppUser.query.filter_by( - id_application=id_app).all() + a = Application.query.filter_by( + code_application=current_app.config["CODE_APPLICATION"] + ).one() + all_users = AppUser.query.filter_by(id_application=id_app).all() - return jsonify([u.as_dict() for u in all_users if u.id_application == a.id_application]) + return jsonify( + [u.as_dict() for u in all_users if u.id_application == a.id_application] + ) -# TODO : remove this view ! + +# TODO : remove this view ! # use in the front at each refresh ... but why ? -@api.route('/api/me/', methods=['GET']) +@api.route("/api/me/", methods=["GET"]) @fnauth.check_auth(2) def returnCurrentUser(): id_role = current_user.id_role - user_data = AppUser.query.filter_by( - id_role=id_role - ).all() + user_data = AppUser.query.filter_by(id_role=id_role).all() if not user_data: raise NotFound(f"No User with id {id_role}") return jsonify([d.as_dict() for d in user_data]) -@api.route('/api/site/', methods=['DELETE']) +@api.route("/api/site/", methods=["DELETE"]) @fnauth.check_auth(6) def deleteSite(id_site): - base_path = '/app/static/upload/images/' + base_path = "/app/static/upload/images/" models.CorSiteSthemeTheme.query.filter_by(id_site=id_site).delete() photos = models.TPhoto.query.filter_by(id_site=id_site).all() photos = photo_schema.dump(photos) models.TPhoto.query.filter_by(id_site=id_site).delete() site = models.TSite.query.filter_by(id_site=id_site).delete() for photo in photos: - photo_name = photo.get('path_file_photo') + photo_name = photo.get("path_file_photo") for fileName in os.listdir(base_path): if fileName.endswith(photo_name): os.remove(base_path + fileName) db.session.commit() if site: - return jsonify('site has been deleted'), 200 + return jsonify("site has been deleted"), 200 else: - return jsonify('error'), 400 + return jsonify("error"), 400 -@api.route('/api/addSite', methods=['POST']) +@api.route("/api/addSite", methods=["POST"]) @fnauth.check_auth(2) def add_site(): data = dict(request.get_json()) @@ -297,161 +317,172 @@ def add_site(): return jsonify(id_site=site.id_site), 200 -@api.route('/api/updateSite', methods=['PATCH']) +@api.route("/api/updateSite", methods=["PATCH"]) @fnauth.check_auth(2) def update_site(): site = request.get_json() - models.CorSiteSthemeTheme.query.filter_by( - id_site=site.get('id_site')).delete() - models.TSite.query.filter_by(id_site=site.get('id_site')).update(site) + models.CorSiteSthemeTheme.query.filter_by(id_site=site.get("id_site")).delete() + models.TSite.query.filter_by(id_site=site.get("id_site")).update(site) db.session.commit() - return jsonify('site updated successfully'), 200 + return jsonify("site updated successfully"), 200 -@api.route('/api/addThemes', methods=['POST']) +@api.route("/api/addThemes", methods=["POST"]) @fnauth.check_auth(2) def add_cor_site_theme_stheme(): - data = request.get_json().get('data') + data = request.get_json().get("data") for d in data: get_id_stheme_theme = models.CorSthemeTheme.query.filter_by( - id_theme=d.get('id_theme'), id_stheme=d.get('id_stheme')).all() - id_stheme_theme = corThemeStheme_Schema.dump( - get_id_stheme_theme) - id_stheme_theme[0]['id_site'] = d.get('id_site') + id_theme=d.get("id_theme"), id_stheme=d.get("id_stheme") + ).all() + id_stheme_theme = corThemeStheme_Schema.dump(get_id_stheme_theme) + id_stheme_theme[0]["id_site"] = d.get("id_site") site_theme_stheme = models.CorSiteSthemeTheme(**id_stheme_theme[0]) db.session.add(site_theme_stheme) db.session.commit() - return jsonify('success'), 200 + return jsonify("success"), 200 -@api.route('/api/addPhotos', methods=['POST']) +@api.route("/api/addPhotos", methods=["POST"]) @fnauth.check_auth(2) def upload_file(): - base_path = '/app/static/upload/images/' - data = request.form.getlist('data') - new_site = request.form.getlist('new_site') - uploaded_images = request.files.getlist('image') + base_path = "/app/static/upload/images/" + data = request.form.getlist("data") + new_site = request.form.getlist("new_site") + uploaded_images = request.files.getlist("image") for d in data: d_serialized = json.loads(d) check_exist = models.TPhoto.query.filter_by( - path_file_photo=d_serialized.get('path_file_photo')).first() - if(check_exist): - if (new_site == 'true'): + path_file_photo=d_serialized.get("path_file_photo") + ).first() + if check_exist: + if new_site == "true": models.TSite.query.filter_by( - id_site=d_serialized.get('id_site')).delete() + id_site=d_serialized.get("id_site") + ).delete() models.CorSiteSthemeTheme.query.filter_by( - id_site=d_serialized.get('id_site')).delete() + id_site=d_serialized.get("id_site") + ).delete() db.session.commit() - return jsonify(error='image_already_exist', image=d_serialized.get('path_file_photo')), 400 - main_photo = d_serialized.get('main_photo') - del d_serialized['main_photo'] + return ( + jsonify( + error="image_already_exist", + image=d_serialized.get("path_file_photo"), + ), + 400, + ) + main_photo = d_serialized.get("main_photo") + del d_serialized["main_photo"] photo = models.TPhoto(**d_serialized) db.session.add(photo) db.session.commit() - if (main_photo == True): + if main_photo == True: photos_query = models.TPhoto.query.filter_by( - path_file_photo=d_serialized.get('path_file_photo')).all() - photo_id = photo_schema.dump( - photos_query)[0].get('id_photo') - models.TSite.query.filter_by(id_site=d_serialized.get( - 'id_site')).update({models.TSite.main_photo: photo_id}) + path_file_photo=d_serialized.get("path_file_photo") + ).all() + photo_id = photo_schema.dump(photos_query)[0].get("id_photo") + models.TSite.query.filter_by(id_site=d_serialized.get("id_site")).update( + {models.TSite.main_photo: photo_id} + ) db.session.commit() for image in uploaded_images: image.save(os.path.join(base_path + image.filename)) - return jsonify('photo added successfully'), 200 + return jsonify("photo added successfully"), 200 -@api.route('/api/addNotices', methods=['POST']) +@api.route("/api/addNotices", methods=["POST"]) @fnauth.check_auth(2) def upload_notice(): - base_path = './static/upload/notice-photo/' - notice = request.files.get('notice') + base_path = "./static/upload/notice-photo/" + notice = request.files.get("notice") notice.save(os.path.join(base_path + notice.filename)) - return jsonify('notice added successfully'), 200 + return jsonify("notice added successfully"), 200 -@api.route('/api/deleteNotice/', methods=['DELETE']) +@api.route("/api/deleteNotice/", methods=["DELETE"]) @fnauth.check_auth(2) def delete_notice(notice): - base_path = './static/upload/notice-photo/' + base_path = "./static/upload/notice-photo/" for fileName in os.listdir(base_path): - if (fileName == notice): + if fileName == notice: os.remove(base_path + fileName) - return jsonify('notice removed successfully'), 200 + return jsonify("notice removed successfully"), 200 -@api.route('/api/updatePhoto', methods=['PATCH']) +@api.route("/api/updatePhoto", methods=["PATCH"]) @fnauth.check_auth(2) def update_photo(): - base_path = '/app/static/upload/images/' - data = request.form.get('data') - image = request.files.get('image') + base_path = "/app/static/upload/images/" + data = request.form.get("data") + image = request.files.get("image") data_serialized = json.loads(data) photos_query = models.TPhoto.query.filter_by( - id_photo=data_serialized.get('id_photo')).all() - photo_name = photo_schema.dump( - photos_query)[0].get('path_file_photo') - if (data_serialized.get('main_photo') == True): - models.TSite.query.filter_by(id_site=data_serialized.get('id_site')).update( - {models.TSite.main_photo: data_serialized.get('id_photo')}) + id_photo=data_serialized.get("id_photo") + ).all() + photo_name = photo_schema.dump(photos_query)[0].get("path_file_photo") + if data_serialized.get("main_photo") == True: + models.TSite.query.filter_by(id_site=data_serialized.get("id_site")).update( + {models.TSite.main_photo: data_serialized.get("id_photo")} + ) db.session.commit() - if (data_serialized.get('main_photo')): - del data_serialized['main_photo'] - models.TPhoto.query.filter_by( - id_photo=data_serialized.get('id_photo')).update(data_serialized) + if data_serialized.get("main_photo"): + del data_serialized["main_photo"] + models.TPhoto.query.filter_by(id_photo=data_serialized.get("id_photo")).update( + data_serialized + ) db.session.commit() - if (image): + if image: for fileName in os.listdir(base_path): if fileName.endswith(photo_name): os.remove(base_path + fileName) image.save(os.path.join(base_path + image.filename)) else: for fileName in os.listdir(base_path): - if (fileName != photo_name and fileName.endswith(photo_name)): + if fileName != photo_name and fileName.endswith(photo_name): os.remove(base_path + fileName) - return jsonify('photo added successfully'), 200 + return jsonify("photo added successfully"), 200 -@api.route('/api/deletePhotos', methods=['POST']) +@api.route("/api/deletePhotos", methods=["POST"]) @fnauth.check_auth(6) def deletePhotos(): - base_path = '/app/static/upload/images/' + base_path = "/app/static/upload/images/" photos = request.get_json() for photo in photos: photos_query = models.TPhoto.query.filter_by( - id_photo=photo.get('id_photo')).all() + id_photo=photo.get("id_photo") + ).all() photo_dump = photo_schema.dump(photos_query)[0] - photo_name = photo_dump.get('path_file_photo') - models.TPhoto.query.filter_by( - id_photo=photo.get('id_photo')).delete() - get_site_by_id = models.TSite.query.filter_by( - id_site=photo_dump.get('t_site')) + photo_name = photo_dump.get("path_file_photo") + models.TPhoto.query.filter_by(id_photo=photo.get("id_photo")).delete() + get_site_by_id = models.TSite.query.filter_by(id_site=photo_dump.get("t_site")) site = site_schema.dump(get_site_by_id)[0] - if (site.get('main_photo') == photo_dump.get('id_photo')): - models.TSite.query.filter_by(id_site=photo_dump.get( - 't_site')).update({models.TSite.main_photo: None}) + if site.get("main_photo") == photo_dump.get("id_photo"): + models.TSite.query.filter_by(id_site=photo_dump.get("t_site")).update( + {models.TSite.main_photo: None} + ) db.session.commit() for fileName in os.listdir(base_path): if fileName.endswith(photo_name): os.remove(base_path + fileName) - return jsonify('site has been deleted'), 200 + return jsonify("site has been deleted"), 200 -@api.route('/api/communes', methods=['GET']) +@api.route("/api/communes", methods=["GET"]) def returnAllcommunes(): - get_all_communes = models.Communes.query.order_by('nom_commune').all() + get_all_communes = models.Communes.query.order_by("nom_commune").all() communes = models.CommunesSchema(many=True).dump(get_all_communes) return jsonify(communes), 200 -@api.route('/api/logout', methods=['GET']) +@api.route("/api/logout", methods=["GET"]) def logout(): - resp = Response('', 200) - resp.delete_cookie('token') + resp = Response("", 200) + resp.delete_cookie("token") return resp diff --git a/backend/app.py b/backend/app.py index 743587de..caaff381 100755 --- a/backend/app.py +++ b/backend/app.py @@ -13,10 +13,11 @@ from env import db, migrate + class ReverseProxied(object): - '''Wrap the application in this middleware and configure the - front-end server to add these headers, to let you quietly bind - this to a URL other than / and to an HTTP scheme that is + """Wrap the application in this middleware and configure the + front-end server to add these headers, to let you quietly bind + this to a URL other than / and to an HTTP scheme that is different than what is used locally. In nginx: @@ -29,36 +30,38 @@ class ReverseProxied(object): } :param app: the WSGI application - ''' + """ + def __init__(self, app): self.app = app def __call__(self, environ, start_response): - script_name = environ.get('HTTP_X_SCRIPT_NAME', '') + script_name = environ.get("HTTP_X_SCRIPT_NAME", "") if script_name: - environ['SCRIPT_NAME'] = script_name - path_info = environ['PATH_INFO'] + environ["SCRIPT_NAME"] = script_name + path_info = environ["PATH_INFO"] if path_info.startswith(script_name): - environ['PATH_INFO'] = path_info[len(script_name):] + environ["PATH_INFO"] = path_info[len(script_name) :] - scheme = environ.get('HTTP_X_SCHEME', '') + scheme = environ.get("HTTP_X_SCHEME", "") if scheme: - environ['wsgi.url_scheme'] = scheme + environ["wsgi.url_scheme"] = scheme return self.app(environ, start_response) + app = Flask(__name__) -app.config['BABEL_DEFAULT_LOCALE'] = 'fr' -app.config['BABEL_TRANSLATION_DIRECTORIES'] = config.BABEL_TRANSLATION_DIRECTORIES +app.config["BABEL_DEFAULT_LOCALE"] = "fr" +app.config["BABEL_TRANSLATION_DIRECTORIES"] = config.BABEL_TRANSLATION_DIRECTORIES babel = Babel(app) -#app.wsgi_app = ReverseProxied(app.wsgi_app) +# app.wsgi_app = ReverseProxied(app.wsgi_app) CORS(app, supports_credentials=True) app.register_blueprint(main_blueprint) app.register_blueprint(api) app.register_blueprint(custom_app.custom) -app.register_blueprint(routes.routes, url_prefix='/api/auth') +app.register_blueprint(routes.routes, url_prefix="/api/auth") -app.config.from_pyfile('config.py') +app.config.from_pyfile("config.py") db.init_app(app) login_manager.init_app(app) migrate.init_app(app, db) @@ -68,9 +71,9 @@ def __call__(self, environ, start_response): def inject_to_tpl(): custom = custom_app.custom_inject_to_tpl() data = dict( - dbconf=utils.getDbConf(), - debug=app.debug, - locale=get_locale(), + dbconf=utils.getDbConf(), + debug=app.debug, + locale=get_locale(), isMultiObservatories=utils.isMultiObservatories, getThumborUrl=utils.getThumborUrl, getCustomTpl=utils.getCustomTpl, @@ -78,5 +81,6 @@ def inject_to_tpl(): data.update(custom) return data + if __name__ == "__main__": app.run(debug=True) diff --git a/backend/config.py b/backend/config.py index 2ff10f61..0963c4d0 100644 --- a/backend/config.py +++ b/backend/config.py @@ -5,18 +5,18 @@ SQLALCHEMY_MAX_OVERFLOW = 30 # Choose between 'hash' or 'md5' -PASS_METHOD = 'hash' +PASS_METHOD = "hash" TRAP_ALL_EXCEPTIONS = False COOKIE_EXPIRATION = 36000 COOKIE_AUTORENEW = True -SESSION_TYPE = 'filesystem' +SESSION_TYPE = "filesystem" SECRET_KEY = os.getenv("FLASK_SECRET_KEY") # Do not edit except in exceptional cases -BABEL_TRANSLATION_DIRECTORIES = './i18n' # From ./ dir +BABEL_TRANSLATION_DIRECTORIES = "./i18n" # From ./ dir # !!! Do not change, this is the only supported value -COMPARATOR_VERSION = 2 +COMPARATOR_VERSION = 2 # Application code for UsersHub-Authentification-Module needs -CODE_APPLICATION = os.getenv("CODE_APPLICATION") \ No newline at end of file +CODE_APPLICATION = os.getenv("CODE_APPLICATION") diff --git a/backend/env.py b/backend/env.py index 08c2f502..aee9e1f0 100644 --- a/backend/env.py +++ b/backend/env.py @@ -3,10 +3,9 @@ from flask_migrate import Migrate from flask_marshmallow import Marshmallow -os.environ['FLASK_SQLALCHEMY_DB'] = 'env.db' -os.environ['FLASK_MARSHMALLOW'] = 'env.ma' +os.environ["FLASK_SQLALCHEMY_DB"] = "env.db" +os.environ["FLASK_MARSHMALLOW"] = "env.ma" db = SQLAlchemy() migrate = Migrate() ma = Marshmallow() - diff --git a/backend/migrations/env.py b/backend/migrations/env.py index d70e23cd..70e534ac 100644 --- a/backend/migrations/env.py +++ b/backend/migrations/env.py @@ -14,26 +14,28 @@ # Interpret the config file for Python logging. # This line sets up loggers basically. fileConfig(config.config_file_name) -logger = logging.getLogger('alembic.env') +logger = logging.getLogger("alembic.env") def include_name(name, type_, parent_names): if type_ == "schema": return name in ["geopaysages", "utilisateurs"] elif type_ == "table": - return parent_names['schema_name'] == "geopaysages" or name == "t_roles" + return parent_names["schema_name"] == "geopaysages" or name == "t_roles" elif type_ != "column": - return parent_names['table_name'] != "t_roles" + return parent_names["table_name"] != "t_roles" return ["column", "index", "unique_constraint", "foreign_key_constraint"] + + # add your model's MetaData object here # for 'autogenerate' support # from myapp import mymodel # target_metadata = mymodel.Base.metadata config.set_main_option( - 'sqlalchemy.url', - str(current_app.extensions['migrate'].db.get_engine().url).replace( - '%', '%%')) -target_metadata = current_app.extensions['migrate'].db.metadata + "sqlalchemy.url", + str(current_app.extensions["migrate"].db.get_engine().url).replace("%", "%%"), +) +target_metadata = current_app.extensions["migrate"].db.metadata # other values from the config, defined by the needs of env.py, # can be acquired: @@ -55,12 +57,12 @@ def run_migrations_offline(): """ url = config.get_main_option("sqlalchemy.url") context.configure( - url = url, - target_metadata = target_metadata, - literal_binds = True, - include_schemas = True, - include_name = include_name, - version_table_schema = 'geopaysages' + url=url, + target_metadata=target_metadata, + literal_binds=True, + include_schemas=True, + include_name=include_name, + version_table_schema="geopaysages", ) with context.begin_transaction(): @@ -79,13 +81,13 @@ def run_migrations_online(): # when there are no changes to the schema # reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html def process_revision_directives(context, revision, directives): - if getattr(config.cmd_opts, 'autogenerate', False): + if getattr(config.cmd_opts, "autogenerate", False): script = directives[0] if script.upgrade_ops.is_empty(): directives[:] = [] - logger.info('No changes in schema detected.') + logger.info("No changes in schema detected.") - connectable = current_app.extensions['migrate'].db.get_engine() + connectable = current_app.extensions["migrate"].db.get_engine() current_tenant = context.get_x_argument(as_dictionary=True).get("tenant") with connectable.connect() as connection: @@ -93,13 +95,13 @@ def process_revision_directives(context, revision, directives): connection=connection, target_metadata=target_metadata, process_revision_directives=process_revision_directives, - include_schemas = True, - include_name = include_name, - version_table_schema = 'geopaysages', - **current_app.extensions['migrate'].configure_args + include_schemas=True, + include_name=include_name, + version_table_schema="geopaysages", + **current_app.extensions["migrate"].configure_args ) - connection.execute('ALTER ROLE ALL SET search_path = public') + connection.execute("ALTER ROLE ALL SET search_path = public") connection.dialect.default_schema_name = current_tenant with context.begin_transaction(): context.run_migrations() diff --git a/backend/migrations/versions/33990994eeae_observatory_images.py b/backend/migrations/versions/33990994eeae_observatory_images.py index ae9cbf36..434d6eea 100644 --- a/backend/migrations/versions/33990994eeae_observatory_images.py +++ b/backend/migrations/versions/33990994eeae_observatory_images.py @@ -5,30 +5,53 @@ Create Date: 2022-06-28 14:03:56.705384 """ + from alembic import op import sqlalchemy as sa # revision identifiers, used by Alembic. -revision = '33990994eeae' -down_revision = 'ba8ff1826771' +revision = "33990994eeae" +down_revision = "ba8ff1826771" branch_labels = None depends_on = None def upgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.add_column('t_observatory', sa.Column('photo', sa.String(), nullable=True), schema='geopaysages') - op.add_column('t_observatory', sa.Column('logo', sa.String(), nullable=True), schema='geopaysages') + op.add_column( + "t_observatory", + sa.Column("photo", sa.String(), nullable=True), + schema="geopaysages", + ) + op.add_column( + "t_observatory", + sa.Column("logo", sa.String(), nullable=True), + schema="geopaysages", + ) # op.create_index('idx_t_observatory_geom', 't_observatory', ['geom'], unique=False, schema='geopaysages', postgresql_using='gist', postgresql_ops={}) - op.create_index('idx_t_site_geom', 't_site', ['geom'], unique=False, schema='geopaysages', postgresql_using='gist', postgresql_ops={}) + op.create_index( + "idx_t_site_geom", + "t_site", + ["geom"], + unique=False, + schema="geopaysages", + postgresql_using="gist", + postgresql_ops={}, + ) # ### end Alembic commands ### def downgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.drop_index('idx_t_site_geom', table_name='t_site', schema='geopaysages', postgresql_using='gist', postgresql_ops={}) + op.drop_index( + "idx_t_site_geom", + table_name="t_site", + schema="geopaysages", + postgresql_using="gist", + postgresql_ops={}, + ) # op.drop_index('idx_t_observatory_geom', table_name='t_observatory', schema='geopaysages', postgresql_using='gist', postgresql_ops={}) - op.drop_column('t_observatory', 'logo', schema='geopaysages') - op.drop_column('t_observatory', 'photo', schema='geopaysages') + op.drop_column("t_observatory", "logo", schema="geopaysages") + op.drop_column("t_observatory", "photo", schema="geopaysages") # ### end Alembic commands ### diff --git a/backend/migrations/versions/4a02bd35bb30_observatory_photo_to_thumbnail.py b/backend/migrations/versions/4a02bd35bb30_observatory_photo_to_thumbnail.py index dfc2b76b..79a1cf65 100644 --- a/backend/migrations/versions/4a02bd35bb30_observatory_photo_to_thumbnail.py +++ b/backend/migrations/versions/4a02bd35bb30_observatory_photo_to_thumbnail.py @@ -5,28 +5,37 @@ Create Date: 2022-09-27 13:03:47.075429 """ + from alembic import op import sqlalchemy as sa # revision identifiers, used by Alembic. -revision = '4a02bd35bb30' -down_revision = 'f545b345135c' +revision = "4a02bd35bb30" +down_revision = "f545b345135c" branch_labels = None depends_on = None def upgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.add_column('t_observatory', sa.Column('thumbnail', sa.String(), nullable=True), schema='geopaysages') + op.add_column( + "t_observatory", + sa.Column("thumbnail", sa.String(), nullable=True), + schema="geopaysages", + ) op.execute("UPDATE geopaysages.t_observatory set thumbnail = photo") - op.drop_column('t_observatory', 'photo', schema='geopaysages') + op.drop_column("t_observatory", "photo", schema="geopaysages") # ### end Alembic commands ### def downgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.add_column('t_observatory', sa.Column('photo', sa.VARCHAR(), autoincrement=False, nullable=True), schema='geopaysages') + op.add_column( + "t_observatory", + sa.Column("photo", sa.VARCHAR(), autoincrement=False, nullable=True), + schema="geopaysages", + ) op.execute("UPDATE geopaysages.t_observatory set photo = thumbnail") - op.drop_column('t_observatory', 'thumbnail', schema='geopaysages') + op.drop_column("t_observatory", "thumbnail", schema="geopaysages") # ### end Alembic commands ### diff --git a/backend/migrations/versions/6443d436740a_site_main_observatory.py b/backend/migrations/versions/6443d436740a_site_main_observatory.py index 8a749022..53213566 100644 --- a/backend/migrations/versions/6443d436740a_site_main_observatory.py +++ b/backend/migrations/versions/6443d436740a_site_main_observatory.py @@ -5,26 +5,39 @@ Create Date: 2022-07-07 15:04:40.049161 """ + from alembic import op import sqlalchemy as sa # revision identifiers, used by Alembic. -revision = '6443d436740a' -down_revision = 'd7b6052f4dad' +revision = "6443d436740a" +down_revision = "d7b6052f4dad" branch_labels = None depends_on = None def upgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.add_column('t_site', sa.Column('main_theme_id', sa.Integer(), nullable=True), schema='geopaysages') - op.create_foreign_key(None, 't_site', 'dico_theme', ['main_theme_id'], ['id_theme'], source_schema='geopaysages', referent_schema='geopaysages') + op.add_column( + "t_site", + sa.Column("main_theme_id", sa.Integer(), nullable=True), + schema="geopaysages", + ) + op.create_foreign_key( + None, + "t_site", + "dico_theme", + ["main_theme_id"], + ["id_theme"], + source_schema="geopaysages", + referent_schema="geopaysages", + ) # ### end Alembic commands ### def downgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.drop_constraint(None, 't_site', schema='geopaysages', type_='foreignkey') - op.drop_column('t_site', 'main_theme_id', schema='geopaysages') + op.drop_constraint(None, "t_site", schema="geopaysages", type_="foreignkey") + op.drop_column("t_site", "main_theme_id", schema="geopaysages") # ### end Alembic commands ### diff --git a/backend/migrations/versions/ba8ff1826771_some_icons.py b/backend/migrations/versions/ba8ff1826771_some_icons.py index 1ffaa031..e2f39de5 100644 --- a/backend/migrations/versions/ba8ff1826771_some_icons.py +++ b/backend/migrations/versions/ba8ff1826771_some_icons.py @@ -5,26 +5,35 @@ Create Date: 2022-06-27 08:45:05.556930 """ + from alembic import op import sqlalchemy as sa # revision identifiers, used by Alembic. -revision = 'ba8ff1826771' -down_revision = 'd8d449eefa0f' +revision = "ba8ff1826771" +down_revision = "d8d449eefa0f" branch_labels = None depends_on = None def upgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.add_column('dico_theme', sa.Column('icon', sa.String(), nullable=True), schema='geopaysages') - op.add_column('t_observatory', sa.Column('icon', sa.String(), nullable=True), schema='geopaysages') + op.add_column( + "dico_theme", + sa.Column("icon", sa.String(), nullable=True), + schema="geopaysages", + ) + op.add_column( + "t_observatory", + sa.Column("icon", sa.String(), nullable=True), + schema="geopaysages", + ) # ### end Alembic commands ### def downgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.drop_column('t_observatory', 'icon', schema='geopaysages') - op.drop_column('dico_theme', 'icon', schema='geopaysages') + op.drop_column("t_observatory", "icon", schema="geopaysages") + op.drop_column("dico_theme", "icon", schema="geopaysages") # ### end Alembic commands ### diff --git a/backend/migrations/versions/d7b6052f4dad_photo_id_observatory.py b/backend/migrations/versions/d7b6052f4dad_photo_id_observatory.py index 52d635f5..4a363ce3 100644 --- a/backend/migrations/versions/d7b6052f4dad_photo_id_observatory.py +++ b/backend/migrations/versions/d7b6052f4dad_photo_id_observatory.py @@ -5,26 +5,39 @@ Create Date: 2022-07-07 14:41:15.285056 """ + from alembic import op import sqlalchemy as sa # revision identifiers, used by Alembic. -revision = 'd7b6052f4dad' -down_revision = '33990994eeae' +revision = "d7b6052f4dad" +down_revision = "33990994eeae" branch_labels = None depends_on = None def upgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.add_column('t_photo', sa.Column('id_observatory', sa.Integer(), nullable=True), schema='geopaysages') - op.create_foreign_key(None, 't_photo', 't_observatory', ['id_observatory'], ['id'], source_schema='geopaysages', referent_schema='geopaysages') + op.add_column( + "t_photo", + sa.Column("id_observatory", sa.Integer(), nullable=True), + schema="geopaysages", + ) + op.create_foreign_key( + None, + "t_photo", + "t_observatory", + ["id_observatory"], + ["id"], + source_schema="geopaysages", + referent_schema="geopaysages", + ) # ### end Alembic commands ### def downgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.drop_constraint(None, 't_photo', schema='geopaysages', type_='foreignkey') - op.drop_column('t_photo', 'id_observatory', schema='geopaysages') + op.drop_constraint(None, "t_photo", schema="geopaysages", type_="foreignkey") + op.drop_column("t_photo", "id_observatory", schema="geopaysages") # ### end Alembic commands ### diff --git a/backend/migrations/versions/d8d449eefa0f_observatory.py b/backend/migrations/versions/d8d449eefa0f_observatory.py index c1e61881..6f07c8cc 100644 --- a/backend/migrations/versions/d8d449eefa0f_observatory.py +++ b/backend/migrations/versions/d8d449eefa0f_observatory.py @@ -5,33 +5,53 @@ Create Date: 2022-06-02 08:11:40.570916 """ + from alembic import op import sqlalchemy as sa from geoalchemy2.types import Geometry # revision identifiers, used by Alembic. -revision = 'd8d449eefa0f' -down_revision = 'ffd0e83f3c4c' +revision = "d8d449eefa0f" +down_revision = "ffd0e83f3c4c" branch_labels = None depends_on = None def upgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.create_table('t_observatory', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('title', sa.String(), nullable=True), - sa.Column('ref', sa.String(), nullable=True), - sa.Column('color', sa.String(), nullable=True), - sa.Column('comparator', sa.Enum('sidebyside', 'split', name='comparator_enum'), nullable=True), - sa.Column('geom', Geometry(geometry_type='MULTIPOLYGON', srid=4326), nullable=True), - sa.Column('is_published', sa.Boolean(), nullable=True), - sa.PrimaryKeyConstraint('id'), - schema='geopaysages' + op.create_table( + "t_observatory", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("title", sa.String(), nullable=True), + sa.Column("ref", sa.String(), nullable=True), + sa.Column("color", sa.String(), nullable=True), + sa.Column( + "comparator", + sa.Enum("sidebyside", "split", name="comparator_enum"), + nullable=True, + ), + sa.Column( + "geom", Geometry(geometry_type="MULTIPOLYGON", srid=4326), nullable=True + ), + sa.Column("is_published", sa.Boolean(), nullable=True), + sa.PrimaryKeyConstraint("id"), + schema="geopaysages", + ) + op.add_column( + "t_site", + sa.Column("id_observatory", sa.Integer(), nullable=True), + schema="geopaysages", + ) + op.create_foreign_key( + "t_site_fk_observatory", + "t_site", + "t_observatory", + ["id_observatory"], + ["id"], + source_schema="geopaysages", + referent_schema="geopaysages", ) - op.add_column('t_site', sa.Column('id_observatory', sa.Integer(), nullable=True), schema='geopaysages') - op.create_foreign_key('t_site_fk_observatory', 't_site', 't_observatory', ['id_observatory'], ['id'], source_schema='geopaysages', referent_schema='geopaysages') # ### end Alembic commands ### coords = """ 6.58681072 45.18200501, @@ -47,21 +67,30 @@ def upgrade(): 6.58681072 45.18200501 """ t = { - "title": "Parc national de la Vanoise", + "title": "Parc national de la Vanoise", "ref": "PNV", "color": "#b50000", "comparator": "split", - "geom": "MULTIPOLYGON ((("+ coords +")))" + "geom": "MULTIPOLYGON (((" + coords + ")))", } - op.get_bind().execute(sa.sql.text("INSERT INTO geopaysages.t_observatory (title, ref, color, comparator, geom, is_published) "+ - "values (:title, :ref, :color, :comparator, :geom, true)"), **t) - op.execute("UPDATE geopaysages.t_site set id_observatory = (SELECT id FROM geopaysages.t_observatory limit 1)") + op.get_bind().execute( + sa.sql.text( + "INSERT INTO geopaysages.t_observatory (title, ref, color, comparator, geom, is_published) " + + "values (:title, :ref, :color, :comparator, :geom, true)" + ), + **t + ) + op.execute( + "UPDATE geopaysages.t_site set id_observatory = (SELECT id FROM geopaysages.t_observatory limit 1)" + ) def downgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.drop_constraint('t_site_fk_observatory', 't_site', schema='geopaysages', type_='foreignkey') - op.drop_column('t_site', 'id_observatory', schema='geopaysages') - op.drop_table('t_observatory', schema='geopaysages') + op.drop_constraint( + "t_site_fk_observatory", "t_site", schema="geopaysages", type_="foreignkey" + ) + op.drop_column("t_site", "id_observatory", schema="geopaysages") + op.drop_table("t_observatory", schema="geopaysages") # ### end Alembic commands ### op.execute("DROP TYPE comparator_enum") diff --git a/backend/migrations/versions/f545b345135c_.py b/backend/migrations/versions/f545b345135c_.py index 65aa6f13..6b29e8dd 100644 --- a/backend/migrations/versions/f545b345135c_.py +++ b/backend/migrations/versions/f545b345135c_.py @@ -5,24 +5,29 @@ Create Date: 2022-07-11 08:07:37.574533 """ + from alembic import op import sqlalchemy as sa # revision identifiers, used by Alembic. -revision = 'f545b345135c' -down_revision = '6443d436740a' +revision = "f545b345135c" +down_revision = "6443d436740a" branch_labels = None depends_on = None def upgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.drop_column('t_observatory', 'icon', schema='geopaysages') + op.drop_column("t_observatory", "icon", schema="geopaysages") # ### end Alembic commands ### def downgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.add_column('t_observatory', sa.Column('icon', sa.VARCHAR(), autoincrement=False, nullable=True), schema='geopaysages') + op.add_column( + "t_observatory", + sa.Column("icon", sa.VARCHAR(), autoincrement=False, nullable=True), + schema="geopaysages", + ) # ### end Alembic commands ### diff --git a/backend/migrations/versions/ffd0e83f3c4c_initial_migration.py b/backend/migrations/versions/ffd0e83f3c4c_initial_migration.py index 37fb007c..cd086c32 100644 --- a/backend/migrations/versions/ffd0e83f3c4c_initial_migration.py +++ b/backend/migrations/versions/ffd0e83f3c4c_initial_migration.py @@ -5,12 +5,13 @@ Create Date: 2022-05-31 19:48:45.442620 """ + from alembic import op import sqlalchemy as sa # revision identifiers, used by Alembic. -revision = 'ffd0e83f3c4c' +revision = "ffd0e83f3c4c" down_revision = None branch_labels = None depends_on = None @@ -19,7 +20,9 @@ def upgrade(): # ### commands auto generated by Alembic - please adjust! ### # ### end Alembic commands ### - op.execute('ALTER TABLE "geopaysages"."conf" ADD CONSTRAINT pk_conf PRIMARY KEY ("key")') + op.execute( + 'ALTER TABLE "geopaysages"."conf" ADD CONSTRAINT pk_conf PRIMARY KEY ("key")' + ) def downgrade(): diff --git a/backend/models.py b/backend/models.py index a1271507..820c0eea 100644 --- a/backend/models.py +++ b/backend/models.py @@ -12,43 +12,44 @@ class Conf(db.Model): - __tablename__ = 'conf' - __table_args__ = {'schema': 'geopaysages'} + __tablename__ = "conf" + __table_args__ = {"schema": "geopaysages"} key = db.Column(db.String, primary_key=True) value = db.Column(db.String) + class ComparatorEnum(Enum): - sidebyside = 'sidebyside' - split = 'split' + sidebyside = "sidebyside" + split = "split" + class Observatory(db.Model): - __tablename__ = 't_observatory' - __table_args__ = {'schema': 'geopaysages'} + __tablename__ = "t_observatory" + __table_args__ = {"schema": "geopaysages"} - id = db.Column(db.Integer, primary_key=True, - server_default=db.FetchedValue()) + id = db.Column(db.Integer, primary_key=True, server_default=db.FetchedValue()) title = db.Column(db.String) ref = db.Column(db.String) color = db.Column(db.String) thumbnail = db.Column(db.String) logo = db.Column(db.String) comparator = db.Column(db.Enum(ComparatorEnum, name="comparator_enum")) - geom = db.Column(Geometry(geometry_type='MULTIPOLYGON', srid=4326)) + geom = db.Column(Geometry(geometry_type="MULTIPOLYGON", srid=4326)) is_published = db.Column(db.Boolean) - class TSite(db.Model): - __tablename__ = 't_site' - __table_args__ = {'schema': 'geopaysages'} + __tablename__ = "t_site" + __table_args__ = {"schema": "geopaysages"} - id_site = db.Column(db.Integer, primary_key=True, - server_default=db.FetchedValue()) - id_observatory = db.Column(db.ForeignKey( - 'geopaysages.t_observatory.id', name='t_site_fk_observatory')) + id_site = db.Column(db.Integer, primary_key=True, server_default=db.FetchedValue()) + id_observatory = db.Column( + db.ForeignKey("geopaysages.t_observatory.id", name="t_site_fk_observatory") + ) observatory = db.relationship( - 'Observatory', primaryjoin='TSite.id_observatory == Observatory.id') + "Observatory", primaryjoin="TSite.id_observatory == Observatory.id" + ) name_site = db.Column(db.String) ref_site = db.Column(db.String) desc_site = db.Column(db.String) @@ -58,131 +59,180 @@ class TSite(db.Model): alti_site = db.Column(db.Integer) path_file_guide_site = db.Column(db.String) publish_site = db.Column(db.Boolean) - geom = db.Column(Geometry(geometry_type='POINT', srid=4326)) + geom = db.Column(Geometry(geometry_type="POINT", srid=4326)) main_photo = db.Column(db.Integer) - main_theme_id = db.Column(db.ForeignKey('geopaysages.dico_theme.id_theme')) + main_theme_id = db.Column(db.ForeignKey("geopaysages.dico_theme.id_theme")) main_theme = db.relationship( - 'DicoTheme', primaryjoin='TSite.main_theme_id == DicoTheme.id_theme') + "DicoTheme", primaryjoin="TSite.main_theme_id == DicoTheme.id_theme" + ) class CorSiteSthemeTheme(db.Model): - __tablename__ = 'cor_site_stheme_theme' - __table_args__ = {'schema': 'geopaysages'} + __tablename__ = "cor_site_stheme_theme" + __table_args__ = {"schema": "geopaysages"} id_site_stheme_theme = db.Column( - db.Integer, nullable=False, server_default=db.FetchedValue()) - id_site = db.Column(db.ForeignKey( - 'geopaysages.t_site.id_site'), primary_key=True, nullable=False) - id_stheme_theme = db.Column(db.ForeignKey( - 'geopaysages.cor_stheme_theme.id_stheme_theme'), primary_key=True, nullable=False) + db.Integer, nullable=False, server_default=db.FetchedValue() + ) + id_site = db.Column( + db.ForeignKey("geopaysages.t_site.id_site"), primary_key=True, nullable=False + ) + id_stheme_theme = db.Column( + db.ForeignKey("geopaysages.cor_stheme_theme.id_stheme_theme"), + primary_key=True, + nullable=False, + ) t_site = db.relationship( - 'TSite', primaryjoin='CorSiteSthemeTheme.id_site == TSite.id_site', backref='cor_site_stheme_themes') + "TSite", + primaryjoin="CorSiteSthemeTheme.id_site == TSite.id_site", + backref="cor_site_stheme_themes", + ) cor_stheme_theme = db.relationship( - 'CorSthemeTheme', primaryjoin='CorSiteSthemeTheme.id_stheme_theme == CorSthemeTheme.id_stheme_theme', backref='cor_site_stheme_themes') + "CorSthemeTheme", + primaryjoin="CorSiteSthemeTheme.id_stheme_theme == CorSthemeTheme.id_stheme_theme", + backref="cor_site_stheme_themes", + ) class CorSthemeTheme(db.Model): - __tablename__ = 'cor_stheme_theme' - __table_args__ = {'schema': 'geopaysages'} + __tablename__ = "cor_stheme_theme" + __table_args__ = {"schema": "geopaysages"} id_stheme_theme = db.Column( - db.Integer, nullable=False, unique=True, server_default=db.FetchedValue()) - id_stheme = db.Column(db.ForeignKey( - 'geopaysages.dico_stheme.id_stheme'), primary_key=True, nullable=False) - id_theme = db.Column(db.ForeignKey( - 'geopaysages.dico_theme.id_theme'), primary_key=True, nullable=False) + db.Integer, nullable=False, unique=True, server_default=db.FetchedValue() + ) + id_stheme = db.Column( + db.ForeignKey("geopaysages.dico_stheme.id_stheme"), + primary_key=True, + nullable=False, + ) + id_theme = db.Column( + db.ForeignKey("geopaysages.dico_theme.id_theme"), + primary_key=True, + nullable=False, + ) dico_stheme = db.relationship( - 'DicoStheme', primaryjoin='CorSthemeTheme.id_stheme == DicoStheme.id_stheme', backref='cor_stheme_themes') + "DicoStheme", + primaryjoin="CorSthemeTheme.id_stheme == DicoStheme.id_stheme", + backref="cor_stheme_themes", + ) dico_theme = db.relationship( - 'DicoTheme', primaryjoin='CorSthemeTheme.id_theme == DicoTheme.id_theme', backref='cor_stheme_themes') + "DicoTheme", + primaryjoin="CorSthemeTheme.id_theme == DicoTheme.id_theme", + backref="cor_stheme_themes", + ) class DicoLicencePhoto(db.Model): - __tablename__ = 'dico_licence_photo' - __table_args__ = {'schema': 'geopaysages'} + __tablename__ = "dico_licence_photo" + __table_args__ = {"schema": "geopaysages"} id_licence_photo = db.Column( - db.Integer, primary_key=True, server_default=db.FetchedValue()) + db.Integer, primary_key=True, server_default=db.FetchedValue() + ) name_licence_photo = db.Column(db.String) description_licence_photo = db.Column(db.String) class DicoStheme(db.Model): - __tablename__ = 'dico_stheme' - __table_args__ = {'schema': 'geopaysages'} + __tablename__ = "dico_stheme" + __table_args__ = {"schema": "geopaysages"} - id_stheme = db.Column(db.Integer, primary_key=True, - server_default=db.FetchedValue()) + id_stheme = db.Column( + db.Integer, primary_key=True, server_default=db.FetchedValue() + ) name_stheme = db.Column(db.String) class DicoTheme(db.Model): - __tablename__ = 'dico_theme' - __table_args__ = {'schema': 'geopaysages'} + __tablename__ = "dico_theme" + __table_args__ = {"schema": "geopaysages"} - id_theme = db.Column(db.Integer, primary_key=True, - server_default=db.FetchedValue()) + id_theme = db.Column(db.Integer, primary_key=True, server_default=db.FetchedValue()) name_theme = db.Column(db.String) icon = db.Column(db.String) class TRole(db.Model): - __tablename__ = 't_roles' - __table_args__ = {'schema': 'utilisateurs', 'extend_existing': True} + __tablename__ = "t_roles" + __table_args__ = {"schema": "utilisateurs", "extend_existing": True} - groupe = db.Column(db.Boolean, nullable=False, - server_default=db.FetchedValue()) - id_role = db.Column(db.Integer, primary_key=True, - server_default=db.FetchedValue()) + groupe = db.Column(db.Boolean, nullable=False, server_default=db.FetchedValue()) + id_role = db.Column(db.Integer, primary_key=True, server_default=db.FetchedValue()) identifiant = db.Column(db.String(100)) nom_role = db.Column(db.String(50)) prenom_role = db.Column(db.String(50)) desc_role = db.Column(db.Text) - _pass = db.Column('pass', db.String(100)) - _pass_plus = db.Column('pass_plus', db.String(100)) + _pass = db.Column("pass", db.String(100)) + _pass_plus = db.Column("pass_plus", db.String(100)) email = db.Column(db.String(250)) - id_organisme = db.Column('id_organisme', db.INTEGER(), autoincrement=False, nullable=True) + id_organisme = db.Column( + "id_organisme", db.INTEGER(), autoincrement=False, nullable=True + ) remarques = db.Column(db.Text) date_insert = db.Column(db.DateTime) date_update = db.Column(db.DateTime) - uuid_role = db.Column('uuid_role', postgresql.UUID(), server_default=db.text('uuid_generate_v4()'), autoincrement=False, nullable=False) - active = db.Column('active', db.BOOLEAN(), server_default=db.text('true'), autoincrement=False, nullable=True) - champs_addi = db.Column('champs_addi', postgresql.JSONB(astext_type=db.Text()), autoincrement=False, nullable=True) + uuid_role = db.Column( + "uuid_role", + postgresql.UUID(), + server_default=db.text("uuid_generate_v4()"), + autoincrement=False, + nullable=False, + ) + active = db.Column( + "active", + db.BOOLEAN(), + server_default=db.text("true"), + autoincrement=False, + nullable=True, + ) + champs_addi = db.Column( + "champs_addi", + postgresql.JSONB(astext_type=db.Text()), + autoincrement=False, + nullable=True, + ) class TPhoto(db.Model): - __tablename__ = 't_photo' - __table_args__ = {'schema': 'geopaysages'} + __tablename__ = "t_photo" + __table_args__ = {"schema": "geopaysages"} - id_photo = db.Column(db.Integer, primary_key=True, - server_default=db.FetchedValue()) - id_site = db.Column(db.ForeignKey('geopaysages.t_site.id_site')) - id_observatory = db.Column(db.ForeignKey('geopaysages.t_observatory.id')) + id_photo = db.Column(db.Integer, primary_key=True, server_default=db.FetchedValue()) + id_site = db.Column(db.ForeignKey("geopaysages.t_site.id_site")) + id_observatory = db.Column(db.ForeignKey("geopaysages.t_observatory.id")) path_file_photo = db.Column(db.String) - id_role = db.Column(db.ForeignKey('utilisateurs.t_roles.id_role')) + id_role = db.Column(db.ForeignKey("utilisateurs.t_roles.id_role")) date_photo = db.Column(db.String) filter_date = db.Column(db.Date) legende_photo = db.Column(db.String) display_gal_photo = db.Column(db.Boolean) - id_licence_photo = db.Column(db.ForeignKey( - 'geopaysages.dico_licence_photo.id_licence_photo')) + id_licence_photo = db.Column( + db.ForeignKey("geopaysages.dico_licence_photo.id_licence_photo") + ) dico_licence_photo = db.relationship( - 'DicoLicencePhoto', primaryjoin='TPhoto.id_licence_photo == DicoLicencePhoto.id_licence_photo', backref='t_photos') + "DicoLicencePhoto", + primaryjoin="TPhoto.id_licence_photo == DicoLicencePhoto.id_licence_photo", + backref="t_photos", + ) t_role = db.relationship( - 'TRole', primaryjoin='TPhoto.id_role == TRole.id_role', backref='t_photos') + "TRole", primaryjoin="TPhoto.id_role == TRole.id_role", backref="t_photos" + ) t_site = db.relationship( - 'TSite', primaryjoin='TPhoto.id_site == TSite.id_site', backref='t_photos') + "TSite", primaryjoin="TPhoto.id_site == TSite.id_site", backref="t_photos" + ) class Communes(db.Model): - __tablename__ = 'communes' - __table_args__ = {'schema': 'geopaysages'} + __tablename__ = "communes" + __table_args__ = {"schema": "geopaysages"} - code_commune = db.Column(db.String, primary_key=True, - server_default=db.FetchedValue()) + code_commune = db.Column( + db.String, primary_key=True, server_default=db.FetchedValue() + ) nom_commune = db.Column(db.String) @@ -191,8 +241,11 @@ def _serialize(self, value, attr, obj): if value is None: return value else: - if attr == 'geom': - return [db.session.scalar(geo_funcs.ST_Y(value)), db.session.scalar(geo_funcs.ST_X(value))] + if attr == "geom": + return [ + db.session.scalar(geo_funcs.ST_Y(value)), + db.session.scalar(geo_funcs.ST_X(value)), + ] else: return None @@ -200,17 +253,22 @@ def _deserialize(self, value, attr, data): if value is None: return value else: - if attr == 'geom': - return WKTGeographyElement('POINT({0} {1})'.format(str(value.get('longitude')), str(value.get('latitude')))) + if attr == "geom": + return WKTGeographyElement( + "POINT({0} {1})".format( + str(value.get("longitude")), str(value.get("latitude")) + ) + ) else: return None -#schemas# + +# schemas# class DicoThemeSchema(ma.SQLAlchemyAutoSchema): class Meta: - fields = ('id_theme', 'name_theme', 'icon') + fields = ("id_theme", "name_theme", "icon") class DicoSthemeSchema(ma.SQLAlchemyAutoSchema): @@ -221,19 +279,17 @@ class Meta: class CorThemeSthemeSchema(ma.SQLAlchemyAutoSchema): class Meta: - fields = ('id_stheme_theme',) + fields = ("id_stheme_theme",) class LicencePhotoSchema(ma.SQLAlchemyAutoSchema): class Meta: - fields = ('id_licence_photo', 'name_licence_photo', - 'description_licence_photo') + fields = ("id_licence_photo", "name_licence_photo", "description_licence_photo") class RoleSchema(ma.SQLAlchemyAutoSchema): class Meta: - fields = ('id_role', 'identifiant', 'nom_role', - 'id_organisme') + fields = ("id_role", "identifiant", "nom_role", "id_organisme") class TPhotoSchema(ma.SQLAlchemyAutoSchema): @@ -247,34 +303,34 @@ class Meta: class CorSthemeThemeSchema(ma.SQLAlchemyAutoSchema): dico_theme = ma.Nested(DicoThemeSchema, only=["id_theme", "name_theme"]) - dico_stheme = ma.Nested(DicoSthemeSchema, only=[ - "id_stheme", "name_stheme"]) + dico_stheme = ma.Nested(DicoSthemeSchema, only=["id_stheme", "name_stheme"]) class Meta: - fields = ('dico_theme', 'dico_stheme') - #model = CorSthemeTheme + fields = ("dico_theme", "dico_stheme") + # model = CorSthemeTheme class ObservatorySchema(ma.SQLAlchemyAutoSchema): comparator = EnumField(ComparatorEnum, by_value=True) geom = fields.Method("geomSerialize") - + @staticmethod def geomSerialize(obj): if obj.geom is None: return None p = to_shape(obj.geom) - s = p.simplify(.0001, preserve_topology=False) + s = p.simplify(0.0001, preserve_topology=False) return s.wkt class Meta: model = Observatory include_relationships = True + class ObservatorySchemaFull(ma.SQLAlchemyAutoSchema): comparator = EnumField(ComparatorEnum, by_value=True) geom = fields.Method("geomSerialize") - + @staticmethod def geomSerialize(obj): if obj.geom is None: @@ -286,16 +342,17 @@ class Meta: model = Observatory include_relationships = True + class ObservatorySchemaLite(ma.SQLAlchemyAutoSchema): comparator = EnumField(ComparatorEnum, by_value=False) geom = fields.Method("geomSerialize") - + @staticmethod def geomSerialize(obj): if obj.geom is None: return None p = to_shape(obj.geom) - s = p.simplify(.001, preserve_topology=True) + s = p.simplify(0.001, preserve_topology=True) return s.wkt class Meta: @@ -304,8 +361,10 @@ class Meta: class TSiteSchema(ma.SQLAlchemyAutoSchema): - geom = GeographySerializationField(attribute='geom') - observatory = ma.Nested(ObservatorySchema, only=["id", "title", "ref", "color", "logo"]) + geom = GeographySerializationField(attribute="geom") + observatory = ma.Nested( + ObservatorySchema, only=["id", "title", "ref", "color", "logo"] + ) main_theme = ma.Nested(DicoThemeSchema, only=["id_theme", "name_theme", "icon"]) class Meta: diff --git a/backend/routes.py b/backend/routes.py index bf8661ba..19b7b6db 100644 --- a/backend/routes.py +++ b/backend/routes.py @@ -9,9 +9,9 @@ import math import os -main = Blueprint('main', __name__, template_folder='tpl') +main = Blueprint("main", __name__, template_folder="tpl") -from env import db +from env import db dicotheme_schema = models.DicoThemeSchema(many=True) dicostheme_schema = models.DicoSthemeSchema(many=True) @@ -22,10 +22,11 @@ communes_schema = models.CommunesSchema(many=True) - -@main.route('/') +@main.route("/") def home(): - sql = text("SELECT * FROM geopaysages.t_site p join geopaysages.t_observatory o on o.id=p.id_observatory where p.publish_site=true and o.is_published is true ORDER BY RANDOM() LIMIT 6") + sql = text( + "SELECT * FROM geopaysages.t_site p join geopaysages.t_observatory o on o.id=p.id_observatory where p.publish_site=true and o.is_published is true ORDER BY RANDOM() LIMIT 6" + ) sites_proxy = db.engine.execute(sql).fetchall() sites = [dict(row.items()) for row in sites_proxy] @@ -38,25 +39,27 @@ def home(): sites_without_photo = [] code_communes = [] for site in sites: - photo_id = site.get('main_photo') + photo_id = site.get("main_photo") if photo_id: - photo_ids.append(site.get('main_photo')) + photo_ids.append(site.get("main_photo")) else: - sites_without_photo.append(str(site.get('id_site'))) - code_communes.append(site.get('code_city_site')) + sites_without_photo.append(str(site.get("id_site"))) + code_communes.append(site.get("code_city_site")) - query_photos = models.TPhoto.query.filter( - models.TPhoto.id_photo.in_(photo_ids) - ) + query_photos = models.TPhoto.query.filter(models.TPhoto.id_photo.in_(photo_ids)) dump_photos = photo_schema.dump(query_photos) # WAHO tordu l'histoire! if len(sites_without_photo): - sql_missing_photos_str = "select distinct on (id_site) p.* from geopaysages.t_photo p join geopaysages.t_observatory o on o.id=p.id_observatory where p.id_site IN (" + ",".join(sites_without_photo) + ") and o.is_published is true order by id_site, filter_date desc" + sql_missing_photos_str = ( + "select distinct on (id_site) p.* from geopaysages.t_photo p join geopaysages.t_observatory o on o.id=p.id_observatory where p.id_site IN (" + + ",".join(sites_without_photo) + + ") and o.is_published is true order by id_site, filter_date desc" + ) sql_missing_photos = text(sql_missing_photos_str) missing_photos_result = db.engine.execute(sql_missing_photos).fetchall() missing_photos = [dict(row) for row in missing_photos_result] for missing_photo in missing_photos: - missing_photo['t_site'] = missing_photo.get('id_site') + missing_photo["t_site"] = missing_photo.get("id_site") dump_photos.append(missing_photo) query_commune = models.Communes.query.filter( @@ -64,131 +67,177 @@ def home(): ) dump_communes = communes_schema.dump(query_commune) for site in sites: - id_site = site.get('id_site') + id_site = site.get("id_site") photo = None try: - photo = next(photo for photo in dump_photos if (photo.get('t_site') == id_site)) + photo = next( + photo for photo in dump_photos if (photo.get("t_site") == id_site) + ) except StopIteration: pass if photo: - site['photo'] = photo.get('path_file_photo') #utils.getMedium(photo).get('output_url') - site['commune'] = next(commune for commune in dump_communes if (commune.get('code_commune') == site.get('code_city_site'))) - - all_sites=site_schema.dump(models.TSite.query.join(models.Observatory).filter(models.TSite.publish_site == True, models.Observatory.is_published == True)) + site["photo"] = photo.get( + "path_file_photo" + ) # utils.getMedium(photo).get('output_url') + site["commune"] = next( + commune + for commune in dump_communes + if (commune.get("code_commune") == site.get("code_city_site")) + ) + + all_sites = site_schema.dump( + models.TSite.query.join(models.Observatory).filter( + models.TSite.publish_site == True, models.Observatory.is_published == True + ) + ) - carousel_photos = [fileName for fileName in os.listdir('/app/static/custom/home-carousel')] - carousel_photos = list(filter(lambda x: x != '.gitkeep', carousel_photos)) + carousel_photos = [ + fileName for fileName in os.listdir("/app/static/custom/home-carousel") + ] + carousel_photos = list(filter(lambda x: x != ".gitkeep", carousel_photos)) - if (utils.isMultiObservatories() == True ) : - observatories = models.Observatory.query.filter(models.Observatory.is_published == True).order_by(models.Observatory.title) + if utils.isMultiObservatories() == True: + observatories = models.Observatory.query.filter( + models.Observatory.is_published == True + ).order_by(models.Observatory.title) dump_observatories = observatory_schema_lite.dump(observatories) col_max = 5 - nb_obs = len(dump_observatories)+1 - nb_rows = math.ceil( nb_obs / col_max ) - nb_cols = math.ceil( nb_obs / nb_rows ) - - - patchwork_options = { - "nb_cols" : nb_cols - } - return render_template('home_multi_obs.jinja', carousel_photos=carousel_photos, observatories=dump_observatories, sites=all_sites, patchwork_options=patchwork_options) + nb_obs = len(dump_observatories) + 1 + nb_rows = math.ceil(nb_obs / col_max) + nb_cols = math.ceil(nb_obs / nb_rows) + + patchwork_options = {"nb_cols": nb_cols} + return render_template( + "home_multi_obs.jinja", + carousel_photos=carousel_photos, + observatories=dump_observatories, + sites=all_sites, + patchwork_options=patchwork_options, + ) + + return render_template( + "home_mono_obs.jinja", + carousel_photos=carousel_photos, + blocks=sites, + sites=all_sites, + ) - return render_template('home_mono_obs.jinja', carousel_photos=carousel_photos, blocks=sites, sites=all_sites) -@main.route('/gallery') +@main.route("/gallery") def gallery(): data = utils.getFiltersData() - return render_template('gallery.jinja', filters=data['filters'], sites=data['sites'], observatories=data['observatories']) + return render_template( + "gallery.jinja", + filters=data["filters"], + sites=data["sites"], + observatories=data["observatories"], + ) -@main.route('/sites/') +@main.route("/sites/") def site(id_site): - get_site_by_id = models.TSite.query.filter_by(id_site = id_site, publish_site = True) - site=site_schema.dump(get_site_by_id) + get_site_by_id = models.TSite.query.filter_by(id_site=id_site, publish_site=True) + site = site_schema.dump(get_site_by_id) if len(site) == 0: return abort(404) site = site[0] - - get_villes = models.Communes.query.filter_by(code_commune = site.get('code_city_site')) - site['ville'] = communes_schema.dump(get_villes)[0] - get_photos_by_site = models.TPhoto.query.filter_by(id_site = id_site, display_gal_photo=True).order_by('filter_date') + get_villes = models.Communes.query.filter_by( + code_commune=site.get("code_city_site") + ) + site["ville"] = communes_schema.dump(get_villes)[0] + + get_photos_by_site = models.TPhoto.query.filter_by( + id_site=id_site, display_gal_photo=True + ).order_by("filter_date") photos = photo_schema.dump(get_photos_by_site) - cor_sthemes_themes = site.get('cor_site_stheme_themes') + cor_sthemes_themes = site.get("cor_site_stheme_themes") cor_list = [] subthemes_list = [] for cor in cor_sthemes_themes: - cor_list.append(cor.get('id_stheme_theme')) + cor_list.append(cor.get("id_stheme_theme")) query = models.CorSthemeTheme.query.filter( - models.CorSthemeTheme.id_stheme_theme.in_(cor_list)) + models.CorSthemeTheme.id_stheme_theme.in_(cor_list) + ) themes_sthemes = themes_sthemes_schema.dump(query) for item in themes_sthemes: - if item.get('dico_stheme').get('id_stheme') not in subthemes_list: - subthemes_list.append(item.get('dico_stheme').get('name_stheme')) - - site['stheme'] = list(set(subthemes_list)) + if item.get("dico_stheme").get("id_stheme") not in subthemes_list: + subthemes_list.append(item.get("dico_stheme").get("name_stheme")) + + site["stheme"] = list(set(subthemes_list)) def getPhoto(photo): captions = [] - licence_photo = photo.get('dico_licence_photo') + licence_photo = photo.get("dico_licence_photo") if licence_photo: - captions.append(licence_photo.get('name_licence_photo')) - caption = ' | '.join(captions) + captions.append(licence_photo.get("name_licence_photo")) + caption = " | ".join(captions) return { - 'id': photo.get('id_photo'), - 'filename': photo.get('path_file_photo'), - 'shot_on': photo.get('filter_date'), - 'date_approx': photo.get('date_photo'), - 'caption': caption + "id": photo.get("id_photo"), + "filename": photo.get("path_file_photo"), + "shot_on": photo.get("filter_date"), + "date_approx": photo.get("date_photo"), + "caption": caption, } photos = [getPhoto(photo) for photo in photos] - - return render_template('site.jinja', site=site, photos=photos, comparator_version=COMPARATOR_VERSION) + + return render_template( + "site.jinja", site=site, photos=photos, comparator_version=COMPARATOR_VERSION + ) -@main.route('/sites//photos/latest') +@main.route("/sites//photos/latest") def site_photos_last(id_site): - get_site_by_id = models.TSite.query.filter_by(id_site = id_site, publish_site = True) - site=site_schema.dump(get_site_by_id) + get_site_by_id = models.TSite.query.filter_by(id_site=id_site, publish_site=True) + site = site_schema.dump(get_site_by_id) if len(site) == 0: return abort(404) site = site[0] - get_photos_by_site = models.TPhoto.query.filter_by(id_site = id_site, display_gal_photo=True).order_by(desc(models.TPhoto.filter_date)).limit(1) + get_photos_by_site = ( + models.TPhoto.query.filter_by(id_site=id_site, display_gal_photo=True) + .order_by(desc(models.TPhoto.filter_date)) + .limit(1) + ) photos = photo_schema.dump(get_photos_by_site) - photo=photos[0] + photo = photos[0] - date_approx = photo.get('date_photo') + date_approx = photo.get("date_photo") if date_approx: - photo['date_display'] = date_approx + photo["date_display"] = date_approx else: - date_obj = datetime.strptime(photo.get('filter_date'), '%Y-%m-%d') - photo['date_display'] = date_obj.strftime('%d-%m-%Y') + date_obj = datetime.strptime(photo.get("filter_date"), "%Y-%m-%d") + photo["date_display"] = date_obj.strftime("%d-%m-%Y") - return render_template('site_photo.jinja', site=site, photo=photo) + return render_template("site_photo.jinja", site=site, photo=photo) -@main.route('/sites') +@main.route("/sites") def sites(): data = utils.getFiltersData() - return render_template('sites.jinja', filters=data['filters'], sites=data['sites'], observatories=data['observatories']) + return render_template( + "sites.jinja", + filters=data["filters"], + sites=data["sites"], + observatories=data["observatories"], + ) -@main.route('/legal-notices') +@main.route("/legal-notices") def legal_notices(): - tpl = utils.getCustomTpl('legal_notices') + tpl = utils.getCustomTpl("legal_notices") if not tpl: return abort(404) - return render_template(tpl) \ No newline at end of file + return render_template(tpl) diff --git a/backend/utils.py b/backend/utils.py index d4c326ef..0890cb8f 100644 --- a/backend/utils.py +++ b/backend/utils.py @@ -21,31 +21,40 @@ site_schema = models.TSiteSchema(many=True) themes_sthemes_schema = models.CorSthemeThemeSchema(many=True) + def getCustomTpl(name): - tpl_local = f'custom/{name}_{get_locale().__str__()}.jinja' - tpl_common = f'custom/{name}.jinja' - if os.path.exists(f'tpl/{tpl_local}'): + tpl_local = f"custom/{name}_{get_locale().__str__()}.jinja" + tpl_common = f"custom/{name}.jinja" + if os.path.exists(f"tpl/{tpl_local}"): return tpl_local - if os.path.exists(f'tpl/{tpl_common}'): + if os.path.exists(f"tpl/{tpl_common}"): return tpl_common return None + def getThumborSignature(url): - key = bytes(os.getenv("THUMBOR_SECURITY_KEY"), 'UTF-8') - msg = bytes(url, 'UTF-8') + key = bytes(os.getenv("THUMBOR_SECURITY_KEY"), "UTF-8") + msg = bytes(url, "UTF-8") h = hmac.new(key, msg, hashlib.sha1) return urlsafe_b64encode(h.digest()).decode("ascii") + def getThumborUrl(params, filename): - if params.startswith('/'): + if params.startswith("/"): params = params[1:] - url = params + '/' + urllib.parse.quote(f'http://backend/static/upload/images/{filename}', safe='') + url = ( + params + + "/" + + urllib.parse.quote(f"http://backend/static/upload/images/{filename}", safe="") + ) signature = getThumborSignature(url) - return f'/thumbor/{signature}/{url}' + return f"/thumbor/{signature}/{url}" + def getRandStr(nb): chars = string.ascii_lowercase + string.digits - return ''.join(random.choice(chars) for i in range(nb)) + return "".join(random.choice(chars) for i in range(nb)) + def getDbConf(): sql = text("SELECT key, value FROM geopaysages.conf") @@ -54,209 +63,240 @@ def getDbConf(): conf = {} for row in rows: try: - conf[row.get('key')] = json.loads(row.get('value')) + conf[row.get("key")] = json.loads(row.get("value")) except Exception as exception: - conf[row.get('key')] = row.get('value') + conf[row.get("key")] = row.get("value") - conf['default_sort_sites'] = conf.get('default_sort_sites', 'name_site') + conf["default_sort_sites"] = conf.get("default_sort_sites", "name_site") return conf + def isMultiObservatories(): # Pourrait passer par un count sql sql = text("SELECT id FROM geopaysages.t_observatory where is_published is true") result = db.engine.execute(sql).fetchall() rows = [dict(row) for row in result] - if len(rows) > 1 : + if len(rows) > 1: return True return False + def getFiltersData(): dbconf = getDbConf() - sites=site_schema.dump(models.TSite.query.join(models.Observatory).filter(models.TSite.publish_site == True, models.Observatory.is_published == True).order_by(dbconf['default_sort_sites'])) + sites = site_schema.dump( + models.TSite.query.join(models.Observatory) + .filter( + models.TSite.publish_site == True, models.Observatory.is_published == True + ) + .order_by(dbconf["default_sort_sites"]) + ) for site in sites: - cor_sthemes_themes = site.get('cor_site_stheme_themes') + cor_sthemes_themes = site.get("cor_site_stheme_themes") cor_list = [] themes_list = [] subthemes_list = [] for cor in cor_sthemes_themes: - cor_list.append(cor.get('id_stheme_theme')) + cor_list.append(cor.get("id_stheme_theme")) query = models.CorSthemeTheme.query.filter( - models.CorSthemeTheme.id_stheme_theme.in_(cor_list)) + models.CorSthemeTheme.id_stheme_theme.in_(cor_list) + ) themes_sthemes = themes_sthemes_schema.dump(query) for item in themes_sthemes: - if item.get('dico_theme').get('id_theme') not in themes_list: - themes_list.append(item.get('dico_theme').get('id_theme')) - if item.get('dico_stheme').get('id_stheme') not in subthemes_list: - subthemes_list.append(item.get('dico_stheme').get('id_stheme')) + if item.get("dico_theme").get("id_theme") not in themes_list: + themes_list.append(item.get("dico_theme").get("id_theme")) + if item.get("dico_stheme").get("id_stheme") not in subthemes_list: + subthemes_list.append(item.get("dico_stheme").get("id_stheme")) - get_photos_by_site = models.TPhoto.query.filter_by( - id_site=site.get('id_site')) + get_photos_by_site = models.TPhoto.query.filter_by(id_site=site.get("id_site")) photos = photo_schema.dump(get_photos_by_site) - site['link'] = url_for('main.site', id_site=site.get('id_site'), _external=True) - site['latlon'] = site.get('geom') - site['themes'] = themes_list - site['subthemes'] = subthemes_list - site['township'] = site.get('code_city_site') + site["link"] = url_for("main.site", id_site=site.get("id_site"), _external=True) + site["latlon"] = site.get("geom") + site["themes"] = themes_list + site["subthemes"] = subthemes_list + site["township"] = site.get("code_city_site") - site['years'] = set() + site["years"] = set() for photo in photos: - year = str(photo.get('filter_date')).split('-')[0] - site['years'].add(year) - photo['year'] = year - site['years'] = list(site['years']) - site['photos'] = photos + year = str(photo.get("filter_date")).split("-")[0] + site["years"].add(year) + photo["year"] = year + site["years"] = list(site["years"]) + site["photos"] = photos subthemes = dicostheme_schema.dump(models.DicoStheme.query.all()) for sub in subthemes: themes_of_subthemes = [] - for item in sub.get('cor_stheme_themes'): - themes_of_subthemes.append(item.get('id_theme')) - sub['themes'] = themes_of_subthemes - - filters = [{ - 'name': 'themes', - 'label': gettext(u'sites.filter.themes'), - 'items': set() - }, { - 'name': 'subthemes', - 'label': gettext(u'sites.filter.subthemes'), - 'items': set() - }, { - 'name': 'township', - 'hideNoMatched': True, - 'label': gettext(u'sites.filter.township'), - 'items': set() - }, { - 'name': 'years', - 'hideNoMatched': True, - 'label': gettext(u'sites.filter.years'), - 'items': set() - }] + for item in sub.get("cor_stheme_themes"): + themes_of_subthemes.append(item.get("id_theme")) + sub["themes"] = themes_of_subthemes + + filters = [ + {"name": "themes", "label": gettext("sites.filter.themes"), "items": set()}, + { + "name": "subthemes", + "label": gettext("sites.filter.subthemes"), + "items": set(), + }, + { + "name": "township", + "hideNoMatched": True, + "label": gettext("sites.filter.township"), + "items": set(), + }, + { + "name": "years", + "hideNoMatched": True, + "label": gettext("sites.filter.years"), + "items": set(), + }, + ] for site in sites: # Compute the prop years - site['years'] = set() - for photo in site.get('photos'): - site['years'].add(photo.get('year')) - site['years'] = list(site['years']) + site["years"] = set() + for photo in site.get("photos"): + site["years"].add(photo.get("year")) + site["years"] = list(site["years"]) for filter in filters: - val = site.get(filter.get('name')) + val = site.get(filter.get("name")) if isinstance(val, (list, set)): - filter.get('items').update(val) + filter.get("items").update(val) else: - filter.get('items').add(val) + filter.get("items").add(val) themes = dicotheme_schema.dump(models.DicoTheme.query.all()) - themes = [{ - 'id': item['id_theme'], - 'label': item['name_theme'], - 'icon': item['icon'], - } for item in themes] - - subthemes = [{ - 'id': item['id_stheme'], - 'label': item['name_stheme'], - 'themes': item['themes'] - } for item in subthemes] + themes = [ + { + "id": item["id_theme"], + "label": item["name_theme"], + "icon": item["icon"], + } + for item in themes + ] + + subthemes = [ + { + "id": item["id_stheme"], + "label": item["name_stheme"], + "themes": item["themes"], + } + for item in subthemes + ] filter_township = [ - filter for filter in filters if filter.get('name') == 'township'][0] - str_map_in = ["'" + township + - "'" for township in filter_township.get('items')] - sql_map_str = "SELECT code_commune AS id, nom_commune AS label FROM geopaysages.communes WHERE code_commune IN (" + ",".join( - str_map_in) + ")" + filter for filter in filters if filter.get("name") == "township" + ][0] + str_map_in = ["'" + township + "'" for township in filter_township.get("items")] + sql_map_str = ( + "SELECT code_commune AS id, nom_commune AS label FROM geopaysages.communes WHERE code_commune IN (" + + ",".join(str_map_in) + + ")" + ) sql_map = text(sql_map_str) townships_result = db.engine.execute(sql_map).fetchall() townships = [dict(row) for row in townships_result] for site in sites: - site['ville'] = next(township for township in townships if township.get('id') == site.get('township')) - - dbs = { - 'themes': themes, - 'subthemes': subthemes, - 'township': townships - } - + site["ville"] = next( + township + for township in townships + if township.get("id") == site.get("township") + ) + + dbs = {"themes": themes, "subthemes": subthemes, "township": townships} + photo_ids = [] sites_without_photo = [] for site in sites: - photo_id = site.get('main_photo') + photo_id = site.get("main_photo") if photo_id: - photo_ids.append(site.get('main_photo')) + photo_ids.append(site.get("main_photo")) else: - sites_without_photo.append(str(site.get('id_site'))) + sites_without_photo.append(str(site.get("id_site"))) - query_photos = models.TPhoto.query.filter( - models.TPhoto.id_photo.in_(photo_ids) - ) + query_photos = models.TPhoto.query.filter(models.TPhoto.id_photo.in_(photo_ids)) dump_photos = photo_schema.dump(query_photos) if len(sites_without_photo): - sql_missing_photos_str = "select distinct on (id_site) * from geopaysages.t_photo where id_site IN (" + ",".join(sites_without_photo) + ") order by id_site, filter_date desc" + sql_missing_photos_str = ( + "select distinct on (id_site) * from geopaysages.t_photo where id_site IN (" + + ",".join(sites_without_photo) + + ") order by id_site, filter_date desc" + ) sql_missing_photos = text(sql_missing_photos_str) missing_photos_result = db.engine.execute(sql_missing_photos).fetchall() missing_photos = [dict(row) for row in missing_photos_result] for missing_photo in missing_photos: - missing_photo['t_site'] = missing_photo.get('id_site') + missing_photo["t_site"] = missing_photo.get("id_site") dump_photos.append(missing_photo) for site in sites: - id_site = site.get('id_site') + id_site = site.get("id_site") try: - photo = next(photo for photo in dump_photos if (photo.get('t_site') == id_site)) - site['photo'] = photo.get('path_file_photo') #getThumbnail(photo).get('output_url') + photo = next( + photo for photo in dump_photos if (photo.get("t_site") == id_site) + ) + site["photo"] = photo.get( + "path_file_photo" + ) # getThumbnail(photo).get('output_url') except StopIteration: pass def getItem(name, id): - return next(item for item in dbs.get(name) if item.get('id') == id) + return next(item for item in dbs.get(name) if item.get("id") == id) for filter in filters: - if (filter.get('name') == 'years'): - filter['items'] = [{ - 'label': str(year), - 'id': year - } for year in filter.get('items')] - filter['items'] = sorted(filter['items'], key=lambda k: k['label'], reverse=True) + if filter.get("name") == "years": + filter["items"] = [ + {"label": str(year), "id": year} for year in filter.get("items") + ] + filter["items"] = sorted( + filter["items"], key=lambda k: k["label"], reverse=True + ) else: - filter['items'] = [getItem(filter.get('name'), item_id) - for item_id in filter.get('items')] - filter['items'] = sorted(filter['items'], key=lambda k: k['label']) + filter["items"] = [ + getItem(filter.get("name"), item_id) for item_id in filter.get("items") + ] + filter["items"] = sorted(filter["items"], key=lambda k: k["label"]) observatories = [] for site in sites: try: - next((item for item in observatories if item["id"] == site['id_observatory'])) + next( + (item for item in observatories if item["id"] == site["id_observatory"]) + ) except StopIteration: - observatory_row = models.Observatory.query.filter_by(id = site['id_observatory']) - observatory=observatory_schema.dump(observatory_row) - observatory=observatory[0] - observatories.append({ - 'id': site['id_observatory'], - 'label': site['observatory']['title'], - 'data': { - 'geom': observatory['geom'], - 'color': observatory['color'], - 'logo': observatory['logo'] + observatory_row = models.Observatory.query.filter_by( + id=site["id_observatory"] + ) + observatory = observatory_schema.dump(observatory_row) + observatory = observatory[0] + observatories.append( + { + "id": site["id_observatory"], + "label": site["observatory"]["title"], + "data": { + "geom": observatory["geom"], + "color": observatory["color"], + "logo": observatory["logo"], + }, } - }) + ) - observatories = sorted(observatories, key=lambda d: d['label']) + observatories = sorted(observatories, key=lambda d: d["label"]) if len(observatories) > 1: - filters.insert(0, { - 'name': 'id_observatory', - 'label': gettext(u'sites.filter.obervatories'), - 'items': observatories - }) - - return { - 'filters': filters, - 'sites': sites, - 'observatories': observatories - } \ No newline at end of file + filters.insert( + 0, + { + "name": "id_observatory", + "label": gettext("sites.filter.obervatories"), + "items": observatories, + }, + ) + + return {"filters": filters, "sites": sites, "observatories": observatories}