From 8fa697b65ee01019ccba1c75ea59d6cf4e201514 Mon Sep 17 00:00:00 2001 From: Jacques Fize <4259846+jacquesfize@users.noreply.github.com> Date: Wed, 8 Jan 2025 16:47:11 +0100 Subject: [PATCH] release(2.15.1) : minor fixes (#3293) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(mapping,import): enable save of public mapping when user got U>3 + TODO -> check if user is owner * feat(import, fieldmapping): now owner of public mapping can edit * feat(install): add new grid layer to database population script * fix(import,upload): fix autofill of the dataset form with query params * feat(import, mapping) : update public mapping : (owner + u>0) ou (u>3) * fix(admin) import mapping * fix(import, mapping) fix checkbox and import step * fix(doc): fix some rendering issues in the documentation * Delete docs/import-level-2.rst Moved to https://github.com/PnX-SI/Ressources-techniques/blob/master/GeoNature/V2/import-avance/readme.md * Delete docs/import-level-1.rst * change theme to furo * feat(doc): limit depth of right section Moved to https://github.com/PnX-SI/Ressources-techniques/blob/master/GeoNature/V2/import-basique/readme.md * Doc admin - Move manual import into https://github.com/PnX-SI/Ressources-techniques/tree/master/GeoNature/V2 * Complement CHANGELOG 2.15.1 * feat(gitignore): add geonature.local.env in the gitignore * fix(pdf_ca): make modifs for acquisition framework pdf export List of modifications: - Display label rather than ID for nomenclature_territorial_level associated to "Etendue territoriale" - Do not display "Cible taxonomique" section if `data['target_description']` is empty i.e. if there is no information to display - Change "Liste des jeux de données associés au cadre" to "Liste des jeux de données associés" - Add count of Synthese observations for each dataset listed, associated to "Nombre d'observations" // change `get_export_pdf_acquisition_frameworks` endpoint to add these counts - Use `data.datasets` rather than `data.t_datasets` * fix(pdf_ca): fix typo error in af pdf export * Feat(CA, pdf) add occhabs by datasets * feat(tests) add test * feat(ca) add dataset detail to acquisiton framework * update version and requirements * fix(test): fix frontend test on field mapping with the modification on the public mapping update --------- Co-authored-by: Pierre-Narcisi Co-authored-by: Camille Monchicourt Co-authored-by: VincentCauchois --- .gitignore | 3 +- VERSION | 2 +- .../geonature/core/gn_meta/models/datasets.py | 21 + backend/geonature/core/gn_meta/schemas.py | 2 + backend/geonature/core/imports/admin.py | 36 +- backend/geonature/core/imports/models.py | 23 +- backend/geonature/core/imports/schemas.py | 2 + .../acquisition_framework_template_pdf.html | 21 +- .../tests/imports/jsonschema_definitions.py | 3 + backend/geonature/tests/test_gn_meta.py | 13 + backend/requirements-dev.txt | 44 +- backend/requirements.txt | 44 +- docs/CHANGELOG.md | 11 + docs/admin-manual.rst | 197 +++++---- docs/conf.py | 8 +- docs/development.rst | 96 ++--- docs/https.rst | 10 +- docs/import-level-1.rst | 168 -------- docs/import-level-2.rst | 379 ------------------ docs/installation-all.rst | 40 +- docs/installation-standalone.rst | 56 +-- docs/installation.rst | 16 +- docs/requirements.in | 5 +- docs/requirements.txt | 29 +- docs/tests_backend.rst | 40 +- docs/tests_frontend.rst | 16 +- .../e2e/import/step3-field-mapping-spec.js | 23 +- .../metadataModule/af/af-card.component.html | 6 + .../fields-mapping-step.component.ts | 43 +- .../upload-file-step.component.ts | 6 + .../import_report.component.html | 2 +- .../mappings/field-mapping.service.ts | 8 +- frontend/src/assets/i18n/fr.json | 2 +- install/03b_populate_db.sh | 16 + install/install_all/install_all.ini | 2 +- setup.py | 3 +- 36 files changed, 481 insertions(+), 915 deletions(-) delete mode 100644 docs/import-level-1.rst delete mode 100644 docs/import-level-2.rst diff --git a/.gitignore b/.gitignore index 87ea72f8f0..e03de8810e 100755 --- a/.gitignore +++ b/.gitignore @@ -155,4 +155,5 @@ install_all/install_all.log /docs/CHANGELOG.html /contrib/*/frontend/node_modules -Makefile.local \ No newline at end of file +Makefile.local +geonature.local.env \ No newline at end of file diff --git a/VERSION b/VERSION index 68e69e405e..3b1fc7950f 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.15.0 +2.15.1 diff --git a/backend/geonature/core/gn_meta/models/datasets.py b/backend/geonature/core/gn_meta/models/datasets.py index eacb7785e8..ea199f4204 100644 --- a/backend/geonature/core/gn_meta/models/datasets.py +++ b/backend/geonature/core/gn_meta/models/datasets.py @@ -139,6 +139,27 @@ def user_actors(self): def organism_actors(self): return [actor.organism for actor in self.cor_dataset_actor if actor.organism is not None] + @hybrid_property + def obs_count(self): + from geonature.core.gn_synthese.models import Synthese + + return db.session.scalar( + select(func.count(Synthese.id_synthese)) + .select_from(Synthese) + .where(Synthese.id_dataset == self.id_dataset) + ) + + @hybrid_property + def hab_count(self): + from gn_module_occhab.models import OccurenceHabitat, Station + + return db.session.scalar( + select(func.count(OccurenceHabitat.id_habitat)) + .select_from(OccurenceHabitat) + .where(Station.id_station == OccurenceHabitat.id_station) + .where(Station.id_dataset == self.id_dataset) + ) + def is_deletable(self): return not DB.session.execute(self.synthese_records.exists().select()).scalar() diff --git a/backend/geonature/core/gn_meta/schemas.py b/backend/geonature/core/gn_meta/schemas.py index d4e04bf44f..b34f67aba3 100644 --- a/backend/geonature/core/gn_meta/schemas.py +++ b/backend/geonature/core/gn_meta/schemas.py @@ -67,6 +67,8 @@ class Meta: cor_territories = MA.Nested(NomenclatureSchema, many=True, unknown=EXCLUDE) acquisition_framework = MA.Nested("AcquisitionFrameworkSchema", dump_only=True) sources = MA.Nested(SourceSchema, many=True, dump_only=True) + obs_count = fields.Int(dump_only=True) + hab_count = fields.Int(dump_only=True) @post_dump(pass_many=False, pass_original=True) def module_input(self, item, original, many, **kwargs): diff --git a/backend/geonature/core/imports/admin.py b/backend/geonature/core/imports/admin.py index 5f48485c8a..cf4dd0844e 100644 --- a/backend/geonature/core/imports/admin.py +++ b/backend/geonature/core/imports/admin.py @@ -14,7 +14,7 @@ from pypnnomenclature.models import TNomenclatures -from geonature.core.imports.models import FieldMapping, ContentMapping +from geonature.core.imports.models import Destination, FieldMapping, ContentMapping from flask_admin.contrib.sqla.form import AdminModelConverter from flask_admin.model.form import converts @@ -25,35 +25,21 @@ class MappingView(CruvedProtectedMixin, ModelView): object_code = "MAPPING" can_view_details = True - column_list = ( - "label", - "active", - "public", - ) + column_list = ("label", "active", "public", "destination") column_searchable_list = ("label",) column_filters = ( "active", "public", ) - form_columns = ( - "label", - "active", - "public", - "owners", - "values", - ) - column_details_list = ( - "label", - "active", - "public", - "owners", - "values", - ) + form_columns = ("label", "active", "public", "owners", "values", "destination") + column_details_list = ("label", "active", "public", "owners", "values", "destination") column_labels = { "active": "Actif", "owners": "Propriétaires", "values": "Correspondances", + "destination": "Destinations", } + column_formatters = {"destination": lambda v, c, m, p: m.destination.label} column_export_list = ( "label", "values", @@ -61,15 +47,21 @@ class MappingView(CruvedProtectedMixin, ModelView): def FieldMappingValuesValidator(form, field): + destination = db.session.execute( + db.select(Destination).where(Destination.id_destination == form.destination.raw_data[0]) + ).scalar_one_or_none() try: - FieldMapping.validate_values(field.data) + FieldMapping.validate_values(field.data, destination) except ValueError as e: raise StopValidation(*e.args) def ContentMappingValuesValidator(form, field): + destination = db.session.execute( + db.select(Destination).where(Destination.id_destination == form.destination.raw_data[0]) + ).scalar_one_or_none() try: - ContentMapping.validate_values(field.data) + ContentMapping.validate_values(field.data, destination) except ValueError as e: raise StopValidation(*e.args) diff --git a/backend/geonature/core/imports/models.py b/backend/geonature/core/imports/models.py index 958b359a9d..cbab0bc599 100644 --- a/backend/geonature/core/imports/models.py +++ b/backend/geonature/core/imports/models.py @@ -219,6 +219,9 @@ def has_instance_permission(self, user: Optional[User] = None, action_code: str ] return max_scope > 0 + def __repr__(self): + return self.label + @serializable class BibThemes(db.Model): @@ -520,7 +523,7 @@ class MappingTemplate(db.Model): __table_args__ = {"schema": "gn_imports"} id = db.Column(db.Integer, primary_key=True) - id_destination = db.Column(db.Integer, ForeignKey(Destination.id_destination)) + id_destination = db.Column(db.Integer, ForeignKey(Destination.id_destination), nullable=False) destination = relationship(Destination) label = db.Column(db.Unicode(255), nullable=False) type = db.Column(db.Unicode(10), nullable=False) @@ -667,7 +670,7 @@ class FieldMapping(MappingTemplate): } @staticmethod - def validate_values(field_mapping_json): + def validate_values(field_mapping_json, destination=None): """ Validate the field mapping values returned by the client form. @@ -689,8 +692,13 @@ def validate_values(field_mapping_json): "optional_conditions", "mandatory_conditions", ] + (g.destination if (destination is None) else destination) entities_for_destination: List[Entity] = ( - Entity.query.filter_by(destination=g.destination).order_by(sa.desc(Entity.order)).all() + Entity.query.filter_by( + destination=(g.destination if (destination is None) else destination) + ) + .order_by(sa.desc(Entity.order)) + .all() ) fields = [] for entity in entities_for_destination: @@ -717,7 +725,9 @@ def validate_values(field_mapping_json): entity, columns=bib_fields_col, optional_where_clause=sa.and_( - BibFields.destination == g.destination, BibFields.display == True + BibFields.destination + == (g.destination if (destination is None) else destination), + BibFields.display == True, ), ) ) @@ -771,10 +781,11 @@ class ContentMapping(MappingTemplate): } @staticmethod - def validate_values(values): + def validate_values(values, destination=None): nomenclature_fields = ( BibFields.query.filter( - BibFields.destination == g.destination, BibFields.nomenclature_type != None + BibFields.destination == (g.destination if (destination is None) else destination), + BibFields.nomenclature_type != None, ) .options( joinedload(BibFields.nomenclature_type).joinedload( diff --git a/backend/geonature/core/imports/schemas.py b/backend/geonature/core/imports/schemas.py index 7035f2e815..bdedb79bd1 100644 --- a/backend/geonature/core/imports/schemas.py +++ b/backend/geonature/core/imports/schemas.py @@ -4,6 +4,7 @@ from utils_flask_sqla.schema import SmartRelationshipsMixin from geonature.core.imports.models import Destination, FieldMapping, MappingTemplate +from pypnusershub.schemas import UserSchema from geonature.core.gn_commons.schemas import ModuleSchema from marshmallow import fields @@ -27,3 +28,4 @@ class Meta: cruved = fields.Dict() values = fields.Dict() + owners = fields.List(fields.Nested(UserSchema(only=["identifiant"]))) diff --git a/backend/geonature/templates/acquisition_framework_template_pdf.html b/backend/geonature/templates/acquisition_framework_template_pdf.html index e716288462..2bf12cee04 100644 --- a/backend/geonature/templates/acquisition_framework_template_pdf.html +++ b/backend/geonature/templates/acquisition_framework_template_pdf.html @@ -187,7 +187,7 @@

Territoires concernés

{% if data['id_nomenclature_territorial_level']: %} - Etendue territoriale : {{ data['id_nomenclature_territorial_level'] }} + Étendue territoriale : {{ data['nomenclature_territorial_level']['label_fr'] }} {% endif %} cible) -`````````````````````````````````````````` - -Le schéma gn_imports comporte trois tables permettant de préparer le mapping des champs entre la table importée (source) et une table de destination (target). - -* ``gn_imports.matching_tables`` permet de déclarer la table source et la table de destination. Noter le ``id_matching_table`` généré par la séquence lors de l'insertion d'un nouveau "matching" dans cette table. -* ``gn_imports.matching_fields`` permet de faire le matching entre les champs de la table source et de la table de destination. Vous devez indiquer le type de chacun des champs de la table de destination ainsi que le ``id_matching_table``. -* ``gn_imports.matching_geoms`` permet de préparer la création du geom dans la table de destination à partir du ou des champs constituant le geom fourni dans la table source : champs contenant les x et y pour un format ``xy`` ou le champ comportant le wkt pour le format ``wkt``. - -En attendant la création d'une interface permettant de faciliter l'import, vous devez remplir ces tables manuellement. Cependant, la fonction ``gn_imports.fct_generate_mapping('table_source', 'table_cible', forcedelete)`` permet de pré-générer un mapping. - -Si le mapping source/cible existe, la fonction ne fait rien et un message d'erreur est levé. Si le mapping n'existe pas ou si le paramètre ``forcedelete (boolean default = false)`` est à ``true``, la fonction crée le mapping en remplissant la table ``gn_imports.matching_tables`` et la table``gn_imports.matching_fields`` avec une ligne par champ de la table cible. Il ne vous reste plus qu'à manuellement supprimer ou remplacer les valeurs 'replace me' dans le champs source_field ou les valeurs par défaut proposées par la fonction. - -**Pré-générer les champs à mapper** - -.. code:: sql - - SELECT gn_imports.fct_generate_matching('gn_imports.testimport', 'gn_synthese.synthese'); - SELECT gn_imports.fct_generate_matching('gn_imports.testimport', 'gn_synthese.cor_observer_synthese'); - -OU si besoin d'écraser un mapping des champs existants - -.. code:: sql - - SELECT gn_imports.fct_generate_matching('gn_imports.testimport', 'gn_synthese.synthese', true); - SELECT gn_imports.fct_generate_matching('gn_imports.testimport', 'gn_synthese.cor_observer_synthese',true); - -IL FAUT ICI METTRE A JOUR LA TABLE ``gn_imports_matching_fields`` pour établir manuellement la correspondance des champs entre la table source et la table cible (voir le mapping final pour le fichier CSV fourni en exemple à la fin de cette page). - -:notes: - - * Au moins un des 2 champs ``source_field`` ou ``source_default_value`` doit être renseigné. - * Si le champ ``source_field`` est renseigné, le champ ``source_default_value`` est ignoré. - -Une fois que le mapping est renseigné, vous pouvez passer à l'étape suivante. - - -5 - Construire la requête d'import -`````````````````````````````````` - -Attention, pgAdmin va tronquer le résultat. Pour obtenir l'ensemble de la requête utiliser le bouton d'export du résultat dans un fichier ou executé la requête avec psql. - -**Génération de la requête d'import dans les tables de destination** - -.. code:: sql - - SELECT gn_imports.fct_generate_import_query('gn_imports.testimport', 'gn_synthese.synthese'); - SELECT gn_imports.fct_generate_import_query('gn_imports.testimport', 'gn_synthese.cor_observer_synthese'); - -:notes: - - UTILISER LE BOUTON D'EXPORT DU RESULTAT DE LA REQUETE DE PGADMIN ou utiliser psql. - IL EST NECESSAIRE D'ADAPTER LA REQUETE SI BESOIN DE FAIRE DES JOIN POUR RECUPERER DES VALEURS DANS D'AUTRES TABLES - - -6 - Chargement des données dans la table de destination (synthese ici) -`````````````````````````````````````````````````````````````````````` - -Voir la requête d'import en synthèse à la fin de cette page. - - -7 - Déplacement de la table importée (facultatif) -````````````````````````````````````````````````` - -On peut si on le souhaite déplacer la table vers une destination d'archivage - -.. code:: sql - - ALTER TABLE gn_imports.testimport SET SCHEMA schema_destination; - -On peut la mettre dans le schéma gn_exports pour l'exercice afin de tester mais ce n'est pas sa vocation. - -RESULTAT FINAL -`````````````` - -.. code:: sql - - --DELETE FROM gn_imports.matching_fields WHERE id_matching_table IN (1,2); - INSERT INTO gn_imports.matching_fields (id_matching_field, source_field, source_default_value, target_field, target_field_type, field_comments, id_matching_table) VALUES (207, NULL, 'uuid_generate_v4()', 'unique_id_sinp', 'uuid', NULL, 1); - INSERT INTO gn_imports.matching_fields (id_matching_field, source_field, source_default_value, target_field, target_field_type, field_comments, id_matching_table) VALUES (208, NULL, 'uuid_generate_v4()', 'unique_id_sinp_grp', 'uuid', NULL, 1); - INSERT INTO gn_imports.matching_fields (id_matching_field, source_field, source_default_value, target_field, target_field_type, field_comments, id_matching_table) VALUES (219, NULL, 'gn_synthese.get_default_nomenclature_value(''PREUVE_EXIST''::character varying)', 'id_nomenclature_exist_proof', 'integer', NULL, 1); - INSERT INTO gn_imports.matching_fields (id_matching_field, source_field, source_default_value, target_field, target_field_type, field_comments, id_matching_table) VALUES (210, 'id_data', NULL, 'entity_source_pk_value', 'character varying', NULL, 1); - INSERT INTO gn_imports.matching_fields (id_matching_field, source_field, source_default_value, target_field, target_field_type, field_comments, id_matching_table) VALUES (211, 'id_lot', NULL, 'id_dataset', 'integer', NULL, 1); - INSERT INTO gn_imports.matching_fields (id_matching_field, source_field, source_default_value, target_field, target_field_type, field_comments, id_matching_table) VALUES (209, 'id_source', NULL, 'id_source', 'integer', NULL, 1); - INSERT INTO gn_imports.matching_fields (id_matching_field, source_field, source_default_value, target_field, target_field_type, field_comments, id_matching_table) VALUES (213, NULL, 'gn_synthese.get_default_nomenclature_value(''TYP_GRP''::character varying)', 'id_nomenclature_grp_typ', 'integer', NULL, 1); - INSERT INTO gn_imports.matching_fields (id_matching_field, source_field, source_default_value, target_field, target_field_type, field_comments, id_matching_table) VALUES (212, NULL, 'gn_synthese.get_default_nomenclature_value(''NAT_OBJ_GEO''::character varying)', 'id_nomenclature_geo_object_nature', 'integer', NULL, 1); - INSERT INTO gn_imports.matching_fields (id_matching_field, source_field, source_default_value, target_field, target_field_type, field_comments, id_matching_table) VALUES (214, NULL, 'gn_synthese.get_default_nomenclature_value(''METH_OBS''::character varying)', 'id_nomenclature_obs_meth', 'integer', NULL, 1); - INSERT INTO gn_imports.matching_fields (id_matching_field, source_field, source_default_value, target_field, target_field_type, field_comments, id_matching_table) VALUES (215, NULL, 'gn_synthese.get_default_nomenclature_value(''TECHNIQUE_OBS''::character varying)', 'id_nomenclature_obs_technique', 'integer', NULL, 1); - INSERT INTO gn_imports.matching_fields (id_matching_field, source_field, source_default_value, target_field, target_field_type, field_comments, id_matching_table) VALUES (217, NULL, 'gn_synthese.get_default_nomenclature_value(''ETA_BIO''::character varying)', 'id_nomenclature_bio_condition', 'integer', NULL, 1); - INSERT INTO gn_imports.matching_fields (id_matching_field, source_field, source_default_value, target_field, target_field_type, field_comments, id_matching_table) VALUES (216, NULL, 'gn_synthese.get_default_nomenclature_value(''STATUT_BIO''::character varying)', 'id_nomenclature_bio_status', 'integer', NULL, 1); - INSERT INTO gn_imports.matching_fields (id_matching_field, source_field, source_default_value, target_field, target_field_type, field_comments, id_matching_table) VALUES (218, NULL, 'gn_synthese.get_default_nomenclature_value(''NATURALITE''::character varying)', 'id_nomenclature_naturalness', 'integer', NULL, 1); - INSERT INTO gn_imports.matching_fields (id_matching_field, source_field, source_default_value, target_field, target_field_type, field_comments, id_matching_table) VALUES (220, NULL, 'gn_synthese.get_default_nomenclature_value(''STATUT_VALID''::character varying)', 'id_nomenclature_valid_status', 'integer', NULL, 1); - INSERT INTO gn_imports.matching_fields (id_matching_field, source_field, source_default_value, target_field, target_field_type, field_comments, id_matching_table) VALUES (221, NULL, 'gn_synthese.get_default_nomenclature_value(''NIV_PRECIS''::character varying)', 'id_nomenclature_diffusion_level', 'integer', NULL, 1); - INSERT INTO gn_imports.matching_fields (id_matching_field, source_field, source_default_value, target_field, target_field_type, field_comments, id_matching_table) VALUES (223, NULL, 'gn_synthese.get_default_nomenclature_value(''SEXE''::character varying)', 'id_nomenclature_sex', 'integer', NULL, 1); - INSERT INTO gn_imports.matching_fields (id_matching_field, source_field, source_default_value, target_field, target_field_type, field_comments, id_matching_table) VALUES (222, NULL, 'gn_synthese.get_default_nomenclature_value(''STADE_VIE''::character varying)', 'id_nomenclature_life_stage', 'integer', NULL, 1); - INSERT INTO gn_imports.matching_fields (id_matching_field, source_field, source_default_value, target_field, target_field_type, field_comments, id_matching_table) VALUES (224, NULL, 'gn_synthese.get_default_nomenclature_value(''OBJ_DENBR''::character varying)', 'id_nomenclature_obj_count', 'integer', NULL, 1); - INSERT INTO gn_imports.matching_fields (id_matching_field, source_field, source_default_value, target_field, target_field_type, field_comments, id_matching_table) VALUES (226, NULL, 'gn_synthese.get_default_nomenclature_value(''SENSIBILITE''::character varying)', 'id_nomenclature_sensitivity', 'integer', NULL, 1); - INSERT INTO gn_imports.matching_fields (id_matching_field, source_field, source_default_value, target_field, target_field_type, field_comments, id_matching_table) VALUES (225, NULL, 'gn_synthese.get_default_nomenclature_value(''TYP_DENBR''::character varying)', 'id_nomenclature_type_count', 'integer', NULL, 1); - INSERT INTO gn_imports.matching_fields (id_matching_field, source_field, source_default_value, target_field, target_field_type, field_comments, id_matching_table) VALUES (227, NULL, 'gn_synthese.get_default_nomenclature_value(''STATUT_OBS''::character varying)', 'id_nomenclature_observation_status', 'integer', NULL, 1); - INSERT INTO gn_imports.matching_fields (id_matching_field, source_field, source_default_value, target_field, target_field_type, field_comments, id_matching_table) VALUES (228, NULL, 'gn_synthese.get_default_nomenclature_value(''DEE_FLOU''::character varying)', 'id_nomenclature_blurring', 'integer', NULL, 1); - INSERT INTO gn_imports.matching_fields (id_matching_field, source_field, source_default_value, target_field, target_field_type, field_comments, id_matching_table) VALUES (230, NULL, 'gn_synthese.get_default_nomenclature_value(''TYP_INF_GEO''::character varying)', 'id_nomenclature_info_geo_type', 'integer', NULL, 1); - INSERT INTO gn_imports.matching_fields (id_matching_field, source_field, source_default_value, target_field, target_field_type, field_comments, id_matching_table) VALUES (229, NULL, 'gn_synthese.get_default_nomenclature_value(''STATUT_SOURCE''::character varying)', 'id_nomenclature_source_status', 'integer', NULL, 1); - INSERT INTO gn_imports.matching_fields (id_matching_field, source_field, source_default_value, target_field, target_field_type, field_comments, id_matching_table) VALUES (233, 'cd_nom', NULL, 'cd_nom', 'integer', NULL, 1); - INSERT INTO gn_imports.matching_fields (id_matching_field, source_field, source_default_value, target_field, target_field_type, field_comments, id_matching_table) VALUES (237, NULL, 'NULL', 'digital_proof', 'text', NULL, 1); - INSERT INTO gn_imports.matching_fields (id_matching_field, source_field, source_default_value, target_field, target_field_type, field_comments, id_matching_table) VALUES (238, NULL, 'NULL', 'non_digital_proof', 'text', NULL, 1); - INSERT INTO gn_imports.matching_fields (id_matching_field, source_field, source_default_value, target_field, target_field_type, field_comments, id_matching_table) VALUES (239, 'altitude_retenue', NULL, 'altitude_min', 'integer', NULL, 1); - INSERT INTO gn_imports.matching_fields (id_matching_field, source_field, source_default_value, target_field, target_field_type, field_comments, id_matching_table) VALUES (240, 'altitude_retenue', NULL, 'altitude_max', 'integer', NULL, 1); - INSERT INTO gn_imports.matching_fields (id_matching_field, source_field, source_default_value, target_field, target_field_type, field_comments, id_matching_table) VALUES (244, 'dateobs', NULL, 'date_min', 'timestamp without time zone', NULL, 1); - INSERT INTO gn_imports.matching_fields (id_matching_field, source_field, source_default_value, target_field, target_field_type, field_comments, id_matching_table) VALUES (245, 'dateobs', NULL, 'date_max', 'timestamp without time zone', NULL, 1); - INSERT INTO gn_imports.matching_fields (id_matching_field, source_field, source_default_value, target_field, target_field_type, field_comments, id_matching_table) VALUES (246, NULL, 'NULL', 'validator', 'character varying', NULL, 1); - INSERT INTO gn_imports.matching_fields (id_matching_field, source_field, source_default_value, target_field, target_field_type, field_comments, id_matching_table) VALUES (248, NULL, 'NULL', 'observers', 'character varying', NULL, 1); - INSERT INTO gn_imports.matching_fields (id_matching_field, source_field, source_default_value, target_field, target_field_type, field_comments, id_matching_table) VALUES (247, NULL, 'NULL', 'validation_comment', 'text', NULL, 1); - INSERT INTO gn_imports.matching_fields (id_matching_field, source_field, source_default_value, target_field, target_field_type, field_comments, id_matching_table) VALUES (250, NULL, 'gn_synthese.get_default_nomenclature_value(''METH_DETERMIN''::character varying)', 'id_nomenclature_determination_method', 'integer', NULL, 1); - INSERT INTO gn_imports.matching_fields (id_matching_field, source_field, source_default_value, target_field, target_field_type, field_comments, id_matching_table) VALUES (252, NULL, 'now()', 'meta_validation_date', 'timestamp without time zone', NULL, 1); - INSERT INTO gn_imports.matching_fields (id_matching_field, source_field, source_default_value, target_field, target_field_type, field_comments, id_matching_table) VALUES (253, NULL, 'now()', 'meta_create_date', 'timestamp without time zone', NULL, 1); - INSERT INTO gn_imports.matching_fields (id_matching_field, source_field, source_default_value, target_field, target_field_type, field_comments, id_matching_table) VALUES (254, NULL, 'now()', 'meta_update_date', 'timestamp without time zone', NULL, 1); - INSERT INTO gn_imports.matching_fields (id_matching_field, source_field, source_default_value, target_field, target_field_type, field_comments, id_matching_table) VALUES (255, NULL, '''c''', 'last_action', 'character', NULL, 1); - INSERT INTO gn_imports.matching_fields (id_matching_field, source_field, source_default_value, target_field, target_field_type, field_comments, id_matching_table) VALUES (235, NULL, 'gn_commons.get_default_parameter(''taxref_version'',NULL)::character varying', 'meta_v_taxref', 'character varying', NULL, 1); - INSERT INTO gn_imports.matching_fields (id_matching_field, source_field, source_default_value, target_field, target_field_type, field_comments, id_matching_table) VALUES (251, 'remarques', NULL, 'comments', 'text', NULL, 1); - INSERT INTO gn_imports.matching_fields (id_matching_field, source_field, source_default_value, target_field, target_field_type, field_comments, id_matching_table) VALUES (231, 'effectif_total', NULL, 'count_min', 'integer', NULL, 1); - INSERT INTO gn_imports.matching_fields (id_matching_field, source_field, source_default_value, target_field, target_field_type, field_comments, id_matching_table) VALUES (232, 'effectif_total', NULL, 'count_max', 'integer', NULL, 1); - INSERT INTO gn_imports.matching_fields (id_matching_field, source_field, source_default_value, target_field, target_field_type, field_comments, id_matching_table) VALUES (258, 'taxon_saisi', NULL, 'nom_cite', 'character varying', NULL, 1); - INSERT INTO gn_imports.matching_fields (id_matching_field, source_field, source_default_value, target_field, target_field_type, field_comments, id_matching_table) VALUES (249, NULL, 'u.nom_role || '' '' || u.prenom_role', 'determiner', 'character varying', NULL, 1); - INSERT INTO gn_imports.matching_fields (id_matching_field, source_field, source_default_value, target_field, target_field_type, field_comments, id_matching_table) VALUES (234, 'taxon_saisi', NULL, 'nom_cite', 'character varying', NULL, 1); - INSERT INTO gn_imports.matching_fields (id_matching_field, source_field, source_default_value, target_field, target_field_type, field_comments, id_matching_table) VALUES (256, 'id_data', NULL, 'entity_source_pk_value', 'integer', NULL, 2); - INSERT INTO gn_imports.matching_fields (id_matching_field, source_field, source_default_value, target_field, target_field_type, field_comments, id_matching_table) VALUES (257, 'observateurs', NULL, 'id_role', 'integer', NULL, 2); - INSERT INTO gn_imports.matching_geoms (id_matching_geom, source_x_field, source_y_field, source_geom_field, source_geom_format, source_srid, target_geom_field, target_geom_srid, geom_comments, id_matching_table) VALUES (1, 'x', 'y', NULL, 'xy', 2154, 'the_geom_local', 2154, NULL, 1); - INSERT INTO gn_imports.matching_geoms (id_matching_geom, source_x_field, source_y_field, source_geom_field, source_geom_format, source_srid, target_geom_field, target_geom_srid, geom_comments, id_matching_table) VALUES (2, NULL, NULL, 'POINT(6.064544 44.28787)', 'wkt', 4326, 'the_geom_4326', 4326, NULL, 1); - INSERT INTO gn_imports.matching_geoms (id_matching_geom, source_x_field, source_y_field, source_geom_field, source_geom_format, source_srid, target_geom_field, target_geom_srid, geom_comments, id_matching_table) VALUES (1, 'x', 'y', NULL, 'xy', 4326, 'the_geom_point', 4326, NULL, 1); - - SELECT pg_catalog.setval('gn_imports.matching_fields_id_matching_field_seq', 258, true); - SELECT pg_catalog.setval('gn_imports.matching_geoms_id_matching_geom_seq', 3, true); - SELECT pg_catalog.setval('gn_imports.matching_tables_id_matching_table_seq', 2, true); - - --------------- - --IMPORT DATA-- - --------------- - --autogenerated query by - --SELECT gn_imports.fct_generate_import_query('gn_imports.testimport', 'gn_synthese.cor_observer_synthese'); - INSERT INTO gn_synthese.synthese( - unique_id_sinp - ,unique_id_sinp_grp - ,id_nomenclature_exist_proof - ,entity_source_pk_value - ,id_dataset - ,id_source - ,id_nomenclature_grp_typ - ,id_nomenclature_geo_object_nature - ,id_nomenclature_obs_meth - ,id_nomenclature_obs_technique - ,id_nomenclature_bio_condition - ,id_nomenclature_bio_status - ,id_nomenclature_naturalness - ,id_nomenclature_valid_status - ,id_nomenclature_diffusion_level - ,id_nomenclature_sex - ,id_nomenclature_life_stage - ,id_nomenclature_obj_count - ,id_nomenclature_sensitivity - ,id_nomenclature_type_count - ,id_nomenclature_observation_status - ,id_nomenclature_blurring - ,id_nomenclature_info_geo_type - ,id_nomenclature_source_status - ,cd_nom - ,digital_proof - ,non_digital_proof - ,altitude_min - ,altitude_max - ,date_min - ,date_max - ,validator - ,observers - ,validation_comment - ,id_nomenclature_determination_method - ,meta_validation_date - ,meta_create_date - ,meta_update_date - ,last_action - ,meta_v_taxref - ,comments - ,count_min - ,count_max - ,nom_cite - ) - SELECT - uuid_generate_v4()::uuid AS unique_id_sinp - ,uuid_generate_v4()::uuid AS unique_id_sinp_grp - ,gn_synthese.get_default_nomenclature_value('PREUVE_EXIST'::character varying)::integer AS id_nomenclature_exist_proof - ,a.id_data::character varying AS entity_source_pk_value - ,a.id_lot::integer AS id_dataset - ,a.id_source::integer AS id_source - ,gn_synthese.get_default_nomenclature_value('TYP_GRP'::character varying)::integer AS id_nomenclature_grp_typ - ,gn_synthese.get_default_nomenclature_value('NAT_OBJ_GEO'::character varying)::integer AS id_nomenclature_geo_object_nature - ,gn_synthese.get_default_nomenclature_value('METH_OBS'::character varying)::integer AS id_nomenclature_obs_meth - ,gn_synthese.get_default_nomenclature_value('TECHNIQUE_OBS'::character varying)::integer AS id_nomenclature_obs_technique - ,gn_synthese.get_default_nomenclature_value('ETA_BIO'::character varying)::integer AS id_nomenclature_bio_condition - ,gn_synthese.get_default_nomenclature_value('STATUT_BIO'::character varying)::integer AS id_nomenclature_bio_status - ,gn_synthese.get_default_nomenclature_value('NATURALITE'::character varying)::integer AS id_nomenclature_naturalness - ,gn_synthese.get_default_nomenclature_value('STATUT_VALID'::character varying)::integer AS id_nomenclature_valid_status - ,gn_synthese.get_default_nomenclature_value('NIV_PRECIS'::character varying)::integer AS id_nomenclature_diffusion_level - ,gn_synthese.get_default_nomenclature_value('SEXE'::character varying)::integer AS id_nomenclature_sex - ,gn_synthese.get_default_nomenclature_value('STADE_VIE'::character varying)::integer AS id_nomenclature_life_stage - ,gn_synthese.get_default_nomenclature_value('OBJ_DENBR'::character varying)::integer AS id_nomenclature_obj_count - ,gn_synthese.get_default_nomenclature_value('SENSIBILITE'::character varying)::integer AS id_nomenclature_sensitivity - ,gn_synthese.get_default_nomenclature_value('TYP_DENBR'::character varying)::integer AS id_nomenclature_type_count - ,gn_synthese.get_default_nomenclature_value('STATUT_OBS'::character varying)::integer AS id_nomenclature_observation_status - ,gn_synthese.get_default_nomenclature_value('DEE_FLOU'::character varying)::integer AS id_nomenclature_blurring - ,gn_synthese.get_default_nomenclature_value('TYP_INF_GEO'::character varying)::integer AS id_nomenclature_info_geo_type - ,gn_synthese.get_default_nomenclature_value('STATUT_SOURCE'::character varying)::integer AS id_nomenclature_source_status - ,a.cd_nom::integer AS cd_nom - ,NULL::text AS digital_proof - ,NULL::text AS non_digital_proof - ,a.altitude_retenue::integer AS altitude_min - ,a.altitude_retenue::integer AS altitude_max - ,a.dateobs::timestamp without time zone AS date_min - ,a.dateobs::timestamp without time zone AS date_max - ,NULL::character varying AS validator - ,NULL::character varying AS observers - ,NULL::text AS validation_comment - ,gn_synthese.get_default_nomenclature_value('METH_DETERMIN'::character varying)::integer AS id_nomenclature_determination_method - ,now()::timestamp without time zone AS meta_validation_date - ,now()::timestamp without time zone AS meta_create_date - ,now()::timestamp without time zone AS meta_update_date - ,'c'::character AS last_action - ,gn_commons.get_default_parameter('taxref_version',NULL)::character varying::character varying AS meta_v_taxref - ,a.remarques::text AS comments - ,a.effectif_total::integer AS count_min - ,a.effectif_total::integer AS count_max - ,taxon_saisi - FROM gn_imports.testimport a - ; - - --autogenerated query by - --SELECT gn_imports.fct_generate_import_query('gn_imports.testimport', 'gn_synthese.cor_observer_synthese'); - INSERT INTO gn_synthese.cor_observer_synthese( - id_role - ,id_synthese - ) - SELECT - a.observateurs::integer AS id_role - ,s.id_synthese::integer AS id_synthese - FROM gn_imports.testimport a - --self addition - JOIN gn_synthese.synthese s ON s.entity_source_pk_value::integer = a.id_data - WHERE s.id_source = 4; - ; diff --git a/docs/installation-all.rst b/docs/installation-all.rst index acccff9f1b..ade7f723ee 100644 --- a/docs/installation-all.rst +++ b/docs/installation-all.rst @@ -40,11 +40,11 @@ Configuration * Renseignez à minima : - * ``my_url`` : l'URL (ou IP) de votre serveur (avec un ``/`` à la fin) - * ``user_pg`` : l'utilisateur PostgreSQL que vous souhaitez voir créé - * ``user_pg_pass`` : mot de passe de l'utilisateur PostgreSQL - - Le script se chargera d'installer PostgreSQL, de crééer la base de donnée et de créer l'utilisateur que vous avez renseigné. + * ``my_url`` : l'URL (ou IP) de votre serveur (avec un ``/`` à la fin) + * ``user_pg`` : l'utilisateur PostgreSQL que vous souhaitez voir créé + * ``user_pg_pass`` : mot de passe de l'utilisateur PostgreSQL + + Le script se chargera d'installer PostgreSQL, de crééer la base de donnée et de créer l'utilisateur que vous avez renseigné. * Variable ``mode`` @@ -57,17 +57,17 @@ Installation * Lancer l'installation : - .. code:: console + .. code:: shell - $ touch install_all.log - $ chmod +x install_all.sh - $ ./install_all.sh 2>&1 | tee install_all.log + touch install_all.log + chmod +x install_all.sh + ./install_all.sh 2>&1 | tee install_all.log Une fois l'installation terminée, lancez la commande suivante: - .. code:: console + .. code:: shell - $ exec bash + exec bash Les applications sont disponibles aux adresses suivantes : @@ -77,36 +77,36 @@ Les applications sont disponibles aux adresses suivantes : Vous pouvez vous connecter avec l'utilisateur intégré par défaut (admin/admin). -:Note: +.. note:: Pour en savoir plus TaxHub, sa configuration et son utilisation, reportez-vous à sa documentation : https://taxhub.readthedocs.io. Idem pour UsersHub et sa documentation : https://usershub.readthedocs.io -:Note: +.. note:: * GeoNature-atlas compatible avec GeoNature V2 est disponible sur https://github.com/PnX-SI/GeoNature-atlas * Vous pouvez utiliser le schéma ``ref_geo`` de GeoNature pour votre territoire, les communes et les mailles. Si vous rencontrez une erreur, se reporter aux fichiers de logs ``/home/`whoami`/install_all.log``. -:Note: +.. note:: Si vous souhaitez que GeoNature soit à la racine du serveur, ou à une autre adresse, editez le fichier de configuration Apache (``/etc/apache2/sites-available/geonature.conf``) en modifiant l'alias : - Pour ``/``: ``Alias / /home/test/geonature/frontend/dist`` - Pour ``/saisie`` : ``Alias /saisie /home/test/geonature/frontend/dist`` -:Note: +.. note:: Par défaut et par mesure de sécurité, la base de données est accessible uniquement localement par la machine où elle est installée. Pour accéder à la BDD depuis une autre machine (pour s'y connecter avec QGIS, pgAdmin ou autre), vous pouvez consulter cette documentation https://github.com/PnX-SI/Ressources-techniques/blob/master/PostgreSQL/acces-bdd.rst. Attention, exposer la base de données sur internet n'est pas recommandé. Il est préférable de se connecter via un tunnel SSH. QGIS et la plupart des outils d'administration de base de données permettent d'établir une connexion à la base de cette manière. Attention si vous redémarrez PostgreSQL (``sudo service postgresql restart``), il faut ensuite redémarrer les API de GeoNature et UsersHub : - .. code:: console + .. code:: shell - $ sudo systemctl restart geonature - $ sudo systemctl restart geonature-worker - $ sudo systemctl restart usershub + sudo systemctl restart geonature + sudo systemctl restart geonature-worker + sudo systemctl restart usershub -:Note: +.. note:: Il est aussi important de configurer l'accès au serveur en HTTPS plutôt qu'en HTTP pour chiffrer le contenu des échanges entre le navigateur et le serveur (https://docs.ovh.com/fr/hosting/les-certificats-ssl-sur-les-hebergements-web/). diff --git a/docs/installation-standalone.rst b/docs/installation-standalone.rst index 1fc059f8cd..0654aa0b43 100644 --- a/docs/installation-standalone.rst +++ b/docs/installation-standalone.rst @@ -9,9 +9,9 @@ Installation des dépendances Installer les paquets suivants : -:: +.. code:: shell - $ sudo apt install unzip git postgresql-postgis postgis python3-pip python3-venv python3-dev libpq-dev libgdal-dev libffi-dev libpangocairo-1.0-0 apache2 redis + sudo apt install unzip git postgresql-postgis postgis python3-pip python3-venv python3-dev libpq-dev libgdal-dev libffi-dev libpangocairo-1.0-0 apache2 redis Récupération de l'application @@ -21,30 +21,30 @@ Récupération de l'application * Récupérer l'application (``X.Y.Z`` à remplacer par le numéro de la `dernière version stable de GeoNature `_). - :: + .. code:: shell - $ wget https://github.com/PnX-SI/GeoNature/archive/X.Y.Z.zip + wget https://github.com/PnX-SI/GeoNature/archive/X.Y.Z.zip * Dézipper l'archive de l'application - :: + .. code:: shell - $ unzip X.Y.Z.zip - $ rm X.Y.Z.zip + unzip X.Y.Z.zip + rm X.Y.Z.zip * Renommer le répertoire de l'application puis placez-vous dedans : - :: + .. code:: shell - $ mv GeoNature-X.Y.Z /home/`whoami`/geonature/ - $ cd geonature + mv GeoNature-X.Y.Z /home/`whoami`/geonature/ + cd geonature * Copier puis mettre à jour le fichier de configuration (``config/settings.ini``) comportant les informations relatives à votre environnement serveur : - :: + .. code:: shell - $ cp config/settings.ini.sample config/settings.ini - $ nano config/settings.ini + cp config/settings.ini.sample config/settings.ini + nano config/settings.ini Installation de l'application @@ -70,39 +70,39 @@ Configuration Apache * Créez la configuration du vhost, incluant la configuration par défaut créée précédemment : - .. code:: console + .. code:: shell - $ sudo cp install/assets/vhost_apache.conf /etc/apache2/sites-available/geonature.conf # Copier le vhost - $ sudo nano /etc/apache2/sites-available/geonature.conf # Modifier la variable ``${DOMAIN_NAME}`` + sudo cp install/assets/vhost_apache.conf /etc/apache2/sites-available/geonature.conf # Copier le vhost + sudo nano /etc/apache2/sites-available/geonature.conf # Modifier la variable ``${DOMAIN_NAME}`` * Activez la nouvelle configuration : - .. code:: console + .. code:: shell - $ sudo a2ensite geonature.conf + sudo a2ensite geonature.conf * et redémarrez Apache : - .. code:: console + .. code:: shell - $ sudo systemctl reload apache2 + sudo systemctl reload apache2 * L'application est disponible à l'adresse suivante : http://monurl.fr/geonature Une page HTML de maintenance et un vhost dédié sont aussi disponibles. Pour les mettre en place : - .. code:: console - - $ sudo cp install/assets/vhost_apache_maintenance.conf /etc/apache2/sites-available/geonature_maintenance.conf # Copier le vhost - $ sudo nano /etc/apache2/sites-available/geonature_maintenance.conf # Modifier la variable ``${DOMAIN_NAME}`` - $ sudo cp install/assets/maintenance.html /var/www/geonature_maintenance/index.html +.. code:: shell + + sudo cp install/assets/vhost_apache_maintenance.conf /etc/apache2/sites-available/geonature_maintenance.conf # Copier le vhost + sudo nano /etc/apache2/sites-available/geonature_maintenance.conf # Modifier la variable ``${DOMAIN_NAME}`` + sudo cp install/assets/maintenance.html /var/www/geonature_maintenance/index.html Pour passer votre GeoNature en maintenance, vous pouvez alors désactiver le vhost de GeoNature et activer celui de la page de maintenance : - .. code:: console +.. code:: shell - $ sudo a2dissite geonature.conf - $ sudo a2ensite geonature_maintenance.conf + sudo a2dissite geonature.conf + sudo a2ensite geonature_maintenance.conf Dépendances ----------- diff --git a/docs/installation.rst b/docs/installation.rst index 2424058582..996191ac3b 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -95,27 +95,27 @@ Commencer la procédure en se connectant au serveur en SSH avec l'utilisateur li * Lui donner ensuite les droits administrateur en l’ajoutant au groupe ``sudo`` : - .. code:: console + .. code:: shell - # adduser geonatureadmin sudo + adduser geonatureadmin sudo .. note:: Vérifier les droits du répertoire HOME de l'utilisateur - Il doit être en ``7XX`` soit ``drwxr-xr-x``. Si ce n'est pas le cas, exécuter la commande suivante : - - .. code:: console + Il doit être en ``7XX`` soit ``drwxr-xr-x``. Si ce n'est pas le cas, exécuter la commande suivante : - # chmod 755 /home/geonatureadmin + .. code:: console + + # chmod 755 /home/geonatureadmin * Pour la suite du processus d’installation, on utilisera l'utilisateur non privilégié nouvellement créé. Si besoin d'éxecuter des commandes avec les droits d'administrateur, on les précèdera de ``sudo``. Il est d'ailleurs possible renforcer la sécurité du serveur en bloquant la connexion SSH au serveur avec ``root``. Voir https://docs.ovh.com/fr/vps/conseils-securisation-vps/ pour plus d'informations sur le sécurisation du serveur. Pour passer de l’utilisateur ``root`` à ``geonatureadmin``, vous pouvez aussi utiliser la commande : - .. code:: console + .. code:: shell - # su - geonatureadmin + su - geonatureadmin .. _installation-all: diff --git a/docs/requirements.in b/docs/requirements.in index ba2fc4bfca..5c09bf5705 100644 --- a/docs/requirements.in +++ b/docs/requirements.in @@ -1,4 +1,5 @@ sphinx -sphinx-book-theme +furo myst-parser -sphinx-autoapi \ No newline at end of file +sphinx-autoapi +sphinx-copybutton \ No newline at end of file diff --git a/docs/requirements.txt b/docs/requirements.txt index 566459a632..fcec0ad78e 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -4,18 +4,14 @@ # # pip-compile requirements.in # -accessible-pygments==0.0.5 - # via pydata-sphinx-theme alabaster==0.7.16 # via sphinx astroid==3.3.5 # via sphinx-autoapi babel==2.16.0 - # via - # pydata-sphinx-theme - # sphinx + # via sphinx beautifulsoup4==4.12.3 - # via pydata-sphinx-theme + # via furo certifi==2024.8.30 # via requests charset-normalizer==3.4.0 @@ -23,8 +19,9 @@ charset-normalizer==3.4.0 docutils==0.21.2 # via # myst-parser - # pydata-sphinx-theme # sphinx +furo==2024.8.6 + # via -r requirements.in idna==3.10 # via requests imagesize==1.4.1 @@ -50,12 +47,9 @@ myst-parser==3.0.1 # via -r requirements.in packaging==24.1 # via sphinx -pydata-sphinx-theme==0.16.0 - # via sphinx-book-theme pygments==2.18.0 # via - # accessible-pygments - # pydata-sphinx-theme + # furo # sphinx pyyaml==6.0.2 # via @@ -70,13 +64,16 @@ soupsieve==2.6 sphinx==7.4.7 # via # -r requirements.in + # furo # myst-parser - # pydata-sphinx-theme # sphinx-autoapi - # sphinx-book-theme + # sphinx-basic-ng + # sphinx-copybutton sphinx-autoapi==3.3.3 # via -r requirements.in -sphinx-book-theme==1.1.3 +sphinx-basic-ng==1.0.0b2 + # via furo +sphinx-copybutton==0.5.2 # via -r requirements.in sphinxcontrib-applehelp==2.0.0 # via sphinx @@ -95,9 +92,7 @@ stdlib-list==0.11.0 tomli==2.0.2 # via sphinx typing-extensions==4.12.2 - # via - # astroid - # pydata-sphinx-theme + # via astroid urllib3==2.2.3 # via requests zipp==3.20.2 diff --git a/docs/tests_backend.rst b/docs/tests_backend.rst index 32ae1652cb..40329918ad 100644 --- a/docs/tests_backend.rst +++ b/docs/tests_backend.rst @@ -70,7 +70,7 @@ inséré/supprimé/modifié dans la base de données pendant le test. Enfin, les fixtures peuvent aussi être définies directement dans le fichier Python du test. Elles sont définies comme suit : -.. code-block:: python +.. code:: python # Obligatoire pour accéder au décorateur import pytest @@ -82,7 +82,7 @@ Python du test. Elles sont définies comme suit : Et s'utilisent comme suit : -.. code-block:: python +.. code:: python # On passe directement la fixture en argument du test. Il est # nécessaire de l'importer si elle n'est pas définie dans le même fichier @@ -95,7 +95,7 @@ Et s'utilisent comme suit : Il est aussi possible de définir un ``scope`` d'une fixture comme ceci : -.. code-block:: python +.. code:: python # Définit une fixture qui renverra 2 mais qui sera exécutée qu'une # seule fois par classe (une classe regroupe plusieurs tests) au @@ -110,7 +110,7 @@ Exemple Voici un exemple de test qui a été fait dans GeoNature -.. code-block:: python +.. code:: python def test_get_consistancy_data(self): synthese_record = Synthese.query.first() @@ -148,7 +148,7 @@ Coverage Le coverage est un système permettant de quantifier les lignes de code exécutées par le test. Exemple rapide : -.. code-block:: python +.. code:: python # Définition d'une fonction quelconque def ma_fonction_a_tester(verbose=False): @@ -204,20 +204,20 @@ Exécuter un ou plusieurs test(s) en ligne de commande Pour exécuter les tests de GeoNature placez vous à la racine du dossier où est installé GeoNature et exécutez la commande suivante : -.. code-block:: +.. code:: shell pytest Assurez vous d'avoir bien installé les librairies de développement avant (en étant toujours placé à la racine de l'installation de GeoNature) : -.. code-block:: +.. code:: shell pip install -e .[tests] Pour exécuter un seul test l'option ``-k`` est très utile : -.. code-block:: +.. code:: shell pytest -k 'test_uuid_report_with_dataset_id' @@ -226,7 +226,7 @@ ficher ``test_gn_meta.py``). Enfin, pour générer le coverage en même temps que les tests : -.. code-block:: +.. code:: shell pytest --cov --cov-report xml @@ -259,17 +259,18 @@ La création de tests de performance s'effectue à l'aide de la classe ``geonatu L'objet ``BenchmarkTest`` prend en argument : - - La fonction dont on souhaite mesurer la performance - - Le nom du test - - Les ``args`` de la fonction - - les ``kwargs`` de la fonction +- La fonction dont on souhaite mesurer la performance +- Le nom du test +- Les ``args`` de la fonction +- les ``kwargs`` de la fonction Cette classe permet de générer une fonction de test utilisable dans le _framework_ existant de ``pytest``. Pour cela, rien de plus simple ! Créer un fichier de test (de préférence dans le sous-dossier ``backend/geonature/tests/benchmarks``). Import la classe BenchmarkTest dans le fichier de test. -.. code-block:: +.. code:: python + import pytest from geonature.tests.benchmarks import BenchmarkTest @@ -277,14 +278,15 @@ Import la classe BenchmarkTest dans le fichier de test. Ajouter un test de performance, ici le test ``test_print`` qui teste la fonction ``print`` de Python. -.. code-block:: +.. code:: python bench = BenchmarkTest(print,"test_print",["Hello","World"],{}) Ajouter la fonction générée dans ``bench`` dans une classe de test: -.. code-block:: +.. code:: python + @pytest.mark.benchmark(group="occhab") # Pas obligatoire mais permet de compartimenter les tests de performances @pytest.mark.usefixtures("client_class", "temporary_transaction") class TestBenchie: @@ -307,7 +309,8 @@ de l'application flask, il faudra utiliser l'objet ``geonature.tests.benchmarks. de déclarer un expression python retournant un objet (fonction ou variable) dans une chaîne de caractère qui sera _évalué_ (voir la fonction ``eval()`` de Python) uniquement lors de l'exécution du benchmark. -.. code-block:: +.. code:: python + test_get_default_nomenclatures = BenchmarkTest( CLater("self.client.get"), [CLater("""url_for("gn_synthese.getDefaultsNomenclatures")""")], @@ -320,7 +323,8 @@ il suffit d'utiliser la clé ``user_profile`` dans l'argument ``kwargs`` (Voir c Si l'utilisation de _fixtures_ est nécessaire à votre test de performance, utilisé la clé ``fixture`` dans l'argument ``kwargs``: -.. code-block:: +.. code:: python + test_get_station = BenchmarkTest( CLater("self.client.get"), [CLater("""url_for("occhab.get_station", id_station=8)""")], diff --git a/docs/tests_frontend.rst b/docs/tests_frontend.rst index 2aa3c1e7c1..594d47138e 100644 --- a/docs/tests_frontend.rst +++ b/docs/tests_frontend.rst @@ -29,7 +29,7 @@ Afin d'améliorer la lisibilité des fichiers de test si un module contient beau Exemple """"""" -.. code-block:: bash +.. code:: bash e2e ├── import @@ -46,7 +46,7 @@ Dans chaque fichier la structure des tests est de la forme Exemple """"""" -.. code-block:: js +.. code-block:: javascript describe("description général de la partie testée", () => { @@ -65,7 +65,7 @@ Afin d'homogénéiser les descriptions des tests il est établi que l'on nomme u Exemple """"""" -.. code-block:: js +.. code:: javascript it('should change the state',() => ... @@ -85,7 +85,7 @@ Il est recommandé d'utiliser un nom explicite pour éviter toutes confusions. Exemple """"""" -.. code-block:: HTML +.. code:: HTML @@ -97,12 +97,12 @@ Lancement Pour lancer Cypress et executer les tests à la main il faut exécuter la commande (nécessite qu'une instance GeoNature fonctionne (backend+frontend)): -.. code-block:: bash +.. code:: bash - $ npm run cypress:open + npm run cypress:open Pour lancer les test en mode automatique, il faut exécuter la commande (utilisée dans l'intégration continue (GitHub Action)): -.. code-block:: bash +.. code:: bash - $ npm run e2e:ci && npm run e2e:coverage + npm run e2e:ci && npm run e2e:coverage diff --git a/frontend/cypress/e2e/import/step3-field-mapping-spec.js b/frontend/cypress/e2e/import/step3-field-mapping-spec.js index 2598ecb528..79317eb885 100644 --- a/frontend/cypress/e2e/import/step3-field-mapping-spec.js +++ b/frontend/cypress/e2e/import/step3-field-mapping-spec.js @@ -129,6 +129,17 @@ function restartTheProcess(user) { runTheProcess(user); } +function checkThatMappingCanBeSaved() { + // Trigger the modal + cy.get(SELECTOR_IMPORT_FIELDMAPPING_VALIDATE).should('exist').should('be.enabled').click(); + + // Validation modal appear + cy.get(SELECTOR_IMPORT_FIELDMAPPING_MODAL_OK, { force: true }).should('exist'); + + // Close the modal + cy.get(SELECTOR_IMPORT_FIELDMAPPING_MODAL_CLOSE, { force: true }).click(); +} + function checkThatMappingCanNotBeSaved() { // Trigger the modal cy.get(SELECTOR_IMPORT_FIELDMAPPING_VALIDATE).should('exist').should('be.enabled').click(); @@ -303,15 +314,23 @@ describe('Import - Field mapping step', () => { deleteCurrentMapping(); }); - it('Should not be able to modifiy the default mapping. A save to alternative should be offered to the user.', () => { + it('Should be able to modifiy the default mapping if user got rights. A save to alternative should be offered to the user.', () => { // Mapping Synthese selectMapping(DEFAULT_FIELDMAPPINGS[0]); selectField(SELECTOR_IMPORT_FIELDMAPPING_DATE_MIN, 'date_fin'); - checkThatMappingCanNotBeSaved(); + checkThatMappingCanBeSaved(); restartTheProcess(USER_ADMIN); selectMapping(DEFAULT_FIELDMAPPINGS[1]); fillTheFormRaw(); + checkThatMappingCanBeSaved(); + }); + it('Should not be able to modifiy the default mapping if user does not got rights', () => { + cy.geonatureLogout(); + cy.geonatureLogin(USER_AGENT.login.username, USER_AGENT.login.password); + runTheProcess(USER_AGENT); + selectMapping(DEFAULT_FIELDMAPPINGS[0]); + selectField(SELECTOR_IMPORT_FIELDMAPPING_DATE_MIN, 'date_fin'); checkThatMappingCanNotBeSaved(); }); diff --git a/frontend/src/app/metadataModule/af/af-card.component.html b/frontend/src/app/metadataModule/af/af-card.component.html index 8b6aff2f33..1a812625f2 100644 --- a/frontend/src/app/metadataModule/af/af-card.component.html +++ b/frontend/src/app/metadataModule/af/af-card.component.html @@ -421,6 +421,12 @@

Jeux de données associés
{{ dataset.unique_dataset_id }}
+
+ Nombre d'observations dans la Synthèse : {{ dataset['obs_count'] }} +
+
+ Nombre d'habitats dans occhab : {{ dataset['hab_count'] }} +
diff --git a/frontend/src/app/modules/imports/components/import_process/fields-mapping-step/fields-mapping-step.component.ts b/frontend/src/app/modules/imports/components/import_process/fields-mapping-step/fields-mapping-step.component.ts index 918521e00a..6f43bd9168 100644 --- a/frontend/src/app/modules/imports/components/import_process/fields-mapping-step/fields-mapping-step.component.ts +++ b/frontend/src/app/modules/imports/components/import_process/fields-mapping-step/fields-mapping-step.component.ts @@ -17,6 +17,7 @@ import { } from '@geonature/modules/imports/models/mapping.model'; import { ConfigService } from '@geonature/services/config.service'; import { FormControl } from '@angular/forms'; +import { AuthService } from '@geonature/components/auth/auth.service'; @Component({ selector: 'pnx-fields-mapping-step', @@ -41,7 +42,8 @@ export class FieldsMappingStepComponent implements OnInit { private _cruvedStore: CruvedStoreService, private _importDataService: ImportDataService, private _modalService: NgbModal, - private _configService: ConfigService + private _configService: ConfigService, + private _authService: AuthService ) {} ngOnInit() { @@ -94,21 +96,42 @@ export class FieldsMappingStepComponent implements OnInit { if (!this._fieldMappingService.mappingFormGroup?.valid) { return; } + + // Mapping stored data let mappingValue = this._fieldMappingService.currentFieldMapping.value; - if ( - this._fieldMappingService.mappingFormGroup.dirty && - (this.cruved.C || (mappingValue && mappingValue.cruved.U && !mappingValue.public)) // - ) { - if (mappingValue && !mappingValue.public) { - this.updateAvailable = true; - this.modalCreateMappingForm.setValue(mappingValue.label); + // is mapping update right for the current user is at admin level + const hasAdminUpdateMappingRight = + this._cruvedStore.cruved.IMPORT.module_objects.MAPPING.cruved.U > 2; + const hasOwnMappingUpdateRight = + this._cruvedStore.cruved.IMPORT.module_objects.MAPPING.cruved.U > 0; + // + const currentUser = this._authService.getCurrentUser(); + + if (this._fieldMappingService.mappingFormGroup.dirty && this.cruved.C) { + if (mappingValue) { + const intersectMappingOwnerUser = mappingValue['owners'].filter((x) => + x.identifiant == currentUser.user_login ? mappingValue['owners'] : false + ); + + if ( + mappingValue.public && + (hasAdminUpdateMappingRight || + (hasOwnMappingUpdateRight && intersectMappingOwnerUser.length > 0)) + ) { + this.updateAvailable = true; + this.modalCreateMappingForm.setValue(mappingValue.label); + } else if (!mappingValue.public) { + this.updateAvailable = true; + this.modalCreateMappingForm.setValue(mappingValue.label); + } else { + this.updateAvailable = false; + } } else { + console.log(4); this.updateAvailable = false; - this.modalCreateMappingForm.setValue(''); } this._modalService.open(this.saveMappingModal, { size: 'lg' }); } else { - // this.spinner = true; this.processNextStep(); } } diff --git a/frontend/src/app/modules/imports/components/import_process/upload-file-step/upload-file-step.component.ts b/frontend/src/app/modules/imports/components/import_process/upload-file-step/upload-file-step.component.ts index d34403894e..9cb702b5b9 100644 --- a/frontend/src/app/modules/imports/components/import_process/upload-file-step/upload-file-step.component.ts +++ b/frontend/src/app/modules/imports/components/import_process/upload-file-step/upload-file-step.component.ts @@ -50,9 +50,15 @@ export class UploadFileStepComponent implements OnInit { this.setupDatasetSelect(); this.step = this.route.snapshot.data.step; this.importData = this.importProcessService.getImportData(); + if (this.importData) { this.uploadForm.patchValue({ dataset: this.importData.id_dataset }); this.fileName = this.importData.full_file_name; + } else { + const idDatasetQueryParams = this.route.snapshot.queryParamMap.get('datasetId'); + if (idDatasetQueryParams) { + this.uploadForm.patchValue({ dataset: parseInt(idDatasetQueryParams) }); + } } } diff --git a/frontend/src/app/modules/imports/components/import_report/import_report.component.html b/frontend/src/app/modules/imports/components/import_report/import_report.component.html index 43b87d6392..04c3d44d32 100644 --- a/frontend/src/app/modules/imports/components/import_report/import_report.component.html +++ b/frontend/src/app/modules/imports/components/import_report/import_report.component.html @@ -12,7 +12,7 @@
- Rapport d'import: + Rapport d'import : {{ importData?.id_import }}
{ - this.fieldMappingStatus = { - mapped: new Set(), - unmapped: new Set(this.sourceFields), - autogenerated: new Set(), - }; + this.fieldMappingStatus.unmapped = new Set(this.sourceFields); if (fieldMapping === null) { this.mappingFormGroup.reset(); @@ -347,7 +343,7 @@ export class FieldMappingService { } else { if ( !this.sourceFields.includes(source) && - !(target in this.fieldMappingStatus.autogenerated) + !this.fieldMappingStatus.autogenerated.has(target) ) { return; } diff --git a/frontend/src/assets/i18n/fr.json b/frontend/src/assets/i18n/fr.json index a437e1745a..88b2f5b44d 100644 --- a/frontend/src/assets/i18n/fr.json +++ b/frontend/src/assets/i18n/fr.json @@ -296,7 +296,7 @@ "SourceFieldsAllMapped": "L'ensemble des {{ sourceFieldsLength }} champs du fichier d’import ont été associés à un champs cible.", "SourceFieldsUnmapped": "{{ unmappedFieldsLength }} champs du fichier d’import ne sont actuellement associés à aucun champs cible et seront donc ignorés.", "SaveMapping": "Enregistrement du modèle", - "MappingCreate?": "Souhaitez sauvegarder vos correspondances dans un modèle pour les réutiliser lors d’un futur import ?", + "MappingCreate?": "Souhaitez-vous sauvegarder vos correspondances dans un modèle pour les réutiliser lors d’un futur import ?", "MappingChanged?": "Le modèle de correspondance a été modifié ; souhaitez-vous mettre à jour le modèle existant ou créer un nouveau modèle ?", "MappingName": "Nom du modèle", "MappingNameWarning": "Un nom doit être renseigné pour pouvoir créer un modèle", diff --git a/install/03b_populate_db.sh b/install/03b_populate_db.sh index c0495f6168..f65a4d21d4 100755 --- a/install/03b_populate_db.sh +++ b/install/03b_populate_db.sh @@ -38,6 +38,22 @@ then geonature db upgrade ref_geo_inpn_grids_10@head fi +if [ "$install_grid_layer" = true ] || [ "$install_grid_layer_2" = true ]; +then + geonature db upgrade ref_geo_inpn_grids_2@head +fi + +if [ "$install_grid_layer" = true ] || [ "$install_grid_layer_20" = true ]; +then + geonature db upgrade ref_geo_inpn_grids_20@head +fi + +if [ "$install_grid_layer" = true ] || [ "$install_grid_layer_50" = true ]; +then + geonature db upgrade ref_geo_inpn_grids_50@head +fi + + geonature db exec "DO 'BEGIN ASSERT EXISTS (SELECT 1 FROM gn_sensitivity.t_sensitivity_rules); END'" 2>/dev/null \ || if [ "$install_ref_sensitivity" = true ]; then diff --git a/install/install_all/install_all.ini b/install/install_all/install_all.ini index 770ac2372f..b9fef98906 100644 --- a/install/install_all/install_all.ini +++ b/install/install_all/install_all.ini @@ -32,7 +32,7 @@ usershub_release=2.4.4 ### CONFIGURATION GEONATURE ### # Version de GeoNature -geonature_release=2.15.0 +geonature_release=2.15.1 # 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/setup.py b/setup.py index 40bde971db..ede27b522a 100644 --- a/setup.py +++ b/setup.py @@ -42,9 +42,10 @@ ], "doc": [ "sphinx", - "sphinx-book-theme", + "furo", "myst-parser", "sphinx-autoapi", + "sphinx-copybutton", ], }, classifiers=[