diff --git a/VERSION b/VERSION index c9a9c229..c2c731ab 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.99.3-dev \ No newline at end of file +0.99.4-dev diff --git a/backend/dev-requirements.txt b/backend/dev-requirements.txt deleted file mode 100644 index 07f7b7da..00000000 --- a/backend/dev-requirements.txt +++ /dev/null @@ -1,22 +0,0 @@ --i https://pypi.org/simple -astroid==2.0.4 -autopep8==1.4.2 -future==0.17.1 -isort==4.3.4 -jedi==0.13.3 -lazy-object-proxy==1.3.1 -mccabe==0.6.1 -parso==0.4.0 -pluggy==0.9.0 -pycodestyle==2.4.0 -pydocstyle==3.0.0 -pyflakes==2.1.1 -pylint==2.1.1 -python-jsonrpc-server==0.1.2 -python-language-server[all]==0.26.1 -rope==0.14.0 -six==1.11.0 -snowballstemmer==1.2.1 -typed-ast==1.1.0 -wrapt==1.10.11 -yapf==0.27.0 diff --git a/backend/gncitizen/core/badges/routes.py b/backend/gncitizen/core/badges/routes.py index 8c0b2906..3e51dcd8 100644 --- a/backend/gncitizen/core/badges/routes.py +++ b/backend/gncitizen/core/badges/routes.py @@ -1,12 +1,14 @@ -from flask import Flask, request, Blueprint, Response, jsonify, current_app -from gncitizen.utils.sqlalchemy import json_resp -from gncitizen.core.observations.models import ObservationModel +from calendar import monthrange +from datetime import datetime, timedelta + +from flask import Blueprint, Flask, Response, current_app, jsonify, request +from sqlalchemy.sql.expression import func +from utils_flask_sqla.response import json_resp + from gncitizen.core.commons.models import ProgramsModel -from gncitizen.core.users.models import UserModel +from gncitizen.core.observations.models import ObservationModel from gncitizen.core.taxonomy.models import Taxref -from sqlalchemy.sql.expression import func -from datetime import date, datetime, timedelta -from calendar import monthrange +from gncitizen.core.users.models import UserModel from server import db badges_api = Blueprint("badges", __name__) @@ -35,7 +37,8 @@ def get_rewards(id): total_obs = total_obs + item.nb_obs taxon_classe_query = ( db.session.query( - Taxref.classe.label("classe"), func.count(Taxref.famille).label("nb_obs") + Taxref.classe.label("classe"), + func.count(Taxref.famille).label("nb_obs"), ) .join(ObservationModel, Taxref.cd_nom == ObservationModel.cd_nom) .filter(ObservationModel.id_role == id) @@ -46,7 +49,8 @@ def get_rewards(id): taxon_famille_query = ( db.session.query( - Taxref.famille.label("famille"), func.count(Taxref.famille).label("nb_obs") + Taxref.famille.label("famille"), + func.count(Taxref.famille).label("nb_obs"), ) .join(ObservationModel, Taxref.cd_nom == ObservationModel.cd_nom) .filter(ObservationModel.id_role == id) @@ -59,7 +63,9 @@ def get_rewards(id): user = UserModel.query.filter(UserModel.id_user == id).one() result = user.as_secured_dict(True) user_date_create = result["timestamp_create"] - user_date_create = datetime.strptime(user_date_create, "%Y-%m-%dT%H:%M:%S.%f") + user_date_create = datetime.strptime( + user_date_create, "%Y-%m-%dT%H:%M:%S.%f" + ) for reward in rewards: diff --git a/backend/gncitizen/core/commons/admin.py b/backend/gncitizen/core/commons/admin.py index 35c99939..ddefd066 100644 --- a/backend/gncitizen/core/commons/admin.py +++ b/backend/gncitizen/core/commons/admin.py @@ -1,42 +1,23 @@ #!/usr/bin/python3 # -*- coding:utf-8 -*- - -import json -import urllib.parse - import requests - -from flask import Blueprint, current_app, request, flash -from flask_admin.contrib.geoa import ModelView -from flask_admin.form import SecureForm +from flask import current_app, flash +from flask_admin.contrib.sqla.view import ModelView from flask_admin.form.upload import FileUploadField -from flask_ckeditor import CKEditorField - -from geoalchemy2.shape import from_shape -from geojson import FeatureCollection -from shapely.geometry import MultiPolygon, asShape +from flask_admin.model.form import InlineFormAdmin +from flask_ckeditor import CKEditorField from wtforms import SelectField -import json -from flask_admin.contrib.sqla.view import ModelView -from jinja2 import Markup - -from gncitizen.core.users.models import UserModel from gncitizen.core.sites.models import CorProgramSiteTypeModel -from gncitizen.utils.env import admin, MEDIA_DIR -from gncitizen.utils.errors import GeonatureApiError -from gncitizen.utils.sqlalchemy import json_resp -from server import db - -from .models import ProgramsModel from gncitizen.core.taxonomy.models import BibListes -import os +from gncitizen.utils.admin import ( + CustomJSONField, + CustomTileView, + json_formatter, +) +from gncitizen.utils.env import MEDIA_DIR - -def json_formatter(view, context, model, name): - value = getattr(model, name) - json_value = json.dumps(value, ensure_ascii=False, indent=2) - return Markup("
{}".format(json_value)) +logger = current_app.logger def taxonomy_lists(): @@ -64,9 +45,6 @@ def taxonomy_lists(): return taxonomy_lists -from flask_admin.model.form import InlineFormAdmin - - class CorProgramSiteTypeModelInlineForm(InlineFormAdmin): form_columns = ("site_type",) @@ -74,20 +52,24 @@ class CorProgramSiteTypeModelInlineForm(InlineFormAdmin): class ProjectView(ModelView): form_overrides = {"long_desc": CKEditorField} create_template = "edit.html" - create_template = "edit.html" edit_template = "edit.html" form_excluded_columns = ["timestamp_create", "timestamp_update"] - column_exclude_list = ['long_desc','short_desc'] + column_exclude_list = ["long_desc", "short_desc"] class ProgramView(ModelView): - # form_base_class = SecureForm form_overrides = {"long_desc": CKEditorField, "taxonomy_list": SelectField} form_args = {"taxonomy_list": {"choices": taxonomy_lists(), "coerce": int}} create_template = "edit.html" edit_template = "edit.html" form_excluded_columns = ["timestamp_create", "timestamp_update"] - column_exclude_list = ['long_desc','form_message','short_desc','image','logo'] + column_exclude_list = [ + "long_desc", + "form_message", + "short_desc", + "image", + "logo", + ] inline_models = [ ( CorProgramSiteTypeModel, @@ -100,6 +82,7 @@ class ProgramView(ModelView): class CustomFormView(ModelView): + form_overrides = {"json_schema": CustomJSONField} column_formatters = { "json_schema": json_formatter, } @@ -107,15 +90,19 @@ class CustomFormView(ModelView): class UserView(ModelView): column_exclude_list = ["password"] - form_excluded_columns = ["timestamp_create", "timestamp_update", "password"] + form_excluded_columns = [ + "timestamp_create", + "timestamp_update", + "password", + ] def get_geom_file_path(obj, file_data): return "geometries/{}".format(file_data.filename) -class GeometryView(ModelView): - column_exclude_list = ["geom"] +class GeometryView(CustomTileView): + # column_exclude_list = ["geom"] form_excluded_columns = ["timestamp_create", "timestamp_update"] form_overrides = dict(geom_file=FileUploadField) form_args = dict( @@ -124,7 +111,7 @@ class GeometryView(ModelView): description=""" Le fichier contenant la géométrie de la zone doit être au format geojson ou kml.
" + trace + ""), 500 raise e - current_app.logger.critical("[get_program_observations] Error: %s", str(e)) + current_app.logger.critical( + "[get_program_observations] Error: %s", str(e) + ) return {"message": str(e)}, 400 @@ -596,7 +645,7 @@ def get_all_observations() -> Union[FeatureCollection, Tuple[Dict, int]]: responses: 200: description: A list of all species lists - """ + """ try: observations = ( db.session.query( @@ -607,7 +656,11 @@ def get_all_observations() -> Union[FeatureCollection, Tuple[Dict, int]]: LAreas.area_code, ) .filter(ProgramsModel.is_active) - .join(LAreas, LAreas.id_area == ObservationModel.municipality, isouter=True) + .join( + LAreas, + LAreas.id_area == ObservationModel.municipality, + isouter=True, + ) .join( ProgramsModel, ProgramsModel.id_program == ObservationModel.id_program, @@ -615,7 +668,8 @@ def get_all_observations() -> Union[FeatureCollection, Tuple[Dict, int]]: ) .join( ObservationMediaModel, - ObservationMediaModel.id_data_source == ObservationModel.id_observation, + ObservationMediaModel.id_data_source + == ObservationModel.id_observation, isouter=True, ) .join( @@ -623,10 +677,16 @@ def get_all_observations() -> Union[FeatureCollection, Tuple[Dict, int]]: ObservationMediaModel.id_media == MediaModel.id_media, isouter=True, ) - .join(UserModel, ObservationModel.id_role == UserModel.id_user, full=True) + .join( + UserModel, + ObservationModel.id_role == UserModel.id_user, + full=True, + ) ) - observations = observations.order_by(desc(ObservationModel.timestamp_create)) + observations = observations.order_by( + desc(ObservationModel.timestamp_create) + ) # current_app.logger.debug(str(observations)) observations = observations.all() @@ -636,7 +696,9 @@ def get_all_observations() -> Union[FeatureCollection, Tuple[Dict, int]]: taxon_repository = [] for program in programs: taxhub_list_id = ( - ProgramsModel.query.filter_by(id_program=program.id_program) + ProgramsModel.query.filter_by( + id_program=program.id_program + ) .one() .taxonomy_list ) @@ -657,12 +719,18 @@ def get_all_observations() -> Union[FeatureCollection, Tuple[Dict, int]]: } # Observer - feature["properties"]["observer"] = {"username": observation.username} + feature["properties"]["observer"] = { + "username": observation.username + } # Observer submitted media feature["properties"]["image"] = ( "/".join( - ["/api", current_app.config["MEDIA_FOLDER"], observation.image,] + [ + "/api", + current_app.config["MEDIA_FOLDER"], + observation.image, + ] ) if observation.image else None @@ -694,7 +762,8 @@ def get_all_observations() -> Union[FeatureCollection, Tuple[Dict, int]]: taxon = next( taxon for taxon in taxon_repository - if taxon and taxon["cd_nom"] == feature["properties"]["cd_nom"] + if taxon + and taxon["cd_nom"] == feature["properties"]["cd_nom"] ) feature["properties"]["taxref"] = taxon["taxref"] feature["properties"]["medias"] = taxon["medias"] @@ -716,16 +785,16 @@ def get_all_observations() -> Union[FeatureCollection, Tuple[Dict, int]]: # trace = traceback.format_exc() # return("
" + trace + ""), 500 raise e - current_app.logger.critical("[get_program_observations] Error: %s", str(e)) + current_app.logger.critical( + "[get_program_observations] Error: %s", str(e) + ) return {"message": str(e)}, 400 - - @obstax_api.route("/dev_rewards/
{}".format(json_value)) + + +class CustomJSONField(JSONField): + """Prettify JSON fields in flask admin editor""" + + def _value(self): + if self.raw_data: + return self.raw_data[0] + elif self.data: + return json.dumps(self.data, ensure_ascii=False, indent=2) + else: + return "" + + +class CustomTileView(ModelView): + tile_layer_url = "a.tile.openstreetmap.org/{z}/{x}/{y}.png" + tile_layer_attribution = "some string or html goes here" diff --git a/backend/gncitizen/utils/env.py b/backend/gncitizen/utils/env.py index 8c6fb26f..0e08badb 100644 --- a/backend/gncitizen/utils/env.py +++ b/backend/gncitizen/utils/env.py @@ -1,18 +1,16 @@ -import os import logging +import os import sys from pathlib import Path -from urllib.parse import urlparse -from flask import current_app from flasgger import Swagger +from flask_admin import Admin +from flask_ckeditor import CKEditor from flask_jwt_extended import JWTManager from flask_sqlalchemy import SQLAlchemy -from flask_admin import Admin -from flask_ckeditor import CKEditor, CKEditorField -from gncitizen.utils.toml import load_toml from gncitizen import __version__ +from gncitizen.utils.toml import load_toml ROOT_DIR = Path(__file__).absolute().parent.parent.parent.parent BACKEND_DIR = ROOT_DIR / "backend" @@ -28,11 +26,11 @@ def get_config_file_path(config_file=None): - """ Return the config file path by checking several sources + """Return the config file path by checking several sources - 1 - Parameter passed - 2 - GNCITIZEN_CONFIG_FILE env var - 3 - Default config file value + 1 - Parameter passed + 2 - GNCITIZEN_CONFIG_FILE env var + 3 - Default config file value """ config_file = config_file or os.environ.get("GNCITIZEN_CONFIG_FILE") return Path(config_file or DEFAULT_CONFIG_FILE) @@ -69,12 +67,18 @@ def valid_api_url(url): "info": { "title": f"API Doc {app_conf['appName']}", "description": f"Backend API for {app_conf['appName']}, source code available at https://github.com/PnX-SI/GeoNature-citizen", - "contact": {"url": "https://github.com/PnX-SI/GeoNature-citizen",}, + "contact": { + "url": "https://github.com/PnX-SI/GeoNature-citizen", + }, "version": __version__, }, "components": { "securitySchemes": { - "bearerAuth": {"type": "http", "scheme": "bearer", "bearerFormat": "JWT",} + "bearerAuth": { + "type": "http", + "scheme": "bearer", + "bearerFormat": "JWT", + } } }, } @@ -113,7 +117,7 @@ def valid_api_url(url): def list_and_import_gnc_modules(app, mod_path=GNC_EXTERNAL_MODULE): """ - Get all the module enabled from gn_commons.t_modules + Get all the module enabled from gn_commons.t_modules """ # with app.app_context(): # data = db.session.query(TModules).filter( @@ -131,7 +135,6 @@ def list_and_import_gnc_modules(app, mod_path=GNC_EXTERNAL_MODULE): module_parent_dir = str(module_path.parent) module_name = "{}.config.conf_schema_toml".format(module_path.name) sys.path.insert(0, module_parent_dir) - module = __import__(module_name, globals=globals()) module_name = "{}.backend.blueprint".format(module_path.name) module_blueprint = __import__(module_name, globals=globals()) sys.path.pop(0) diff --git a/backend/gncitizen/utils/errors.py b/backend/gncitizen/utils/errors.py index 903edd80..5e54e10b 100644 --- a/backend/gncitizen/utils/errors.py +++ b/backend/gncitizen/utils/errors.py @@ -16,8 +16,8 @@ class GNCModuleInstallError(GeoNatureError): class ConfigError(GeoNatureError): """ - Configuration error class - Quand un fichier de configuration n'est pas conforme aux attentes + Configuration error class + Quand un fichier de configuration n'est pas conforme aux attentes """ def __init__(self, file, value): @@ -50,7 +50,9 @@ def to_dict(self): def __str__(self): message = "Error {}, Message: {}, raised error: {}" - return message.format(self.status_code, self.message, self.__class__.__name__) + return message.format( + self.status_code, self.message, self.__class__.__name__ + ) class InsufficientRightsError(GeonatureApiError): diff --git a/backend/gncitizen/utils/geo.py b/backend/gncitizen/utils/geo.py index 3efd79ab..c9bec9c2 100644 --- a/backend/gncitizen/utils/geo.py +++ b/backend/gncitizen/utils/geo.py @@ -2,11 +2,10 @@ # -*- coding: utf-8 -*- from flask import current_app - -from gncitizen.core.ref_geo.models import LAreas, BibAreasTypes -from gncitizen.utils.env import db from geoalchemy2 import func +from gncitizen.core.ref_geo.models import BibAreasTypes, LAreas +from gncitizen.utils.env import db # Get municipality id # newobs.municipality = get_municipality_id_from_wkb_point( @@ -24,7 +23,9 @@ def get_municipality_id_from_wkb(wkb): :rtype: int """ try: - srid = db.session.query(func.Find_SRID("ref_geo", "l_areas", "geom")).one()[0] + srid = db.session.query( + func.Find_SRID("ref_geo", "l_areas", "geom") + ).one()[0] current_app.logger.debug( "[get_municipality_id_from_wkb_point] SRID: {}".format(srid) ) diff --git a/backend/gncitizen/utils/init_data.py b/backend/gncitizen/utils/init_data.py index 6d012870..9f112174 100644 --- a/backend/gncitizen/utils/init_data.py +++ b/backend/gncitizen/utils/init_data.py @@ -1,9 +1,10 @@ #!/usr/bin/python """Init db datas""" -from gncitizen.core.commons.models import TModules from flask import current_app +from gncitizen.core.commons.models import TModules + def create_schemas(db): """create db schemas at first launch @@ -19,7 +20,12 @@ def create_schemas(db): def populate_modules(db): - if db.session.query(TModules).filter(TModules.label == "observations").count() == 0: + if ( + db.session.query(TModules) + .filter(TModules.label == "observations") + .count() + == 0 + ): current_app.logger.info('Insert "Observations" into modules table') data = { "id_module": 1, @@ -30,7 +36,10 @@ def populate_modules(db): m = TModules(**data) db.session.add(m) db.session.commit() - if db.session.query(TModules).filter(TModules.label == "sites").count() == 0: + if ( + db.session.query(TModules).filter(TModules.label == "sites").count() + == 0 + ): current_app.logger.info('Insert "Sites" into modules table') data = { "id_module": 2, diff --git a/backend/gncitizen/utils/jwt.py b/backend/gncitizen/utils/jwt.py index 01054794..71480606 100644 --- a/backend/gncitizen/utils/jwt.py +++ b/backend/gncitizen/utils/jwt.py @@ -5,11 +5,29 @@ from functools import wraps -from flask import jsonify +from flask import current_app, jsonify from flask_jwt_extended import get_jwt_identity +from sqlalchemy import func, or_ from gncitizen.core.users.models import UserModel +logger = current_app.logger + + +def get_user_if_exists() -> UserModel: + """[summary]""" + current_user = get_jwt_identity() + return ( + UserModel.query.filter( + or_( + UserModel.email == current_user, + UserModel.username == current_user, + ) + ).one() + if current_user + else None + ) + def get_id_role_if_exists(): """get id_role if exists from ``get_jwt_identity()`` @@ -17,12 +35,8 @@ def get_id_role_if_exists(): :return: user id :rtype: int """ - if get_jwt_identity() is not None: - current_user = get_jwt_identity() - id_role = UserModel.query.filter_by(email=current_user).first().id_user - else: - id_role = None - return id_role + logger.debug(f"GET_USER_IF_EXISTS() {get_user_if_exists()}") + return get_user_if_exists().id_user if get_user_if_exists() else None def admin_required(func): @@ -37,10 +51,9 @@ def admin_required(func): @wraps(func) def decorated_function(*args, **kwargs): - current_user = get_jwt_identity() + current_user = get_user_if_exists() try: - is_admin = UserModel.query.filter_by(email=current_user).first().admin - if not is_admin: + if not current_user.admin: return {"message": "Special authorization required"}, 403 return func(*args, **kwargs) except Exception as e: diff --git a/backend/gncitizen/utils/mail_check.py b/backend/gncitizen/utils/mail_check.py index abdf2e52..c4a1ec31 100644 --- a/backend/gncitizen/utils/mail_check.py +++ b/backend/gncitizen/utils/mail_check.py @@ -1,35 +1,37 @@ -from flask import current_app -from itsdangerous import URLSafeTimedSerializer import smtplib from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText +from flask import current_app +from itsdangerous import URLSafeTimedSerializer -def confirm_user_email(newuser): - token = generate_confirmation_token(newuser.email) +def send_user_email( + subject: str, to: str, plain_message: str = None, html_message: str = None +): msg = MIMEMultipart("alternative") - msg["Subject"] = current_app.config["CONFIRM_EMAIL"]["SUBJECT"] - msg["From"] = current_app.config["CONFIRM_EMAIL"]["FROM"] - msg["To"] = newuser.email - - # Check URL_APPLICATION: - url_application = current_app.config["URL_APPLICATION"] - if url_application[-1] == "/": - url_application = url_application - else: - url_application = url_application + "/" - print("url_application", url_application) - activate_url = url_application + "confirmEmail/" + token - # Record the MIME text/html. - msg_body = MIMEText( - current_app.config["CONFIRM_EMAIL"]["HTML_TEMPLATE"].format( - activate_url=activate_url - ), - "html", + msg["Subject"] = subject + msg["From"] = current_app.config["MAIL"]["MAIL_AUTH_LOGIN"] + msg["To"] = to + plain_msg = MIMEText( + html_message, + "plain", ) + msg.attach(plain_msg) - msg.attach(msg_body) + if plain_message: + plain_msg = MIMEText( + plain_message, + "html", + ) + msg.attach(plain_msg) + + if html_message: + html_msg = MIMEText( + html_message, + "html", + ) + msg.attach(html_msg) try: if current_app.config["MAIL"]["MAIL_USE_SSL"]: @@ -51,24 +53,66 @@ def confirm_user_email(newuser): str(current_app.config["MAIL"]["MAIL_AUTH_PASSWD"]), ) server.sendmail( - current_app.config["CONFIRM_EMAIL"]["FROM"], newuser.email, msg.as_string() + current_app.config["MAIL"]["MAIL_AUTH_LOGIN"], + to, + msg.as_string(), ) server.quit() + except Exception as e: + current_app.logger.warning("send email failled. %s", str(e)) + return {"message": """ send email failled: "{}".""".format(str(e))} + + +def confirm_user_email(newuser, with_confirm_link=True): + + token = generate_confirmation_token(newuser.email) + subject = current_app.config["CONFIRM_EMAIL"]["SUBJECT"] + to = newuser.email + + # Check URL_APPLICATION: + url_application = current_app.config["URL_APPLICATION"] + if url_application[-1] == "/": + url_application = url_application + else: + url_application = url_application + "/" + print("url_application", url_application) + activate_url = url_application + "confirmEmail/" + token + + # Record the MIME text/html. + template = current_app.config["CONFIRM_EMAIL"]["HTML_TEMPLATE"] + if not with_confirm_link: + template = current_app.config["CONFIRM_EMAIL"][ + "NO_VALIDATION_HTML_TEMPLATE" + ] + try: + send_user_email( + subject, + to, + html_message=template.format(activate_url=activate_url), + ) + except Exception as e: current_app.logger.warning("send confirm_email failled. %s", str(e)) - return {"message": """ send confirm_email failled: "{}".""".format(str(e))} + return { + "message": """ send confirm_email failled: "{}".""".format(str(e)) + } def generate_confirmation_token(email): serializer = URLSafeTimedSerializer(current_app.config["SECRET_KEY"]) - return serializer.dumps(email, salt=current_app.config["CONFIRM_MAIL_SALT"]) + return serializer.dumps( + email, salt=current_app.config["CONFIRM_MAIL_SALT"] + ) def confirm_token(token): serializer = URLSafeTimedSerializer(current_app.config["SECRET_KEY"]) try: - email = serializer.loads(token, salt=current_app.config["CONFIRM_MAIL_SALT"],) + email = serializer.loads( + token, + salt=current_app.config["CONFIRM_MAIL_SALT"], + ) except: raise Exception("error token") return email diff --git a/backend/gncitizen/utils/media.py b/backend/gncitizen/utils/media.py index 7b67ca17..72dbd02f 100644 --- a/backend/gncitizen/utils/media.py +++ b/backend/gncitizen/utils/media.py @@ -10,9 +10,8 @@ from werkzeug.datastructures import FileStorage from gncitizen.core.commons.models import MediaModel -from gncitizen.utils.env import MEDIA_DIR +from gncitizen.utils.env import ALLOWED_EXTENSIONS, MEDIA_DIR from gncitizen.utils.errors import GeonatureApiError -from gncitizen.utils.env import ALLOWED_EXTENSIONS from server import db @@ -26,12 +25,19 @@ def allowed_file(filename): :rtype: bool """ - if "." in filename and filename.rsplit(".", 1)[1].lower() in ALLOWED_EXTENSIONS: + if ( + "." in filename + and filename.rsplit(".", 1)[1].lower() in ALLOWED_EXTENSIONS + ): return True def save_upload_files( - request_file, prefix="none", cdnom="0", id_data_source=None, matching_model=None + request_file, + prefix="none", + cdnom="0", + id_data_source=None, + matching_model=None, ): """Save files on server and filenames in db from POST request @@ -79,12 +85,16 @@ def save_upload_files( ) ) ext = filename.rsplit(".", 1)[1].lower() - timestamp = datetime.datetime.utcnow().strftime("%Y%m%d_%H%M%S") + timestamp = datetime.datetime.utcnow().strftime( + "%Y%m%d_%H%M%S" + ) filename = "{}_{}_{}_{}.{}".format( prefix, str(cdnom), i, timestamp, ext ) current_app.logger.debug( - "[save_upload_files] new filename : {}".format(filename) + "[save_upload_files] new filename : {}".format( + filename + ) ) file.save(os.path.join(str(MEDIA_DIR), filename)) # Save media filename to Database @@ -97,12 +107,16 @@ def save_upload_files( db.session.commit() id_media = newmedia.id_media current_app.logger.debug( - "[save_upload_files] id_media : ".format(str(id_media)) + "[save_upload_files] id_media : ".format( + str(id_media) + ) ) # return id_media except Exception as e: current_app.logger.debug( - "[save_upload_files] ERROR MEDIAMODEL: {}".format(e) + "[save_upload_files] ERROR MEDIAMODEL: {}".format( + e + ) ) raise GeonatureApiError(e) # Save id_media in matching table @@ -118,13 +132,17 @@ def save_upload_files( ) except Exception as e: current_app.logger.debug( - "[save_upload_files] ERROR MATCH MEDIA: {}".format(e) + "[save_upload_files] ERROR MATCH MEDIA: {}".format( + e + ) ) raise GeonatureApiError(e) # log current_app.logger.debug( - "[save_upload_files] Fichier {} enregistré".format(filename) + "[save_upload_files] Fichier {} enregistré".format( + filename + ) ) files.append(filename) diff --git a/backend/gncitizen/utils/rewards/__init__.py b/backend/gncitizen/utils/rewards/__init__.py index edcb11d1..c9111533 100644 --- a/backend/gncitizen/utils/rewards/__init__.py +++ b/backend/gncitizen/utils/rewards/__init__.py @@ -1,15 +1,16 @@ import datetime + from flask import current_app + from .classifier import Classifier +from .queries import get_stats from .rules import ( attendance_rule, - seniority_rule, program_attendance_rule, program_date_bounds_rule, recognition_rule, + seniority_rule, ) -from .queries import get_stats - default_ruleset = { attendance_rule, diff --git a/backend/gncitizen/utils/rewards/classifier.py b/backend/gncitizen/utils/rewards/classifier.py index 6827a741..6321a320 100644 --- a/backend/gncitizen/utils/rewards/classifier.py +++ b/backend/gncitizen/utils/rewards/classifier.py @@ -1,4 +1,5 @@ from typing import Any, List, Optional + from .rule import Rule diff --git a/backend/gncitizen/utils/rewards/models.py b/backend/gncitizen/utils/rewards/models.py index 9088cb5a..4b2033d9 100644 --- a/backend/gncitizen/utils/rewards/models.py +++ b/backend/gncitizen/utils/rewards/models.py @@ -64,7 +64,10 @@ def config_duration2timestamp(s: Optional[str]) -> Optional[Timestamp]: seniority_model = OrderedDict( reversed( sorted( - [(k, config_duration2timestamp(v)) for k, v in conf["seniority"].items()], + [ + (k, config_duration2timestamp(v)) + for k, v in conf["seniority"].items() + ], key=lambda t: t[1], ) ) @@ -89,7 +92,10 @@ def config_duration2timestamp(s: Optional[str]) -> Optional[Timestamp]: "specialization": conf["recognition"][i]["specialization"], "attendance": OrderedDict( reversed( - sorted(conf["recognition"][i]["attendance"].items(), key=lambda t: t[1]) + sorted( + conf["recognition"][i]["attendance"].items(), + key=lambda t: t[1], + ) ) ), } diff --git a/backend/gncitizen/utils/rewards/queries.py b/backend/gncitizen/utils/rewards/queries.py index 5fc7a5af..5ab216bb 100644 --- a/backend/gncitizen/utils/rewards/queries.py +++ b/backend/gncitizen/utils/rewards/queries.py @@ -1,28 +1,18 @@ import logging -from gncitizen.core.observations.models import ( - # ObservationMediaModel, + +from gncitizen.core.commons.models import ProgramsModel # MediaModel, +from gncitizen.core.observations.models import ( # ObservationMediaModel, ObservationModel, ) -from gncitizen.core.users.models import ( - # ObserverMixinModel, - # UserGroupsModel, - # GroupsModel, - UserModel, -) -from gncitizen.core.taxonomy.models import ( - # BibNoms, - # BibListes, - # CorNomListe, - # TMedias, +from gncitizen.core.taxonomy.models import ( # BibNoms,; BibListes,; CorNomListe,; TMedias, Taxref, ) -from .models import recognition_model - -from gncitizen.core.commons.models import ( - # MediaModel, - ProgramsModel, +from gncitizen.core.users.models import ( # ObserverMixinModel,; UserGroupsModel,; GroupsModel, + UserModel, ) +from .models import recognition_model + logger = logging.getLogger() # TEST DATA @@ -41,8 +31,12 @@ def attendance_data(role_id): # Count observations the current user submitted program wise def program_attendance(attendance_data): return [ - attendance_data.filter(ObservationModel.id_program == program.id_program) - for program in ProgramsModel.query.distinct(ProgramsModel.id_program).all() + attendance_data.filter( + ObservationModel.id_program == program.id_program + ) + for program in ProgramsModel.query.distinct( + ProgramsModel.id_program + ).all() ] @@ -61,13 +55,16 @@ def filter_class_or_order(model, query): def get_occ(attendance_data): - base_query = attendance_data.join(Taxref, Taxref.cd_nom == ObservationModel.cd_nom) + base_query = attendance_data.join( + Taxref, Taxref.cd_nom == ObservationModel.cd_nom + ) # .filter( # ObservationModel.id_role == role_id, # ObservationModel.id_program == program_id # ) return [ - filter_class_or_order(item, base_query).count() for item in recognition_model + filter_class_or_order(item, base_query).count() + for item in recognition_model ] diff --git a/backend/gncitizen/utils/rewards/rules.py b/backend/gncitizen/utils/rewards/rules.py index c5b47315..360c4c62 100644 --- a/backend/gncitizen/utils/rewards/rules.py +++ b/backend/gncitizen/utils/rewards/rules.py @@ -1,13 +1,14 @@ import logging -from typing import Union, List -from .rule import Rule +from typing import List, Union + from .models import ( attendance_model, - seniority_model, program_attendance_model, program_date_bounds_model, recognition_model, + seniority_model, ) +from .rule import Rule # ATTENDANCE @@ -62,7 +63,9 @@ def program_attendance_action(data) -> str: ] -program_attendance_rule = Rule(program_attendance_condition, program_attendance_action) +program_attendance_rule = Rule( + program_attendance_condition, program_attendance_action +) # PROGRAM_DATE_BOUNDS @@ -99,7 +102,9 @@ def recognition_action(data) -> Union[List[str], str]: for category, threshold in recognition_model[i]["attendance"].items(): if q and q[i] >= threshold: r.append( - "{}.{}".format(recognition_model[i]["specialization"], category) + "{}.{}".format( + recognition_model[i]["specialization"], category + ) ) return r if len(r) > 0 else "Recognition.None" diff --git a/backend/gncitizen/utils/sqlalchemy.py b/backend/gncitizen/utils/sqlalchemy.py deleted file mode 100644 index 6f50a1bd..00000000 --- a/backend/gncitizen/utils/sqlalchemy.py +++ /dev/null @@ -1,215 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -"""A module to manage database and datas with sqlalchemy""" - -import json -from functools import wraps - -from flask import Response, current_app -from geoalchemy2.shape import from_shape, to_shape -from geojson import Feature -from shapely.geometry import asShape -from werkzeug.datastructures import Headers - - -""" - Liste des types de données sql qui - nécessite une sérialisation particulière en - @TODO MANQUE FLOAT -""" -SERIALIZERS = { - "date": lambda x: str(x) if x else None, - "datetime": lambda x: str(x) if x else None, - "time": lambda x: str(x) if x else None, - "timestamp": lambda x: str(x) if x else None, - "uuid": lambda x: str(x) if x else None, - "numeric": lambda x: str(x) if x else None, - "enum": lambda x: x.name if x else None, -} - - - -def geom_from_geojson(data): - """this function transform geojson geometry into `WKB\ -
Bonjour,
Nous vous confirmons que votre compte a bien été créé.
@@ -47,6 +49,10 @@ MEDIA_FOLDER = 'media'Nous vous souhaitons la bienvenue sur notre site.
Bien à vous.
''' + NO_VALIDATION_HTML_TEMPLATE = '''Bonjour,
Nous vous confirmons que votre compte a bien été créé.
+Nous vous souhaitons la bienvenue sur notre site.
Bien à vous.
+ ''' [MAIL] MAIL_USE_SSL = false diff --git a/data/maintenance/index.html b/data/maintenance/index.html new file mode 100644 index 00000000..71e0b659 --- /dev/null +++ b/data/maintenance/index.html @@ -0,0 +1,64 @@ + + + + +{{ d.name }} | +{{ d.value }} | +
Aucune donnée | +
{{ d.name }} | -{{ d.value }} | -
{{ d.name }} | +{{ d.value }} | +