diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000..89acae8486 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,18 @@ +[submodule "backend/dependencies/UsersHub-authentification-module"] + path = backend/dependencies/UsersHub-authentification-module + url = https://github.com/PnX-SI/UsersHub-authentification-module +[submodule "backend/dependencies/Nomenclature-api-module"] + path = backend/dependencies/Nomenclature-api-module + url = https://github.com/PnX-SI/Nomenclature-api-module +[submodule "backend/dependencies/Habref-api-module"] + path = backend/dependencies/Habref-api-module + url = https://github.com/PnX-SI/Habref-api-module +[submodule "backend/dependencies/Utils-Flask-SQLAlchemy"] + path = backend/dependencies/Utils-Flask-SQLAlchemy + url = https://github.com/PnX-SI/Utils-Flask-SQLAlchemy +[submodule "backend/dependencies/TaxHub"] + path = backend/dependencies/TaxHub + url = https://github.com/PnX-SI/TaxHub +[submodule "backend/dependencies/Utils-Flask-SQLAlchemy-Geo"] + path = backend/dependencies/Utils-Flask-SQLAlchemy-Geo + url = https://github.com/PnX-SI/Utils-Flask-SQLAlchemy-Geo diff --git a/VERSION b/VERSION index 37c2961c24..2c9b4ef42e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.7.2 +2.7.3 diff --git a/backend/dependencies/Habref-api-module b/backend/dependencies/Habref-api-module new file mode 160000 index 0000000000..36bc1cd3d8 --- /dev/null +++ b/backend/dependencies/Habref-api-module @@ -0,0 +1 @@ +Subproject commit 36bc1cd3d8de2605221950784c15e25c6a424fd4 diff --git a/backend/dependencies/Nomenclature-api-module b/backend/dependencies/Nomenclature-api-module new file mode 160000 index 0000000000..871e410851 --- /dev/null +++ b/backend/dependencies/Nomenclature-api-module @@ -0,0 +1 @@ +Subproject commit 871e4108511be889f9b4836355f281dac998d665 diff --git a/backend/dependencies/TaxHub b/backend/dependencies/TaxHub new file mode 160000 index 0000000000..c88e94845a --- /dev/null +++ b/backend/dependencies/TaxHub @@ -0,0 +1 @@ +Subproject commit c88e94845a85041842d800d59391a7ec4c84679a diff --git a/backend/dependencies/UsersHub-authentification-module b/backend/dependencies/UsersHub-authentification-module new file mode 160000 index 0000000000..2d21e8785d --- /dev/null +++ b/backend/dependencies/UsersHub-authentification-module @@ -0,0 +1 @@ +Subproject commit 2d21e8785d6459bfc1490bba63ad536110b32452 diff --git a/backend/dependencies/Utils-Flask-SQLAlchemy b/backend/dependencies/Utils-Flask-SQLAlchemy new file mode 160000 index 0000000000..16bf68c5f1 --- /dev/null +++ b/backend/dependencies/Utils-Flask-SQLAlchemy @@ -0,0 +1 @@ +Subproject commit 16bf68c5f1b68f25be9cd65b12555693c2350572 diff --git a/backend/dependencies/Utils-Flask-SQLAlchemy-Geo b/backend/dependencies/Utils-Flask-SQLAlchemy-Geo new file mode 160000 index 0000000000..83d4a4240a --- /dev/null +++ b/backend/dependencies/Utils-Flask-SQLAlchemy-Geo @@ -0,0 +1 @@ +Subproject commit 83d4a4240a06bf0bbbc954af05d5e32b77ba0953 diff --git a/backend/geonature/app.py b/backend/geonature/app.py index d0d9707743..0578490b30 100755 --- a/backend/geonature/app.py +++ b/backend/geonature/app.py @@ -12,7 +12,7 @@ from pkg_resources import iter_entry_points from geonature.utils.config import config -from geonature.utils.env import MAIL, DB, MA, migrate +from geonature.utils.env import MAIL, DB, MA, migrate, BACKEND_DIR from geonature.utils.logs import config_loggers from geonature.utils.module import import_backend_enabled_modules @@ -50,7 +50,7 @@ def create_app(with_external_mods=True, with_flask_admin=True): # Bind app to DB DB.init_app(app) - migrate.init_app(app, DB) + migrate.init_app(app, DB, directory=BACKEND_DIR / 'geonature' / 'migrations') MAIL.init_app(app) @@ -161,4 +161,4 @@ def on_before_models_committed(sender, changes): app.config[module_config['MODULE_CODE']] = module_config app.register_blueprint(module_blueprint, url_prefix=module_config['MODULE_URL']) _app = app - return app \ No newline at end of file + return app diff --git a/backend/geonature/core/command/create_gn_module.py b/backend/geonature/core/command/create_gn_module.py index 8403bdd376..44c5e41353 100644 --- a/backend/geonature/core/command/create_gn_module.py +++ b/backend/geonature/core/command/create_gn_module.py @@ -11,13 +11,17 @@ import sys import logging import subprocess - +from pkg_resources import load_entry_point from pathlib import Path import click +from click import ClickException +from flask import current_app +from flask_migrate import upgrade as db_upgrade from sqlalchemy.orm.exc import NoResultFound -from geonature.utils.env import DB, DEFAULT_CONFIG_FILE +from geonature.utils.env import DB, db, DEFAULT_CONFIG_FILE, GN_EXTERNAL_MODULE +from geonature.utils.module import get_dist_from_code from geonature.utils.command import ( build_geonature_front, @@ -48,6 +52,69 @@ log = logging.getLogger(__name__) +@main.command() +@click.argument("module_path") +@click.argument("module_code") +@click.option("--build", type=bool, required=False, default=True) +def install_packaged_gn_module(module_path, module_code, build): + # install python package and dependencies + subprocess.run(f"pip install -e {module_path}", shell=True, check=True) + + # load python package + module_dist = get_dist_from_code(module_code) + if not module_dist: + raise ClickException(f"Unable to load module with code {module_code}") + + # add module to database + try: + module_picto = load_entry_point(module_dist, 'gn_module', 'picto') + except ImportError: + module_picto = "fa-puzzle-piece" + module_object = TModules.query.filter_by(module_code=module_code).one() + if not module_object: + module_object = TModules( + module_code=module_code, + module_label=module_code.lower(), + module_path=module_code.lower(), + module_target="_self", + module_picto=module_picto, + active_frontend=True, + active_backend=True, + ) + db.session.add(module_object) + else: + module_object.module_picto=module_picto + db.session.merge(module_object) + db.session.commit() + + db_upgrade(revision=module_code.lower()) + + # symlink module in exernal module directory + module_symlink = GN_EXTERNAL_MODULE / module_code.lower() + if os.path.exists(module_symlink): + target = os.readlink(module_symlink) + if os.path.abspath(module_path) != target: + raise ClickException(f"Module symlink has wrong target '{target}'") + else: + os.symlink(os.path.abspath(module_path), module_symlink) + + ### Frontend + # creation du lien symbolique des assets externes + enable_frontend = create_external_assets_symlink( + module_path, module_code.lower() + ) + + install_frontend_dependencies(module_path) + # generation du fichier tsconfig.app.json + tsconfig_app_templating(app=current_app) + # generation du routing du frontend + frontend_routes_templating(app=current_app) + # generation du fichier de configuration du frontend + create_module_config(current_app, module_code, build=False) + if build: + # Rebuild the frontend + build_geonature_front(rebuild_sass=True) + @main.command() @click.argument("module_path") diff --git a/backend/geonature/core/command/main.py b/backend/geonature/core/command/main.py index 4bbcd44d1b..29bb41c065 100644 --- a/backend/geonature/core/command/main.py +++ b/backend/geonature/core/command/main.py @@ -6,6 +6,8 @@ from os import environ import click +from flask import current_app +from flask.cli import run_command from geonature.utils.env import ( DEFAULT_CONFIG_FILE, @@ -23,16 +25,18 @@ update_app_configuration, ) from geonature import create_app +from geonature.core.gn_meta.mtd.mtd_utils import import_all_dataset_af_and_actors # from rq import Queue, Connection, Worker # import redis from flask import Flask +from flask.cli import FlaskGroup log = logging.getLogger() -@click.group() +@click.group(cls=FlaskGroup, create_app=create_app) @click.version_option(version=GEONATURE_VERSION) @click.pass_context def main(ctx): @@ -78,7 +82,8 @@ def start_gunicorn(uri, worker): @main.command() @click.option("--host", default="0.0.0.0") @click.option("--port", default=8000) -def dev_back(host, port): +@click.pass_context +def dev_back(ctx, host, port): """ Lance l'api du backend avec flask @@ -90,8 +95,7 @@ def dev_back(host, port): """ if not environ.get('FLASK_ENV'): environ['FLASK_ENV'] = 'development' - app = create_app() - app.run(host=host, port=int(port)) + ctx.invoke(run_command, host=host, port=port) @main.command() @@ -171,7 +175,4 @@ def import_jdd_from_mtd(table_name): """ Import les JDD et CA (et acters associé) à partir d'une table (ou vue) listant les UUID des JDD dans MTD """ - app = create_app() - with app.app_context(): - from geonature.core.gn_meta.mtd.mtd_utils import import_all_dataset_af_and_actors - import_all_dataset_af_and_actors(table_name) + import_all_dataset_af_and_actors(table_name) diff --git a/backend/geonature/core/gn_meta/mtd/__init__.py b/backend/geonature/core/gn_meta/mtd/__init__.py index e69de29bb2..0adc7d0ad6 100644 --- a/backend/geonature/core/gn_meta/mtd/__init__.py +++ b/backend/geonature/core/gn_meta/mtd/__init__.py @@ -0,0 +1,162 @@ +from urllib.parse import urljoin + +import requests +from lxml import etree + +from flask import current_app +from sqlalchemy.dialects.postgresql import insert as pg_insert +from sqlalchemy.sql import func + +from geonature.utils.config import config +from geonature.utils.env import db +from geonature.core.gn_meta.models import TAcquisitionFramework, TDatasets, CorAcquisitionFrameworkActor, CorDatasetActor +from geonature.core.auth.routes import insert_user_and_org + +from pypnusershub.db.models import User, Organisme + +from .xml_parser import parse_acquisition_framework, parse_jdd_xml +from .mtd_utils import create_cor_object_actors, NOMENCLATURE_MAPPING + + +class MTDInstanceApi: + af_path = '/mtd/cadre/export/xml/GetRecordsByInstanceId?id={ID_INSTANCE}' + ds_path = '/mtd/cadre/jdd/export/xml/GetRecordsByInstanceId?id={ID_INSTANCE}' + + def __init__(self, api_endpoint, instance_id): + self.api_endpoint = api_endpoint + self.instance_id = instance_id + + def _get_xml(self, path): + url = urljoin(self.api_endpoint, path) + url = url.format(ID_INSTANCE=self.instance_id) + response = requests.get(url) + assert(response.status_code == 200) + return response.content + + def _get_af_xml(self): + return self._get_xml(self.af_path) + + def get_af_list(self): + xml = self._get_af_xml() + root = etree.fromstring(xml) + af_iter = root.iterfind(".//{http://inpn.mnhn.fr/mtd}CadreAcquisition") + af_list = [] + for af in af_iter: + af_list.append(parse_acquisition_framework(af)) + return af_list + + def _get_ds_xml(self): + return self._get_xml(self.ds_path) + + def get_ds_list(self): + xml = self._get_ds_xml() + return parse_jdd_xml(xml) + + +class INPNCAS: + base_url = config['CAS']['CAS_USER_WS']['BASE_URL'] + user = config['CAS']['CAS_USER_WS']['ID'] + password = config['CAS']['CAS_USER_WS']['PASSWORD'] + id_search_path = 'rechercheParId/{user_id}' + + @classmethod + def _get_user_json(cls, user_id): + url = urljoin(cls.base_url, cls.id_search_path) + url = url.format(user_id=user_id) + response = requests.get(url, auth=(cls.user, cls.password)) + return response.json() + + @classmethod + def get_user(cls, user_id): + return cls._get_user_json(user_id) + + +def add_unexisting_digitizer(id_digitizer): + if not db.session.query(User.query.filter_by(id_role=id_digitizer).exists()).scalar(): + user = INPNCAS.get_user(id_digitizer) + insert_user_and_org(user) + + +def associate_actors(actors, CorActor, pk_name, pk_value): + for actor in actors: + if not actor['uuid_organism']: + continue + statement = pg_insert(Organisme) \ + .values( + uuid_organisme=actor['uuid_organism'], + nom_organisme=actor['organism'], + email_organisme=actor['email'], + ).on_conflict_do_update( + index_elements=['uuid_organisme'], + set_=dict( + nom_organisme=actor['organism'], + email_organisme=actor['email'], + ), + ) + db.session.execute(statement) + # retrieve organism id + org = Organisme.query \ + .filter_by(uuid_organisme=actor['uuid_organism']) \ + .first() + statement = pg_insert(CorActor) \ + .values( + #id_acquisition_framework=af.id_acquisition_framework, + id_organism=org.id_organisme, + id_nomenclature_actor_role=func.ref_nomenclatures.get_id_nomenclature( + "ROLE_ACTEUR", actor["actor_role"] + ), + **{pk_name: pk_value}, + ).on_conflict_do_nothing( + index_elements=[pk_name, 'id_organism', 'id_nomenclature_actor_role'], + ) + db.session.execute(statement) + + +def sync_af_and_ds(): + cas_api = INPNCAS() + mtd_api = MTDInstanceApi( + config["MTD_API_ENDPOINT"], + config['MTD']['ID_INSTANCE_FILTER']) + + af_list = mtd_api.get_af_list() + for af in af_list: + add_unexisting_digitizer(af['id_digitizer']) + actors = af.pop('actors') + statement = pg_insert(TAcquisitionFramework) \ + .values(**af) \ + .on_conflict_do_update( + index_elements=['unique_acquisition_framework_id'], + set_=af) + db.session.execute(statement) + af = TAcquisitionFramework.query \ + .filter_by(unique_acquisition_framework_id=af['unique_acquisition_framework_id']) \ + .first() + associate_actors(actors, CorAcquisitionFrameworkActor, + 'id_acquisition_framework', af.id_acquisition_framework) + db.session.commit() + + ds_list = mtd_api.get_ds_list() + for ds in ds_list: + add_unexisting_digitizer(ds['id_digitizer']) + actors = ds.pop('actors') + af_uuid = ds.pop('uuid_acquisition_framework') + af = TAcquisitionFramework.query.filter_by(unique_acquisition_framework_id=af_uuid).first() + if af is None: + continue + ds['id_acquisition_framework'] = af.id_acquisition_framework + ds = { k: func.ref_nomenclatures.get_id_nomenclature(NOMENCLATURE_MAPPING[k], v) + if k.startswith('id_nomenclature') else v + for k, v in ds.items() + if v is not None } + statement = pg_insert(TDatasets) \ + .values(**ds) \ + .on_conflict_do_update( + index_elements=['unique_dataset_id'], + set_=ds) + db.session.execute(statement) + ds = TDatasets.query \ + .filter_by(unique_dataset_id=ds['unique_dataset_id']) \ + .first() + associate_actors(actors, CorDatasetActor, + 'id_dataset', ds.id_dataset) + db.session.commit() diff --git a/backend/geonature/core/gn_meta/mtd/mtd_webservice.py b/backend/geonature/core/gn_meta/mtd/mtd_webservice.py index d8143cc656..e960104e21 100644 --- a/backend/geonature/core/gn_meta/mtd/mtd_webservice.py +++ b/backend/geonature/core/gn_meta/mtd/mtd_webservice.py @@ -1,9 +1,8 @@ from geonature.utils import utilsrequests from geonature.utils.errors import GeonatureApiError +from geonature.utils.config import config -from flask import current_app - -api_endpoint = current_app.config["MTD_API_ENDPOINT"] +api_endpoint = config["MTD_API_ENDPOINT"] def get_acquisition_framework(uuid_af): @@ -49,4 +48,4 @@ def get_jdd_by_uuid(uuid): assert r.status_code == 200 except AssertionError: print(f'NO JDD FOUND FOR UUID {uuid}') - return r.content \ No newline at end of file + return r.content diff --git a/backend/geonature/core/gn_meta/mtd/xml_parser.py b/backend/geonature/core/gn_meta/mtd/xml_parser.py index 3b15697590..c4ebf419f8 100644 --- a/backend/geonature/core/gn_meta/mtd/xml_parser.py +++ b/backend/geonature/core/gn_meta/mtd/xml_parser.py @@ -4,7 +4,9 @@ from flask import current_app from lxml import etree as ET -namespace = current_app.config["XML_NAMESPACE"] +from geonature.utils.config import config + +namespace = config["XML_NAMESPACE"] _xml_parser = ET.XMLParser(ns_clean=True, recover=True, encoding="utf-8") @@ -65,6 +67,10 @@ def parse_acquisition_framwork_xml(xml): """ root = ET.fromstring(xml, parser=_xml_parser) ca = root.find(".//" + namespace + "CadreAcquisition") + return parse_acquisition_framework(ca) + + +def parse_acquisition_framework(ca): # We extract all the required informations from the different tags of the XML file ca_uuid = get_tag_content(ca, "identifiantCadre") diff --git a/backend/geonature/core/gn_meta/routes.py b/backend/geonature/core/gn_meta/routes.py index b025e311dc..2a89b30e0f 100644 --- a/backend/geonature/core/gn_meta/routes.py +++ b/backend/geonature/core/gn_meta/routes.py @@ -5,6 +5,7 @@ import json import logging import threading +import click from pathlib import Path @@ -69,6 +70,7 @@ from geonature.core.gn_permissions import decorators as permissions from geonature.core.gn_permissions.tools import cruved_scope_for_user_in_module from geonature.core.gn_meta.mtd import mtd_utils +from .mtd import sync_af_and_ds as mtd_sync_af_and_ds import geonature.utils.filemanager as fm import geonature.utils.utilsmails as mail from geonature.utils.errors import GeonatureApiError @@ -76,7 +78,7 @@ -routes = Blueprint("gn_meta", __name__) +routes = Blueprint("gn_meta", __name__, cli_group='metadata') # get the root logger log = logging.getLogger() @@ -965,8 +967,6 @@ def get_acquisition_framework_stats(info_role, id_acquisition_framework): + str(dataset_ids).strip("[]") + ")" ) - for obj in objectif_nom: - af.cor_objectifs.append(obj) nb_habitat = DB.engine.execute(text(query)).first()[0] @@ -1140,3 +1140,8 @@ def post_jdd_from_user_id(id_user=None, id_organism=None): .. :quickref: Metadata; """ return mtd_utils.post_jdd_from_user(id_user=id_user, id_organism=id_organism) + + +@routes.cli.command() +def mtd_sync(): + mtd_sync_af_and_ds() diff --git a/backend/geonature/core/gn_synthese/routes.py b/backend/geonature/core/gn_synthese/routes.py index 3506a6f0a0..8090322dd0 100644 --- a/backend/geonature/core/gn_synthese/routes.py +++ b/backend/geonature/core/gn_synthese/routes.py @@ -748,11 +748,7 @@ def getDefaultsNomenclatures(): ) if len(types) > 0: q = q.filter(DefaultsNomenclaturesValue.mnemonique_type.in_(tuple(types))) - try: - data = q.all() - except Exception: - DB.session.rollback() - raise + data = q.all() if not data: return {"message": "not found"}, 404 return {d[0]: d[1] for d in data} diff --git a/backend/geonature/core/users/routes.py b/backend/geonature/core/users/routes.py index 52d074c68c..13ad11be68 100644 --- a/backend/geonature/core/users/routes.py +++ b/backend/geonature/core/users/routes.py @@ -15,6 +15,7 @@ CorRole, TListes, ) +from geonature.utils.config import config from pypnusershub.db.models import Organisme as BibOrganismes from geonature.core.users.register_post_actions import function_dict from pypnusershub.db.models import User @@ -27,12 +28,11 @@ routes = Blueprint("users", __name__, template_folder="templates") log = logging.getLogger() s = requests.Session() -config = current_app.config # configuration of post_request actions for registrations -current_app.config["after_USERSHUB_request"] = function_dict +config["after_USERSHUB_request"] = function_dict @routes.route("/menu/", methods=["GET"]) diff --git a/backend/geonature/utils/config_schema.py b/backend/geonature/utils/config_schema.py index 7595855ac4..dbfa7ae946 100644 --- a/backend/geonature/utils/config_schema.py +++ b/backend/geonature/utils/config_schema.py @@ -45,6 +45,7 @@ def _check_email(self, value): class CasUserSchemaConf(Schema): URL = fields.Url(missing="https://inpn.mnhn.fr/authentication/information") + BASE_URL = fields.Url(missing="https://inpn.mnhn.fr/authentication/") ID = fields.String(missing="mon_id") PASSWORD = fields.String(missing="mon_pass") @@ -144,7 +145,8 @@ class MetadataConfig(Schema): CLOSED_MODAL_LABEL = fields.String(missing="Fermer un cadre d'acquisition") CLOSED_MODAL_CONTENT = fields.String(missing="""L'action de fermeture est irréversible. Il ne sera plus possible d'ajouter des jeux de données au cadre d'acquisition par la suite.""") - + CD_NOMENCLATURE_ROLE_TYPE_DS = fields.List(fields.Str(), missing=[]) + CD_NOMENCLATURE_ROLE_TYPE_AF = fields.List(fields.Str(), missing=[]) # class a utiliser pour les paramètres que l'on ne veut pas passer au frontend diff --git a/backend/geonature/utils/env.py b/backend/geonature/utils/env.py index 3f32d88f42..3e9eee200a 100644 --- a/backend/geonature/utils/env.py +++ b/backend/geonature/utils/env.py @@ -33,6 +33,7 @@ os.environ['FLASK_SQLALCHEMY_DB'] = 'geonature.utils.env.DB' os.environ['FLASK_MARSHMALLOW'] = 'geonature.utils.env.MA' DB = SQLAlchemy() +db = DB MA = Marshmallow() migrate = Migrate() diff --git a/backend/geonature/utils/module.py b/backend/geonature/utils/module.py index acc158dc4b..dee48b1469 100644 --- a/backend/geonature/utils/module.py +++ b/backend/geonature/utils/module.py @@ -52,11 +52,15 @@ def import_packaged_module(module_dist, module_object): 'FRONTEND_PATH': frontend_path, } - module_schema = load_entry_point(module_dist, 'gn_module', 'config_schema') - config_path = os.environ.get(f'GEONATURE_{module_object.module_code}_CONFIG_FILE') - if not config_path: # fallback to legacy conf path guessing - config_path = str(module_dir / 'config/conf_gn_module.toml') - module_config.update(load_and_validate_toml(config_path, module_schema)) + try: + module_schema = load_entry_point(module_dist, 'gn_module', 'config_schema') + except ImportError: + pass + else: + config_path = os.environ.get(f'GEONATURE_{module_object.module_code}_CONFIG_FILE') + if not config_path: # fallback to legacy conf path guessing + config_path = str(module_dir / 'config/conf_gn_module.toml') + module_config.update(load_and_validate_toml(config_path, module_schema)) blueprint_entry_point = get_entry_info(module_dist, 'gn_module', 'blueprint') if blueprint_entry_point: diff --git a/backend/requirements-dev.txt b/backend/requirements-dev.txt index 63db44070a..a1addabe15 100644 --- a/backend/requirements-dev.txt +++ b/backend/requirements-dev.txt @@ -1,12 +1,5 @@ -r requirements-common.txt -git+https://github.com/PnX-SI/UsersHub-authentification-module@develop#egg=pypnusershub -git+https://github.com/PnX-SI/Nomenclature-api-module@develop#egg=pypnnomenclature -git+https://github.com/PnX-SI/Habref-api-module@develop#egg=pypn_habref_api -git+https://github.com/PnX-SI/TaxHub@develop#egg=taxhub -git+https://github.com/PnX-SI/Utils-Flask-SQLAlchemy@develop#egg=utils-flask-sqlalchemy -git+https://github.com/PnX-SI/Utils-Flask-SQLAlchemy-Geo@develop#egg=utils-flask-sqlalchemy-geo - alabaster==0.7.10 attrs==19.3.0 babel==2.5.3 @@ -42,6 +35,7 @@ pygments==2.7.4 pytest-cov==2.5.1 pytest-flask==0.15.1 pytest-pythonpath==0.7.1 +pytest-mock==3.6.1 pytest==6.1.2 pytz==2017.3 pylint==2.6.0 diff --git a/backend/requirements-submodules.txt b/backend/requirements-submodules.txt new file mode 100644 index 0000000000..226a2666da --- /dev/null +++ b/backend/requirements-submodules.txt @@ -0,0 +1,6 @@ +-e file:dependencies/UsersHub-authentification-module#egg=pypnusershub +-e file:dependencies/Nomenclature-api-module#egg=pypnnomenclature +-e file:dependencies/Habref-api-module#egg=pypn_habref_api +-e file:dependencies/TaxHub#egg=taxhub +-e file:dependencies/Utils-Flask-SQLAlchemy#egg=utils-flask-sqlalchemy +-e file:dependencies/Utils-Flask-SQLAlchemy-Geo#egg=utils-flask-sqlalchemy-geo diff --git a/backend/requirements.txt b/backend/requirements.txt index 1fb8e792ac..7d2673cc03 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -1,7 +1,7 @@ -r requirements-common.txt -pypnusershub@https://github.com/PnX-SI/UsersHub-authentification-module/archive/1.4.6.zip +pypnusershub@https://github.com/PnX-SI/UsersHub-authentification-module/archive/1.4.7.zip pypnnomenclature@https://github.com/PnX-SI/Nomenclature-api-module/archive/1.3.8.zip pypn_habref_api@https://github.com/PnX-SI/Habref-api-module/archive/0.1.6.zip -utils-flask-sqlalchemy-geo@https://github.com/PnX-SI/Utils-Flask-SQLAlchemy-Geo/archive/0.2.0.zip +utils-flask-sqlalchemy-geo@https://github.com/PnX-SI/Utils-Flask-SQLAlchemy-Geo/archive/0.2.1.zip utils-flask-sqlalchemy@https://github.com/PnX-SI/Utils-Flask-SQLAlchemy/archive/0.2.3.zip diff --git a/config/default_config.toml.example b/config/default_config.toml.example index 697ed8aec3..462e17322e 100644 --- a/config/default_config.toml.example +++ b/config/default_config.toml.example @@ -131,6 +131,7 @@ MAIL_ON_ERROR = false [CAS.CAS_USER_WS] URL = "https://inpn2.mnhn.fr/authentication/information" + BASE_URL = "https://inpn2.mnhn.fr/authentication/" ID = "mon_id" PASSWORD = "mon_pass" @@ -508,3 +509,6 @@ MAIL_ON_ERROR = false MAIL_CONTENT_AF_CLOSED_GREETINGS = "" CLOSED_MODAL_LABEL = "Dépôt d'un cadre d'acquisition" CLOSED_MODAL_CONTENT = L'action de dépôt est une action irréversible. Il ne sera plus possible d'ajouter des jeux de données au cadre d'acquisition par la suite. Vous ne pourrez pas supprimer votre dépôt. " + # liste des types de role à afficher sur les formulaires JDD et CA + CD_NOMENCLATURE_ROLE_TYPE_DS = ["2"] + CD_NOMENCLATURE_ROLE_TYPE_AF = ["3"] \ No newline at end of file diff --git a/contrib/occtax/backend/blueprint.py b/contrib/occtax/backend/blueprint.py index 625e8cd213..3e95c21f02 100644 --- a/contrib/occtax/backend/blueprint.py +++ b/contrib/occtax/backend/blueprint.py @@ -386,7 +386,6 @@ def insertOrUpdateOneReleve(info_role): # Check if user can add a releve in the current dataset allowed = releve.user_is_in_dataset_actor(info_role) if not allowed: - print('PASSE LA ?????') raise Forbidden( "User {} has no right in dataset {}".format( info_role.id_role, releve.id_dataset @@ -702,11 +701,7 @@ def getDefaultNomenclatures(): ) if len(types) > 0: q = q.filter(DefaultNomenclaturesValue.mnemonique_type.in_(tuple(types))) - try: - data = q.all() - except Exception: - DB.session.rollback() - raise + data = q.all() if not data: return {"message": "not found"}, 404 return {d[0]: d[1] for d in data} diff --git a/docs/CHANGELOG.rst b/docs/CHANGELOG.rst index 4f949a1ef5..fe493157b4 100644 --- a/docs/CHANGELOG.rst +++ b/docs/CHANGELOG.rst @@ -2,6 +2,31 @@ CHANGELOG ========= +2.7.3 (2021-07-22) +------------------ + +**🚀 Nouveautés** + +* Métadonnées : ajout des paramètres ``CD_NOMENCLATURE_ROLE_TYPE_DS`` et ``CD_NOMENCLATURE_ROLE_TYPE_AF`` pour limiter les rôles utilisables au niveau des jeux de données et des cadres d'acquisition (#1417) +* Ajout de la commande ``mtd_sync`` qui permet de synchroniser les métadonnées de toute une instance depuis le flux MTD du SINP + +**🐛 Corrections** + +* Correction de l'affichage des jeux de données sur les fiches des cadres d'acquisition (#1410) +* Doc : Précision des OS supportés (Debian 10 uniquement en production) + +**💻 Développement** + +* Support des commandes Flask au niveau de la commande ``geonature`` (``run``, ``db``, ``routes``, ``shell``...) +* Ajout des sous-modules en tant que dépendances +* Ajout d'une commande ``install_packaged_gn_module`` + +**⚠️ Notes de version** + +Si vous mettez à jour GeoNature : + +* Vous pouvez passer directement à cette version mais en suivant les notes des versions intermédiaires + 2.7.2 (2021-07-05) ------------------ @@ -236,7 +261,6 @@ Si vous mettez à jour GeoNature : * Suivez la procédure classique de mise à jour de GeoNature (http://docs.geonature.fr/installation-standalone.html#mise-a-jour-de-l-application) * Si vous utilisez Occtax-mobile, vous pouvez modifier la valeur du nouveau paramètre ``gn_commons.t_parameters.occtaxmobile_area_type`` pour lui indiquer le code du type de zonage que vous utilisez pour les unités géographiques (mailles de 5km par défaut) * Si vous disposez du module d'import, vous devez le mettre à jour en version 1.1.1 ->>>>>>> develop 2.5.5 (2020-11-19) ------------------ diff --git a/docs/admin-manual.rst b/docs/admin-manual.rst index 229bae7b47..f2bd0ed627 100644 --- a/docs/admin-manual.rst +++ b/docs/admin-manual.rst @@ -1084,19 +1084,23 @@ Etapes : - Attribuer le rôle 1, 'lecteur' 2/ Configuration GeoNature : - - Reporter identifiant et mot de passe dans le fichier de configuration de GeoNature -``` -$ cd config -$ nano geonature_config.toml -``` -`PUBLIC_LOGIN = 'public'` -`PUBLIC_PASSWORD = 'public'` - - - Mettre à jour la configuration de GeoNature -``` -$ source backend/venv/bin/activate -$ geonature update_configuration -``` + - Reporter identifiant et mot de passe dans le fichier de configuration de GeoNature + +.. code-block:: + + $ cd config + $ nano geonature_config.toml + PUBLIC_LOGIN = 'public' + PUBLIC_PASSWORD = 'public' +.. + + - Mettre à jour la configuration de GeoNature + +.. code-block:: + + $ source backend/venv/bin/activate + $ geonature update_configuration +.. A ce moment là, cet utilisateur a tous les droits sur GeoNature. Il s'agit donc de gérer ses permissions dans GeoNature même. @@ -1324,7 +1328,7 @@ L'administration des droits des utilisateurs pour le module Occtax se fait dans Module Admin -"""""""""""" +------------- Administration des champs additionnels ************************************** @@ -1349,13 +1353,13 @@ Un champ additionnel est définit par: Exemples de configuration : - Un champs type "select" : -.. image :: https://github.com/PnX-SI/GeoNature/blob/cc2f86a0fa6d9cd81e1a9926b05c5b5fc3039d2b/docs/images/select_exemple.png +.. image :: https://raw.githubusercontent.com/PnX-SI/GeoNature/cc2f86a0fa6d9cd81e1a9926b05c5b5fc3039d2b/docs/images/select_exemple.png - Un champs type "multiselect" (la clé "value" est obligatoire dans le dictionnaire de valeurs) : -.. image :: https://github.com/PnX-SI/GeoNature/blob/cc2f86a0fa6d9cd81e1a9926b05c5b5fc3039d2b/docs/images/multiselect3.png +.. image :: https://raw.githubusercontent.com/PnX-SI/GeoNature/cc2f86a0fa6d9cd81e1a9926b05c5b5fc3039d2b/docs/images/multiselect3.png - Un champs type "html". C'est un champs de type "présentation", aucune valeur ne sera enregistré en base de données pour ce champs : -.. image :: https://github.com/PnX-SI/GeoNature/blob/cc2f86a0fa6d9cd81e1a9926b05c5b5fc3039d2b/docs/images/html1.png +.. image :: https://raw.githubusercontent.com/PnX-SI/GeoNature/cc2f86a0fa6d9cd81e1a9926b05c5b5fc3039d2b/docs/images/html1.png Module OCCHAB diff --git a/docs/development.rst b/docs/development.rst index 87dd21795c..8cfb2cac7c 100644 --- a/docs/development.rst +++ b/docs/development.rst @@ -16,7 +16,7 @@ Mainteneurs : - Theo LECHEMIA (PnEcrins) : Frontend / Angular - Camille MONCHICOURT (PnEcrins) : Documentation / Gestion du projet -.. image :: http://geonature.fr/docs/img/developpement/geonature-techno.png +.. image :: https://geonature.fr/docs/img/developpement/geonature-techno.png API --- @@ -49,6 +49,7 @@ Release Pour sortir une nouvelle version de GeoNature : - Faites les éventuelles Releases des dépendances (UsersHub, TaxHub, UsersHub-authentification-module, Nomenclature-api-module, GeoNature-atlas) +- Assurez-vous que les sous-modules git de GeoNature pointent sur les bonnes versions des dépendances - Mettez à jour la version de GeoNature et éventuellement des dépendances dans ``install/install_all/install_all.ini``, ``config/settings.ini.sample``, ``backend/requirements.txt`` - Complétez le fichier ``docs/CHANGELOG.rst`` (en comparant les branches https://github.com/PnX-SI/GeoNature/compare/develop) et dater la version à sortir - Mettez à jour le fichier ``VERSION`` @@ -65,7 +66,7 @@ Mettre à jour le ``ref_geo`` à partir des données IGN scan express : - Télécharger le dernier millesime : http://professionnels.ign.fr/adminexpress - Intégrer le fichier Shape dans la BDD grâce à QGIS dans une table nommée ``ref_geo.temp_fr_municipalities`` - Générer le SQL de création de la table : ``pg_dump --table=ref_geo.temp_fr_municipalities --column-inserts -U -h -d > fr_municipalities.sql``. Le fichier en sortie doit s'appeler ``fr_municipalities.sql`` -- Zipper le fichier SQL et le mettre sur le serveur http://geonature.fr/data +- Zipper le fichier SQL et le mettre sur le serveur https://geonature.fr/data - Adapter le script ``install_db.sh`` pour récupérer le nouveau fichier zippé Pratiques et règles de developpement @@ -89,7 +90,8 @@ Backend - Une fonction ou classe doit contenir une docstring en français. Les doctrings doivent suivre le modèle NumPy/SciPy (voir https://numpydoc.readthedocs.io/en/latest/format.html et https://realpython.com/documenting-python-code/#numpyscipy-docstrings-example) - Les commentaires dans le codes doivent être en anglais (ne pas s'empêcher de mettre un commentaire en français sur une partie du code complexe !) -- Installer les requirements-dev (``pip install -r backend/requirements-dev.txt``) qui contiennent une série d'outils indispensables au développement dans GeoNature. +- Assurez-vous d’avoir récupérer les dépendances dans les sous-modules git : ``git submodule init && git submodule update`` +- Installer les requirements-dev (``cd backend && pip install -r requirements-dev.txt``) qui contiennent une série d'outils indispensables au développement dans GeoNature. - Utiliser *blake* comme formateur de texte et activer l'auto-formatage dans son éditeur de texte (Tuto pour VsCode : https://medium.com/@marcobelo/setting-up-python-black-on-visual-studio-code-5318eba4cd00) - Utiliser *pylint* comme formatteur de code - Respecter la norme PEP8 (assurée par les deux outils précédents) @@ -545,10 +547,10 @@ De nombreux paramètres sont néammoins passés à l'application via un schéma Marshmallow (voir fichier ``backend/geonature/utils/config_schema.py``). Dans l'application flask, l'ensemble des paramètres de configuration sont -utilisables via le dictionnaire ``config`` de l'application Flask :: +utilisables via le dictionnaire ``config`` :: - from flask import current_app - MY_PARAMETER = current_app.config['MY_PARAMETER'] + from geonature.utils.config import config + MY_PARAMETER = config['MY_PARAMETER'] Chaque module GeoNature dispose de son propre fichier de configuration, (``module/config/cong_gn_module.toml``) contrôlé de la même manière par un diff --git a/docs/installation-all.rst b/docs/installation-all.rst index a164cf2740..bdc1fdb4e4 100644 --- a/docs/installation-all.rst +++ b/docs/installation-all.rst @@ -6,12 +6,12 @@ Prérequis - Ressources minimum serveur : -Un serveur Linux (Debian 10 ou Ubuntu 18 **architecture 64-bits**) disposant d’au moins de 4 Go RAM et de 20 Go d’espace disque. +Un serveur Debian 10 disposant d’au moins de 4 Go RAM et de 20 Go d’espace disque. Le script global d'installation de GeoNature va aussi se charger d'installer les dépendances nécessaires : - PostgreSQL / PostGIS -- Python 3 et dépendances Python nécessaires à l'application +- Python 3.7 et dépendances Python nécessaires à l'application - Flask (framework web Python) - Apache - Angular 7, Angular CLI, NodeJS diff --git a/docs/installation-standalone.rst b/docs/installation-standalone.rst index 7f60fa270a..b7001f7546 100644 --- a/docs/installation-standalone.rst +++ b/docs/installation-standalone.rst @@ -76,7 +76,7 @@ Sur Ubuntu 18, installez la version 10 de postgresql-server-dev avec la commande Python 3.7 sur Debian 9 ^^^^^^^^^^^^^^^^^^^^^^^ -A partir la version 2.5.0 de GeoNature, la version Python 3.5 n'est plus supportée. Seules les version 3.6+ le sont. +A partir la version 2.5.0 de GeoNature, la version Python 3.5 n'est plus supportée. Seules les version 3.7+ le sont. Si vous êtes encore sur Debian 9 (fourni par défaut avec Python 3.5), veuillez suivre les instructions suivantes pour monter la version de Python sur Debian 9 : @@ -334,6 +334,11 @@ Si vous avez téléchargé GeoNature zippé (via la procédure d'installation gl --- Reset sur HEAD pour mettre à jour les status --- git reset HEAD -> vous êtes à jour sur la branche master + --- Cloner les sous-modules pour récupérer les dépendances + git submodule init + git submodule update + --- Installer les dépendances de développement + cd backend && pip install -r requirements-dev.txt @TODO : A relire et à basculer dans DOC DEVELOPEMENT ? diff --git a/docs/versions-compatibility.rst b/docs/versions-compatibility.rst index d79241cb13..0becc1fed8 100644 --- a/docs/versions-compatibility.rst +++ b/docs/versions-compatibility.rst @@ -3,6 +3,23 @@ COMPATIBILITE Versions fournies et testées des dépendances +GeoNature 2.7.3 +--------------- + ++------------------------+-----------+ +| Application / Module | Version | ++========================+===========+ +| TaxHub | 1.8.1 | ++------------------------+-----------+ +| UsersHub | 2.1.3 | ++------------------------+-----------+ +| Nomenclature-Api | 1.3.8 | ++------------------------+-----------+ +| Authentification-Api | 1.4.7 | ++------------------------+-----------+ +| Habref-Api | 0.1.6 | ++------------------------+-----------+ + GeoNature 2.7.2 --------------- diff --git a/frontend/src/app/metadataModule/actors/actors.component.ts b/frontend/src/app/metadataModule/actors/actors.component.ts index 557e880719..519a2c4b67 100644 --- a/frontend/src/app/metadataModule/actors/actors.component.ts +++ b/frontend/src/app/metadataModule/actors/actors.component.ts @@ -5,6 +5,7 @@ import { MatDialog } from "@angular/material"; import { ActorFormService, ID_ROLE_DATASET_ACTORS, ID_ROLE_AF_ACTORS } from '../services/actor-form.service'; import { ConfirmationDialog } from "@geonature_common/others/modal-confirmation/confirmation.dialog"; +import { AppConfig } from '../../../conf/app.config'; @Component({ selector: 'pnx-metadata-actor', @@ -30,16 +31,19 @@ export class ActorComponent implements OnInit { //liste des types de role pour peupler le select HTML get role_types() { return this.actorFormS.role_types - .filter(e => { - if (this.metadataType == 'dataset') { - //contact principal est enlevé de cette liste déroulante - return e.cd_nomenclature !== "1" ? ID_ROLE_DATASET_ACTORS.includes(e.cd_nomenclature) : false; - } else if (this.metadataType == 'af') { - return e.cd_nomenclature !== "1" ? ID_ROLE_AF_ACTORS.includes(e.cd_nomenclature) : false; - } else { - return true; - } - }); + .filter(e => { + if(e.cd_nomenclature == 1) { + return false + } else { + if(this.metadataType == "dataset" && AppConfig.METADATA.CD_NOMENCLATURE_ROLE_TYPE_DS.length > 0) { + return AppConfig.METADATA.CD_NOMENCLATURE_ROLE_TYPE_DS.includes(e.cd_nomenclature); + } + if(this.metadataType == "af" && AppConfig.METADATA.CD_NOMENCLATURE_ROLE_TYPE_AF.length > 0) { + return AppConfig.METADATA.CD_NOMENCLATURE_ROLE_TYPE_AF.includes(e.cd_nomenclature); + } + } + return true; + }); } //Retourne l'objet organisme à partir de son identifiant issu du formulaire (pour affiche son label en mode edition = false) diff --git a/frontend/src/app/metadataModule/af/af-card.component.html b/frontend/src/app/metadataModule/af/af-card.component.html index d97302fa51..4c6ea317d0 100644 --- a/frontend/src/app/metadataModule/af/af-card.component.html +++ b/frontend/src/app/metadataModule/af/af-card.component.html @@ -255,7 +255,7 @@
Créateur
-
Créateur du cadre d'acquisition : {{af.creator?.nom_complet || 'Non renseigné'}} +
Créateur du cadre d'acquisition : {{af?.creator?.nom_complet || 'Non renseigné'}}

@@ -341,7 +341,7 @@
Jeux de données associés
- +
ID_ROLE_DATASET_ACTORS.includes(e.cd_nomenclature)); - } - - get af_role_types() { - return this._role_types.getValue() - .filter((e) => !(ID_ROLE_DATASET_ACTORS.includes(e.cd_nomenclature))); - } + getIDRoleTypeByCdNomenclature(code) { const role_type = this._role_types.getValue().find((e) => e.cd_nomenclature == code); diff --git a/install/install_all/install_all.ini b/install/install_all/install_all.ini index 2437c8a8e4..28d62b7822 100644 --- a/install/install_all/install_all.ini +++ b/install/install_all/install_all.ini @@ -28,7 +28,7 @@ usershub_release=2.1.3 ### CONFIGURATION GEONATURE ### # Version de GeoNature -geonature_release=2.7.2 +geonature_release=2.7.3 # Effacer la base de données GeoNature existante lors de la réinstallation drop_geonaturedb=false # Nom de la base de données GeoNature diff --git a/install/install_app.sh b/install/install_app.sh index 1a82e92e28..d5cad8a718 100755 --- a/install/install_app.sh +++ b/install/install_app.sh @@ -146,7 +146,7 @@ pip install --upgrade pip pip install -r requirements.txt if [[ $MODE == "dev" ]] then - pip install -r requirements-dev.txt + pip install -r requirements-dev.txt -r requirements-submodules.txt fi echo "Installation du backend geonature..." diff --git a/tox.ini b/tox.ini index ae516315ce..c3c7573e10 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py37-{prod,dev,last} +envlist = py37-{prod,dev} [base] deps = @@ -12,20 +12,14 @@ setenv = GEONATURE_CONFIG_FILE=config/test_config.toml extras = tests deps = {[base]deps} - prod: -r backend/requirements.txt - dev: -r backend/requirements-dev.txt -# last: -e git+git://github.com/PnX-SI/UsersHub-authentification-module@develop#egg=pypnusershub -# last: -e git+git://github.com/PnX-SI/Nomenclature-api-module@develop#egg=pypnnomenclature -# last: -e git+git://github.com/PnX-SI/Habref-api-module@develop#egg=pypn_habref_api -# last: -e git+git://github.com/PnX-SI/TaxHub@develop#egg=taxhub -# last: -e git+git://github.com/PnX-SI/Utils-Flask-SQLAlchemy@tests#egg=utils-flask-sqlalchemy -# last: -e git+git://github.com/PnX-SI/Utils-Flask-SQLAlchemy-Geo@develop#egg=utils-flask-sqlalchemy-geo - last: /home/elie/data/pnx-si/UsersHub-authentification-module.develop - last: /home/elie/data/pnx-si/Nomenclature-api-module.develop - last: /home/elie/data/pnx-si/Habref-api-module.develop - last: /home/elie/data/pnx-si/Taxhub.develop - last: /home/elie/data/pnx-si/utils-flask-sqlalchemy.develop - last: /home/elie/data/pnx-si/utils-flask-sqlalchemy-geo.develop + prod: -r {toxinidir}/backend/requirements.txt + dev: -r {toxinidir}/backend/requirements-dev.txt + dev: -e git+file:{toxinidir}/backend/dependencies/UsersHub-authentification-module#egg=pypnusershub + dev: -e git+file:{toxinidir}/backend/dependencies/Nomenclature-api-module#egg=pypnnomenclature + dev: -e git+file:{toxinidir}/backend/dependencies/Habref-api-module#egg=pypn_habref_api + dev: -e git+file:{toxinidir}/backend/dependencies/TaxHub#egg=taxhub + dev: -e git+file:{toxinidir}/backend/dependencies/Utils-Flask-SQLAlchemy#egg=utils-flask-sqlalchemy + dev: -e git+file:{toxinidir}/backend/dependencies/Utils-Flask-SQLAlchemy-Geo#egg=utils-flask-sqlalchemy-geo commands = pytest -s --verbose {envsitepackagesdir}/geonature/tests