diff --git a/backend/gn_module_monitoring/command/utils.py b/backend/gn_module_monitoring/command/utils.py index 4d1c261a1..137435999 100644 --- a/backend/gn_module_monitoring/command/utils.py +++ b/backend/gn_module_monitoring/command/utils.py @@ -55,6 +55,8 @@ "MONITORINGS_GRP_SITES": {"label": "groupes de sites", "actions": ["C", "R", "U", "D"]}, "MONITORINGS_SITES": {"label": "sites", "actions": ["C", "R", "U", "D"]}, "MONITORINGS_VISITES": {"label": "visites", "actions": ["C", "R", "U", "D"]}, + "MONITORINGS_INDIVIDUALS": {"label": "individus", "actions": ["C", "R", "U", "D"]}, + "MONITORINGS_MARKINGS": {"label": "marquages", "actions": ["C", "R", "U", "D"]}, } ACTION_LABEL = { diff --git a/backend/gn_module_monitoring/conf_schema_toml.py b/backend/gn_module_monitoring/conf_schema_toml.py index bdc2c10b2..5d14c60b9 100644 --- a/backend/gn_module_monitoring/conf_schema_toml.py +++ b/backend/gn_module_monitoring/conf_schema_toml.py @@ -15,6 +15,8 @@ "visit": "MONITORINGS_VISITES", "observation": "MONITORINGS_VISITES", "observation_detail": "MONITORINGS_VISITES", + "individual": "MONITORINGS_INDIVIDUALS", + "marking": "MONITORINGS_MARKINGS", } diff --git a/backend/gn_module_monitoring/config/generic/config.json b/backend/gn_module_monitoring/config/generic/config.json index 76365477f..8fa736b3b 100644 --- a/backend/gn_module_monitoring/config/generic/config.json +++ b/backend/gn_module_monitoring/config/generic/config.json @@ -18,6 +18,7 @@ "observer_list": "nom_liste", "taxonomy": "__MODULE.TAXONOMY_DISPLAY_FIELD_NAME", "taxonomy_list": "nom_liste", + "cd_nom": "cd_nom", "sites_group": "sites_group_name", "habitat": "lb_hab_fr", "area": "area_name", diff --git a/backend/gn_module_monitoring/config/generic/individual.json b/backend/gn_module_monitoring/config/generic/individual.json new file mode 100644 index 000000000..69352ffc7 --- /dev/null +++ b/backend/gn_module_monitoring/config/generic/individual.json @@ -0,0 +1,90 @@ +{ + "id_field_name": "id_individual", + "description_field_name": "individual_name", + "chained": true, + "filters": { + "active": true + }, + "label": "Individu", + "genre": "M", + "display_properties": ["uuid_individual", + "individual_name", + "active", + "id_nomenclature_sex", + "comment", + "cd_nom", + "id_digitiser", + "id_operator" + ], + "display_list": [ + "individual_name", + "uuid_individual", + "active", + "nb_sites" + ], + "uuid_field_name": "uuid_individual", + "generic": { + "id_individual": { + "type_widget": "text", + "attribut_label": "Id individual", + "hidden": true + }, + "nb_sites": { + "attribut_label": "Nombre de sites associés" + }, + "uuid_individual": { + "attribut_label": "uuid" + }, + "individual_name": { + "type_widget": "text", + "attribut_label": "Nom de l'individu", + "required": true + }, + "id_nomenclature_sex": { + "type_widget": "nomenclature", + "attribut_label": "Sexe", + "code_nomenclature_type": "SEXE", + "type_util": "nomenclature" + }, + "active": { + "type_widget": "bool_checkbox", + "attribut_label": "Actif", + "definition": "Activer cet individu", + "value": true + }, + "comment": { + "type_widget": "textarea", + "attribut_label": "Commentaires" + }, + "cd_nom": { + "type_widget": "taxonomy", + "attribut_label": "Espèce", + "type_util": "taxonomy", + "id_list": "__MODULE.ID_LIST_TAXONOMY", + "required": "({value}) => '__MODULE.CD_NOM' === 'None'", + "hidden": "({value}) => '__MODULE.CD_NOM' !== 'None'" + }, + "id_digitiser": { + "type_widget": "text", + "attribut_label": "Numérisateur", + "required": true, + "hidden": true, + "type_util": "user" + }, + "medias": { + "type_widget": "medias", + "attribut_label": "Médias", + "schema_dot_table": "gn_monitoring.t_marking_events" + } + }, + "change": [ + "({objForm, meta}) => {", + "const cd_nom = __MODULE.CD_NOM", + "if (!objForm.controls.cd_nom.dirty) {", + "objForm.patchValue({cd_nom: cd_nom})", + "}", + "}", + "" + ] +} + \ No newline at end of file diff --git a/backend/gn_module_monitoring/config/generic/marking.json b/backend/gn_module_monitoring/config/generic/marking.json new file mode 100644 index 000000000..2e2c0531b --- /dev/null +++ b/backend/gn_module_monitoring/config/generic/marking.json @@ -0,0 +1,89 @@ +{ + "id_field_name": "id_marking", + "description_field_name": "id_marking", + "chained": true, + "label": "Marquage", + "genre": "M", + "display_properties": [ + "marking_location", + "marking_code", + "marking_details" + ], + "display_list": [ + "marking_location", + "marking_code", + "marking_details", + "id_nomenclature_marking_type", + "id_base_marking_site", + "id_operator" + ], + "uuid_field_name": "id_marking", + "generic": { + "id_marking": { + "type_widget": "text", + "attribut_label": "Id marquage", + "hidden": true + }, + "id_individual": { + "type_widget": "individuals", + "attribut_label": "Choix de l'individu", + "id_module": "__MODULE.ID_MODULE", + "hidden": true + }, + "marking_date": { + "type_widget": "date", + "attribut_label": "Date de marquage", + "required": true + }, + "id_operator": { + "type_widget": "datalist", + "attribut_label": "Opérateur", + "api": "users/menu/__MODULE.ID_LIST_OBSERVER", + "application": "GeoNature", + "keyValue": "id_role", + "keyLabel": "nom_complet", + "type_util": "user", + "required": true + }, + "id_base_marking_site": { + "type_widget": "datalist", + "attribut_label": "Site", + "type_util": "site", + "keyValue": "id_base_site", + "keyLabel": "base_site_name", + "api": "__MONITORINGS_PATH/list/__MODULE.MODULE_CODE/site?id_module=__MODULE.ID_MODULE&fields=id_base_site&fields=base_site_name", + "application": "GeoNature" + }, + "id_nomenclature_marking_type": { + "type_widget": "nomenclature", + "attribut_label": "Type de marquage", + "code_nomenclature_type": "TYP_MARQUAGE", + "type_util": "nomenclature", + "required": true + }, + "marking_location": { + "type_widget": "text", + "attribut_label": "Localisation du marquage" + }, + "marking_code": { + "type_widget": "text", + "attribut_label": "Code du marquage" + }, + "marking_details": { + "type_widget": "text", + "attribut_label": "Détails du marquage" + }, + "id_digitiser": { + "type_widget": "text", + "attribut_label": "Numérisateur", + "required": true, + "hidden": true, + "type_util": "user" + }, + "medias": { + "type_widget": "medias", + "attribut_label": "Médias", + "schema_dot_table": "gn_monitoring.t_marking_events" + } + } +} \ No newline at end of file diff --git a/backend/gn_module_monitoring/config/generic/module.json b/backend/gn_module_monitoring/config/generic/module.json index 64875e008..a36229ed0 100644 --- a/backend/gn_module_monitoring/config/generic/module.json +++ b/backend/gn_module_monitoring/config/generic/module.json @@ -99,8 +99,6 @@ "definition": "Affichage des groupes de site en dessinant l'enveloppe des sites du groupe et en affichant l'aire du groupe de sites", "hidden": true }, - - "taxonomy_display_field_name": { "type_widget": "datalist", "attribut_label": "Affichage des taxons", @@ -116,7 +114,13 @@ "required": true, "designStyle": "bootstrap" }, - + "cd_nom": { + "type_widget": "taxonomy", + "attribut_label": "Espèce", + "type_util": "taxonomy", + "required": false, + "hidden": true + }, "active_frontend": { "type_widget": "bool_checkbox", "attribut_label": "Afficher dans le menu ?", diff --git a/backend/gn_module_monitoring/config/generic/observation.json b/backend/gn_module_monitoring/config/generic/observation.json index c15997d73..9263d5a63 100644 --- a/backend/gn_module_monitoring/config/generic/observation.json +++ b/backend/gn_module_monitoring/config/generic/observation.json @@ -4,7 +4,10 @@ "chained": true, "label": "Observation", "genre": "F", - "display_properties": ["cd_nom", "comments"], + "display_properties": [ + "cd_nom", + "comments" + ], "uuid_field_name": "uuid_observation", "generic": { "id_observation": { @@ -24,6 +27,11 @@ "hidden": true, "type_util": "user" }, + "id_individual": { + "type_widget": "text", + "attribut_label": "Id individu", + "hidden": true + }, "cd_nom": { "type_widget": "taxonomy", "attribut_label": "Espèce", @@ -44,4 +52,4 @@ "schema_dot_table": "gn_monitoring.t_observations" } } -} +} \ No newline at end of file diff --git a/backend/gn_module_monitoring/config/generic/site.json b/backend/gn_module_monitoring/config/generic/site.json index 4f0a52aed..d965891f6 100644 --- a/backend/gn_module_monitoring/config/generic/site.json +++ b/backend/gn_module_monitoring/config/generic/site.json @@ -6,7 +6,11 @@ "genre": "M", "geom_field_name": "geom", "uuid_field_name": "uuid_base_site", - "geometry_type": ["Point", "LineString", "Polygon"], + "geometry_type": [ + "Point", + "LineString", + "Polygon" + ], "display_properties": [ "base_site_name", "base_site_code", @@ -25,7 +29,8 @@ "last_visit", "id_inventor", "nb_visits", - "types_site" + "types_site", + "nb_individuals" ], "sorts": [ { @@ -80,6 +85,9 @@ "nb_visits": { "attribut_label": "Nb. visites" }, + "nb_individuals": { + "attribut_label": "Nb. individus" + }, "uuid_base_site": { "attribut_label": "uuid" }, @@ -115,4 +123,4 @@ "required": false } } -} +} \ No newline at end of file diff --git a/backend/gn_module_monitoring/config/repositories.py b/backend/gn_module_monitoring/config/repositories.py index 5251f7327..8b9d1736e 100644 --- a/backend/gn_module_monitoring/config/repositories.py +++ b/backend/gn_module_monitoring/config/repositories.py @@ -193,6 +193,7 @@ def get_config(module_code=None, force=False): "b_draw_sites_group", "taxonomy_display_field_name", "id_module", + "cd_nom", ]: var_name = "__MODULE.{}".format(field_name.upper()) config["custom"][var_name] = getattr(module, field_name) diff --git a/backend/gn_module_monitoring/migrations/398f94b364f7_add_cd_nom_t_module_complements.py b/backend/gn_module_monitoring/migrations/398f94b364f7_add_cd_nom_t_module_complements.py new file mode 100644 index 000000000..8906bf1bd --- /dev/null +++ b/backend/gn_module_monitoring/migrations/398f94b364f7_add_cd_nom_t_module_complements.py @@ -0,0 +1,36 @@ +"""[individuals] add cd_nom t_module_complements + +Revision ID: 398f94b364f7 +Revises: 6f90dd1aaf69 +Create Date: 2023-12-20 13:52:18.563621 + +""" + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = "398f94b364f7" +down_revision = "6f90dd1aaf69" +branch_labels = None +depends_on = None + + +def upgrade(): + op.add_column( + schema="gn_monitoring", + table_name="t_module_complements", + column=sa.Column( + "cd_nom", + sa.Integer, + ), + ) + + +def downgrade(): + op.drop_column( + schema="gn_monitoring", + table_name="t_module_complements", + column_name="cd_nom", + ) diff --git a/backend/gn_module_monitoring/migrations/461b82ee737a_add_individual_permissions.py b/backend/gn_module_monitoring/migrations/461b82ee737a_add_individual_permissions.py new file mode 100644 index 000000000..6115ad5a0 --- /dev/null +++ b/backend/gn_module_monitoring/migrations/461b82ee737a_add_individual_permissions.py @@ -0,0 +1,47 @@ +"""[individuals] add individual permissions + +Revision ID: 461b82ee737a +Revises: 398f94b364f7 +Create Date: 2023-11-21 14:14:48.084725 + +""" + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = "461b82ee737a" +down_revision = "398f94b364f7" +branch_labels = None +depends_on = None + + +def upgrade(): + op.execute( + """ + INSERT INTO gn_permissions.t_objects (code_object, description_object) + VALUES ('MONITORINGS_INDIVIDUALS', 'Permissions sur les individus'), + ('MONITORINGS_MARKINGS', 'Permissions sur les marquages'); + """ + ) + + +def downgrade(): + op.execute( + """ + DELETE FROM gn_permissions.t_permissions WHERE id_object in + (SELECT id_object FROM gn_permissions.t_objects WHERE code_object in ('MONITORINGS_INDIVIDUALS', 'MONITORINGS_MARKINGS')) + """ + ) + op.execute( + """ + DELETE FROM gn_permissions.t_permissions_available WHERE id_object in + (SELECT id_object FROM gn_permissions.t_objects WHERE code_object in ('MONITORINGS_INDIVIDUALS', 'MONITORINGS_MARKINGS')) + """ + ) + op.execute( + """ + DELETE FROM gn_permissions.t_objects where code_object in ('MONITORINGS_INDIVIDUALS', 'MONITORINGS_MARKINGS'); + """ + ) diff --git a/backend/gn_module_monitoring/monitoring/definitions.py b/backend/gn_module_monitoring/monitoring/definitions.py index d4763329f..91721ef84 100644 --- a/backend/gn_module_monitoring/monitoring/definitions.py +++ b/backend/gn_module_monitoring/monitoring/definitions.py @@ -5,8 +5,14 @@ TMonitoringObservations, TMonitoringObservationDetails, TMonitoringSitesGroups, + TMonitoringIndividuals, + TMonitoringMarkingEvent, +) +from gn_module_monitoring.monitoring.objects import ( + MonitoringModule, + MonitoringSite, + MonitoringIndividual, ) -from gn_module_monitoring.monitoring.objects import MonitoringModule, MonitoringSite from gn_module_monitoring.monitoring.base import monitoring_definitions from gn_module_monitoring.monitoring.repositories import MonitoringObject from gn_module_monitoring.monitoring.geom import MonitoringObjectGeom @@ -25,6 +31,8 @@ "observation": TMonitoringObservations, "observation_detail": TMonitoringObservationDetails, "sites_group": TMonitoringSitesGroups, + "individual": TMonitoringIndividuals, + "marking": TMonitoringMarkingEvent, } MonitoringObjects_dict = { @@ -34,6 +42,8 @@ "observation": MonitoringObject, "observation_detail": MonitoringObject, "sites_group": MonitoringObjectGeom, + "individual": MonitoringIndividual, + "marking": MonitoringObject, } monitoring_definitions.set(MonitoringObjects_dict, MonitoringModels_dict) diff --git a/backend/gn_module_monitoring/monitoring/models.py b/backend/gn_module_monitoring/monitoring/models.py index 0594da176..badf62069 100644 --- a/backend/gn_module_monitoring/monitoring/models.py +++ b/backend/gn_module_monitoring/monitoring/models.py @@ -28,6 +28,9 @@ BibTypeSite, cor_visit_observer, TObservations, + TIndividuals, + TMarkingEvent, + corIndividualModule, ) from geonature.core.gn_meta.models import TDatasets from geonature.core.gn_commons.models import TModules, cor_module_dataset @@ -301,8 +304,24 @@ class TMonitoringSites(TBaseSites, PermissionModel, SitesQuery): ) geom_geojson = column_property(func.ST_AsGeoJSON(TBaseSites.geom), deferred=True) + types_site = DB.relationship("BibTypeSite", secondary=cor_site_type, overlaps="sites") + nb_individuals = column_property( + select([func.count(func.distinct(TIndividuals.id_individual))]) + .join_from( + TBaseVisits, TObservations, TBaseVisits.id_base_visit == TObservations.id_base_visit + ) + .join_from( + TObservations, TIndividuals, TObservations.id_individual == TIndividuals.id_individual + ) + .where(TBaseVisits.id_base_site == id_base_site) + .correlate_except( + TBaseVisits + ) # Correlate permet d'éviter une répétition de la condition WHERE dans la sous requête + .scalar_subquery() + ) + @hybrid_property def last_visit(self): query = select(func.max(TBaseVisits.visit_date_min)).where( @@ -512,6 +531,7 @@ class TMonitoringModules(TModules, PermissionModel, MonitoringQuery): id_list_observer = DB.Column(DB.Integer) id_list_taxonomy = DB.Column(DB.Integer) + cd_nom = DB.Column(DB.Integer) taxonomy_display_field_name = DB.Column(DB.Unicode) b_synthese = DB.Column(DB.Boolean) @@ -543,18 +563,28 @@ class TMonitoringModules(TModules, PermissionModel, MonitoringQuery): sites_groups = DB.relationship(TMonitoringSitesGroups, secondary=cor_sites_group_module) + individuals = DB.relationship( + "TMonitoringIndividuals", + lazy="select", + enable_typechecks=False, + secondary=corIndividualModule, + primaryjoin=(corIndividualModule.c.id_module == id_module), + secondaryjoin=(corIndividualModule.c.id_individual == TIndividuals.id_individual), + foreign_keys=[corIndividualModule.c.id_individual, corIndividualModule.c.id_module], + overlaps="modules", + # viewonly=True, + ) + datasets = DB.relationship( "TDatasets", secondary=cor_module_dataset, join_depth=0, overlaps="modules", ) - types_site = DB.relationship( "BibTypeSite", secondary=cor_module_type, ) - data = DB.Column(JSONB) # visits = DB.relationship( @@ -572,3 +602,36 @@ class TMonitoringModules(TModules, PermissionModel, MonitoringQuery): cascade="all", overlaps="sites,sites_group,module", ) + + +@serializable +class TMonitoringMarkingEvent(TMarkingEvent, PermissionModel, MonitoringQuery): + pass + + +@serializable +class TMonitoringIndividuals(TIndividuals, PermissionModel, MonitoringQuery): + + nb_sites = column_property( + select([func.count(func.distinct(TMonitoringSites.id_base_site))]) + .join_from( + TObservations, TBaseVisits, TBaseVisits.id_base_visit == TObservations.id_base_visit + ) + .join_from( + TBaseVisits, + TMonitoringSites, + TMonitoringSites.id_base_site == TBaseVisits.id_base_site, + ) + .where(TObservations.id_individual == TIndividuals.id_individual) + .correlate_except( + TBaseVisits + ) # Correlate permet d'éviter une répétition de la condition WHERE dans la sous requête + .scalar_subquery() + ) + + # Redéfinition de la relation marking pour utiliser la classe TMonitoringMarkingEvent + # qui hérite de PermissionModel pour le CRUVED + markings = DB.relationship( + TMonitoringMarkingEvent, + primaryjoin=(TIndividuals.id_individual == TMonitoringMarkingEvent.id_individual), + ) diff --git a/backend/gn_module_monitoring/monitoring/objects.py b/backend/gn_module_monitoring/monitoring/objects.py index 78b9eaae0..b6e6292a2 100644 --- a/backend/gn_module_monitoring/monitoring/objects.py +++ b/backend/gn_module_monitoring/monitoring/objects.py @@ -1,5 +1,3 @@ -from geonature.utils.env import DB - from gn_module_monitoring.monitoring.repositories import MonitoringObject from gn_module_monitoring.monitoring.geom import MonitoringObjectGeom @@ -53,3 +51,23 @@ def preprocess_data(self, properties, data=[]): # ] # properties["types_site"] = types_site # TODO: A enlever une fois qu'on aura enelever le champ "id_nomenclature_type_site" du model et de la bdd + + +class MonitoringIndividual(MonitoringObject): + """ + PATCH + pour pouvoir renseigner la table cor_individual_module + avec la méthode from_dict + """ + + def get_value_specific(self, param_name): + # DO NOT LOAD data here + pass + + def preprocess_data(self, data): + module_ids = [module.id_module for module in self._model.modules] + id_module = int(data["id_module"]) + if id_module not in module_ids: + module_ids.append(id_module) + + data["modules"] = module_ids diff --git a/backend/gn_module_monitoring/monitoring/repositories.py b/backend/gn_module_monitoring/monitoring/repositories.py index a7f79eff1..8423099cb 100644 --- a/backend/gn_module_monitoring/monitoring/repositories.py +++ b/backend/gn_module_monitoring/monitoring/repositories.py @@ -12,6 +12,9 @@ from gn_module_monitoring.monitoring.models import PermissionModel, TMonitoringModules import logging +from ..utils.utils import to_int +from .base import monitoring_definitions +from sqlalchemy.orm import joinedload log = logging.getLogger(__name__) diff --git a/backend/gn_module_monitoring/monitoring/schemas.py b/backend/gn_module_monitoring/monitoring/schemas.py index b33e37c89..acd45adcf 100644 --- a/backend/gn_module_monitoring/monitoring/schemas.py +++ b/backend/gn_module_monitoring/monitoring/schemas.py @@ -18,6 +18,7 @@ TMonitoringModules, TMonitoringObservations, TMonitoringObservationDetails, + TMonitoringIndividuals, ) @@ -196,3 +197,10 @@ class Meta: load_relationships = True medias = MA.Nested(MediaSchema, many=True) + + +class MonitoringIndividualsSchema(MA.SQLAlchemyAutoSchema): + class Meta: + model = TMonitoringIndividuals + include_fk = True + load_relationships = True diff --git a/backend/gn_module_monitoring/monitoring/serializer.py b/backend/gn_module_monitoring/monitoring/serializer.py index bcc2b3b94..86f0be31e 100644 --- a/backend/gn_module_monitoring/monitoring/serializer.py +++ b/backend/gn_module_monitoring/monitoring/serializer.py @@ -9,6 +9,10 @@ from geonature.utils.env import DB from geonature.core.gn_permissions.tools import get_scopes_by_action +from geonature.core.gn_monitoring.models import ( + TIndividuals, +) +from geonature.core.gn_monitoring.schema import TMarkingEventSchema from gn_module_monitoring.utils.utils import to_int from gn_module_monitoring.routes.data_utils import id_field_name_dict from gn_module_monitoring.utils.routes import get_objet_with_permission_boolean @@ -21,6 +25,7 @@ MonitoringVisitsSchema, MonitoringObservationsSchema, MonitoringObservationsDetailsSchema, + MonitoringIndividualsSchema, ) MonitoringSerializer_dict = { @@ -30,6 +35,8 @@ "sites_group": MonitoringSitesGroupsSchema, "observation": MonitoringObservationsSchema, "observation_detail": MonitoringObservationsDetailsSchema, + "individual": MonitoringIndividualsSchema, + "marking": TMarkingEventSchema, } @@ -108,6 +115,7 @@ def unflatten_specific_properties(self, properties): not is_in_model and prop not in self.config_schema("generic").keys() and prop != "id_module" + and prop != "data" ): properties["data"][prop] = properties.pop(prop) @@ -199,6 +207,7 @@ def serialize(self, depth=1, is_child=False): return None self._model = Model() + # Liste des propriétés de l'objet qui doivent être récupérées display_properties = [] # Liste des propriétés spécifique de l'objet qui doivent être récupérées @@ -219,8 +228,8 @@ def serialize(self, depth=1, is_child=False): for k in display_properties if k in module_config[self._object_type]["specific"].keys() ] - - display_generic.append("data") + if hasattr(self._model, "data"): + display_generic.append("data") display_generic.append(self.config_param("id_field_name")) # Sérialisation de l'objet @@ -284,7 +293,6 @@ def serialize(self, depth=1, is_child=False): return monitoring_object_dict def preprocess_data(self, data): - # a redefinir dans la classe pass def populate(self, post_data): diff --git a/backend/gn_module_monitoring/routes/data_utils.py b/backend/gn_module_monitoring/routes/data_utils.py index a398d2260..81db1a225 100644 --- a/backend/gn_module_monitoring/routes/data_utils.py +++ b/backend/gn_module_monitoring/routes/data_utils.py @@ -88,9 +88,13 @@ def get_init_data(module_code): out["nomenclature"] = [] for code_type in data.get("nomenclature"): nomenclature_list = get_nomenclature_list(code_type=code_type) - for nomenclature in nomenclature_list["values"]: - nomenclature["code_type"] = code_type - out["nomenclature"].append(nomenclature) + # TODO : exception quand pas de valeur + try: + for nomenclature in nomenclature_list["values"]: + nomenclature["code_type"] = code_type + out["nomenclature"].append(nomenclature) + except KeyError: + pass # user if data.get("user"): diff --git a/dependencies/GeoNature b/dependencies/GeoNature index 30c272664..352f607da 160000 --- a/dependencies/GeoNature +++ b/dependencies/GeoNature @@ -1 +1 @@ -Subproject commit 30c27266495b4affc635f79748c9984feb81a6d7 +Subproject commit 352f607da4bacefefa2ec562cb4bd4d10daf5272 diff --git a/docs/images/2023-11-MCD-individuals.png b/docs/images/2023-11-MCD-individuals.png new file mode 100644 index 000000000..55f0a3572 Binary files /dev/null and b/docs/images/2023-11-MCD-individuals.png differ diff --git a/docs/images/individual_widget.png b/docs/images/individual_widget.png new file mode 100644 index 000000000..fb04cbb40 Binary files /dev/null and b/docs/images/individual_widget.png differ diff --git a/docs/images/individual_widget_create.png b/docs/images/individual_widget_create.png new file mode 100644 index 000000000..a3f3ae1ec Binary files /dev/null and b/docs/images/individual_widget_create.png differ diff --git a/docs/individuals.md b/docs/individuals.md new file mode 100644 index 000000000..dbb596b39 --- /dev/null +++ b/docs/individuals.md @@ -0,0 +1,188 @@ +# Gestion des individus + +## Introduction + +Le suivi d'individus dans Monitoring permet de : + +- Créer des individus +- Créer des marquages associés aux individus (un individu peut + avoir plusieurs marquages) +- Partager des individus entre les modules (pas encore possible ?) + +## Base de données + +Le schéma de base de données est le suivant : + +![MCD](images/2023-11-MCD-individuals.png) + +> **_NOTE:_** Les tables en gris sont affichées à des fins de compréhension. + +## Nomenclature + +Le type de marquage est stocké dans une nomenclature dont le type +est `TYP_MARQUAGE`. Ce type a été spécialement créé pour le marquage des +individus. + +## Implémentation dans le module + +### Objets à déclarer + +Il y a donc 2 objets déclarés dans le module : + +- `individual` +- `marking` + +L'objet `marking` doit être un enfant de l'objet `individual`. Dans +le fichier `config.json` d'un sous-module, il suffit de déclarer +le `tree` comme suit : + +```json +"tree": { + "module": { + "site": { + "visit": { + "observation": null + } + }, + "individual": { + "marking": null + } + } + } +``` + +L'objet `individual` peut être inséré comme tel pour créer un onglet +au même niveau que le site afin d'avoir la liste d'individus +facilement accessible. + +Des champs additionnels personnalisés peuvent être définis au niveau des marquages +des individus via la création d'un fichier `marking.json`. +Il n'est pas possible d'ajouter de champs additionnels au niveau des individus eux-mêmes. +Cette impossibilité émane du fait que le [widget individu](#le-widget-individu), +créé côté GeoNature, propose un formulaire de création disposant de champs +fixes qui ne doit donc pas différer du formulaire côté Monitoring. + +### Le widget individu + +Ce widget, disponible dans les composants de GeoNature, permet de : + +- Sélectionner un individu déjà présent +- Créer un nouvel individu + +Il se présente comme tel : + +![Widget](images/individual_widget.png) + +Et en cliquant sur le "+", un formulaire de création d'individu apparaîtra : + +![WidgetCreate](images/individual_widget_create.png) + +Il est paramétrable en json comme ceci : + +```json +"id_individual": { + "type_widget": "individuals", + "attribut_label": "Choix de l'individu", + "id_module": "__MODULE.ID_MODULE", + "id_list": "__MODULE.ID_LIST_TAXONOMY", + "cd_nom": "__MODULE.CD_NOM" +} +``` + +Les attributs sont optionnels (si le contraire n'est pas spécifié) et sont les suivants : + +- `id_module` (**obligatoire**) : permet de spécifier le sous-module auquel + doivent être rattachés les individus proposés dans le menu déroulant. + Il est obligatoire pour assurer le calcul de permissions de + l'utilisateur en "Read" et en "Create". +- `id_list` : dans le formulaire de saisie, restreint la saisie d'espèces à + une liste taxonomique +- `cd_nom` : fixe le champ Taxon au cd_nom donné et donc ne le fait pas + apparaître dans le formulaire. + +### Cas d'un protocole mono-spécifique + +Il est possible de renseigner une seule espèce pour un protocole. +Comme spécifié dans la documentation du sous-module, une variable +`__MODULE.CD_NOM` est disponible pour renseigner un même `cd_nom` +pour chaque widget. + +Dans le cas des individus, le fichier `config.json` doit +paramétrer un champ `cd_nom` et masquer le champ `id_list_taxonomy` +(qui devient inutile si une seule espèce est définie) : + +```json +{ + "module_label": "Test", + "module_desc": "Module de test individus", + "specific": { + "cd_nom": { + "type_widget": "taxonomy", + "attribut_label": "Espèce", + "type_util": "taxonomy", + "required": true + }, + "id_list_taxonomy": { + "hidden": true + } + } +} +``` + +Le fichier `observation.json` peut donc être écrit de cette manière : + +```json +{ + "specific": { + "cd_nom": { + "type_widget": "text", + "required": false, + "hidden": true + }, + "id_individual": { + "type_widget": "individuals", + "attribut_label": "Choix de l'individu", + "id_module": "__MODULE.ID_MODULE", + "id_list": "__MODULE.ID_LIST_TAXONOMY", + "cd_nom": "__MODULE.CD_NOM", + "hidden": false + } + } +} +``` + +## Cas des observations + +Pour que les individus soient implémentés dans le module, la contrainte +`NOT NULL` sur la colonne `cd_nom` de `gn_monitoring.t_observations` a du +être supprimée au profit d'une contrainte `NOT NULL` sur la colonne `cd_nom` +**OU** la nouvelle colonne `id_individual`. + +Pour pouvoir donc saisir des individus au lieu d'espèces dans une observation, +la configuration minimale du fichier `observation.json` doit être la suivante : + +```json +{ + "specific": { + "cd_nom": { + "type_widget": "text", + "required": false, + "hidden": true + }, + "id_individual": { + "type_widget": "individuals", + "attribut_label": "Choix de l'individu", + "id_module": "__MODULE.ID_MODULE", + "id_list": "__MODULE.ID_LIST_TAXONOMY", + "hidden": false + } + } +} +``` + +Elle permet de désactiver la saisie du `cd_nom` au profit de l'individu. + +## Permissions + +Comme tout objet Monitoring, des permissions seront ajoutées à l'installation +pour CRUD sur les objets `MONITORINGS_INDIVIDUALS` et `MONITORINGS_MARKINGS`. diff --git a/docs/sous_module.md b/docs/sous_module.md index 1b8e8cc36..dd0018dec 100644 --- a/docs/sous_module.md +++ b/docs/sous_module.md @@ -209,6 +209,7 @@ Pour cela il faut utiliser les variables suivantes : * `__MODULE.TAXONOMY_DISPLAY_FIELD_NAME` * `__MODULE.TYPES_SITE` * `__MODULE.IDS_TYPES_SITE` +* `__MODULE.CD_NOM` qui peuvent servir dans la définition des formulaires (en particulier pour les datalist). Voir ci dessous @@ -216,7 +217,7 @@ pour les datalist). Voir ci dessous #### Liste des widgets disponibles | Widgets | Commentaire | -|--------------|--------------------------------------------------------------------------| +| ------------ | ------------------------------------------------------------------------ | | text | Texte sur une seule ligne | | textarea | Texte sur une plusieurs lignes | | radio | Choix multiples uniques | @@ -236,9 +237,9 @@ pour les datalist). Voir ci dessous #### Listes des paramètres disponibles par type de widgets : | Widgets | Paramètres | Commentaire | -|-----------------------------------------|------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------| +| --------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | | Tous | attribut_label | Label du formulaire | -| Tous | definition | Ajoute une tooltip avec le contenu de ce paramètre (le paramètre `link_definition` ne doit pas être défini) | +| Tous | definition | Ajoute une tooltip avec le contenu de ce paramètre (le paramètre `link_definition` ne doit pas être défini) | | Tous | required | Booléen : permet de rendre obligatoire cet input | | Tous | hidden | Booléen : permet de cacher un formulaire | | Tous | link_definition | Ajoute un lien vers l'addresse pointé par ce paramètre. Le paramètre `definition` doit également être définit | @@ -252,7 +253,7 @@ pour les datalist). Voir ci dessous | nomenclature | cd_nomenclatures | Liste des codes nomenclatures à afficher (afin d'éliminer certains items de nomenclatures que l'on ne veut pas pour ce sous-module) | | nomenclature / dataset | multi_select | Booléan : permet de seléctionner plusieurs items de nomenclatures | | dataset | module_code | Limite aux jeu de données associés à ce module | -| html | html | Contenu du bloc html | +| html | html | Contenu du bloc html | ## Définir une nouvelle variable