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)

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…)

Liste des routes
Resource |
Operation |
Description |
---|---|---|
GET /meta/acquisition_frameworks/export_pdf/(id_acquisition_framework) |
||
Commons |
||
Generic |
||
Habref |
||
Import |
Get synthesis fields. |
|
Add a mapping. |
||
Return all active named mappings. |
||
Get all imports. |
||
Get values of a mapping. |
||
Get fields of a mapping. |
||
Update mapping name. |
||
Add an import or update an existing import. |
||
Get invalid rows of an import as CSV. |
||
Get errors of an import. |
||
Import the valid data. |
||
Return a mapping. |
||
Delete a mapping. |
||
Get an import. |
||
Delete an import. |
||
Metadata |
||
GET /meta/acquisition_framework/(id_acquisition_framework)/stats |
||
GET /meta/acquisition_framework/(id_acquisition_framework)/bbox |
||
POST /meta/acquisition_framework/(int:id_acquisition_framework) |
||
Monitoring |
||
Nomenclatures |
||
OccHab |
||
Occhab |
||
Occtax |
Post one Occtax data (Releve + Occurrence + Counting) |
|
Post one Occtax data (Releve + Occurrence + Counting) |
||
Export data from pr_occtax.v_export_occtax |
||
Permissions |
||
Profiles |
||
Ref Geo |
||
Synthese |
||
Get filtered observations |
||
Deprecated |
||
User |
||
Validation |
||
View_Permission |
||
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) |
||
POST /permissions_backoffice/filter_form/id_filter_type/(int:id_filter_type) |
||
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) |
||
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
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 atstatic_url_path
ifstatic_folder
is set.New in version 0.5.
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 à sortirMettez à jour le fichier
VERSION
Remplissez le tableau de compatibilité des dépendances (
docs/versions-compatibility.rst
)Mergez la branche
develop
dans la branchemaster
Faites la release (https://github.com/PnX-SI/GeoNature/releases) en la taguant
X.Y.Z
(sansv
devant) et en copiant le contenu du ChangelogDans la branche
develop
, modifiez le fichierVERSION
enX.Y.Z.dev0
et pareil dans le fichierdocs/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’appelerfr_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 branchedevelop
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 commitLes messages de commits font référence à un ticket ou le ferment (
ref #12
oufixes #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 ungit 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 linterLa 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 :
Utiliser la librairie material icons fournie avec le projet : https://material.io/resources/icons/?style=baseline (
<mat-icon> add </mat-icon>
)
Formulaire :
Nous utilisons pour l’instant le style des formulaires Bootstrap (https://getbootstrap.com/docs/4.0/components/forms/). Une reflexion de migration vers les formulaires materials est en cours.
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 moduleinstall_env.sh
: script bash d’installation des paquets Linuxrequirements.txt
: liste des librairies python necessaires au modulemanifest.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 classeGnModuleSchemaConf
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 fonctiongnmodule_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 FlaskLe 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 dossierapp
comprend les fichiers typescript du module, et le dossierassets
l’ensemble des médias (images, son).Le dossier
app
doit comprendre le “module Angular racine”, celui-ci doit impérativement s’appelergnModule.module.ts
Le dossier
app
doit contenir un fichiermodule.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 commandegeonature 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 fichierpackage.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 …). Lepackage.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 GeoNaturePour 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 exceptionNotFound
Si l’utilisateur n’a pas le droit d’effectuer une action, utiliser l’exception
Forbidden
(code HTTP 403), et non l’exceptionUnauthorized
(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
ourequest.args
) et lever une exceptionBadRequest
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')
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 typeFormControl
(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 tableauL’input
searchBar
(boolean, défaut = false) permet de rajouter une barre de recherche sur les composants multiselectL’input
disabled
(boolean) permet de rendre le composant non-saisissableL’input
debounceTime
définit une durée en ms après laquelle les évenementsonChange
etonDelete
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 composantMapListComponent
. 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'}])
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ètresapiEndPoint
etparams
sont les mêmes que pour la fonctiongetData
. Le paramètremethod
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 templatesdans 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
.