DEVELOPPEMENT

Général

GeoNature a été développé par Gil Deluermoz depuis 2010 avec PHP/Symfony/ExtJS.

En 2017, les parcs nationaux français ont décidé de refondre GeoNature complètement avec une nouvelle version (V2) réalisée en Python/Flask/Angular.

Mainteneurs :

  • Elie BOUTTIER (PnEcrins)

  • Theo LECHEMIA (PnEcrins)

  • Amandine SAHL (PnCevennes)

  • Camille MONCHICOURT (PnEcrins)

_images/geonature-techno.png

API

GeoNature utilise :

  • l’API de TaxHub (recherche taxon, règne et groupe d’un taxon…)

  • l’API du sous-module Nomenclatures (typologies et listes déroulantes)

  • l’API du sous-module d’authentification de UsersHub (login/logout, récupération du CRUVED d’un utilisateur)

  • l’API de GeoNature (get, post, update des données des différents modules, métadonnées, intersections géographiques, exports…)

_images/api_services.png

Liste des routes

Resource

Operation

Description

POST /admin/bibnomenclaturestypesadmin/ajax/update/

POST /admin/tnomenclaturesadmin/ajax/update/

POST /admin/tadditionalfields/ajax/update/

POST /admin/contentmapping/ajax/update/

POST /admin/fieldmapping/ajax/update/

GET /admin/bibnomenclaturestypesadmin/details/

POST /admin/bibnomenclaturestypesadmin/action/

POST /admin/bibnomenclaturestypesadmin/delete/

POST /admin/bibnomenclaturestypesadmin/edit/

GET /admin/bibnomenclaturestypesadmin/edit/

POST /admin/bibnomenclaturestypesadmin/new/

GET /admin/bibnomenclaturestypesadmin/new/

GET /admin/tnomenclaturesadmin/details/

POST /admin/tnomenclaturesadmin/action/

POST /admin/tnomenclaturesadmin/delete/

POST /admin/tnomenclaturesadmin/edit/

GET /admin/tnomenclaturesadmin/edit/

POST /admin/tnomenclaturesadmin/new/

GET /admin/tnomenclaturesadmin/new/

GET /admin/tadditionalfields/details/

POST /admin/tadditionalfields/action/

POST /admin/tadditionalfields/delete/

POST /admin/tadditionalfields/edit/

GET /admin/tadditionalfields/edit/

POST /admin/tadditionalfields/new/

GET /admin/tadditionalfields/new/

GET /admin/contentmapping/details/

POST /admin/contentmapping/action/

POST /admin/contentmapping/delete/

POST /admin/contentmapping/edit/

GET /admin/contentmapping/edit/

POST /admin/contentmapping/new/

GET /admin/contentmapping/new/

GET /admin/fieldmapping/details/

POST /admin/fieldmapping/action/

POST /admin/fieldmapping/delete/

POST /admin/fieldmapping/edit/

GET /admin/fieldmapping/edit/

POST /admin/fieldmapping/new/

GET /admin/fieldmapping/new/

PUT /users/password/change

PUT /users/password/new

POST /users/login/recovery

GET /pypn/register/test_uh

GET /synthese/taxa_distribution

GET /occtax/vreleveocctax

GET /admin/bibnomenclaturestypesadmin/

GET /admin/tnomenclaturesadmin/

GET /admin/tadditionalfields/

GET /admin/contentmapping/

GET /users/confirmation

GET /admin/fieldmapping/

POST /users/inscription

PUT /users/role

GET /meta/acquisition_frameworks

POST /meta/upload_canvas

POST /import/mappings/(int:mapping_id)/contents

POST /import/mappings/(int:id_mapping)/fields

POST /import/imports/(int:import_id)/prepare

POST /occtax/releve/(int:id_releve)/occurrence

POST /occtax/only/releve/(int:id_releve)

GET /meta/acquisition_frameworks/export_pdf/(id_acquisition_framework)

POST /pypn/register/post_usershub/(string:type_action)

GET /meta/dataset/export_pdf/(id_dataset)

GET /validation/date/(uuid:uuid)

GET /synthese/observation_count_per_column/(column)

GET /synthese/vsynthese/(id_synthese)

POST /occtax/occurrence/(int:id_occurrence)

GET /admin/static/(path:filename)

Commons

GET /gn_commons/list/parameters

GET /gn_commons/t_mobile_apps

GET /gn_commons/modules

PUT /gn_commons/media/(int:id_media)

PUT /gn_commons/media

POST /gn_commons/media/(int:id_media)

POST /gn_commons/media

GET /gn_commons/media/thumbnails/(int:id_media)/(int:size)

GET /gn_commons/medias/(string:uuid_attached_row)

GET /gn_commons/media/(int:id_media)

DELETE /gn_commons/media/(int:id_media)

Generic

GET /config

Habref

GET /habref/habitats/autocomplete

GET /habref/typo

GET /habref/correspondance/(int:cd_hab)

GET /habref/habitat/(int:cd_hab)

GET /habref/search/(field)/(ilike)

Import

GET /import/synthesis/fields

Get synthesis fields.

POST /import/mappings/

Add a mapping.

GET /import/mappings/

Return all active named mappings.

GET /import/imports/

Get all imports.

GET /import/mappings/(int:id_mapping)/contents

Get values of a mapping.

GET /import/mappings/(int:id_mapping)/fields

Get fields of a mapping.

POST /import/mappings/(int:id_mapping)/name

Update mapping name.

POST /import/imports/upload

Add an import or update an existing import.

PUT /import/imports/(int:import_id)/upload

GET /import/imports/(int:import_id)/invalid_rows

Get invalid rows of an import as CSV.

GET /import/imports/(int:import_id)/columns

GET /import/imports/(int:import_id)/errors

Get errors of an import.

GET /import/imports/(int:import_id)/values

POST /import/imports/(int:import_id)/import

Import the valid data.

GET /import/mappings/(int:id_mapping)/

Return a mapping.

DELETE /import/mappings/(int:id_mapping)/

Delete a mapping.

GET /import/imports/(int:import_id)/

Get an import.

DELETE /import/imports/(int:import_id)/

Delete an import.

Metadata

GET /meta/list/acquisition_frameworks

POST /meta/acquisition_framework

GET /meta/update_sensitivity

GET /meta/sensi_report

GET /meta/uuid_report

GET /meta/datasets

POST /meta/dataset

GET /meta/acquisition_framework/publish/(int:af_id)

GET /meta/acquisition_framework/(id_acquisition_framework)/stats

GET /meta/acquisition_framework/(id_acquisition_framework)/bbox

DELETE /meta/acquisition_framework/(int:af_id)

POST /meta/acquisition_framework/(int:id_acquisition_framework)

GET /meta/acquisition_framework/(id_acquisition_framework)

GET /meta/dataset/(int:id_dataset)

DELETE /meta/dataset/(int:ds_id)

PATCH /meta/dataset/(int:id_dataset)

POST /meta/dataset/(int:id_dataset)

Monitoring

GET /gn_monitoring/siteslist

GET /gn_monitoring/siteslist/(int:id_site)

GET /gn_monitoring/siteareas/(int:id_site)

Nomenclatures

GET /nomenclatures/nomenclatures

GET /nomenclatures/nomenclature/(int:id_type)

GET /nomenclatures/nomenclature/(string:code_type)

OccHab

POST /occhab/station

Occhab

GET /occhab/defaultNomenclatures

GET /occhab/stations

POST /occhab/export_stations/(export_format)

GET /occhab/station/(int:id_station)

DELETE /occhab/station/(int:id_station)

Occtax

POST /occtax/only/releve

Post one Occtax data (Releve + Occurrence + Counting)

GET /occtax/defaultNomenclatures

GET /occtax/occurrences

GET /occtax/releves

POST /occtax/releve

Post one Occtax data (Releve + Occurrence + Counting)

GET /occtax/export

Export data from pr_occtax.v_export_occtax

DELETE /occtax/releve/occurrence_counting/(int:id_count)

DELETE /occtax/occurrence/(int:id_occ)

GET /occtax/counting/(int:id_counting)

GET /occtax/releve/(int:id_releve)

DELETE /occtax/releve/(int:id_releve)

Permissions

GET /permissions/logout_cruved

GET /permissions/cruved

Profiles

POST /gn_profiles/check_observation

GET /gn_profiles/cor_taxon_phenology/(int:cd_ref)

GET /gn_profiles/consistancy_data/(id_synthese)

GET /gn_profiles/valid_profile/(int:cd_ref)

Ref Geo

GET /geo/municipalities

POST /geo/area_size

POST /geo/altitude

POST /geo/areas

GET /geo/areas

POST /geo/info

Synthese

GET /synthese/defaultsNomenclatures

POST /synthese/export_observations

GET /synthese/taxons_autocomplete

GET /synthese/observation_count

GET /synthese/observations_bbox

POST /synthese/export_metadata

GET /synthese/export_metadata

POST /synthese/export_statuts

POST /synthese/export_taxons

GET /synthese/general_stats

GET /synthese/taxons_tree

GET /synthese/color_taxon

GET /synthese/taxa_count

POST /synthese/for_web

Get filtered observations

GET /synthese/for_web

GET /synthese/sources

GET /synthese

Deprecated

User

GET /gn_auth/logout_cruved

POST /gn_auth/login_cas

GET /gn_auth/login_cas

GET /users/organisms_dataset_actor

GET /users/organisms

GET /users/roles

GET /users/menu_from_code/(string:code_liste)

GET /users/menu/(int:id_menu)

GET /users/role/(int:id_role)

Validation

POST /validation

GET /validation

View_Permission

GET /permissions_backoffice/users

POST /permissions_backoffice/other_permissions_form/id_permission/(int:id_permission)/user/(int:id_role)/filter_type/(int:id_filter_type)

POST /permissions_backoffice/other_permissions_form/user/(int:id_role)/filter_type/(int:id_filter_type)

GET /permissions_backoffice/other_permissions_form/id_permission/(int:id_permission)/user/(int:id_role)/filter_type/(int:id_filter_type)

GET /permissions_backoffice/other_permissions_form/user/(int:id_role)/filter_type/(int:id_filter_type)

POST /permissions_backoffice/cruved_form/module/(int:id_module)/role/(int:id_role)/object/(int:id_object)

POST /permissions_backoffice/cruved_form/module/(int:id_module)/role/(int:id_role)

GET /permissions_backoffice/cruved_form/module/(int:id_module)/role/(int:id_role)/object/(int:id_object)

GET /permissions_backoffice/cruved_form/module/(int:id_module)/role/(int:id_role)

POST /permissions_backoffice/filter_form/id_filter_type/(int:id_filter_type)/id_filter/(int:id_filter)

POST /permissions_backoffice/filter_form/id_filter_type/(int:id_filter_type)

GET /permissions_backoffice/filter_form/id_filter_type/(int:id_filter_type)/id_filter/(int:id_filter)

GET /permissions_backoffice/filter_form/id_filter_type/(int:id_filter_type)

GET /permissions_backoffice/filter_list/id_filter_type/(int:id_filter_type)

GET /permissions_backoffice/user_other_permissions/(id_role)

GET /permissions_backoffice/user_cruved/(id_role)

POST /permissions_backoffice/filter/(id_filter)

Documentation des routes

POST /admin/bibnomenclaturestypesadmin/ajax/update/

Edits a single column of a record in list view.

POST /admin/tnomenclaturesadmin/ajax/update/

Edits a single column of a record in list view.

POST /admin/tadditionalfields/ajax/update/

Edits a single column of a record in list view.

POST /admin/contentmapping/ajax/update/

Edits a single column of a record in list view.

POST /admin/fieldmapping/ajax/update/

Edits a single column of a record in list view.

GET /gn_commons/list/parameters

Get all parameters from gn_commons.t_parameters

GET /import/synthesis/fields

Get all synthesis fields Use in field mapping steps You can find a jsonschema of the returned data in the associated test.

GET /habref/habitats/autocomplete

Get all habref items of a list for autocomplete

Query Parameters
  • int (limit) – the id of the habref list

  • str (search_name) – the pattern to filter with

  • int – filter by typology

  • int – number of results, default = 20

Returns

Array<AutoCompleteHabitat>

POST /occtax/only/releve

Post one Occtax data (Releve + Occurrence + Counting)

Request JSON object:

{
"geometry":
    {"type":"Point",
    "coordinates":[0.9008789062500001,47.14489748555398]},
    "properties":
        {
        "id_releve_occtax":null,"id_dataset":1,"id_digitiser":1,"date_min":"2019-05-09","date_max":"2019-05-09","hour_min":null,"hour_max":null,"altitude_min":null,"altitude_max":null,"meta_device_entry":"web","comment":null,"id_nomenclature_obs_technique":316,"observers":[1],"observers_txt":null,"id_nomenclature_grp_typ":132,
        "t_occurrences_occtax":[{
            "id_releve_occtax":null,"id_occurrence_occtax":null,"id_nomenclature_obs_technique":41,"id_nomenclature_bio_condition":157,"id_nomenclature_bio_status":29,"id_nomenclature_naturalness":160,"id_nomenclature_exist_proof":81,"id_nomenclature_observation_status":88,"id_nomenclature_blurring":175,"id_nomenclature_source_status":75,"determiner":null,"id_nomenclature_determination_method":445,"cd_nom":67111,"nom_cite":"Ablette =  <i> Alburnus alburnus (Linnaeus, 1758)</i> - [ES - 67111]","meta_v_taxref":null,"sample_number_proof":null,"comment":null,
        "cor_counting_occtax":[{
            "id_counting_occtax":null,"id_nomenclature_life_stage":1,"id_nomenclature_sex":171,"id_nomenclature_obj_count":146,"id_nomenclature_type_count":94,"id_occurrence_occtax":null,"count_min":1,"count_max":1
            }]
        }]
    }
}
Returns

GeoJson<TRelevesOccurrence>

GET /admin/bibnomenclaturestypesadmin/details/

Details model view

POST /admin/bibnomenclaturestypesadmin/action/

Mass-model action view.

POST /admin/bibnomenclaturestypesadmin/delete/

Delete model view. Only POST method is allowed.

POST /admin/bibnomenclaturestypesadmin/edit/

Edit model view

GET /admin/bibnomenclaturestypesadmin/edit/

Edit model view

POST /admin/bibnomenclaturestypesadmin/new/

Create model view

GET /admin/bibnomenclaturestypesadmin/new/

Create model view

GET /admin/tnomenclaturesadmin/details/

Details model view

POST /admin/tnomenclaturesadmin/action/

Mass-model action view.

POST /admin/tnomenclaturesadmin/delete/

Delete model view. Only POST method is allowed.

POST /admin/tnomenclaturesadmin/edit/

Edit model view

GET /admin/tnomenclaturesadmin/edit/

Edit model view

POST /admin/tnomenclaturesadmin/new/

Create model view

GET /admin/tnomenclaturesadmin/new/

Create model view

GET /admin/tadditionalfields/details/

Details model view

POST /admin/tadditionalfields/action/

Mass-model action view.

POST /admin/tadditionalfields/delete/

Delete model view. Only POST method is allowed.

POST /admin/tadditionalfields/edit/

Edit model view

GET /admin/tadditionalfields/edit/

Edit model view

POST /admin/tadditionalfields/new/

Create model view

GET /admin/tadditionalfields/new/

Create model view

GET /admin/contentmapping/details/

Details model view

POST /admin/contentmapping/action/

Mass-model action view.

POST /admin/contentmapping/delete/

Delete model view. Only POST method is allowed.

POST /admin/contentmapping/edit/

Edit model view

GET /admin/contentmapping/edit/

Edit model view

POST /admin/contentmapping/new/

Create model view

GET /admin/contentmapping/new/

Create model view

GET /admin/fieldmapping/details/

Details model view

POST /admin/fieldmapping/action/

Mass-model action view.

POST /admin/fieldmapping/delete/

Delete model view. Only POST method is allowed.

POST /admin/fieldmapping/edit/

Edit model view

GET /admin/fieldmapping/edit/

Edit model view

POST /admin/fieldmapping/new/

Create model view

GET /admin/fieldmapping/new/

Create model view

PUT /users/password/change

Modifie le mot de passe de l’utilisateur connecté et de son ancien mdp Fait appel à l’API UsersHub

PUT /users/password/new

Modifie le mdp d’un utilisateur apres que celui-ci ai demander un renouvelement Necessite un token envoyer par mail a l’utilisateur

POST /users/login/recovery

Call UsersHub API to create a TOKEN for a user A post_action send an email with the user login and a link to reset its password Work only if ‘ENABLE_SIGN_UP’ is set to True

GET /pypn/register/test_uh

route pour tester le décorateur connect_admin ainsi que les paramètres de connexion à USERSHUB:

  • config[‘ADMIN_APPLICATION_LOGIN’]

  • config[‘ADMIN_APPLICATION_PASSWORD’]

GET /meta/list/acquisition_frameworks

Get all AF with their datasets Use in metadata module for list of AF and DS Add the CRUVED permission for each row (Dataset and AD)

Parameters
  • info_role (TRole) – add with kwargs

Query Parameters
  • excluded_fields (list) – fields excluded from serialization

  • nested (boolean) – Default False - serialized relationships. If false: remove add all relationships in excluded_fields

GET /permissions_backoffice/users

Render a list with all users with their number of cruved Link to edit cruved and other permissions Only display user which have profil in GeoNature and active user

GET /nomenclatures/nomenclatures

Route : liste des termes d’un ensemble de nomenclatures Possibilité de filtrer par regne et group2Inpn

GET /gn_monitoring/siteslist

Return the sites list for an application in a dict {id_base_site, nom site} .. :quickref: Monitoring;

Parameters
  • id_base_site – id of base site

  • module_code – code of the module

  • id_module – id of the module

  • base_site_name – part of the name of the site

  • type – int

POST /gn_profiles/check_observation

Check an observation with the related profile Return alert when the observation do not match the profile

GET /permissions/logout_cruved

Route to logout with cruved

To avoid multiples server call, we store the cruved in the session when the user logout we need clear the session to get the new cruved session

GET /permissions/cruved

Get the cruved for a user

Params: :param user: the user who ask the route, auto kwargs via @check_cruved_scope :type user: User :param module_code: the code of the requested module - as querystring :type module_code: str

Returns

dict of the CRUVED

GET /gn_commons/t_mobile_apps

Get all mobile applications

Query Parameters
  • app_code (str) – the app code

Returns

Array<dict<TMobileApps>>

GET /gn_commons/modules

Return the allowed modules of user from its cruved .. :quickref: Commons;

PUT /gn_commons/media/(int: id_media)
PUT /gn_commons/media

Insertion ou mise à jour d’un média avec prise en compte des fichiers joints

POST /gn_commons/media/(int: id_media)
POST /gn_commons/media

Insertion ou mise à jour d’un média avec prise en compte des fichiers joints

GET /synthese/defaultsNomenclatures

Get default nomenclatures

Query Parameters
  • group2_inpn (str) –

  • regne (str) –

  • organism (int) –

POST /synthese/export_observations

Optimized route for observations web export.

This view is customisable by the administrator Some columns are mandatory: id_synthese, geojson and geojson_local to generate the exported files

POST parameters: Use a list of id_synthese (in POST parameters) to filter the v_synthese_for_export_view

Query Parameters
  • export_format (str) – str<’csv’, ‘geojson’, ‘shapefiles’, ‘gpkg’>

GET /synthese/taxons_autocomplete

Autocomplete taxon for web search (based on all taxon in Synthese).

The request use trigram algorithm to get relevent results

Query Parameters
  • search_name (str) – the search name (use sql ilike statement and puts “%” for spaces)

  • regne (str) – filter with kingdom

:query str group2_inpn : filter with INPN group 2

GET /synthese/observation_count

Get observations found in a given dataset

id_dataset: int (query parameter)

count: int:

the number of observation

GET /synthese/observations_bbox

Get bbbox of observations

id_dataset: int: (query parameter)

bbox: geojson:

the bounding box in geojson

GET /synthese/taxa_distribution

Get taxa distribution for a given dataset or acquisition framework and grouped by a certain taxa rank

POST /synthese/export_metadata

Route to export the metadata in CSV

The table synthese is join with gn_synthese.v_metadata_for_export The column jdd_id is mandatory in the view gn_synthese.v_metadata_for_export

POST parameters: Use a list of id_synthese (in POST parameters) to filter the v_synthese_for_export_view

GET /synthese/export_metadata

Route to export the metadata in CSV

The table synthese is join with gn_synthese.v_metadata_for_export The column jdd_id is mandatory in the view gn_synthese.v_metadata_for_export

POST parameters: Use a list of id_synthese (in POST parameters) to filter the v_synthese_for_export_view

POST /synthese/export_statuts

Route to get all the protection status of a synthese search

Get the CRUVED from ‘R’ action because we don’t give observations X/Y but only statuts and to be constistant with the data displayed in the web interface

Parameters:
  • HTTP-GET: the same that the /synthese endpoint (all the filter in web app)

POST /synthese/export_taxons

Optimized route for taxon web export.

This view is customisable by the administrator Some columns are mandatory: cd_ref

POST parameters: Use a list of cd_ref (in POST parameters)

to filter the v_synthese_taxon_for_export_view

Query Parameters
  • export_format (str) – str<’csv’>

GET /synthese/general_stats

Return stats about synthese.

GET /synthese/taxons_tree

Get taxon tree.

GET /synthese/color_taxon

Get color of taxon in areas (vue synthese.v_color_taxon_area).

Query Parameters
  • code_area_type (str) – Type area code (ref_geo.bib_areas_types.type_code)

  • id_area (int) – Id of area (ref_geo.l_areas.id_area)

  • cd_nom (int) – taxon code (taxonomie.taxref.cd_nom)

Those three parameters can be multiples :returns: Array<dict<VColorAreaTaxon>>

GET /synthese/taxa_count

Get taxa count in synthese filtering with generic parameters

id_dataset: int (query parameter)

count: int:

the number of taxon

POST /synthese/for_web

Optimized route to serve data for the frontend with all filters.

Query filtered by any filter, returning all the fields of the view v_synthese_for_export:

properties = {
    "id": r["id_synthese"],
    "date_min": str(r["date_min"]),
    "cd_nom": r["cd_nom"],
    "nom_vern_or_lb_nom": r["nom_vern"] if r["nom_vern"] else r["lb_nom"],
    "lb_nom": r["lb_nom"],
    "dataset_name": r["dataset_name"],
    "observers": r["observers"],
    "url_source": r["url_source"],
    "unique_id_sinp": r["unique_id_sinp"],
    "entity_source_pk_value": r["entity_source_pk_value"],
}
geojson = json.loads(r["st_asgeojson"])
geojson["properties"] = properties
Parameters
  • info_role (str) – Role used to get the associated filters, TBC

Query Parameters
  • limit (str) – Limit number of synthese returned. Defaults to NB_MAX_OBS_MAP.

  • cd_ref (str) – Filter by TAXREF cd_ref attribute

  • taxonomy_group2_inpn (str) – Filter by TAXREF group2_inpn attribute

  • taxonomy_id_hab (str) – Filter by TAXREF id_habitat attribute

  • taxonomy_lr (str) – Filter by TAXREF cd_ref attribute

  • taxhub_attribut* (str) – Generig TAXREF filter, given attribute & value

  • observers (str) – Filter on observer

  • id_organism (str) – Filter on organism

  • date_min (str) – Start date

  • date_max (str) – End date

  • id_acquisition_framework (str) – tbd

  • geoIntersection (str) – Intersect with the geom send from the map

  • period_start (str) – tbd

  • period_end (str) – tbd

  • area* (str) – Generic filter on area

  • * (str) – Generic filter, given by colname & value

Response JSON Array of Objects
  • data (array) – Array of synthese with geojson key, see above

  • nb_total (int) – Number of observations

  • nb_obs_limited (bool) – Is number of observations capped

GET /synthese/for_web

Optimized route to serve data for the frontend with all filters.

Query filtered by any filter, returning all the fields of the view v_synthese_for_export:

properties = {
    "id": r["id_synthese"],
    "date_min": str(r["date_min"]),
    "cd_nom": r["cd_nom"],
    "nom_vern_or_lb_nom": r["nom_vern"] if r["nom_vern"] else r["lb_nom"],
    "lb_nom": r["lb_nom"],
    "dataset_name": r["dataset_name"],
    "observers": r["observers"],
    "url_source": r["url_source"],
    "unique_id_sinp": r["unique_id_sinp"],
    "entity_source_pk_value": r["entity_source_pk_value"],
}
geojson = json.loads(r["st_asgeojson"])
geojson["properties"] = properties
Parameters
  • info_role (str) – Role used to get the associated filters, TBC

Query Parameters
  • limit (str) – Limit number of synthese returned. Defaults to NB_MAX_OBS_MAP.

  • cd_ref (str) – Filter by TAXREF cd_ref attribute

  • taxonomy_group2_inpn (str) – Filter by TAXREF group2_inpn attribute

  • taxonomy_id_hab (str) – Filter by TAXREF id_habitat attribute

  • taxonomy_lr (str) – Filter by TAXREF cd_ref attribute

  • taxhub_attribut* (str) – Generig TAXREF filter, given attribute & value

  • observers (str) – Filter on observer

  • id_organism (str) – Filter on organism

  • date_min (str) – Start date

  • date_max (str) – End date

  • id_acquisition_framework (str) – tbd

  • geoIntersection (str) – Intersect with the geom send from the map

  • period_start (str) – tbd

  • period_end (str) – tbd

  • area* (str) – Generic filter on area

  • * (str) – Generic filter, given by colname & value

Response JSON Array of Objects
  • data (array) – Array of synthese with geojson key, see above

  • nb_total (int) – Number of observations

  • nb_obs_limited (bool) – Is number of observations capped

GET /synthese/sources

Get all sources.

GET /gn_auth/logout_cruved

Route to logout with cruved To avoid multiples server call, we store the cruved in the session when the user logout we need clear the session to get the new cruved session

POST /gn_auth/login_cas

Login route with the INPN CAS

GET /gn_auth/login_cas

Login route with the INPN CAS

GET /occtax/defaultNomenclatures

Get default nomenclatures define in occtax module

Returns

dict: {‘MODULE_CODE’: ‘ID_NOMENCLATURE’}

GET /occhab/defaultNomenclatures

Get default nomenclatures define in occhab module

Returns

dict: {‘MODULE_CODE’: ‘ID_NOMENCLATURE’}

GET /occtax/vreleveocctax

Deprecated

GET /occtax/occurrences

Get all Occurrences

Returns

dict<TOccurrencesOccurrence>

POST /import/mappings/

Post a new mapping (value or content)

GET /import/mappings/

Return all active named (non-temporary) mappings.

Parameters
  • type (str) – Filter mapping of the given type.

GET /occhab/stations

Get all stations with their hab

GET /import/imports/

Get all imports to which logged-in user has access.

GET /occtax/releves

Route for map list web interface

POST /occhab/station

Post one occhab station (station + habitats)

Post one occhab station (station + habitats)

Returns

GeoJson<TStationsOcchab>

POST /occtax/releve

Route utilisée depuis l’appli mobile => depreciée et non utilisée par l’appli web Post one Occtax data (Releve + Occurrence + Counting)

Request JSON object:

{
"geometry":
    {"type":"Point",
    "coordinates":[0.9008789062500001,47.14489748555398]},
    "properties":
        {
        "id_releve_occtax":null,"id_dataset":1,"id_digitiser":1,"date_min":"2019-05-09","date_max":"2019-05-09","hour_min":null,"hour_max":null,"altitude_min":null,"altitude_max":null,"meta_device_entry":"web","comment":null,"id_nomenclature_obs_technique":316,"observers":[1],"observers_txt":null,"id_nomenclature_grp_typ":132,
        "t_occurrences_occtax":[{
            "id_releve_occtax":null,"id_occurrence_occtax":null,"id_nomenclature_obs_technique":41,"id_nomenclature_bio_condition":157,"id_nomenclature_bio_status":29,"id_nomenclature_naturalness":160,"id_nomenclature_exist_proof":81,"id_nomenclature_observation_status":88,"id_nomenclature_blurring":175,"id_nomenclature_source_status":75,"determiner":null,"id_nomenclature_determination_method":445,"cd_nom":67111,"nom_cite":"Ablette =  <i> Alburnus alburnus (Linnaeus, 1758)</i> - [ES - 67111]","meta_v_taxref":null,"sample_number_proof":null,"comment":null,
        "cor_counting_occtax":[{
            "id_counting_occtax":null,"id_nomenclature_life_stage":1,"id_nomenclature_sex":171,"id_nomenclature_obj_count":146,"id_nomenclature_type_count":94,"id_occurrence_occtax":null,"count_min":1,"count_max":1
            }]
        }]
    }
}
Returns

GeoJson<TRelevesOccurrence>

GET /occtax/export

Export data from pr_occtax.v_export_occtax view (parameter)

Query Parameters
  • format (str) – format of the export (‘csv’, ‘geojson’, ‘shapefile’, ‘gpkg’)

GET /habref/typo

Get all typology

Query Parameters
  • id_list (int) – return only the typology of a given id_list

Returns

Array<TypoRef>

GET /admin/bibnomenclaturestypesadmin/

List view

GET /users/organisms_dataset_actor

Get all organisms and the JDD where there are actor and where the current user hase autorization with its cruved

GET /admin/tnomenclaturesadmin/

List view

GET /admin/tadditionalfields/

List view

GET /admin/contentmapping/

List view

GET /admin/fieldmapping/

List view

GET /users/confirmation

Validate a account after a demande (this action is triggered by the link in the email) Create a personnal JDD as post_action if the parameter AUTO_DATASET_CREATION is set to True Fait appel à l’API UsersHub

POST /users/inscription

Ajoute un utilisateur à utilisateurs.temp_user à partir de l’interface geonature Fonctionne selon l’autorisation ‘ENABLE_SIGN_UP’ dans la config. Fait appel à l’API UsersHub

GET /users/organisms

Get all organisms

GET /users/roles

Get all roles

PUT /users/role

Modifie le role de l’utilisateur du token en cours

GET /meta/acquisition_frameworks

Get a simple list of AF without any nested relationships Use for AF select in form Get the GeoNature CRUVED

POST /meta/acquisition_framework

Post one AcquisitionFramework data .. :quickref: Metadata;

GET /meta/update_sensitivity

Update sensitivity of all datasets

POST /meta/upload_canvas

Upload the canvas as a temporary image used while generating the pdf file

GET /meta/sensi_report

get the UUID report of a dataset

GET /meta/uuid_report

get the UUID report of a dataset

GET /meta/datasets

Get datasets list

Parameters
  • info_role (TRole) – add with kwargs

Query Parameters
  • active (boolean) – filter on active fiel

  • id_acquisition_framework (int) – get only dataset of given AF

Returns

list<TDatasets>

POST /meta/dataset

Post one Dataset data .. :quickref: Metadata;

GET /geo/municipalities

Return the municipalities .. :quickref: Ref Geo;

POST /geo/area_size

Return the area size from a given geojson

Returns

An area size (int)

POST /geo/altitude

From a posted geojson get the altitude min/max

POST /geo/areas

From a posted geojson, the route return all the area intersected from l_areas .. :quickref: Ref Geo;

GET /geo/areas

Return the areas of ref_geo.l_areas .. :quickref: Ref Geo;

POST /geo/info

From a posted geojson, the route return the municipalities intersected and the altitude min/max

POST /validation

Return synthese and t_validations data filtered by form params Params must have same synthese fields names

info_role (User):

Information about the user asking the route. Auto add with kwargs

FeatureCollection

GET /validation

Return synthese and t_validations data filtered by form params Params must have same synthese fields names

info_role (User):

Information about the user asking the route. Auto add with kwargs

FeatureCollection

GET /synthese

Return synthese row(s) filtered by form params. NOT USED ANY MORE FOR PERFORMANCE ISSUES

Deprecated since version 2?.

Use :route: /for_web instead

Params must have same synthese fields names

Parameters
  • info_role (str) – Role used to get the associated filters

Returns dict[dict, int, bool]

See description above

GET /config

Parse and return configuration files as toml .. :quickref: Generic;

POST /permissions_backoffice/other_permissions_form/id_permission/(int: id_permission)/user/(int: id_role)/filter_type/(int: id_filter_type)
POST /permissions_backoffice/other_permissions_form/user/(int: id_role)/filter_type/(int: id_filter_type)

Form to define permisisons for a user expect SCOPE permissions .. :quickref: View_Permission;

GET /permissions_backoffice/other_permissions_form/id_permission/(int: id_permission)/user/(int: id_role)/filter_type/(int: id_filter_type)
GET /permissions_backoffice/other_permissions_form/user/(int: id_role)/filter_type/(int: id_filter_type)

Form to define permisisons for a user expect SCOPE permissions .. :quickref: View_Permission;

POST /permissions_backoffice/cruved_form/module/(int: id_module)/role/(int: id_role)/object/(int: id_object)
POST /permissions_backoffice/cruved_form/module/(int: id_module)/role/(int: id_role)
GET /permissions_backoffice/cruved_form/module/(int: id_module)/role/(int: id_role)/object/(int: id_object)
GET /permissions_backoffice/cruved_form/module/(int: id_module)/role/(int: id_role)
POST /permissions_backoffice/filter_form/id_filter_type/(int: id_filter_type)/id_filter/(int: id_filter)
POST /permissions_backoffice/filter_form/id_filter_type/(int: id_filter_type)
GET /permissions_backoffice/filter_form/id_filter_type/(int: id_filter_type)/id_filter/(int: id_filter)
GET /permissions_backoffice/filter_form/id_filter_type/(int: id_filter_type)
GET /permissions_backoffice/filter_list/id_filter_type/(int: id_filter_type)
GET /gn_commons/media/thumbnails/(int: id_media)/(int: size)

Retourne le thumbnail d’un media .. :quickref: Commons;

GET /import/mappings/(int: id_mapping)/contents

Load source and target values from an id_mapping.

POST /import/mappings/(int: mapping_id)/contents

This view add fields mapping to a existing mapping-set. Existing fields mapping are kept.

GET /import/mappings/(int: id_mapping)/fields

Load source and target fields from an id_mapping.

POST /import/mappings/(int: id_mapping)/fields

This view add fields mapping to a existing mapping-set. Existing fields mapping are kept.

POST /import/mappings/(int: id_mapping)/name

Update mapping name.

Params mappingName

new mapping name

POST /import/imports/upload

Add an import or update an existing import.

Form Parameters
  • file – file to import

  • int datasetId – dataset ID to which import data

PUT /import/imports/(int: import_id)/upload

Add an import or update an existing import.

Form Parameters
  • file – file to import

  • int datasetId – dataset ID to which import data

GET /import/imports/(int: import_id)/invalid_rows

Export invalid data in CSV.

GET /import/imports/(int: import_id)/columns

Return all the columns of the file of an import

POST /import/imports/(int: import_id)/prepare

Prepare data to be imported: apply all checks and transformations.

GET /import/imports/(int: import_id)/errors

Get errors of an import.

GET /import/imports/(int: import_id)/values

Return all the values of nomenclatures fields

POST /import/imports/(int: import_id)/import

Import valid data in GeoNature synthese.

DELETE /occtax/releve/occurrence_counting/(int: id_count)

Delete one counting

Params int id_count

ID of the counting to delete

POST /occtax/releve/(int: id_releve)/occurrence

Post one Occurrence data (Occurrence + Counting) for add to Releve

POST /occtax/only/releve/(int: id_releve)

Post one Occurrence data (Occurrence + Counting) for add to Releve

GET /meta/acquisition_frameworks/export_pdf/(id_acquisition_framework)

Get a PDF export of one acquisition

GET /meta/acquisition_framework/publish/(int: af_id)

Publish an acquisition framework .. :quickref: Metadata;

GET /meta/acquisition_framework/(id_acquisition_framework)/stats

Get stats from one AF .. :quickref: Metadata; :param id_acquisition_framework: the id_acquisition_framework :param type: int

GET /meta/acquisition_framework/(id_acquisition_framework)/bbox

Get BBOX from one AF .. :quickref: Metadata; :param id_acquisition_framework: the id_acquisition_framework :param type: int

POST /pypn/register/post_usershub/(string: type_action)

route generique pour appeler les routes UsersHub en tant qu’administrateur de l’appli en cours ex : post_usershub/test_connexion appelle la route URL_USERSHUB/api_register/test_connexion

GET /meta/dataset/export_pdf/(id_dataset)

Get a PDF export of one dataset

GET /permissions_backoffice/user_other_permissions/(id_role)

Get all the permissions define for a user expect SCOPE permissions

GET /permissions_backoffice/user_cruved/(id_role)

Get all scope CRUVED (with heritage) for a user in all modules

POST /permissions_backoffice/filter/(id_filter)
GET /nomenclatures/nomenclature/(int: id_type)

=> Déprécié pour des raisons de volatilité des identifiants en BD

Route : liste des termes d’une nomenclature basées sur les identifiants de nomenclature Possibilité de filtrer par regne et group2Inpn

GET /nomenclatures/nomenclature/(string: code_type)

Route : liste des termes d’une nomenclature basées sur le code mnemonique du type de nomenclature Possibilité de filtrer par regne et group2Inpn

GET /gn_monitoring/siteslist/(int: id_site)

Get minimal information for a site {id_base_site, nom site} .. :quickref: Monitoring;

Parameters
  • id_site – id of base site

  • type – int

GET /gn_monitoring/siteareas/(int: id_site)

Get areas of a site from cor_site_area as geojson

Parameters
  • id_module (int) – int

  • id_area_type (int) –

GET /gn_profiles/cor_taxon_phenology/(int: cd_ref)

Get phenoliques periods for a given taxon

GET /gn_profiles/consistancy_data/(id_synthese)

Return the validation score for a synthese data

GET /gn_profiles/valid_profile/(int: cd_ref)

Return the profile for a cd_ref

GET /gn_commons/medias/(string: uuid_attached_row)

Retourne des medias .. :quickref: Commons;

GET /gn_commons/media/(int: id_media)

Retourne un media .. :quickref: Commons;

DELETE /gn_commons/media/(int: id_media)

Suppression d’un media

GET /validation/date/(uuid: uuid)

Retourne la date de validation pour l’observation uuid

GET /synthese/observation_count_per_column/(column)

Get observations count group by a given column

GET /synthese/vsynthese/(id_synthese)

Get one synthese record for web app with all decoded nomenclature

POST /occhab/export_stations/(export_format)

Download all stations The route is in post to avoid a too large query string

GET /habref/correspondance/(int: cd_hab)

Get all correspondances in other typo from a cd_hab

Params cd_hab

a cd_hab

POST /occtax/occurrence/(int: id_occurrence)

Post one Occurrence data (Occurrence + Counting) for add to Releve

DELETE /occtax/occurrence/(int: id_occ)

Delete one occurrence and associated counting

Params int id_occ

ID of the occurrence to delete

GET /import/mappings/(int: id_mapping)/

Return a mapping. Mapping has to be active.

DELETE /import/mappings/(int: id_mapping)/

Delete a mappping. Only permanent (named) mapping can be deleted. If a mapping is still used by a import, it is not deleted but marked as temporary (unnamed). Return the updated list of mapping of the same type of the deleted mapping.

GET /occtax/counting/(int: id_counting)

Get one counting record, with its id_counting

Parameters
  • id_counting (int) – the pr_occtax.cor_counting_occtax PK

Returns

a dict representing a counting record

Rtype

dict<CorCountingOccurrence>

GET /habref/habitat/(int: cd_hab)

Get one habitat with its correspondances

Params cd_hab

a cd_hab

GET /import/imports/(int: import_id)/

Get an import.

DELETE /import/imports/(int: import_id)/

Delete an import.

GET /occhab/station/(int: id_station)

Return one station

Parameters
  • id_station (int) – the id_station

Return

a dict representing one station with its habitats

:rtype dict<TStationsOcchab>

DELETE /occhab/station/(int: id_station)

Delete a station with its habitat and its observers

GET /habref/search/(field)/(ilike)

Get the first 20 result of Habref table for a given field with an ilike query Use trigram algo to add relevance

Params field

a Habref column

Parameters
  • ilike – the ilike where expression to filter

:type ilike:str

Returns

Array of dict

GET /occtax/releve/(int: id_releve)

Get one releve

Parameters
  • id_releve (int) – the id releve from pr_occtax.t_releve_occtax

Returns

Return a releve with its attached Cruved

Rtype

dict{‘releve’:<TRelevesOccurrence>, ‘cruved’: Cruved}

DELETE /occtax/releve/(int: id_releve)

Delete one releve and its associated occurrences and counting

Params int id_releve

ID of the releve to delete

GET /users/menu_from_code/(string: code_liste)

Retourne la liste des roles associés à une liste (identifiée par son code)

Parameters
  • code_liste (string) – the code of user list (utilisateurs.t_lists)

Query Parameters
  • nom_complet (str) – begenning of complet name of the role

GET /admin/static/(path: filename)

The view function used to serve files from static_folder. A route is automatically registered for this view at static_url_path if static_folder is set.

New in version 0.5.

GET /users/menu/(int: id_menu)

Retourne la liste des roles associés à un menu

Parameters
  • id_menu (int) – the id of user list (utilisateurs.bib_list)

Query Parameters
  • nom_complet (str) – begenning of complet name of the role

GET /users/role/(int: id_role)

Get role detail

Parameters
  • id_role (int) – the id user

DELETE /meta/acquisition_framework/(int: af_id)

Delete an acquisition framework .. :quickref: Metadata;

POST /meta/acquisition_framework/(int: id_acquisition_framework)

Post one AcquisitionFramework data for update acquisition_framework .. :quickref: Metadata;

GET /meta/acquisition_framework/(id_acquisition_framework)

Get one AF with nomenclatures .. :quickref: Metadata;

Parameters
  • id_acquisition_framework – the id_acquisition_framework

  • type – int

Returns

dict<TAcquisitionFramework>

GET /meta/dataset/(int: id_dataset)

Get one dataset

Parameters
  • id_dataset – the id_dataset

  • type – int

Returns

dict<TDataset>

DELETE /meta/dataset/(int: ds_id)

Delete a dataset

PATCH /meta/dataset/(int: id_dataset)

Post one Dataset data for update dataset .. :quickref: Metadata;

POST /meta/dataset/(int: id_dataset)

Post one Dataset data for update dataset .. :quickref: Metadata;

Release

Pour sortir une nouvelle version de GeoNature :

  • Faites les éventuelles Releases des dépendances (UsersHub, TaxHub, UsersHub-authentification-module, Nomenclature-api-module, GeoNature-atlas)

  • Assurez-vous que les sous-modules git de GeoNature pointent sur les bonnes versions des dépendances

  • Mettez à jour la version de GeoNature et éventuellement des dépendances dans install/install_all/install_all.ini, config/settings.ini.sample, backend/requirements.txt

  • Complétez le fichier docs/CHANGELOG.rst (en comparant les branches https://github.com/PnX-SI/GeoNature/compare/develop) et dater la version à sortir

  • Mettez à jour le fichier VERSION

  • Remplissez le tableau de compatibilité des dépendances (docs/versions-compatibility.rst)

  • Mergez la branche develop dans la branche master

  • Faites la release (https://github.com/PnX-SI/GeoNature/releases) en la taguant X.Y.Z (sans v devant) et en copiant le contenu du Changelog

  • Dans la branche develop, modifiez le fichier VERSION en X.Y.Z.dev0 et pareil dans le fichier docs/CHANGELOG.rst

BDD

Mettre à jour le ref_geo à partir des données IGN scan express :

  • Télécharger le dernier millésime : http://professionnels.ign.fr/adminexpress

  • Intégrer le fichier Shape dans la BDD grâce à QGIS dans une table nommée ref_geo.temp_fr_municipalities

  • Générer le SQL de création de la table : pg_dump --table=ref_geo.temp_fr_municipalities --column-inserts -U <MON_USER> -h <MON_HOST> -d <MA_BASE> > fr_municipalities.sql. Le fichier en sortie doit s’appeler fr_municipalities.sql

  • Zipper le fichier SQL et le mettre sur le serveur https://geonature.fr/data

  • Adapter le script install_db.sh pour récupérer le nouveau fichier zippé

Pratiques et règles de developpement

Afin de partager des règles communes de développement et faciliter l’intégration de nouveau code, veuillez lire les recommandations et bonnes pratiques recommandées pour contribuer au projet GeoNature.

Git

  • Ne jamais faire de commit dans la branche master mais dans la branche develop ou idéalement dans une branche dédiée à la fonctionnalité (feature branch)

  • Faire des pull request vers la branche develop

  • Faire des git pull avant chaque développement et avant chaque commit

  • Les messages de commits font référence à un ticket ou le ferment (ref #12 ou fixes #23)

  • Les messages des commits sont en anglais (dans la mesure du possible)

Backend

  • Une fonction ou classe doit contenir une docstring en français. Les doctrings doivent suivre le modèle NumPy/SciPy (voir https://numpydoc.readthedocs.io/en/latest/format.html et https://realpython.com/documenting-python-code/#numpyscipy-docstrings-example)

  • Les commentaires dans le codes doivent être en anglais (ne pas s’empêcher de mettre un commentaire en français sur une partie du code complexe !)

  • Assurez-vous d’avoir récupérer les dépendances dans les sous-modules git : git submodule init && git submodule update - Après un git pull, il faut mettre à jour les sous-modules : git submodule update

  • Installer les requirements-dev (cd backend && pip install -r requirements-dev.txt) qui contiennent une série d’outils indispensables au développement dans GeoNature.

  • Utiliser blake comme formateur de texte et activer l’auto-formatage dans son éditeur de texte (Tuto pour VsCode : https://medium.com/@marcobelo/setting-up-python-black-on-visual-studio-code-5318eba4cd00)

  • Utiliser pylint comme formatteur de code

  • Respecter la norme PEP8 (assurée par les deux outils précédents)

  • La longueur maximale pour une ligne de code est 100 caractères. Pour VsCode copier ces lignes le fichier settings.json :

  • Respecter le snake case

"python.formatting.blackArgs": [
  "--line-length",
  "100"
]
  • Utiliser des doubles quotes pour les chaines de charactères.

BDD

  • Le noms des tables est préfixé par un “t” pour une table de contenu, de “bib” pour les tables de “dictionnaires” et de “cor” pour les tables de correspondance (relations N-N)

  • Les schémas du coeur de GeoNature sont préfixés de “gn”

  • Les schémas des protocoles ou modules GeoNature sont préfixés de “pr”

  • Ne rien écrire dans le schéma public

  • Ne pas répeter le nom des tables dans les noms des colonnes

Typescript

  • Documenter les fonctions et classes grâce au JSDoc en français (https://jsdoc.app/)

  • Les commentaires dans le codes doivent être en anglais (ne pas s’empêcher de mettre un commentaire en français sur une partie du code complexe !)

  • Les messages renvoyés aux utilisateurs sont en français

  • Installer les outils de devéloppement : npm install --only=dev

  • Utiliser prettier comme formateur de texte et activer l’autoformatage dans son éditeur de texte (VsCode dispose d’une extension Prettier : https://github.com/prettier/prettier-vscode)

  • Utiliser tslint comme linter

  • La longueur maximale pour une ligne de code est 100 caractères.

Angular

  • Suivre les recommandations définies par le styleguide Angular: https://angular.io/guide/styleguide. C’est une ressources très fournie en cas de question sur les pratiques de développement (principe de séparation des principes, organisation des services et des composants)

  • On privilégiera l’utilisation des reactive forms pour la construction des formulaires (https://angular.io/guide/reactive-forms). Ce sont des formulaires pilotés par le code, ce qui facilite la lisibilité et le contrôle de ceux-ci.

  • Pour l’ensemble des composants cartographiques et des formulaires (taxonomie, nomenclatures…), il est conseillé d’utiliser les composants présents dans le module ‘GN2CommonModule’.

HTML

  • La longueur maximale pour une ligne de code est 100 caractères.

  • Lorsqu’il y a plus d’un attribut sur une balise, revenir à la ligne et aligner les attributs :

<button
  mat-raised-button
  color="primary"
  class="btn-action hard-shadow uppercase ml-3"
  data-toggle="collapse"
  data-target="#collapseAvance"
>
  Filtrer
</button>
  • VsCode fournit un formatteur de HTML par défaut (Dans les options de VsCode, tapez “wrap attributes” et sélectionner “force-expand-multiline”)

Style et ergonomie

  • Boutons : On utilise les boutons d’Angular materials (https://material.angular.io/components/button/overview).

    • mat-raised-button pour les boutons contenant du texte

    • mat-fab ou mat-mini-fab pour les boutons d’actions avec seulement une icone

  • Couleur des boutons :

    • Action : primary

    • Validation: vert (n’existant pas dans material: utiliser la classe button-success)

    • Suppression: warn

    • Navigation: basic

  • Librairie d’icones :

  • Formulaire :

  • Système de grille et responsive :

    • Utiliser le système de grille de bootstrap pour assurer le responsive design sur l’application. On ne vise pas l’utilisation sur mobile, mais à minima sur ordinateur portable de petite taille.

Développer et installer un gn_module

GeoNature a été conçu pour fonctionner en briques modulaires.

Chaque protocole, répondant à une question scientifique, est amené à avoir son propre module GeoNature comportant son modèle de base de données (dans un schéma séparé), son API et son interface utilisateur.

Les modules développés s’appuieront sur le coeur de GeoNature qui est constitué d’un ensemble de briques réutilisables.

En base de données, le coeur de GeoNature est constitué de l’ensemble des référentiels (utilisateurs, taxonomique, nomenclatures géographique) et du schéma gn_synthese regroupant l’ensemble données saisies dans les différents protocoles (voir doc administrateur pour plus de détail sur le modèle de données).

L’API du coeur permet d’interroger les schémas de la base de données “coeur” de GeoNature. Une documentation complète de l’API est disponible dans la rubrique API.

Du côté interface utilisateur, GeoNature met à disposition un ensemble de composants Angular réutilisables (http://pnx-si.github.io/GeoNature/frontend/modules/GN2CommonModule.html), pour l’affichage des cartes, des formulaires etc…

Développer un gn_module

Avant de développer un gn_module, assurez-vous d’avoir GeoNature bien installé sur votre machine (voir Installation de GeoNature uniquement).

Afin de pouvoir connecter ce module au “coeur”, il est impératif de suivre une arborescence prédéfinie par l’équipe GeoNature. Un template GitHub a été prévu à cet effet (https://github.com/PnX-SI/gn_module_template). Il est possible de créer un nouveau dépôt GitHub à partir de ce template, ou alors de copier/coller le contenu du dépôt dans un nouveau.

Cette arborescence implique de développer le module dans les technologies du coeur de GeoNature à savoir :

  • Le backend est développé en Python grâce au framework Flask.

  • Le frontend est développé grâce au framework Angular (voir la version actuelle du coeur)

GeoNature prévoit cependant l’intégration de module “externe” dont le frontend serait développé dans d’autres technologies. La gestion de l’intégration du module est à la charge du développeur.

  • Le module se placera dans un dossier à part du dossier “GeoNature” et portera le suffixe “gn_module”. Exemple : gn_module_validation

  • La racine du module comportera les fichiers suivants :

    • install_app.sh : script bash d’installation des librairies python ou npm necessaires au module

    • install_env.sh : script bash d’installation des paquets Linux

    • requirements.txt : liste des librairies python necessaires au module

    • manifest.toml : fichier de description du module (nom, version du module, version de GeoNature compatible)

    • conf_gn_module.toml : fichier de configuration de l’application (livré en version sample)

    • conf_schema_toml.py : schéma ‘marshmallow’ (https://marshmallow.readthedocs.io/en/latest/) du fichier de configuration (permet de s’assurer la conformité des paramètres renseignés par l’utilisateur). Ce fichier doit contenir une classe GnModuleSchemaConf dans laquelle toutes les configurations sont synchronisées.

    • install_gn_module.py : script python lançant les commandes relatives à ‘installation du module (Base de données, …). Ce fichier doit comprendre une fonction gnmodule_install_app(gn_db, gn_app) qui est utilisée pour installer le module (Voir l’exemple du module CMR)

  • La racine du module comportera les dossiers suivants :

    • backend : dossier comportant l’API du module utilisant un blueprint Flask

    • Le fichier blueprint.py comprend les routes du module (ou instancie les nouveaux blueprints du module)

    • Le fichier models.py comprend les modèles SQLAlchemy des tables du module.

    • frontend : le dossier app comprend les fichiers typescript du module, et le dossier assets l’ensemble des médias (images, son).

      • Le dossier app doit comprendre le “module Angular racine”, celui-ci doit impérativement s’appeler gnModule.module.ts

      • Le dossier app doit contenir un fichier module.config.ts. Ce fichier est automatiquement synchronisé avec le fichier de configuration du module <GEONATURE_DIRECTORY>/external_modules/<nom_module>/conf_gn_module.toml` grâce à la commande geonature update_module_configuration <nom_module>. C’est à partir de ce fichier que toutes les configuration doivent pointer.

      • A la racine du dossier frontend, on retrouve également un fichier package.json qui décrit l’ensemble des librairies JS necessaires au module.

    • data : ce dossier comprenant les scripts SQL d’installation du module

Le module est ensuite installable à la manière d’un plugin grâce à la commande geonature install_gn_module de la manière suivante :

# se placer dans le répertoire backend de GeoNature
cd <GEONATURE_DIRECTORY>/backend
# activer le virtualenv python
source venv/bin/activate
# lancer la commande d'installation
geonature install_gn_module <CHEMIN_ABSOLU_DU_MODULE> <URL_API>
# example geonature install_gn_module /home/moi/gn_module_validation /validation

Bonnes pratiques Frontend

  • Pour l’ensemble des composants cartographiques et des formulaires (taxonomie, nomenclatures…), il est conseillé d’utiliser les composants présents dans le module ‘GN2CommonModule’.

    Importez ce module dans le module racine de la manière suivante

    import { GN2CommonModule } from '@geonature_common/GN2Common.module';
    
  • Les librairies JS seront installées dans le dossier node_modules de GeoNature. (Il n’est pas nécessaire de réinstaller toutes les librairies déjà présentes dans GeoNature (Angular, Leaflet, ChartJS …). Le package.json de GeoNature liste l’ensemble des librairies déjà installées et réutilisable dans le module.

  • Les fichiers d’assets sont à ranger dans le dossier assets du frontend. Angular-cli impose cependant que tous les assets soient dans le répertoire mère de l’application (celui de GeoNature). Un lien symbolique est créé à l’installation du module pour faire entre le dossier d’assets du module et celui de Geonature.

  • Utiliser node_modules présent dans GeoNature

    Pour utiliser des librairies déjà installées dans GeoNature, utilisez la syntaxe suivante

    import { TreeModule } from "@librairies/angular-tree-component";
    

    L’alias @librairies pointe en effet vers le repertoire des node_modules de GeoNature

    Pour les utiliser à l’interieur du module, utiliser la syntaxe suivante

    <img src="external_assets/<MY_MODULE_CODE>/afb.png">
    

    Exemple pour le module de validation

    <img src="external_assets/<gn_module_validation>/afb.png">
    

Installer un gn_module

Renseignez l’éventuel fichier config/settings.ini du module.

Pour installer un module, rendez vous dans le dossier backend de GeoNature.

Activer ensuite le virtualenv pour rendre disponible les commandes GeoNature

source venv/bin/activate

Lancez ensuite la commande

geonature install_gn_module <mon_chemin_absolu_vers_le_module> <url_api>

Le premier paramètre est l’emplacement absolu du module sur votre machine et le 2ème le chemin derrière lequel on retrouvera les routes de l’API du module.

Exemple pour atteindre les routes du module de validation à l’adresse ‘http://mon-geonature.fr/api/geonature/validation

Cette commande exécute les actions suivantes :

  • Vérification de la conformité de la structure du module (présence des fichiers et dossiers obligatoires)

  • Intégration du blueprint du module dans l’API de GeoNature

  • Vérification de la conformité des paramètres utilisateurs

  • Génération du routing Angular pour le frontend

  • Re-build du frontend pour une mise en production

Complétez l’éventuelle configuration du module (config/conf_gn_module.toml) à partir des paramètres présents dans config/conf_gn_module.toml.example dont vous pouvez surcoucher les valeurs par défaut. Puis relancez la mise à jour de la configuration (depuis le répertoire geonature/backend et une fois dans le venv (source venv/bin/activate) : geonature update_module_configuration nom_du_module)

Développement Backend

Démarrage du serveur de dev backend

La commande geonature fournit la sous-commande dev_back pour lancer un serveur de test :

(venv)...$ geonature dev_back

Base de données avec Flask-SQLAlchemy

L’intégration de la base de données à GeoNature repose sur la bibliothèque Flask-SQLAlchemy.

Celle-ci fournit un objet db à importer comme ceci : from geonature.utils.env import db

Cet objet permet d’accéder à la session SQLAlchemy ainsi :

from geonature.utils.env import db
obj = db.session.query(MyModel).get(1)

Mais il fournit une base déclarative db.Model permettant d’interroger encore plus simplement les modèles via leur attribut query :

from geonature.utils.env import db
class MyModel(db.Model):
    …

obj = MyModel.query.get(1)

L’attribut query fournit plusieurs fonctions très utiles dont la fonction get_or_404 :

obj = MyModel.query.get_or_404(1)

Ceci est typiquement la première ligne de toutes les routes travaillant sur une instance (route de type get/update/delete).

Fonctions de filtrages

L’attribut query est une instance de la classe flask_sqlalchemy.BaseQuery qui peut être sur-chargée afin de définir de nouvelles fonctions de filtrage.

On pourra ainsi implémenter une fonction pour filtrer les objets auxquels l’utilisateur a accès, ou encore pour implémenter des filtres de recherche.

from flask import g
import sqlalchemy as sa
from flask_sqlalchemy import BaseQuery
from geonature.core.gn_permissions import login_required

class MyModelQuery(BaseQuery):
    def filter_by_scope(self, scope):
        if scope == 0:
            self = self.filter(sa.false())
        elif scope in (1, 2):
            filters = [ MyModel.owner==g.current_user ]
            if scope == 2 and g.current_user.id_organism is not None:
                filters.append(MyModel.owner.any(id_organism=g.current_user.id_organism)
            self = self.filter(sa.or_(*filters))
        return self

class MyModel(db.Model):
    query_class = MyModelQuery


@login_required
def list_my_model():
    obj_list = MyModel.query.filter_by_scope(2).all()

Serialisation des modèles

Avec Marshmallow

La bibliothèque Marshmallow fournit des outils de sérialisation et desérialisation.

Elle est intégrée à GeoNature par la bibliothèque Flask-Marshmallow qui fournit l’objet ma à importer comme ceci : from geonature.utils.env import ma.

Cette bibliothèque ajoute notablement une méthode jsonify aux schémas.

Les schémas Marshmallow peuvent être facilement créés à partir des modèles SQLAlchemy grâce à la bibliothèque Marshmallow-SQLAlchemy.

from geonature.utils.env import ma

class MyModelSchema(ma.SQLAlchemyAutoSchema):
    class Meta:
        model = MyModel
        include_fk = True

La propriété include_fk=True concerne les champs de type ForeignKey, mais pas les relationships en elles-même. Pour ces dernières, il est nécessaire d’ajouter manuellement des champs Nested à son schéma :

class ParentModelSchema(ma.SQLAlchemyAutoSchema):
    class Meta:
        model = ParentModel
        include_fk = True

    childs = ma.Nested("ChildModelSchema", many=True)

class ChildModelSchema(ma.SQLAlchemyAutoSchema):
    class Meta:
        model = ChildModel
        include_fk = True

    parent = ma.Nested(ParentModelSchema)

Attention, la sérialisation d’un objet avec un tel schéma va provoquer une récursion infinie, le schéma parent incluant le schéma enfant, et le schéma enfant incluant le schéma parent.

Il est donc nécessaire de restreindre les champs à inclure avec l’argument only ou exclude lors de la création des schémas :

parent_schema = ParentModelSchema(only=['pk', 'childs.pk'])

L’utilisation de only est lourde puisqu’il faut re-spécifier tous les champs à sérialiser. On est alors tenté d’utiliser l’argument exclude :

parent_schema = ParentModelSchema(exclude=['childs.parent'])

Cependant, l’utilisation de exclude est hautement problématique !

En effet, l’ajout d’un nouveau champs Nested au schéma nécessiterait de le rajouter dans la liste des exclusions partout où le schéma est utilisé (que ça soit pour éviter une récursion infinie, d’alourdir une réponse JSON avec des données inutiles ou pour éviter un problème n+1 - voir section dédiée).

La bibliothèque Utils-Flask-SQLAlchemy fournit une classe utilitaire SmartRelationshipsMixin permettant de résoudre ces problématiques.

Elle permet d’exclure par défaut les champs Nested.

Pour demander la sérialisation d’un sous-schéma, il faut le spécifier avec only, mais sans nécessité de spécifier tous les champs basiques (non Nested).

from utils_flask_sqla.schema import SmartRelationshipsMixin

class ParentModelSchema(SmartRelationshipsMixin, ma.SQLAlchemyAutoSchema):
    class Meta:
        model = ParentModel
        include_fk = True

    childs = ma.Nested("ChildModelSchema", many=True)

class ChildModelSchema(SmartRelationshipsMixin, ma.SQLAlchemyAutoSchema):
    class Meta:
        model = ChildModel
        include_fk = True

    parent = ma.Nested(ParentModelSchema)

Avec le décorateur @serializable

Note : l’utilisation des schémas Marshmallow est probablement plus performante.

La bibliothèque maison Utils-Flask-SQLAlchemy fournit le décorateur @serializable qui ajoute une méthode as_dict sur les modèles décorés :

from utils_flask_sqla.serializers import serializable

@serializable
class MyModel(db.Model):
    …


obj = MyModel(…)
obj.as_dict()

La méthode as_dict fournit les arguments fields et exclude permettant de spécifier les champs que l’on souhaite sérialiser.

Par défaut, seules les champs qui ne sont pas des relationshisp sont sérialisées (fonctionnalité similaire à celle fournit par SmartRelationshipsMixin pour Marshmallow).

Les relations que l’on souhaite voir sérialisées doivent être explicitement déclarées via l’argument fields.

L’argument fields supporte la « notation à point » permettant de préciser les champs d’un modèle en relation :

child.as_dict(fields=['parent.pk'])

Les tests unitaires fournissent un ensemble d’exemples d’usage du décorateur.

La fonction as_dict prenait autrefois en argument les paramètres recursif et depth qui sont tous les deux obsolètes. Ces derniers ont différents problèmes :

  • récursion infinie (contournée par un hack qui ne résoud pas tous les problèmes et qu’il serait souhaitable de voir disparaitre)

  • augmentation non prévue des données sérialisées lors de l’ajout d’une nouvelle relationship

  • problème n+1 (voir section dédiée)

Cas des modèles géographiques

La bibliothèque maison Utils-Flask-SQLAlchemy-Geo fournit des décorateurs supplémentaires pour la sérialisation des modèles contenant des champs géographiques.

  • utils_flask_sqla_geo.serializers.geoserializable

    Décorateur pour les modèles SQLA : Ajoute une méthode as_geofeature qui retourne un dictionnaire serialisable sous forme de Feature geojson.

    Fichier définition modèle

    from geonature.utils.env import DB
    from utils_flask_sqla_geo.serializers import geoserializable
    
    
    @geoserializable
    class MyModel(DB.Model):
        __tablename__ = 'bla'
        ...
    

    Fichier utilisation modèle

    instance = DB.session.query(MyModel).get(1)
    result = instance.as_geofeature()
    
  • utils_flask_sqla_geo.serializers.shapeserializable

    Décorateur pour les modèles SQLA :

    • Ajoute une méthode as_list qui retourne l’objet sous forme de tableau (utilisé pour créer des shapefiles)

    • Ajoute une méthode de classe to_shape qui crée des shapefiles à partir des données passées en paramètre

    Fichier définition modèle

    from geonature.utils.env import DB
    from utils_flask_sqla_geo.serializers import shapeserializable
    
    
    @shapeserializable
    class MyModel(DB.Model):
        __tablename__ = 'bla'
        ...
    

    Fichier utilisation modèle :

    # utilisation de as_shape()
    data = DB.session.query(MyShapeserializableClass).all()
    MyShapeserializableClass.as_shape(
        geom_col='geom_4326',
        srid=4326,
        data=data,
        dir_path=str(ROOT_DIR / 'backend/static/shapefiles'),
        file_name=file_name,
    )
    
  • utils_flask_sqla_geo.utilsgeometry.FionaShapeService

    Classe utilitaire pour créer des shapefiles.

    La classe contient 3 méthodes de classe :

  • FionaShapeService.create_shapes_struct() : crée la structure de 3 shapefiles (point, ligne, polygone) à partir des colonens et de la geométrie passée en paramètre

  • FionaShapeService.create_feature() : ajoute un enregistrement aux shapefiles

  • FionaShapeService.save_and_zip_shapefiles() : sauvegarde et zip les shapefiles qui ont au moins un enregistrement:

    data = DB.session.query(MySQLAModel).all()
    
    for d in data:
            FionaShapeService.create_shapes_struct(
                    db_cols=db_cols,
                    srid=current_app.config['LOCAL_SRID'],
                    dir_path=dir_path,
                    file_name=file_name,
                    col_mapping=current_app.config['SYNTHESE']['EXPORT_COLUMNS']
            )
    FionaShapeService.create_feature(row_as_dict, geom)
            FionaShapeService.save_and_zip_shapefiles()
    

Réponses

Voici quelques conseils sur l’envoi de réponse dans vos routes.

  • Privilégier l’envoi du modèle sérialisé (vues de type create/update), ou d’une liste de modèles sérialisés (vues de type list), plutôt que des structures de données non conventionnelles.

    def get_foo(pk):
        foo = Foo.query.get_or_404(pk)
        return jsonify(foo.as_dict(fields=…))
    
    def get_foo(pk):
        foo = Foo.query.get_or_404(pk)
        return FooSchema(only=…).jsonify(foo)
    
    def list_foo():
        q = Foo.query.filter(…)
        return jsonify([foo.as_dict(fields=…) for foo in q.all()])
    
    def list_foo():
        q = Foo.query.filter(…)
        return FooSchema(only=…).jsonify(q.all(), many=True)
    
  • Pour les listes vides, ne pas renvoyer le code d’erreur 404 mais une liste vide !

    return jsonify([])
    
  • Renvoyer une liste et sa longueur dans une structure de données non conventionnelle est strictement inutile, il est très simple d’accéder à la longueur de la liste en javascript via l’attribut length.

  • Traitement des erreurs : utiliser les exceptions prévues à cet effet :

    from werkzeug.exceptions import Forbidden, BadRequest, NotFound
    
    def restricted_action(pk):
        if …:
            raise Forbidden
    
    • Penser à utiliser get_or_404 plutôt que de lancer une exception NotFound

    • Si l’utilisateur n’a pas le droit d’effectuer une action, utiliser l’exception Forbidden (code HTTP 403), et non l’exception Unauthorized (code HTTP 401), cette dernière étant réservée aux utilisateurs non authentifiés.

    • Vérifier la validité des données fournies par l’utilisateur (request.json ou request.args) et lever une exception BadRequest si celles-ci ne sont pas valides (l’utilisateur ne doit pas être en mesure de déclencher une erreur 500 en fournissant une string plutôt qu’un int par exemple !).

      • Marshmallow peut servir à cela :

      from marshmallow import Schema, fields, ValidationError
      def my_route():
          class RequestSchema(Schema):
              value = fields.Float()
          try:
              data = RequestSchema().load(request.json)
          except ValidationError as error:
              raise BadRequest(error.messages)
      
      • Cela peut être fait avec jsonschema :

      from from jsonschema import validate as validate_json, ValidationError
      
      def my_route():
          request_schema = {
              "type": "object",
              "properties": {
                  "value": { "type": "number", },
              },
              "minProperties": 1,
              "additionalProperties": False,
          }
          try:
              validate_json(request.json, request_schema)
          except ValidationError as err:
              raise BadRequest(err.message)
      
  • Pour les réponses vides (exemple : route de type delete), on pourra utiliser le code de retour 204 :

    return '', 204
    

    Lorsque par exemple une action est traitée mais aucun résultat n’est à renvoyer, inutile d’envoyer une réponse « OK ». C’est l’envoi d’une réponse HTTP avec un code égale à 400 ou supérieur qui entrainera le traitement d’une erreur côté frontend, plutôt que de se fonder sur le contenu d’une réponse non normalisée.

Le décorateur @json_resp

Historiquement, beaucoup de vues sont décorées avec le décorateur @json_resp.

Celui-ci apparait aujourd’hui superflu par rapport à l’usage directement de la fonction jsonify fournie par Flask.

  • utils_flask_sqla_geo.serializers.json_resp

    Décorateur pour les routes : les données renvoyées par la route sont automatiquement serialisées en json (ou geojson selon la structure des données).

    S’insère entre le décorateur de route flask et la signature de fonction

    Fichier routes

    from flask import Blueprint
    from utils_flask_sqla.response import json_resp
    
    blueprint = Blueprint(__name__)
    
    @blueprint.route('/myview')
    @json_resp
    def my_view():
        return {'result': 'OK'}
    
    
    @blueprint.route('/myerrview')
    @json_resp
    def my_err_view():
        return {'result': 'Not OK'}, 400
    

Problème « n+1 »

Le problème « n+1 » est un anti-pattern courant des routes de type « liste » (par exemple, récupération de la liste des cadres d’acquisition).

En effet, on souhaite par exemple afficher la liste des cadres d’acquisitions, et pour chacun d’entre eux, la liste des jeux de données :

af_list = AcquisitionFramwork.query.all()

# with Marshmallow (and SmartRelationshipsMixin)
return AcquisitionFrameworkSchema(only=['datasets']).jsonify(af_list, many=True)

# with @serializable
return jsonify([ af.as_dict(fields=['datasets']) for af in af_list])

Ainsi, lors de la sérialisation de chaque AF, on demande à sérialiser l’attribut datasets, qui est une relationships vers la liste des DS associés :

class AcquisitionFramework(db.Model)
    datasets = db.relationships(Dataset, uselist=True)

Sans précision, la stratégie de chargement de la relation datasets est select, c’est-à-dire que l’accès à l’attribut datasets d’un AF provoque une nouvelle requête select afin de récupérer la liste des DS concernés.

Ceci est généralement peu grave lorsque l’on manipule un unique objet, mais dans le cas d’une liste d’objet, cela génère 1+n requêtes SQL : une pour récupérer la liste des AF, puis une lors de la sérialisation de chaque AF pour récupérer les DS de ce dernier.

Cela devient alors un problème de performance notable !

Afin de résoudre ce problème, il nous faut joindre les DS à la requête de récupération des AF.

Pour cela, plusieurs solutions :

  • Le spécifier dans la relationship :

    class AcquisitionFramework(db.Model)
        datasets = db.relationships(Dataset, uselist=True, lazy='joined')
    

    Cependant, cette stratégie s’appliquera (sauf contre-ordre) dans tous les cas, même lorsque les DS ne sont pas nécessaires, alourdissant potentiellement certaines requêtes qui n’en ont pas usage.

  • Le spécifier au moment où la requête est effectuée :

    from sqlalchemy.orm import joinedload
    
    af_list = AcquisitionFramework.query.options(joinedload('datasets')).all()
    

Il est également possible de joindre les relations d’une relation, par exemple le créateur des jeux de données :

af_list = (
    AcquisitionFramework.query
    .options(
        joinedload('datasets').options(
            joinedload('creator'),
        ),
    )
    .all()
)

Afin d’être sûr d’avoir joint toutes les relations nécessaires, il est possible d’utiliser la stratégie raise par défaut, ce qui va provoquer le lancement d’une exception lors de l’accès à un attribut non pré-chargé, nous incitant à le joindre également :

from sqlalchemy.orm import raiseload, joinedload

af_list = (
    AcquisitionFramework.query
    .options(
        raiseload('*'),
        joinedload('datasets'),
    )
    .all()
)

Pour toutes les requêtes récupérant une liste d’objet, l’utilisation de la stratégie raise par défaut est grandement encouragée afin de ne pas tomber dans cet anti-pattern.

La méthode as_dict du décorateur @serializable accepte l’argument unloaded='raise' ou unloaded='warn' pour un résultat similaire (ou un simple warning).

L’utilisation de raiseload, appartenant au cœur de SQLAlchemy, reste à privilégier.

Export des données

TODO

Utilisation de la configuration

La configuration globale de l’application est controlée par le fichier config/geonature_config.toml qui contient un nombre limité de paramètres.

De nombreux paramètres sont néammoins passés à l’application via un schéma Marshmallow (voir fichier backend/geonature/utils/config_schema.py).

Dans l’application flask, l’ensemble des paramètres de configuration sont utilisables via le dictionnaire config

from geonature.utils.config import config
MY_PARAMETER = config['MY_PARAMETER']

Chaque module GeoNature dispose de son propre fichier de configuration, (module/config/cong_gn_module.toml) contrôlé de la même manière par un schéma Marshmallow (module/config/conf_schema_toml.py).

Pour récupérer la configuration du module dans l’application Flask, il existe deux méthodes:

Dans le fichier blueprint.py

# Methode 1 :

from flask import current_app
MY_MODULE_PARAMETER = current_app.config['MY_MODULE_NAME']['MY_PARAMETER]
# ou MY_MODULE_NAME est le nom du module tel qu'il est défini dans le fichier ``manifest.toml`` et la table ``gn_commons.t_modules``

#Méthode 2 :
MY_MODULE_PARAMETER = blueprint.config['MY_MODULE_PARAMETER']

Il peut-être utile de récupérer l’ID du module GeoNature (notamment pour des questions droits). De la même manière que précédement, à l’interieur d’une route, on peut récupérer l’ID du module de la manière suivante

ID_MODULE = blueprint.config['ID_MODULE']
# ou
ID_MODULE = current_app.config['MODULE_NAME']['ID_MODULE']

Si on souhaite récupérer l’ID du module en dehors du contexte d’une route, il faut utiliser la méthode suivante

from geonature.utils.env import get_id_module
ID_MODULE = get_id_module(current_app, 'occtax')

Authentification et authorisations

Restreindre une route aux utilisateurs connectés

Utiliser le décorateur @login_required :

from geonature.core.gn_permissions.decorators import login_required

@login_required
def my_protected_route():
    pass

Connaitre l’utilisateur actuellement connecté

L’utilisateur courant est stocké dans l’espace de nom g :

from flask import g

print(g.current_user)

Il s’agit d’une instance de pypnusershub.db.models.User.

Vérification des droits des utilisateurs

  • geonature.core.gn_permissions.decorators.check_cruved_scope

    Décorateur pour les routes : Vérifie les droits de l’utilisateur à effectuer une action sur la donnée et le redirige en cas de niveau insuffisant ou d’informations de session erronées

    params :

    • action <str:[‘C’,’R’,’U’,’V’,’E’,’D’]> type d’action effectuée par la route (Create, Read, Update, Validate, Export, Delete)

    • get_role <bool:False> : si True, ajoute l’id utilisateur aux kwargs de la vue

    • module_code: <str:None> : Code du module (gn_commons.t_modules) sur lequel on veut récupérer le CRUVED. Si ce paramètre n’est pas passé, on vérifie le CRUVED de GeoNature

    from flask import Blueprint
    from geonature.core.gn_permissions.tools import get_or_fetch_user_cruved
    from utils_flask_sqla.response import json_resp
    from geonature.core.gn_permissions import decorators as permissions
    
    blueprint = Blueprint(__name__)
    
    @blueprint.route('/mysensibleview', methods=['GET'])
    @permissions.check_cruved_scope(
            'R',
            True,
            module_code="OCCTAX"
    )
    @json_resp
    def my_sensible_view(info_role):
        # Récupérer l'id de l'utilisateur qui demande la route
        id_role = info_role.id_role
        # Récupérer la portée autorisée à l'utilisateur pour l'action 'R' (read)
        read_scope = info_role.value_filter
        #récupérer le CRUVED complet de l'utilisateur courant
        user_cruved = get_or_fetch_user_cruved(
                session=session,
                id_role=info_role.id_role,
                module_code=MY_MODULE_CODE,
        )
        return {'result': 'id_role = {}'.format(info_role.id_role)}
    
  • geonature.core.gn_permissions.tools.cruved_scope_for_user_in_module

    • Fonction qui retourne le CRUVED d’un utilisateur pour un module et/ou un objet donné.

    • Si aucun CRUVED n’est défini pour le module, c’est celui de GeoNature qui est retourné, sinon 0.

    • Le CRUVED du module enfant surcharge toujours celui du module parent.

    • Le CRUVED sur les objets n’est pas hérité du module parent.

    params :

    • id_role <integer:None>

    • module_code <str:None> : code du module sur lequel on veut avoir le CRUVED

    • object_code <str:’ALL’> : code de l’objet sur lequel on veut avoir le CRUVED

    • get_id <boolean: False> : retourne l’id_filter et non le code_filter si True

    Valeur retournée : tuple

    A l’indice 0 du tuple: <dict{str:str}> ou <dict{str:int}>, boolean) {‘C’: ‘1’, ‘R’:’2’, ‘U’: ‘1’, ‘V’:’2’, ‘E’:’3’, ‘D’: ‘3’} ou {‘C’: 2, ‘R’:3, ‘U’: 4, ‘V’:1, ‘E’:2, ‘D’: 2} si get_id=True

    A l’indice 1 du tuple: un booléen spécifiant si le CRUVED est hérité depuis un module parent ou non.

    from pypnusershub.db.tools import cruved_for_user_in_app
    
    # récupérer le CRUVED de l'utilisateur 1 dans le module OCCTAX
    cruved, herited = cruved_scope_for_user_in_module(
            id_role=1
            module_code='OCCTAX
    )
    # récupérer le CRUVED de l'utilisateur 1 sur GeoNature
    cruved, herited = cruved_scope_for_user_in_module(id_role=1)
    

Développement Frontend

Bonnes pratiques

  • Chaque gn_module de GeoNature doit être un module Angular indépendant https://angular.io/guide/ngmodule.

  • Ce gn_module peut s’appuyer sur une série de composants génériques intégrés dans le module GN2CommonModule et décrit ci-dessous

Les composants génériques

Un ensemble de composants décrits ci-dessous sont intégrés dans le coeur de GeoNature et permettent aux développeurs de simplifier la mise en place de formulaires ou de bloc cartographiques.

Voir la DOCUMENTATION COMPLETE sur les composants génériques.

NB : les composants de type “formulaire” (balise input ou select) partagent une logique commune et ont des Inputs et des Outputs communs, décrits ci-dessous. (voir https://github.com/PnX-SI/GeoNature/blob/master/frontend/src/app/GN2CommonModule/form/genericForm.component.ts).

Une documentation complète des composants génériques est disponible ici

NB : les composants de type “formulaire” (balise input ou select) partagent une logique commune et ont des Inputs et des Outputs communs, décrits ci-dessous. (voir https://github.com/PnX-SI/GeoNature/blob/master/frontend/src/app/GN2CommonModule/form/genericForm.component.ts).

  • Inputs

    • L’input parentFormControl de type FormControl (https://angular.io/api/forms/FormControl) permet de contrôler la logique et les valeurs du formulaire depuis l’extérieur du composant. Cet input est obligatoire pour le fonctionnement du composant.

    • L’input label (string) permet d’afficher un label au dessus de l’input.

    • L’input displayAll (boolean, défaut = false) permet d’ajouter un item ‘tous’ sur les inputs de type select (Exemple : pour sélectionner tous les jeux de données de la liste)

    • L’input multiSelect (boolean, défaut = false) permet de passer les composants de type select en “multiselect” (sélection multiple sur une liste déroulante). Le parentFormControl devient par conséquent un tableau

    • L’input searchBar (boolean, défaut = false) permet de rajouter une barre de recherche sur les composants multiselect

    • L’input disabled (boolean) permet de rendre le composant non-saisissable

    • L’input debounceTime définit une durée en ms après laquelle les évenements onChange et onDelete sont déclenchés suite à un changement d’un formulaire. (Par défault à 0)

  • Outputs

    Plusieurs Output communs à ses composants permettent d’émettre des événements liés aux formulaires.

    • onChange : événement émit à chaque fois qu’un changement est effectué sur le composant. Renvoie la valeur fraiche de l’input.

    • onDelete : événement émit chaque fois que le champ du formulaire est supprimé. Renvoie un évenement vide.

Ces composants peuvent être considérés comme des “dump components” ou “presentation components”, puisque que la logique de contrôle est déporté au composant parent qui l’accueil (https://blog.angular-university.io/angular-2-smart-components-vs-presentation-components-whats-the-difference-when-to-use-each-and-why/)

Un ensemble de composants permettant de simplifier l’affichage des cartographies Leaflet sont disponibles. Notamment un composant “map-list” permettant de connecter une carte avec une liste d’objets décrits en détail ci dessous.

MapListComponent

Le composant MapList fournit une carte pouvant être synchronisée avec une liste. La liste, pouvant être spécifique à chaque module, elle n’est pas intégrée dans le composant et est laissée à la responsabilité du développeur. Le service MapListService offre cependant des fonctions permettant facilement de synchroniser les deux éléments.

Fonctionnalité et comportement offert par le composant et le service :

  • Charger les données

    Le service expose la fonction getData(apiEndPoint, params?) permettant de charger les données pour la carte et la liste. Cette fonction doit être utilisée dans le composant qui utilise le composant MapListComponent. Elle se charge de faire appel à l’API passée en paramètre et de rendre les données disponibles au service.

    Le deuxième paramètre params est un tableau de paramètre(s) (facultatif). Il permet de filtrer les données sur n’importe quelle propriété du GeoJson, et également de gérer la pagination.

    Exemple : afficher les 10 premiers relevés du cd_nom 212 :

    mapListService.getData('occtax/releve',
    [{'param': 'limit', 'value': 10'},
    {'param': 'cd_nom', 'value': 212'}])
    

    Exemple dans le module OccTax

    L’API doit nécessairement renvoyer un objet comportant un GeoJson. La structure du l’objet doit être la suivante :

    'total': nombre d'élément total,
    'total_filtered': nombre d'élément filtré,
    'page': numéro de page de la liste,
    'limit': limite d'élément renvoyé,
    'items': le GeoJson
    

    Pour une liste simple sans pagination, seule la propriété ‘items’ est obligatoire.

  • Rafraîchir les données

    La fonction refreshData(apiEndPoint, method, params?) permet de raffrachir les données en fonction de filtres personnalisés. Les paramètres apiEndPoint et params sont les mêmes que pour la fonction getData. Le paramètre method permet lui de chosir si on ajoute - append- , ou si on initialise (ou remplace) -set- un filtre.

    Exemple 1 : Pour filtrer sur l’observateur 1, puis ajouter un filtre sur l’observateur 2 :

    mapListService.refreshData('occtax/relevé', 'append, [{'param': 'observers', 'value': 1'}])
    

    puis :

    refreshData('occtax/relevé', 'append, [{'param': 'observers', 'value': 2'}])
    

    Exemple 2: pour filtrer sur le cd_nom 212, supprimer ce filtre et filtrer sur le cd_nom 214

    mapListService.refreshData('occtax/relevé', 'set, [{'param': 'cd_nom', 'value': 1'}])
    

    puis :

    mapListService.refreshData('occtax/relevé', 'set, [{'param': 'cd_nom', 'value': 2'}])
    
  • Gestion des évenements :

    • Au clic sur un marker de la carte, le service MapListService expose la propriété selectedRow qui est un tableau contenant l’id du marker sélectionné. Il est ainsi possible de surligner l’élément séléctionné dans le liste.

    • Au clic sur une ligne du tableau, utiliser la fonction MapListService.onRowSelected(id) (id étant l’id utilisé dans le GeoJson) qui permet de zoomer sur le point séléctionner et de changer la couleur de celui-ci.

Le service contient également deux propriétés publiques geoJsonData (le geojson renvoyé par l’API) et tableData (le tableau de features du Geojson) qui sont respectivement passées à la carte et à la liste. Ces deux propriétés sont utilisables pour interagir (ajouter, supprimer) avec les données de la carte et de la liste.

  • Selector : pnx-map-list

  • Inputs :

    idName

    Libellé de l’id du geojson (id_releve, id)

    Type: string

    height

    Taille de l’affichage de la carte Leaflet

    Type: string

Exemple d’utilisation avec une liste simple :

<pnx-map-list
        idName="id_releve_occtax"
        height="80vh">
</pnx-map-list>
<table>
        <tr ngFor="let row of mapListService.tableData" [ngClass]=" {'selected': mapListService.selectedRow[0]} == row.id ">
                <td (click)="mapListService.onRowSelect(row.id)"> Zoom on map </td>
                <td > {{row.observers}} </td>
                <td > {{row.date}} </td>
        </tr>
</table>

Pnx-Municipalities

Suite à l’ajout d’un input “valueFieldName” pour “pnx-areas” et “pnx-municipalities” dans la version 2.9.0 de GeoNature, pour ceux qui utilisent le composant pnx-municipalities, l’idéal serait de traduire les données et les modèles et de passer du code_insee à id_area. La correspondance est immédiate (area_code = code_insee).

Cependant, pour garder la retrocompatibilité du composant pnx-municipalities veuillez ajouter :

  • dans les templates : [valueFieldName]="'area_code' dans les templates

  • dans les config (js, ts ou json) (attention à la casse) : "value_field_name": "area_code"

  • dans le module Monitoring, ajouter aussi "type_util": "area"

Outils d’aide à la qualité du code

Des outils d’amélioration du code pour les développeurs peuvent être utilisés : flake8, pylint, pytest, coverage.

La documentation peut être générée avec Sphinx.

Les fichiers de configuration de ces outils se trouvent à la racine du projet :

  • .pylint

Un fichier .editorconfig permettant de définir le comportement de votre éditeur de code est également disponible à la racine du projet.

Sphinx

Sphinx est un générateur de documentation.

Pour générer la documentation HTML, se placer dans le répertoire docs et modifier les fichiers .rst:

cd docs
make html

Pylint

Pylint fait la même chose que Flake8 mais il est plus complet, plus configurable mais aussi plus exigeant.

Pour inspecter le répertoire geonature:

cd backend
pylint geonature

tslint

tslint fait la même chose que pylint mais pour la partie frontend en typescript:

cd frontend
npm run lint

Pytest

Pytest permet de mettre en place des tests fonctionnels et automatisés du code Python.

Les fichiers de test sont dans le répertoire backend/geonature/tests

pytest

Coverage

Coverage permet de donner une indication concernant la couverture du code par les tests.

pytest --cov=geonature --cov-report=html

Ceci génénère un rapport html disponible dans htmlcov/index.html.