From 3c4274d2ba1ec892aeba52386f84712e8df7a49c Mon Sep 17 00:00:00 2001 From: Amandine Date: Tue, 2 Jul 2024 15:11:13 +0200 Subject: [PATCH] Feat/config with bib type site (#327) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Unify config * Use cor_type_site data in config * Use new config in frontend * [DEV-SUIIVI-EOLIEN] refact: remove duplicate formValue (#328) * refact: remove duplicate formValue Use only formValue created in formService Reviewed-by: andriacap * style: lint frontend Apply prettier Reviewed-by: andriacap * Remove customSpecConfig * Dict update deeply * Limitation de l'appel à get_config * lint * Add test module config --------- Co-authored-by: andriacap <111564663+andriacap@users.noreply.github.com> --- .../config/repositories.py | 137 ++++-------------- backend/gn_module_monitoring/config/utils.py | 39 +++++ .../modules/repositories.py | 6 +- .../gn_module_monitoring/monitoring/base.py | 80 ++++++++-- .../gn_module_monitoring/monitoring/geom.py | 2 +- .../monitoring/repositories.py | 2 +- .../monitoring/serializer.py | 12 +- backend/gn_module_monitoring/routes/config.py | 6 +- .../gn_module_monitoring/routes/modules.py | 7 +- .../gn_module_monitoring/routes/monitoring.py | 105 ++++++-------- backend/gn_module_monitoring/routes/site.py | 13 +- .../routes/sites_groups.py | 9 +- .../tests/fixtures/module.py | 10 +- .../tests/test_routes/test_config.py | 17 +++ backend/gn_module_monitoring/utils/routes.py | 5 +- backend/gn_module_monitoring/utils/utils.py | 23 +++ frontend/app/class/monitoring-object.ts | 34 ----- .../monitoring-form.component.ts | 22 ++- .../monitoring-object.component.ts | 5 +- frontend/app/services/form.service.ts | 16 +- 20 files changed, 293 insertions(+), 257 deletions(-) diff --git a/backend/gn_module_monitoring/config/repositories.py b/backend/gn_module_monitoring/config/repositories.py index 251dc9afa..7dfae2456 100644 --- a/backend/gn_module_monitoring/config/repositories.py +++ b/backend/gn_module_monitoring/config/repositories.py @@ -9,6 +9,7 @@ from gn_module_monitoring.config.utils import ( customize_config, config_from_files, + json_config_from_db, json_config_from_file, get_id_table_location, process_config_display, @@ -18,13 +19,13 @@ get_data_preload, monitoring_module_config_path, ) - +from gn_module_monitoring.utils.utils import dict_deep_update # pour stocker la config dans current_app.config config_cache_name = "MONITORINGS_CONFIG" -def get_config_objects(module_code, config, tree=None, parent_type=None, customSpecConfig=None): +def get_config_objects(module_code, config, tree=None, parent_type=None): """ recupere la config de chaque object present dans tree pour le module """ @@ -40,7 +41,7 @@ def get_config_objects(module_code, config, tree=None, parent_type=None, customS if not object_type in config: if object_type == "site": config[object_type] = config_object_from_files( - module_code, object_type, customSpecConfig, is_sites_group_child + module_code, object_type, is_sites_group_child ) else: config[object_type] = config_object_from_files(module_code, object_type) @@ -50,6 +51,7 @@ def get_config_objects(module_code, config, tree=None, parent_type=None, customS if not "children_types" in config[object_type]: config[object_type]["children_types"] = [] + config[object_type]["children_types"] += children_types config[object_type]["children_types"] = list( dict.fromkeys(config[object_type]["children_types"]) @@ -84,9 +86,7 @@ def get_config_objects(module_code, config, tree=None, parent_type=None, customS # recursif if tree[object_type]: - get_config_objects( - module_code, config, tree[object_type], object_type, customSpecConfig - ) + get_config_objects(module_code, config, tree[object_type], object_type) def config_object_from_files(module_code, object_type, custom=None, is_sites_group_child=False): @@ -99,8 +99,20 @@ def config_object_from_files(module_code, object_type, custom=None, is_sites_gro if module_code == "generic" else json_config_from_file(module_code, object_type) ) + db_config_object = {"specific": {}} + + if object_type == "site": + db_config_object = json_config_from_db(module_code) + # Mise a jour des configurations de façon récursive + dict_deep_update(specific_config_object["specific"], db_config_object["specific"]) + + elif object_type == "module": + db_config_object = json_config_from_db(module_code) + specific_config_object["types_site"] = db_config_object["types_site"] + db_config_object = {"specific": {}} - # NOTE: Ici on pop la clé "id_sites_group" dans le cas ou l'entre par protocole car l'association de site à un groupe de site doit se faire par l'entrée par site + # NOTE: Ici on pop la clé "id_sites_group" dans le cas ou l'entre par protocole car + # l'association de site à un groupe de site doit se faire par l'entrée par site if module_code != "generic" and object_type == "site" and not is_sites_group_child: generic_config_object["generic"].pop("id_sites_group") @@ -110,33 +122,20 @@ def config_object_from_files(module_code, object_type, custom=None, is_sites_gro "attribut_label": "Type(s) de site", } - if object_type == "site" and custom is not None: - if "specific" in custom and "specific" in specific_config_object: - for key in custom["specific"]: - if key not in specific_config_object["specific"]: - specific_config_object["specific"][key] = custom["specific"][key] + # if object_type == "site" and custom is not None: + # if "specific" in custom and "specific" in specific_config_object: + # for key in custom["specific"]: + # if key not in specific_config_object["specific"]: + # specific_config_object["specific"][key] = custom["specific"][key] config_object = generic_config_object + config_object.update(db_config_object) config_object.update(specific_config_object) return config_object -def get_config_with_specific(module_code=None, force=False, complements=None): - """ - recupere la configuration pour le module monitoring - en prenant en compte les propriétés spécifiques des types de sites - """ - customConfig = {"specific": {}} - for keys in complements.keys(): - if "config" in complements[keys]: - customConfig["specific"].update( - (complements[keys].get("config", {}) or {}).get("specific", {}) - ) - get_config(module_code, force=True, customSpecConfig=customConfig) - - -def get_config(module_code=None, force=False, customSpecConfig=None): +def get_config(module_code=None, force=False): """ recupere la configuration pour le module monitoring @@ -173,7 +172,7 @@ def get_config(module_code=None, force=False, customSpecConfig=None): # return config config = config_from_files("config", module_code) - get_config_objects(module_code, config, customSpecConfig=customSpecConfig) + get_config_objects(module_code, config) # customize config if module: config["custom"] = {} @@ -190,11 +189,11 @@ def get_config(module_code=None, force=False, customSpecConfig=None): config["custom"][var_name] = getattr(module, field_name) config["module"][field_name] = getattr(module, field_name) - # Types de sites - if hasattr(module, field_name): - config["module"]["types_site"] = [ - ts.id_nomenclature_type_site for ts in getattr(module, "types_site") - ] + # # Types de sites + # if hasattr(module, field_name): + # config["module"]["types_site"] = [ + # ts.id_nomenclature_type_site for ts in getattr(module, "types_site") + # ] config["custom"]["__MONITORINGS_PATH"] = get_monitorings_path() @@ -216,78 +215,6 @@ def get_config(module_code=None, force=False, customSpecConfig=None): return config -def config_param(module_code, object_type, param_name): - """ - revoie un parametre de la configuration des objets - - :param module_code: reference le module concerne - :param object_type: le type d'object (site, visit, obervation) - :param param_name: le parametre voulu (id_field_name, label) - :type module_code: str - :type object_type: str - :type param_name: str - :return: valeur du paramètre requis - :rtype: str - - :Exemple: - - config_param('oedic', 'site', 'id_field_name') - renverra 'id_base_site' - - config_param('oedic', 'site', 'label') - renverra 'Site' - - """ - - config = get_config(module_code) - - return config[object_type].get(param_name) - - -def config_schema(module_code, object_type, type_schema="all"): - """ - renvoie une liste d'éléments de configuration de formulaire - - pour type_schema: - 'generic' : renvoie le schema générique - 'specific' : renvoie le schema spécifique - 'all': par defaut renvoie tout le schema - - Un élément est un dictionaire de type - { - "attribut_name": "id_base_site", - "Label": "Id du site", - "type_widget": "integer", - "required": "true", - } - - :param module_code: reference le module concerne - :param object_type: le type d'object (site, visit, obervation) - :param type_schema: le type de schema requis ('all', 'generic', 'specific') - :type module_code: str - :type object_type: str - :type type_schema: str, optional - :return: tableau d'élément de configuration de formulaire - :rtype: list - """ - # recuperation de la configuration - config = get_config(module_code) - - if type_schema in ["generic", "specific"]: - return config[object_type][type_schema] - - # renvoie le schema complet si type_schema == 'all' ou par defaut - schema = dict(config[object_type]["generic"]) - schema.update(config[object_type]["specific"]) - - return schema - - -def get_config_frontend(module_code=None, force=True): - config = dict(get_config(module_code, force)) - return config - - # def get_config_from_backend(module_code=None, force=False): # module_code = 'generic' diff --git a/backend/gn_module_monitoring/config/utils.py b/backend/gn_module_monitoring/config/utils.py index 2bdca4707..e43559698 100644 --- a/backend/gn_module_monitoring/config/utils.py +++ b/backend/gn_module_monitoring/config/utils.py @@ -4,6 +4,7 @@ from pathlib import Path from sqlalchemy import and_, select +from sqlalchemy.orm.exc import NoResultFound from geonature.utils.env import DB from geonature.utils.errors import GeoNatureError @@ -11,6 +12,8 @@ from geonature.core.gn_commons.models import BibTablesLocation, TModules from gn_module_monitoring.monitoring.models import TMonitoringModules +from gn_module_monitoring.modules.repositories import get_module +from gn_module_monitoring.utils.routes import query_all_types_site_from_module_id SUB_MODULE_CONFIG_DIR = Path(gn_config["MEDIA_FOLDER"]) / "monitorings/" @@ -133,6 +136,42 @@ def json_config_from_file(module_code, type_config): return json_from_file(file_path, {}) +def json_config_from_db(module_code): + site_type_config = {"types_site": {}, "specific": {}} + if module_code == "generic": + # Si generic récupération de tous les types de sites + types = query_all_types_site_from_module_id(0) + else: + try: + module = get_module("module_code", module_code) + except NoResultFound: + return site_type_config + types = query_all_types_site_from_module_id(module.id_module) + + for t in types: + fields = [] + + # Configuration des champs + if "specific" in (t.config or {}): + site_type_config["specific"].update(t.config["specific"]) + fields = [k for k in t.config["specific"]] + + # Liste des champs à afficher + display_properties = list(fields) + if "display_properties" in (t.config or {}): + display_properties = [ + key for key in t.config.get("display_properties") if key in fields + ] + display_properties + [key for key in fields if not key in display_properties] + + site_type_config["types_site"][t.id_nomenclature_type_site] = { + "display_properties": display_properties, + "name": t.nomenclature.label_default, + } + + return site_type_config + + def config_from_files(config_type, module_code): generic_config_custom = json_config_from_file("generic", config_type) specific_config_custom = ( diff --git a/backend/gn_module_monitoring/modules/repositories.py b/backend/gn_module_monitoring/modules/repositories.py index d10354ede..83d149812 100644 --- a/backend/gn_module_monitoring/modules/repositories.py +++ b/backend/gn_module_monitoring/modules/repositories.py @@ -48,12 +48,14 @@ def get_module(field_name, value, moduleCls=TMonitoringModules): :rtype : dict """ - if not hasattr(moduleCls, field_name): raise GeoNatureError( "get_module : TMonitoringModules ne possède pas de champs {}".format(field_name) ) + if value == "generic": + return None + try: module = DB.session.execute( select(moduleCls).where(getattr(moduleCls, field_name) == value) @@ -61,6 +63,8 @@ def get_module(field_name, value, moduleCls=TMonitoringModules): return module + except NoResultFound as e: + raise e except MultipleResultsFound: raise GeoNatureError( "get_module : multiple results found for field_name {} and value {}".format( diff --git a/backend/gn_module_monitoring/monitoring/base.py b/backend/gn_module_monitoring/monitoring/base.py index 700427a20..2816414ab 100644 --- a/backend/gn_module_monitoring/monitoring/base.py +++ b/backend/gn_module_monitoring/monitoring/base.py @@ -1,11 +1,5 @@ from geonature.utils.errors import GeoNatureError -from gn_module_monitoring.config.repositories import ( - config_param as repositories_config_param, - config_schema as repositories_config_schema, - get_config as repositories_get_config, -) - class MonitoringDefinitions: """ @@ -39,8 +33,18 @@ def MonitoringObject(self, object_type): ) ) - def monitoring_object_instance(self, module_code, object_type, id=None, model=None): - return self.MonitoringObject(object_type)(module_code, object_type, id, model) + def monitoring_object_instance( + self, + module_code, + object_type, + config, + id=None, + model=None, + ): + # force config + return self.MonitoringObject(object_type)( + module_code, object_type, config=config, id=id, model=model + ) def MonitoringModel(self, object_type): try: @@ -60,17 +64,19 @@ class MonitoringObjectBase: _object_type = None _module_code = None _id = None + _config = None _model = None _children = {} _parent = None cruved = {} - def __init__(self, module_code, object_type, id=None, model=None): + def __init__(self, module_code, object_type, config, id=None, model=None): self._module_code = module_code self._object_type = object_type self._id = id + self._config = config if not self._id and model: self._id = getattr(model, self.config_param("id_field_name")) @@ -100,11 +106,28 @@ def MonitoringModel(self): Model = monitoring_definitions.MonitoringModel(new_object_type) return Model - def config(self): - return repositories_get_config(self._module_code) + def config(self, force=False): + return self._config def config_param(self, param_name): - return repositories_config_param(self._module_code, self._object_type, param_name) + """ + revoie un parametre de la configuration des objets + + :param param_name: le parametre voulu (id_field_name, label) + :return: valeur du paramètre requis + :rtype: str + + :Exemple: + + config_param('id_field_name') + renverra 'id_base_site' + + config_param('label') + renverra 'Site' + + """ + + return self._config[self._object_type].get(param_name) def get_value_generic(self, param_name): if not hasattr(self._model, param_name): @@ -133,11 +156,38 @@ def parent_type(self): def parent_config_param(self, param_name): parent_type = self.parent_type() if parent_type: - return repositories_config_param(self._module_code, parent_type, param_name) + return self.config_param(param_name) def config_schema(self, type_schema="all"): - return repositories_config_schema(self._module_code, self._object_type, type_schema) - pass + """ + renvoie une liste d'éléments de configuration de formulaire + + pour type_schema: + 'generic' : renvoie le schema générique + 'specific' : renvoie le schema spécifique + 'all': par defaut renvoie tout le schema + + Un élément est un dictionaire de type + { + "attribut_name": "id_base_site", + "Label": "Id du site", + "type_widget": "integer", + "required": "true", + } + + :param module_code: reference le module concerne + :param object_type: le type d'object (site, visit, obervation) + :param type_schema: le type de schema requis ('all', 'generic', 'specific') + :return: tableau d'élément de configuration de formulaire + :rtype: list + """ + if type_schema in ["generic", "specific"]: + return self._config[self._object_type][type_schema] + + # renvoie le schema complet si type_schema == 'all' ou par defaut + schema = dict(self._config[self._object_type]["generic"]) + schema.update(self._config[self._object_type]["specific"]) + return schema # def base_type_object(self): # """ diff --git a/backend/gn_module_monitoring/monitoring/geom.py b/backend/gn_module_monitoring/monitoring/geom.py index 8f2659a29..5b02f4db9 100644 --- a/backend/gn_module_monitoring/monitoring/geom.py +++ b/backend/gn_module_monitoring/monitoring/geom.py @@ -16,7 +16,7 @@ def as_geofeature(self, depth=None, columns=()): ) def serialize(self, depth, is_child=False): - monitoring_object_dict = MonitoringObject.serialize(self, depth, is_child) + monitoring_object_dict = super(MonitoringObject, self).serialize(depth, is_child) if len(monitoring_object_dict["properties"].get("types_site", [])) != 0: if hasattr(self._model, "types_site"): diff --git a/backend/gn_module_monitoring/monitoring/repositories.py b/backend/gn_module_monitoring/monitoring/repositories.py index ff6116626..812ec806f 100644 --- a/backend/gn_module_monitoring/monitoring/repositories.py +++ b/backend/gn_module_monitoring/monitoring/repositories.py @@ -165,7 +165,7 @@ def breadcrumbs(self, params): if params["parents_path"]: object_type = params.get("parents_path", []).pop() - next = MonitoringObject(self._module_code, object_type) + next = MonitoringObject(self._module_code, object_type, config=self._config) if next._object_type == "module": next.get(field_name="module_code", value=self._module_code) else: diff --git a/backend/gn_module_monitoring/monitoring/serializer.py b/backend/gn_module_monitoring/monitoring/serializer.py index 3574a33e6..e4d8d5f1b 100644 --- a/backend/gn_module_monitoring/monitoring/serializer.py +++ b/backend/gn_module_monitoring/monitoring/serializer.py @@ -41,7 +41,7 @@ def get_parent(self): if not self._parent: self._parent = monitoring_definitions.monitoring_object_instance( - self._module_code, parent_type, self.id_parent() + self._module_code, parent_type, config=self._config, id=self.id_parent() ).get() return self._parent @@ -77,7 +77,13 @@ def unflatten_specific_properties(self, properties): data = {} for attribut_name, attribut_value in self.config_schema("specific").items(): if "type_widget" in attribut_value and attribut_value["type_widget"] != "html": - val = properties.pop(attribut_name) + if attribut_name in properties: + val = properties.pop(attribut_name) + else: + # TODO évaluer l'incidence + # voir comment générer les proprités spécifiques + # non définies dans le schéma + val = None data[attribut_name] = val if data: @@ -124,7 +130,7 @@ def serialize_children(self, depth): ) for child_model in childs_object_readable: child = monitoring_definitions.monitoring_object_instance( - self._module_code, children_type, model=child_model + self._module_code, children_type, config=self._config, model=child_model ) children_of_type.append(child.serialize(depth, is_child=True)) diff --git a/backend/gn_module_monitoring/routes/config.py b/backend/gn_module_monitoring/routes/config.py index bd00a05aa..332c606dd 100644 --- a/backend/gn_module_monitoring/routes/config.py +++ b/backend/gn_module_monitoring/routes/config.py @@ -1,7 +1,7 @@ from utils_flask_sqla.response import json_resp from gn_module_monitoring.blueprint import blueprint -from gn_module_monitoring.config.repositories import get_config_frontend +from gn_module_monitoring.config.repositories import get_config @blueprint.route("/config/", methods=["GET"]) @@ -11,5 +11,5 @@ def get_config_api(module_code): """ route qui renvoie la config pour un module donné """ - - return get_config_frontend(module_code, force=True) + config = get_config(module_code, force=True) + return dict(config) diff --git a/backend/gn_module_monitoring/routes/modules.py b/backend/gn_module_monitoring/routes/modules.py index 01bb62982..50043b72a 100644 --- a/backend/gn_module_monitoring/routes/modules.py +++ b/backend/gn_module_monitoring/routes/modules.py @@ -15,7 +15,6 @@ get_module, get_modules, ) -from gn_module_monitoring.config.repositories import get_config from gn_module_monitoring.utils.utils import to_int from gn_module_monitoring.utils.routes import ( query_all_types_site_from_module_id, @@ -88,10 +87,10 @@ def get_modules_api(): return modules_out +# TODEL ? @blueprint.route("/modules//types_sites", methods=["GET"]) def get_all_types_site_from_module_id(module_code): - config = get_config(module_code, True) - id_module = config["custom"]["__MODULE.ID_MODULE"] - types_site = query_all_types_site_from_module_id(id_module) + module = get_module("module_code", module_code) + types_site = query_all_types_site_from_module_id(module.id_module) schema = BibTypeSiteSchema() return [schema.dump(res) for res in types_site] diff --git a/backend/gn_module_monitoring/routes/monitoring.py b/backend/gn_module_monitoring/routes/monitoring.py index a1af98977..7a1c8c953 100644 --- a/backend/gn_module_monitoring/routes/monitoring.py +++ b/backend/gn_module_monitoring/routes/monitoring.py @@ -29,10 +29,7 @@ from gn_module_monitoring.monitoring.definitions import monitoring_definitions from gn_module_monitoring.modules.repositories import get_module from gn_module_monitoring.utils.utils import to_int -from gn_module_monitoring.config.repositories import get_config, get_config_with_specific -from gn_module_monitoring.utils.routes import ( - query_all_types_site_from_site_id, -) +from gn_module_monitoring.config.repositories import get_config @blueprint.url_value_preprocessor @@ -102,33 +99,19 @@ def get_monitoring_object_api(scope, module_code=None, object_type="module", id= # value = module_code if object_type == 'module' depth = to_int(request.args.get("depth", 1)) + + config = get_config(module_code, force=True) + + monitoring_obj = monitoring_definitions.monitoring_object_instance( + module_code, object_type, config=config, id=id + ) if id != None: - object = monitoring_definitions.monitoring_object_instance( - module_code, object_type, id - ).get(depth=depth) + object = monitoring_obj.get(depth=depth) if not object._model.has_instance_permission(scope=scope): raise Forbidden(f"User {g.current_user} cannot read {object_type} {object._id}") - if id != None and object_type == "site": - types_site_obj = query_all_types_site_from_site_id(id) - list_types_sites_dict = [ - values - for res in types_site_obj - for (key_type_site, values) in res.as_dict().items() - if key_type_site == "config" - ] - customConfig = {"specific": {}} - for specific_config in list_types_sites_dict: - customConfig["specific"].update((specific_config or {}).get("specific", {})) - - get_config(module_code, force=True, customSpecConfig=customConfig) - else: - get_config(module_code, force=True) - return ( - monitoring_definitions.monitoring_object_instance(module_code, object_type, id).get( - depth=depth - ) + monitoring_obj.get(depth=depth) # .get(value=value, field_name = field_name) .serialize(depth) ) @@ -165,14 +148,17 @@ def create_or_update_object_api(module_code, object_type, id=None): else: post_data["properties"]["id_module"] = module.id_module + config = get_config(module_code, force=True) return ( - monitoring_definitions.monitoring_object_instance(module_code, object_type, id) + monitoring_definitions.monitoring_object_instance( + module_code, object_type, config=config, id=id + ) .create_or_update(post_data) .serialize(depth) ) -def get_config_object(module_code, object_type, id): +def get_serialized_object(module_code, object_type, id): """ renvoie un object, à partir de type de l'object et de son id @@ -189,14 +175,14 @@ def get_config_object(module_code, object_type, id): # field_name = param.get('field_name') # value = module_code if object_type == 'module' - get_config(module_code, force=True) + config = get_config(module_code, force=True) depth = to_int(request.args.get("depth", 1)) return ( - monitoring_definitions.monitoring_object_instance(module_code, object_type, id).get( - depth=depth - ) + monitoring_definitions.monitoring_object_instance( + module_code, object_type, config=config, id=id + ).get(depth=depth) # .get(value=value, field_name = field_name) .serialize(depth) ) @@ -215,17 +201,15 @@ def get_config_object(module_code, object_type, id): def update_object_api(scope, module_code, object_type, id): depth = to_int(request.args.get("depth", 1)) if id != None: + + config = get_config(module_code, force=True) object = monitoring_definitions.monitoring_object_instance( - module_code, object_type, id + module_code, object_type, config=config, id=id ).get(depth=depth) if not object._model.has_instance_permission(scope=scope): raise Forbidden(f"User {g.current_user} cannot update {object_type} {object._id}") post_data = dict(request.get_json()) - if "dataComplement" in post_data: - get_config_with_specific(module_code, force=True, complements=post_data["dataComplement"]) - else: - get_config(module_code, force=True) return create_or_update_object_api(module_code, object_type, id) @@ -242,10 +226,7 @@ def update_object_api(scope, module_code, object_type, id): @json_resp def create_object_api(module_code, object_type, id): post_data = dict(request.get_json()) - if "dataComplement" in post_data: - get_config_with_specific(module_code, force=True, complements=post_data["dataComplement"]) - else: - get_config(module_code, force=True) + # get_config(module_code, force=True) return create_or_update_object_api(module_code, object_type, id) @@ -261,20 +242,23 @@ def create_object_api(module_code, object_type, id): @permissions.check_cruved_scope("D", get_scope=True) def delete_object_api(scope, module_code, object_type, id): depth = to_int(request.args.get("depth", 1)) - if id != None: - object = monitoring_definitions.monitoring_object_instance( - module_code, object_type, id - ).get(depth=depth) - if not object._model.has_instance_permission(scope=scope): - raise Forbidden(f"User {g.current_user} cannot delete {object_type} {object._id}") if object_type in ("site", "sites_group"): raise Exception( f"No right to delete {object_type} from protocol. The {object_type} with id: {id} could be linked with others protocols" ) - get_config(module_code, force=True) + + config = get_config(module_code=module_code, force=True) + monitoring_obj = monitoring_definitions.monitoring_object_instance( + module_code, object_type, config=config, id=id + ) + if id != None: + object = monitoring_obj.get(depth=depth) + if not object._model.has_instance_permission(scope=scope): + raise Forbidden(f"User {g.current_user} cannot delete {object_type} {object._id}") + # NOTE: normalement on ne peut plus supprimer les groupes de site / sites par l'entrée protocoles - return monitoring_definitions.monitoring_object_instance(module_code, object_type, id).delete() + return monitoring_obj.delete() # breadcrumbs @@ -290,11 +274,13 @@ def delete_object_api(scope, module_code, object_type, id): @check_cruved_scope("R") @json_resp def breadcrumbs_object_api(module_code, object_type, id): - get_config(module_code, force=True) query_params = dict(**request.args) query_params["parents_path"] = request.args.getlist("parents_path") + config = get_config(module_code=module_code, force=True) return ( - monitoring_definitions.monitoring_object_instance(module_code, object_type, id) + monitoring_definitions.monitoring_object_instance( + module_code, object_type, config=config, id=id + ) .get() .breadcrumbs(query_params) ) @@ -305,11 +291,11 @@ def breadcrumbs_object_api(module_code, object_type, id): @check_cruved_scope("R") @json_resp_accept_empty_list def list_object_api(module_code, object_type): - get_config(module_code, force=True) + config = get_config(module_code, force=True) - return monitoring_definitions.monitoring_object_instance(module_code, object_type).get_list( - request.args - ) + return monitoring_definitions.monitoring_object_instance( + module_code, object_type, config=config + ).get_list(request.args) # mise à jour de la synthèse @@ -317,10 +303,10 @@ def list_object_api(module_code, object_type): @check_cruved_scope("U", object_code="MONITORINGS_MODULES") @json_resp def update_synthese_api(module_code): - get_config(module_code, force=True) + config = get_config(module_code, force=True) return ( - monitoring_definitions.monitoring_object_instance(module_code, "module") + monitoring_definitions.monitoring_object_instance(module_code, "module", config=config) .get() .process_synthese(process_module=True) ) @@ -389,8 +375,11 @@ def post_export_pdf(module_code, object_type, id): """ depth = to_int(request.args.get("depth", 0)) + config = get_config(module_code, force=True) monitoring_object = ( - monitoring_definitions.monitoring_object_instance(module_code, object_type, id) + monitoring_definitions.monitoring_object_instance( + module_code, object_type, config=config, id=id + ) .get() .serialize(depth) ) diff --git a/backend/gn_module_monitoring/routes/site.py b/backend/gn_module_monitoring/routes/site.py index fd59beda0..8b4e8f493 100644 --- a/backend/gn_module_monitoring/routes/site.py +++ b/backend/gn_module_monitoring/routes/site.py @@ -19,7 +19,7 @@ from gn_module_monitoring import MODULE_CODE from gn_module_monitoring.blueprint import blueprint -from gn_module_monitoring.config.repositories import get_config_with_specific +from gn_module_monitoring.config.repositories import get_config from gn_module_monitoring.monitoring.models import ( TMonitoringModules, TMonitoringSites, @@ -27,7 +27,7 @@ from gn_module_monitoring.monitoring.schemas import BibTypeSiteSchema, MonitoringSitesSchema from gn_module_monitoring.routes.monitoring import ( create_or_update_object_api, - get_config_object, + get_serialized_object, ) from gn_module_monitoring.routes.modules import get_modules from gn_module_monitoring.utils.routes import ( @@ -47,7 +47,8 @@ @blueprint.route("/sites/config", methods=["GET"]) def get_config_sites(id=None, module_code="generic", object_type="site"): - obj = get_config_object(module_code, object_type, id) + # A QUOI SERT CETTE ROUTE ? + obj = get_serialized_object(module_code, object_type, id) return obj["properties"] @@ -232,7 +233,7 @@ def post_sites(object_type): object_type = "site" post_data = dict(request.get_json()) - get_config_with_specific(module_code, force=True, complements=post_data["dataComplement"]) + # get_config(module_code, force=True) return create_or_update_object_api(module_code, object_type), 201 @@ -261,8 +262,6 @@ def patch_sites(scope, _id, object_type): module_code = "generic" post_data = dict(request.get_json()) - get_config_with_specific( - module_code, force=True, complements=post_data.get("dataComplement", {}) - ) + # get_config(module_code, force=True) return create_or_update_object_api(module_code, object_type, _id), 201 diff --git a/backend/gn_module_monitoring/routes/sites_groups.py b/backend/gn_module_monitoring/routes/sites_groups.py index 4d43e3f8e..c1741829c 100644 --- a/backend/gn_module_monitoring/routes/sites_groups.py +++ b/backend/gn_module_monitoring/routes/sites_groups.py @@ -28,14 +28,15 @@ ) from gn_module_monitoring.routes.monitoring import ( create_or_update_object_api, - get_config_object, + get_serialized_object, ) from gn_module_monitoring.utils.utils import to_int @blueprint.route("/sites_groups/config", methods=["GET"]) def get_config_sites_groups(id=None, module_code="generic", object_type="sites_group"): - obj = get_config_object(module_code, object_type, id) + # A QUOI SERT CETTE ROUTE + obj = get_serialized_object(module_code, object_type, id) return obj["properties"] @@ -144,7 +145,7 @@ def patch(scope, _id: int, object_type: str): ) module_code = "generic" - get_config(module_code, force=True) + # get_config(module_code, force=True) return create_or_update_object_api(module_code, object_type, _id), 201 @@ -169,7 +170,7 @@ def delete(scope, _id: int, object_type: str): @check_cruved_scope("C", module_code=MODULE_CODE, object_code="MONITORINGS_GRP_SITES") def post(object_type: str): module_code = "generic" - get_config(module_code, force=True) + # get_config(module_code, force=True) return create_or_update_object_api(module_code, object_type), 201 diff --git a/backend/gn_module_monitoring/tests/fixtures/module.py b/backend/gn_module_monitoring/tests/fixtures/module.py index 8be70288f..fb2504ebd 100644 --- a/backend/gn_module_monitoring/tests/fixtures/module.py +++ b/backend/gn_module_monitoring/tests/fixtures/module.py @@ -21,10 +21,11 @@ ) from gn_module_monitoring.monitoring.models import TMonitoringModules from gn_module_monitoring.tests.fixtures.generic import monitorings_users +from gn_module_monitoring.tests.fixtures.type_site import types_site @pytest.fixture -def install_module_test(): +def install_module_test(types_site): # Copy des fichiers du module de test path_gn_monitoring = Path(__file__).absolute().parent.parent.parent.parent.parent path_module_test = path_gn_monitoring / Path("contrib/test") @@ -36,6 +37,13 @@ def install_module_test(): result = runner.invoke(cmd_install_monitoring_module, ["test"]) assert result.exit_code == 0 + # Association du module aux types de site existant + module = db.session.execute( + select(TMonitoringModules).where(TMonitoringModules.module_code == "test") + ).scalar_one() + with db.session.begin_nested(): + module.types_site = list(types_site.values()) + db.session.add(module) @pytest.fixture diff --git a/backend/gn_module_monitoring/tests/test_routes/test_config.py b/backend/gn_module_monitoring/tests/test_routes/test_config.py index c47650245..3cfdc1511 100644 --- a/backend/gn_module_monitoring/tests/test_routes/test_config.py +++ b/backend/gn_module_monitoring/tests/test_routes/test_config.py @@ -1,9 +1,26 @@ import pytest from flask import url_for +from gn_module_monitoring.tests.fixtures.generic import * +from pypnusershub.tests.utils import set_logged_user_cookie + @pytest.mark.usefixtures("client_class", "temporary_transaction") class TestRouteConfig: def test_get_config(self): response = self.client.get(url_for("monitorings.get_config_api")) + assert response.json["default_display_field_names"]["area"] == "area_name" + + def test_get_config_module(self, install_module_test, monitorings_users): + set_logged_user_cookie(self.client, monitorings_users["admin_user"]) + response = self.client.get(url_for("monitorings.get_config_api", module_code="test")) + + module_type_site = response.json["module"]["types_site"] + type_site_name = [v["name"] for k, v in module_type_site.items()] + + assert set(type_site_name) == set(["Test_Grotte", "Test_Mine"]) + for id, type_site in module_type_site.items(): + assert set(type_site["display_properties"]).issubset( + [k for k in response.json["site"]["specific"]] + ) diff --git a/backend/gn_module_monitoring/utils/routes.py b/backend/gn_module_monitoring/utils/routes.py index b0148a2a4..1e25daaa0 100644 --- a/backend/gn_module_monitoring/utils/routes.py +++ b/backend/gn_module_monitoring/utils/routes.py @@ -144,7 +144,7 @@ def query_all_types_site_from_site_id(id_site: int): return DB.session.scalars(query).unique().all() -def query_all_types_site_from_module_id(id_module: int): +def query_all_types_site_from_module_id(id_module: int = None): query = ( select(BibTypeSite) .join( @@ -152,8 +152,9 @@ def query_all_types_site_from_module_id(id_module: int): BibTypeSite.id_nomenclature_type_site == cor_module_type.c.id_type_site, ) .join(TModules, cor_module_type.c.id_module == TModules.id_module) - .where(cor_module_type.c.id_module == id_module) ) + if id_module: + query = query.where(cor_module_type.c.id_module == id_module) return DB.session.scalars(query).unique().all() diff --git a/backend/gn_module_monitoring/utils/utils.py b/backend/gn_module_monitoring/utils/utils.py index f1f292456..31754697c 100644 --- a/backend/gn_module_monitoring/utils/utils.py +++ b/backend/gn_module_monitoring/utils/utils.py @@ -1,5 +1,28 @@ +import collections.abc + + def to_int(s): try: return int(s) except Exception: return None + + +def dict_deep_update(dct, merge_dct): + """Recursive dict merge. Inspired by :meth:``dict.update()``, instead of + updating only top-level keys, dict_merge recurses down into dicts nested + to an arbitrary depth, updating keys. The ``merge_dct`` is merged into + ``dct``. + :param dct: dict onto which the merge is executed + :param merge_dct: dct merged into dct + :return: None + """ + for k, v in merge_dct.items(): + if ( + k in dct + and isinstance(dct[k], dict) + and isinstance(merge_dct[k], collections.abc.Mapping) + ): + dict_deep_update(dct[k], merge_dct[k]) + else: + dct[k] = merge_dct[k] diff --git a/frontend/app/class/monitoring-object.ts b/frontend/app/class/monitoring-object.ts index 8d960a093..1074ee17a 100644 --- a/frontend/app/class/monitoring-object.ts +++ b/frontend/app/class/monitoring-object.ts @@ -195,40 +195,6 @@ export class MonitoringObject extends MonitoringObjectBase { ); } - /** Formulaires */ - - /** formValues: obj -> from */ - - formValues(schemaUpdate = {}): Observable { - const properties = Utils.copy(this.properties); - const observables = {}; - let schema = {}; - if (Object.keys(schemaUpdate).length == 0) { - schema = this.schema(); - } else { - schema = schemaUpdate; - } - - for (const attribut_name of Object.keys(schema)) { - const elem = schema[attribut_name]; - if (!elem.type_widget) { - continue; - } - observables[attribut_name] = this._objService.toForm(elem, properties[attribut_name]); - } - - return forkJoin(observables).pipe( - concatMap((formValues_in) => { - const formValues = Utils.copy(formValues_in); - // geometry - if (this.config['geometry_type']) { - formValues['geometry'] = this.geometry; // copy??? - } - return of(formValues); - }) - ); - } - /** postData: obj -> from */ postData(formValue, dataComplement) { diff --git a/frontend/app/components/monitoring-form/monitoring-form.component.ts b/frontend/app/components/monitoring-form/monitoring-form.component.ts index ac3f5e355..021b7d46a 100644 --- a/frontend/app/components/monitoring-form/monitoring-form.component.ts +++ b/frontend/app/components/monitoring-form/monitoring-form.component.ts @@ -12,10 +12,10 @@ import { SitesService } from '../../services/api-geom.service'; import { concatMap, distinctUntilChanged, - exhaustMap, mergeMap, switchMap, tap, + map, toArray, } from 'rxjs/operators'; import { EMPTY, from, iif, of } from 'rxjs'; @@ -204,10 +204,9 @@ export class MonitoringFormComponent implements OnInit { this.setQueryParams(); // pour donner la valeur de l'objet au formulaire - this.obj.formValues().subscribe((formValue) => { + this._formService.formValues(this.obj).subscribe((formValue) => { this.objForm.patchValue(formValue); this.setDefaultFormValue(); - // reset geom ? }); } @@ -221,20 +220,17 @@ export class MonitoringFormComponent implements OnInit { this.setQueryParams(); // pour donner la valeur de l'objet au formulaire - this.obj - .formValues() + this._formService + .formValues(this.obj) .pipe( - exhaustMap((formValue) => { - this.objForm.patchValue(formValue); - this.setDefaultFormValue(); - return of(true); + map((formValue) => { + return { ...formValue, types_site: this.idsTypesSite }; }), - concatMap(() => { - return this.obj.formValues(this.schemaUpdate); + concatMap((formValue) => { + return of({ ...formValue, ...this._formService.formValues(this.obj, this.schemaUpdate) }); }) ) .subscribe((formValue) => { - formValue.types_site = this.idsTypesSite; // this.objFormDynamic.disable(); this.objFormDynamic.patchValue(formValue, { onlySelf: true, emitEvent: false }); // this.objFormDynamic.enable(); @@ -246,7 +242,7 @@ export class MonitoringFormComponent implements OnInit { return; } // pour donner la valeur de l'objet au formulaire - this.obj.formValues(this.schemaUpdate).subscribe((formValue) => { + this._formService.formValues(this.obj, this.schemaUpdate).subscribe((formValue) => { formValue.types_site = this.idsTypesSite; // this.objFormDynamic.disable(); this.objFormDynamic.patchValue(formValue, { onlySelf: true, emitEvent: false }); diff --git a/frontend/app/components/monitoring-object/monitoring-object.component.ts b/frontend/app/components/monitoring-object/monitoring-object.component.ts index c5b2fc116..4550300e8 100644 --- a/frontend/app/components/monitoring-object/monitoring-object.component.ts +++ b/frontend/app/components/monitoring-object/monitoring-object.component.ts @@ -165,8 +165,9 @@ export class MonitoringObjectComponent implements OnInit { const queryParams = this._route.snapshot.queryParams || {}; this.pre_filters = {}; - this.pre_filters['types_site'] = - this._configService.config()[this.obj.moduleCode]['module']['types_site']; + this.pre_filters['types_site'] = Object.keys( + this._configService.config()[this.obj.moduleCode]['module']['types_site'] + ); // filtre objet géographique de référence if (this.obj.objectType == 'sites_group') { this.pre_filters['id_sites_group'] = this.obj.id; diff --git a/frontend/app/services/form.service.ts b/frontend/app/services/form.service.ts index 4c812c39a..f7b13b837 100644 --- a/frontend/app/services/form.service.ts +++ b/frontend/app/services/form.service.ts @@ -8,6 +8,7 @@ import { Utils } from '../utils/utils'; import { MonitoringObjectService } from './monitoring-object.service'; import { FormBuilder, FormControl, FormGroup } from '@angular/forms'; import { IExtraForm, IFormMap } from '../interfaces/object'; +import { ConfigService } from './config.service'; @Injectable() export class FormService { @@ -35,7 +36,8 @@ export class FormService { constructor( private _objService: MonitoringObjectService, - private _formBuilder: FormBuilder + private _formBuilder: FormBuilder, + private _configService: ConfigService ) {} changeDataSub( @@ -75,11 +77,18 @@ export class FormService { this.formMap.next(formMapObj); } - formValues(obj): Observable { + formValues(obj, schemaUpdate = {}): Observable { + let schema; // const {properties ,remainaing} = obj const properties = Utils.copy(obj.properties); const observables = {}; - const schema = obj[obj.moduleCode]; + if (obj.moduleCode && Object.keys(schemaUpdate).length != 0) { + schema = schemaUpdate; + } else if (obj.moduleCode) { + schema = this._configService.schema(obj.moduleCode, obj.objectType, 'all'); + } else { + schema = obj[obj.moduleCode]; + } // ADD specific properties if exist if (obj.specific != undefined) { @@ -90,6 +99,7 @@ export class FormService { for (const attribut_name of Object.keys(schema)) { const elem = schema[attribut_name]; + // NOTES: [dev-suivi-eol] ici le formValues possédant uniquement des propriétés sans type_widget ne surcouchent pas les champs specific au type de site if (!elem.type_widget) { continue; }