From 77ab1b9d0269178ebb60ddb37a7172d9446e3a56 Mon Sep 17 00:00:00 2001 From: Etienne Delclaux Date: Fri, 27 Sep 2024 12:51:05 +0200 Subject: [PATCH 1/8] feat: adjust species-sheet config structure --- .../core/gn_synthese/synthese_config.py | 74 ------------------- backend/geonature/utils/config_schema.py | 24 +----- config/default_config.toml.example | 13 +++- .../src/app/syntheseModule/synthese.module.ts | 3 +- .../taxon-sheet/indicator/indicator.ts | 35 ++++----- .../tab-profile/tab-profile.component.ts | 53 ++++++++----- .../taxon-sheet/taxon-sheet.component.ts | 58 +++++++++++---- .../taxon-sheet/taxon-sheet.route.service.ts | 55 +++++--------- 8 files changed, 127 insertions(+), 188 deletions(-) diff --git a/backend/geonature/core/gn_synthese/synthese_config.py b/backend/geonature/core/gn_synthese/synthese_config.py index e75566891f..17d776062f 100644 --- a/backend/geonature/core/gn_synthese/synthese_config.py +++ b/backend/geonature/core/gn_synthese/synthese_config.py @@ -100,77 +100,3 @@ {"prop": "dataset_name", "name": "JDD", "max_width": 200}, {"prop": "observers", "name": "observateur", "max_width": 200}, ] - - -class DefaultProfile: - ENABLED = True - ## DEFAULT PROFILE INDICATORS - LIST_INDICATORS = [ - { - "name": "observation(s) valide(s)", - "matIcon": "search", - "field": "count_valid_data", - "type": "number", - }, - { - "name": "Première observation", - "matIcon": "schedule", - "field": "first_valid_data", - "type": "date", - }, - { - "name": "Dernière observation", - "matIcon": "search", - "field": "last_valid_data", - "type": "date", - }, - { - "name": "Plage d'altitude(s)", - "matIcon": "terrain", - "field": ["altitude_min", "altitude_max"], - "unit": "m", - "type": "number", - }, - ] - - -class DefaultGeographicOverview: - pass - - -class DefaultSpeciesSheet: - ## DEFAULT SPECIES SHEET INDICATORS - LIST_INDICATORS = [ - { - "name": "observation(s)", - "matIcon": "search", - "field": "observation_count", - "type": "number", - }, - { - "name": "observateur(s)", - "matIcon": "people", - "field": "observer_count", - "type": "number", - }, - { - "name": "commune(s)", - "matIcon": "location_on", - "field": "area_count", - "type": "number", - }, - { - "name": "Plage d'altitude(s)", - "matIcon": "terrain", - "unit": "m", - "type": "number", - "field": ["altitude_min", "altitude_max"], - }, - { - "name": "Plage d'observation(s)", - "matIcon": "date_range", - "type": "date", - "field": ["date_min", "date_max"], - "separator": "-", - }, - ] diff --git a/backend/geonature/utils/config_schema.py b/backend/geonature/utils/config_schema.py index 36d4e1ed8f..8183c72ffe 100644 --- a/backend/geonature/utils/config_schema.py +++ b/backend/geonature/utils/config_schema.py @@ -18,13 +18,7 @@ ) from marshmallow.validate import OneOf, Regexp, Email, Length -from geonature.core.gn_synthese.synthese_config import ( - DEFAULT_EXPORT_COLUMNS, - DEFAULT_LIST_COLUMN, - DefaultGeographicOverview, - DefaultProfile, - DefaultSpeciesSheet, -) +from geonature.core.gn_synthese.synthese_config import DEFAULT_EXPORT_COLUMNS, DEFAULT_LIST_COLUMN from geonature.utils.env import GEONATURE_VERSION, BACKEND_DIR, ROOT_DIR from geonature.utils.module import iter_modules_dist, get_module_config from geonature.utils.utilsmails import clean_recipients @@ -278,24 +272,10 @@ class ExportObservationSchema(Schema): geojson_local_field = fields.String(load_default="geojson_local") -class SpeciesSheetProfile(Schema): - ENABLED = fields.Boolean(load_default=DefaultProfile.ENABLED) - LIST_INDICATORS = fields.List(fields.Dict, load_default=DefaultProfile.LIST_INDICATORS) - - -class SpeciesSheetGeographicOverview(Schema): - pass - - class SpeciesSheet(Schema): # -------------------------------------------------------------------- # SYNTHESE - SPECIES_SHEET - LIST_INDICATORS = fields.List(fields.Dict, load_default=DefaultSpeciesSheet.LIST_INDICATORS) - - GEOGRAPHIC_OVERVIEW = fields.Dict( - load_default=SpeciesSheetGeographicOverview().load({}) - ) # rename - PROFILE = fields.Nested(SpeciesSheetProfile, load_default=SpeciesSheetProfile().load({})) + ENABLE_PROFILE = fields.Boolean(load_default=True) class Synthese(Schema): diff --git a/config/default_config.toml.example b/config/default_config.toml.example index 61727d3f11..69c175e96c 100644 --- a/config/default_config.toml.example +++ b/config/default_config.toml.example @@ -307,9 +307,9 @@ MEDIA_CLEAN_CRONTAB = "0 1 * * *" # Vues d'export personnalisées EXPORT_OBSERVATIONS_CUSTOM_VIEWS = [ { - label = "format personnalisé", + label = "format personnalisé", view_name = "schema_name.view_name" - } + } ] # Noms des colonnes obligatoires de la vue ``gn_synthese.v_metadata_for_export`` EXPORT_METADATA_ID_DATASET_COL = "jdd_id" @@ -441,6 +441,11 @@ MEDIA_CLEAN_CRONTAB = "0 1 * * *" # Seulement les données de présence cd_nomenclature_observation_status = ['Pr'] + [SYNTHESE.SPECIES_SHEET] + # Options dédiées à la fiche espèce + #Permet d'activer ou non la section "Profile" de la fiche espèce + ENABLE_PROFILE = True + # Gestion des demandes d'inscription [ACCOUNT_MANAGEMENT] # Activer l'affichage du lien vers le formulaire d'inscription @@ -602,7 +607,7 @@ MEDIA_CLEAN_CRONTAB = "0 1 * * *" [[AUTHENTICATION.PROVIDERS]] module="pypnusershub.auth.providers.default.DefaultConfiguration" id_provider="local_provider" - + [[AUTHENTICATION.PROVIDERS]] module="pypnusershub.auth.providers.openid_provider.OpenIDConnectProvider" id_provider = "google" @@ -610,4 +615,4 @@ MEDIA_CLEAN_CRONTAB = "0 1 * * *" label = "Google" ISSUER = "https://accounts.google.com/" CLIENT_ID = "ID" # API key - CLIENT_SECRET = "SECRET" # API Secret \ No newline at end of file + CLIENT_SECRET = "SECRET" # API Secret diff --git a/frontend/src/app/syntheseModule/synthese.module.ts b/frontend/src/app/syntheseModule/synthese.module.ts index 993b8f5cdd..a8310b63f7 100644 --- a/frontend/src/app/syntheseModule/synthese.module.ts +++ b/frontend/src/app/syntheseModule/synthese.module.ts @@ -19,7 +19,6 @@ import { TaxonSheetComponent } from './taxon-sheet/taxon-sheet.component'; import { RouteService, ALL_TAXON_SHEET_ADVANCED_INFOS_ROUTES, - ROUTE_MANDATORY, } from './taxon-sheet/taxon-sheet.route.service'; const routes: Routes = [ @@ -32,7 +31,7 @@ const routes: Routes = [ children: [ { path: '', - redirectTo: ROUTE_MANDATORY.path, + redirectTo: ALL_TAXON_SHEET_ADVANCED_INFOS_ROUTES[0].path, pathMatch: 'prefix', }, ...ALL_TAXON_SHEET_ADVANCED_INFOS_ROUTES.map((tab) => { diff --git a/frontend/src/app/syntheseModule/taxon-sheet/indicator/indicator.ts b/frontend/src/app/syntheseModule/taxon-sheet/indicator/indicator.ts index 728a454f50..441fb02c80 100644 --- a/frontend/src/app/syntheseModule/taxon-sheet/indicator/indicator.ts +++ b/frontend/src/app/syntheseModule/taxon-sheet/indicator/indicator.ts @@ -1,16 +1,17 @@ -type IndicatorRawType = 'number' | 'string' | 'date'; -export interface IndicatorRaw { +export interface Indicator { name: string; matIcon: string; - field: string | Array; - unit?: string; - type: IndicatorRawType; + value: string | null; } -export interface Indicator { +type IndicatorRawType = 'number' | 'string' | 'date'; +export interface IndicatorDescription { name: string; matIcon: string; - value: string | null; + field: string | Array; + unit?: string; + separator?: string; + type: IndicatorRawType; } type Stats = Record; @@ -18,7 +19,7 @@ type Stats = Record; const DEFAULT_VALUE = '-'; const DEFAULT_SEPARATOR = '-'; -function getValue(field: string, indicatorConfig: IndicatorRaw, stats?: Stats) { +function getValue(field: string, indicatorConfig: IndicatorDescription, stats?: Stats) { if (stats && stats[field]) { let valueAsString = ''; switch (indicatorConfig.type) { @@ -37,24 +38,24 @@ function getValue(field: string, indicatorConfig: IndicatorRaw, stats?: Stats) { return DEFAULT_VALUE; } -export function computeIndicatorFromConfig( - indicatorConfig: IndicatorRaw, +export function computeIndicatorFromDecsription( + indicatorDescription: IndicatorDescription, stats?: Stats ): Indicator { let value = DEFAULT_VALUE; if (stats) { - if (Array.isArray(indicatorConfig.field)) { - const separator = indicatorConfig['separator'] ?? DEFAULT_SEPARATOR; - value = indicatorConfig.field - .map((field) => getValue(field, indicatorConfig, stats)) + if (Array.isArray(indicatorDescription.field)) { + const separator = indicatorDescription.separator ?? DEFAULT_SEPARATOR; + value = indicatorDescription.field + .map((field) => getValue(field, indicatorDescription, stats)) .join(' ' + separator + ' '); } else { - value = getValue(indicatorConfig.field, indicatorConfig, stats); + value = getValue(indicatorDescription.field, indicatorDescription, stats); } } return { - name: indicatorConfig.name, - matIcon: indicatorConfig.matIcon, + name: indicatorDescription.name, + matIcon: indicatorDescription.matIcon, value: value, }; } diff --git a/frontend/src/app/syntheseModule/taxon-sheet/tab-profile/tab-profile.component.ts b/frontend/src/app/syntheseModule/taxon-sheet/tab-profile/tab-profile.component.ts index a0897604bf..0e0f51d908 100644 --- a/frontend/src/app/syntheseModule/taxon-sheet/tab-profile/tab-profile.component.ts +++ b/frontend/src/app/syntheseModule/taxon-sheet/tab-profile/tab-profile.component.ts @@ -1,14 +1,45 @@ import { Component, OnInit } from '@angular/core'; import { GN2CommonModule } from '@geonature_common/GN2Common.module'; import { CommonModule } from '@angular/common'; -import { ConfigService } from '@geonature/services/config.service'; import { DataFormService, Profile } from '@geonature_common/form/data-form.service'; import { Taxon } from '@geonature_common/form/taxonomy/taxonomy.component'; import { CommonService } from '@geonature_common/service/common.service'; -import { computeIndicatorFromConfig, Indicator, IndicatorRaw } from '../indicator/indicator'; +import { + computeIndicatorFromDecsription, + Indicator, + IndicatorDescription, +} from '../indicator/indicator'; import { IndicatorComponent } from '../indicator/indicator.component'; import { TaxonSheetService } from '../taxon-sheet.service'; +const INDICATORS: Array = [ + { + name: 'observation(s) valide(s)', + matIcon: 'search', + field: 'count_valid_data', + type: 'number', + }, + { + name: 'Première observation', + matIcon: 'schedule', + field: 'first_valid_data', + type: 'date', + }, + { + name: 'Dernière observation', + matIcon: 'search', + field: 'last_valid_data', + type: 'date', + }, + { + name: "Plage d'altitude(s)", + matIcon: 'terrain', + field: ['altitude_min', 'altitude_max'], + unit: 'm', + type: 'number', + }, +]; + @Component({ standalone: true, selector: 'tab-profile', @@ -21,7 +52,6 @@ export class TabProfileComponent implements OnInit { _profile: Profile | null; constructor( - private _config: ConfigService, private _ds: DataFormService, private _commonService: CommonService, private _tss: TaxonSheetService @@ -54,19 +84,8 @@ export class TabProfileComponent implements OnInit { set profile(profile: Profile | null) { this._profile = profile; - - if ( - this._config && - this._config['SYNTHESE'] && - this._config['SYNTHESE']['SPECIES_SHEET'] && - this._config['SYNTHESE']['SPECIES_SHEET']['PROFILE'] - ) { - this.indicators = this._config['SYNTHESE']['SPECIES_SHEET']['PROFILE']['LIST_INDICATORS'].map( - (indicatorConfig: IndicatorRaw) => - computeIndicatorFromConfig(indicatorConfig, profile?.properties) - ); - } else { - this.indicators = []; - } + this.indicators = INDICATORS.map((indicatorRaw: IndicatorDescription) => + computeIndicatorFromDecsription(indicatorRaw, profile?.properties) + ); } } diff --git a/frontend/src/app/syntheseModule/taxon-sheet/taxon-sheet.component.ts b/frontend/src/app/syntheseModule/taxon-sheet/taxon-sheet.component.ts index d2fa308d7e..a17055c723 100644 --- a/frontend/src/app/syntheseModule/taxon-sheet/taxon-sheet.component.ts +++ b/frontend/src/app/syntheseModule/taxon-sheet/taxon-sheet.component.ts @@ -6,17 +6,55 @@ import { RouterLinkActive, RouterOutlet, } from '@angular/router'; -import { ConfigService } from '@geonature/services/config.service'; import { GN2CommonModule } from '@geonature_common/GN2Common.module'; import { InfosComponent } from './infos/infos.component'; import { LayoutComponent } from './layout/layout.component'; -import { computeIndicatorFromConfig, Indicator, IndicatorRaw } from './indicator/indicator'; +import { + computeIndicatorFromDecsription, + Indicator, + IndicatorDescription, +} from './indicator/indicator'; import { IndicatorComponent } from './indicator/indicator.component'; import { CommonModule } from '@angular/common'; import { SyntheseDataService } from '@geonature_common/form/synthese-form/synthese-data.service'; import { TaxonSheetService } from './taxon-sheet.service'; import { RouteService } from './taxon-sheet.route.service'; +const INDICATORS: Array = [ + { + name: 'observation(s)', + matIcon: 'search', + field: 'observation_count', + type: 'number', + }, + { + name: 'observateur(s)', + matIcon: 'people', + field: 'observer_count', + type: 'number', + }, + { + name: 'commune(s)', + matIcon: 'location_on', + field: 'area_count', + type: 'number', + }, + { + name: "Plage d'altitude(s)", + matIcon: 'terrain', + unit: 'm', + type: 'number', + field: ['altitude_min', 'altitude_max'], + }, + { + name: "Plage d'observation(s)", + matIcon: 'date_range', + type: 'date', + field: ['date_min', 'date_max'], + separator: '-', + }, +]; + @Component({ standalone: true, selector: 'pnx-taxon-sheet', @@ -44,7 +82,6 @@ export class TaxonSheetComponent implements OnInit { private _route: ActivatedRoute, private _tss: TaxonSheetService, private _syntheseDataService: SyntheseDataService, - private _config: ConfigService, private _routes: RouteService ) { this.TAB_LINKS = this._routes.TAB_LINKS; @@ -62,18 +99,9 @@ export class TaxonSheetComponent implements OnInit { } setIndicators(stats: any) { - if ( - this._config && - this._config['SYNTHESE'] && - this._config['SYNTHESE']['SPECIES_SHEET'] && - this._config['SYNTHESE']['SPECIES_SHEET']['LIST_INDICATORS'] - ) { - this.indicators = this._config['SYNTHESE']['SPECIES_SHEET']['LIST_INDICATORS'].map( - (indicatorConfig: IndicatorRaw) => computeIndicatorFromConfig(indicatorConfig, stats) - ); - } else { - this.indicators = []; - } + this.indicators = INDICATORS.map((indicatorConfig: IndicatorDescription) => + computeIndicatorFromDecsription(indicatorConfig, stats) + ); } goToPath(path: string) { diff --git a/frontend/src/app/syntheseModule/taxon-sheet/taxon-sheet.route.service.ts b/frontend/src/app/syntheseModule/taxon-sheet/taxon-sheet.route.service.ts index 5f73b07b46..239b4f5c4b 100644 --- a/frontend/src/app/syntheseModule/taxon-sheet/taxon-sheet.route.service.ts +++ b/frontend/src/app/syntheseModule/taxon-sheet/taxon-sheet.route.service.ts @@ -1,10 +1,8 @@ import { Injectable } from '@angular/core'; import { - CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router, - ActivatedRoute, CanActivateChild, } from '@angular/router'; import { ConfigService } from '@geonature/services/config.service'; @@ -15,29 +13,23 @@ import { TabProfileComponent } from './tab-profile/tab-profile.component'; interface Tab { label: string; path: string; - configEntry: string; + configEnabledField?: string; component: any; } -const ROUTE_GEOGRAPHIC_OVERVIEW: Tab = { - label: 'Synthèse Géographique', - path: 'geographic_overview', - configEntry: 'GEOGRAPHIC_OVERVIEW', - component: TabGeographicOverviewComponent, -}; - -export const ROUTE_MANDATORY = ROUTE_GEOGRAPHIC_OVERVIEW; - -const ROUTE_PROFILE: Tab = { - label: 'Profil', - path: 'profile', - configEntry: 'PROFILE', - component: TabProfileComponent, -}; - export const ALL_TAXON_SHEET_ADVANCED_INFOS_ROUTES: Array = [ - ROUTE_GEOGRAPHIC_OVERVIEW, - ROUTE_PROFILE, + { + label: 'Synthèse Géographique', + path: 'geographic_overview', + component: TabGeographicOverviewComponent, + configEnabledField: null, // make it always available ! + }, + { + label: 'Profil', + path: 'profile', + configEnabledField: 'ENABLE_PROFILE', + component: TabProfileComponent, + }, ]; @Injectable({ @@ -45,17 +37,15 @@ export const ALL_TAXON_SHEET_ADVANCED_INFOS_ROUTES: Array = [ }) export class RouteService implements CanActivateChild { readonly TAB_LINKS = []; - constructor( private _config: ConfigService, private _router: Router ) { - this.TAB_LINKS.push(ROUTE_MANDATORY); - if (this._config && this._config['SYNTHESE'] && this._config['SYNTHESE']['SPECIES_SHEET']) { + if (this._config['SYNTHESE']?.['SPECIES_SHEET']) { const config = this._config['SYNTHESE']['SPECIES_SHEET']; - if (config['PROFILE'] && config['PROFILE']['ENABLED']) { - this.TAB_LINKS.push(ROUTE_PROFILE); - } + this.TAB_LINKS = ALL_TAXON_SHEET_ADVANCED_INFOS_ROUTES.filter( + (tab) => !tab.configEnabledField || config[tab.configEnabledField] + ); } } @@ -64,18 +54,9 @@ export class RouteService implements CanActivateChild { state: RouterStateSnapshot ): Observable | Promise | boolean { const targetedPath = childRoute.routeConfig.path; - if (ROUTE_MANDATORY.path == targetedPath) { + if (this.TAB_LINKS.map((tab) => tab.path).includes(targetedPath)) { return true; } - const targetedTab = ALL_TAXON_SHEET_ADVANCED_INFOS_ROUTES.find( - (tab) => tab.path === targetedPath - ); - if (this._config && this._config['SYNTHESE'] && this._config['SYNTHESE']['SPECIES_SHEET']) { - const config = this._config['SYNTHESE']['SPECIES_SHEET']; - if (config[targetedTab.configEntry] && config[targetedTab.configEntry]['ENABLED']) { - return true; - } - } this._router.navigate(['/404'], { skipLocationChange: true }); return false; From a6fb2e7041d8bbbe8aeb8cdc5bfe13ffc500a112 Mon Sep 17 00:00:00 2001 From: Etienne Delclaux Date: Fri, 4 Oct 2024 12:39:12 +0200 Subject: [PATCH 2/8] feat: change "species_sheet" to "taxon_sheet" --- backend/geonature/core/gn_synthese/routes.py | 6 +++--- backend/geonature/utils/config_schema.py | 8 ++++---- config/default_config.toml.example | 6 +++--- .../form/synthese-form/synthese-data.service.ts | 4 ++-- .../syntheseModule/taxon-sheet/taxon-sheet.component.ts | 2 +- .../taxon-sheet/taxon-sheet.route.service.ts | 4 ++-- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/backend/geonature/core/gn_synthese/routes.py b/backend/geonature/core/gn_synthese/routes.py index 6ae19f95dd..8e9883f6fe 100644 --- a/backend/geonature/core/gn_synthese/routes.py +++ b/backend/geonature/core/gn_synthese/routes.py @@ -957,11 +957,11 @@ def general_stats(permissions): return data -@routes.route("/species_stats/", methods=["GET"]) +@routes.route("/taxon_stats/", methods=["GET"]) @permissions.check_cruved_scope("R", get_scope=True, module_code="SYNTHESE") @json_resp -def species_stats(scope, cd_ref): - """Return stats about distinct species.""" +def taxon_stats(scope, cd_ref): + """Return stats about distinct taxon""" area_type = request.args.get("area_type") diff --git a/backend/geonature/utils/config_schema.py b/backend/geonature/utils/config_schema.py index 8183c72ffe..3c278e4812 100644 --- a/backend/geonature/utils/config_schema.py +++ b/backend/geonature/utils/config_schema.py @@ -272,9 +272,9 @@ class ExportObservationSchema(Schema): geojson_local_field = fields.String(load_default="geojson_local") -class SpeciesSheet(Schema): +class TaxonSheet(Schema): # -------------------------------------------------------------------- - # SYNTHESE - SPECIES_SHEET + # SYNTHESE - TAXON_SHEET ENABLE_PROFILE = fields.Boolean(load_default=True) @@ -433,8 +433,8 @@ class Synthese(Schema): BLUR_SENSITIVE_OBSERVATIONS = fields.Boolean(load_default=True) # -------------------------------------------------------------------- - # SYNTHESE - SPECIES_SHEET - SPECIES_SHEET = fields.Nested(SpeciesSheet, load_default=SpeciesSheet().load({})) + # SYNTHESE - TAXON_SHEET + TAXON_SHEET = fields.Nested(TaxonSheet, load_default=TaxonSheet().load({})) @pre_load def warn_deprecated(self, data, **kwargs): diff --git a/config/default_config.toml.example b/config/default_config.toml.example index 69c175e96c..1a3ca728d3 100644 --- a/config/default_config.toml.example +++ b/config/default_config.toml.example @@ -441,9 +441,9 @@ MEDIA_CLEAN_CRONTAB = "0 1 * * *" # Seulement les données de présence cd_nomenclature_observation_status = ['Pr'] - [SYNTHESE.SPECIES_SHEET] - # Options dédiées à la fiche espèce - #Permet d'activer ou non la section "Profile" de la fiche espèce + [SYNTHESE.TAXON_SHEET] + # Options dédiées à la fiche taxon + # Permet d'activer ou non la section "Profile" ENABLE_PROFILE = True # Gestion des demandes d'inscription diff --git a/frontend/src/app/GN2CommonModule/form/synthese-form/synthese-data.service.ts b/frontend/src/app/GN2CommonModule/form/synthese-form/synthese-data.service.ts index 24f71d119e..78a2f064f5 100644 --- a/frontend/src/app/GN2CommonModule/form/synthese-form/synthese-data.service.ts +++ b/frontend/src/app/GN2CommonModule/form/synthese-form/synthese-data.service.ts @@ -55,8 +55,8 @@ export class SyntheseDataService { return this._api.get(`${this.config.API_ENDPOINT}/synthese/general_stats`); } - getSyntheseSpeciesSheetStat(cd_ref: number, areaType: string = 'COM') { - return this._api.get(`${this.config.API_ENDPOINT}/synthese/species_stats/${cd_ref}`, { + getSyntheseTaxonSheetStat(cd_ref: number, areaType: string = 'COM') { + return this._api.get(`${this.config.API_ENDPOINT}/synthese/taxon_stats/${cd_ref}`, { params: new HttpParams().append('area_type', areaType), }); } diff --git a/frontend/src/app/syntheseModule/taxon-sheet/taxon-sheet.component.ts b/frontend/src/app/syntheseModule/taxon-sheet/taxon-sheet.component.ts index a17055c723..e665ee7aef 100644 --- a/frontend/src/app/syntheseModule/taxon-sheet/taxon-sheet.component.ts +++ b/frontend/src/app/syntheseModule/taxon-sheet/taxon-sheet.component.ts @@ -91,7 +91,7 @@ export class TaxonSheetComponent implements OnInit { const cd_ref = params['cd_ref']; if (cd_ref) { this._tss.updateTaxonByCdRef(cd_ref); - this._syntheseDataService.getSyntheseSpeciesSheetStat(cd_ref).subscribe((stats) => { + this._syntheseDataService.getSyntheseTaxonSheetStat(cd_ref).subscribe((stats) => { this.setIndicators(stats); }); } diff --git a/frontend/src/app/syntheseModule/taxon-sheet/taxon-sheet.route.service.ts b/frontend/src/app/syntheseModule/taxon-sheet/taxon-sheet.route.service.ts index 239b4f5c4b..66b098d819 100644 --- a/frontend/src/app/syntheseModule/taxon-sheet/taxon-sheet.route.service.ts +++ b/frontend/src/app/syntheseModule/taxon-sheet/taxon-sheet.route.service.ts @@ -41,8 +41,8 @@ export class RouteService implements CanActivateChild { private _config: ConfigService, private _router: Router ) { - if (this._config['SYNTHESE']?.['SPECIES_SHEET']) { - const config = this._config['SYNTHESE']['SPECIES_SHEET']; + if (this._config['SYNTHESE']?.['TAXON_SHEET']) { + const config = this._config['SYNTHESE']['TAXON_SHEET']; this.TAB_LINKS = ALL_TAXON_SHEET_ADVANCED_INFOS_ROUTES.filter( (tab) => !tab.configEnabledField || config[tab.configEnabledField] ); From 42c586334354f5c67bd25d9d90b9fdb094b82e2a Mon Sep 17 00:00:00 2001 From: Etienne Delclaux Date: Fri, 4 Oct 2024 17:38:56 +0200 Subject: [PATCH 3/8] test: adjust synthese_data fixtures to include date test: adjust fixture test: adjust fixture --- backend/geonature/tests/fixtures.py | 125 +++++++++++++++++++++++++--- 1 file changed, 112 insertions(+), 13 deletions(-) diff --git a/backend/geonature/tests/fixtures.py b/backend/geonature/tests/fixtures.py index 53524c5a2c..3a7088b4e2 100644 --- a/backend/geonature/tests/fixtures.py +++ b/backend/geonature/tests/fixtures.py @@ -506,10 +506,22 @@ def sources_modules(modules): def create_synthese( - geom, taxon, user, dataset, source, uuid=func.uuid_generate_v4(), cor_observers=[], **kwargs + geom, + taxon, + user, + dataset, + source, + uuid=func.uuid_generate_v4(), + cor_observers=[], + date_min="", + date_max="", + **kwargs, ): now = datetime.datetime.now() + date_min = date_min if date_min else now + date_max = date_max if date_max else now + return Synthese( id_source=source.id_source, id_module=source.id_module, @@ -522,8 +534,8 @@ def create_synthese( the_geom_4326=geom, the_geom_point=geom, the_geom_local=func.ST_Transform(geom, 2154), # FIXME - date_min=now, - date_max=now, + date_min=date_min, + date_max=date_max, cor_observers=cor_observers, **kwargs, ) @@ -534,19 +546,104 @@ def synthese_data(app, users, datasets, source, sources_modules): point1 = Point(5.92, 45.56) point2 = Point(-1.54, 46.85) point3 = Point(-3.486786, 48.832182) + date_1 = datetime.datetime(2024, 10, 2, 11, 22, 33) + date_2 = datetime.datetime(2024, 10, 3, 8, 9, 10) + date_3 = datetime.datetime(2024, 10, 4, 17, 4, 9) + date_4 = datetime.datetime(2024, 10, 5, 22, 22, 22) data = {} with db.session.begin_nested(): - for name, cd_nom, point, ds, comment_description, source_m in [ + for name, cd_nom, point, ds, comment_description, source_m, date_min, date_max in [ # Donnnées de gypaète : possède des statuts de protection nationale - ("obs1", 2852, point1, datasets["own_dataset"], "obs1", sources_modules[0]), - ("obs2", 212, point2, datasets["own_dataset"], "obs2", sources_modules[0]), - ("obs3", 2497, point3, datasets["own_dataset"], "obs3", sources_modules[1]), - ("p1_af1", 713776, point1, datasets["belong_af_1"], "p1_af1", sources_modules[1]), - ("p1_af1_2", 212, point1, datasets["belong_af_1"], "p1_af1_2", sources_modules[1]), - ("p1_af2", 212, point1, datasets["belong_af_2"], "p1_af2", sources_modules[1]), - ("p2_af2", 2497, point2, datasets["belong_af_2"], "p2_af2", source), - ("p2_af1", 2497, point2, datasets["belong_af_1"], "p2_af1", source), - ("p3_af3", 2497, point3, datasets["belong_af_3"], "p3_af3", source), + ( + "obs1", + 2852, + point1, + datasets["own_dataset"], + "obs1", + sources_modules[0], + date_1, + date_1, + ), + ( + "obs2", + 212, + point2, + datasets["own_dataset"], + "obs2", + sources_modules[0], + date_1, + date_4, + ), + ( + "obs3", + 2497, + point3, + datasets["own_dataset"], + "obs3", + sources_modules[1], + date_2, + date_3, + ), + ( + "p1_af1", + 713776, + point1, + datasets["belong_af_1"], + "p1_af1", + sources_modules[1], + date_1, + date_3, + ), + ( + "p1_af1_2", + 212, + point1, + datasets["belong_af_1"], + "p1_af1_2", + sources_modules[1], + date_3, + date_3, + ), + ( + "p1_af2", + 212, + point1, + datasets["belong_af_2"], + "p1_af2", + sources_modules[1], + date_3, + date_4, + ), + ( + "p2_af2", + 2497, + point2, + datasets["belong_af_2"], + "p2_af2", + source, + date_1, + date_2, + ), + ( + "p2_af1", + 2497, + point2, + datasets["belong_af_1"], + "p2_af1", + source, + date_1, + date_1, + ), + ( + "p3_af3", + 2497, + point3, + datasets["belong_af_3"], + "p3_af3", + source, + date_2, + date_2, + ), ]: unique_id_sinp = ( "f4428222-d038-40bc-bc5c-6e977bbbc92b" if not data else func.uuid_generate_v4() @@ -563,6 +660,8 @@ def synthese_data(app, users, datasets, source, sources_modules): source_m, unique_id_sinp, [users["admin_user"], users["user"]], + date_min, + date_max, **kwargs, ) db.session.add(s) From d47c179b415cf8fe6b4e67dc6fe1ac4040cdc482 Mon Sep 17 00:00:00 2001 From: Etienne Delclaux Date: Fri, 4 Oct 2024 17:58:26 +0200 Subject: [PATCH 4/8] test: adjust synthese_data fixtures to include altitudes --- backend/geonature/tests/fixtures.py | 42 ++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/backend/geonature/tests/fixtures.py b/backend/geonature/tests/fixtures.py index 3a7088b4e2..23b0e9a5da 100644 --- a/backend/geonature/tests/fixtures.py +++ b/backend/geonature/tests/fixtures.py @@ -515,6 +515,8 @@ def create_synthese( cor_observers=[], date_min="", date_max="", + altitude_min=800, + altitude_max=1200, **kwargs, ): now = datetime.datetime.now() @@ -536,6 +538,8 @@ def create_synthese( the_geom_local=func.ST_Transform(geom, 2154), # FIXME date_min=date_min, date_max=date_max, + altitude_min=altitude_min, + altitude_max=altitude_max, cor_observers=cor_observers, **kwargs, ) @@ -550,9 +554,25 @@ def synthese_data(app, users, datasets, source, sources_modules): date_2 = datetime.datetime(2024, 10, 3, 8, 9, 10) date_3 = datetime.datetime(2024, 10, 4, 17, 4, 9) date_4 = datetime.datetime(2024, 10, 5, 22, 22, 22) + altitude_1 = 800 + altitude_2 = 900 + altitude_3 = 1000 + altitude_4 = 1100 + data = {} with db.session.begin_nested(): - for name, cd_nom, point, ds, comment_description, source_m, date_min, date_max in [ + for ( + name, + cd_nom, + point, + ds, + comment_description, + source_m, + date_min, + date_max, + altitude_min, + altitude_max, + ) in [ # Donnnées de gypaète : possède des statuts de protection nationale ( "obs1", @@ -563,6 +583,8 @@ def synthese_data(app, users, datasets, source, sources_modules): sources_modules[0], date_1, date_1, + altitude_1, + altitude_1, ), ( "obs2", @@ -573,6 +595,8 @@ def synthese_data(app, users, datasets, source, sources_modules): sources_modules[0], date_1, date_4, + altitude_1, + altitude_4, ), ( "obs3", @@ -583,6 +607,8 @@ def synthese_data(app, users, datasets, source, sources_modules): sources_modules[1], date_2, date_3, + altitude_2, + altitude_3, ), ( "p1_af1", @@ -593,6 +619,8 @@ def synthese_data(app, users, datasets, source, sources_modules): sources_modules[1], date_1, date_3, + altitude_1, + altitude_3, ), ( "p1_af1_2", @@ -603,6 +631,8 @@ def synthese_data(app, users, datasets, source, sources_modules): sources_modules[1], date_3, date_3, + altitude_3, + altitude_3, ), ( "p1_af2", @@ -613,6 +643,8 @@ def synthese_data(app, users, datasets, source, sources_modules): sources_modules[1], date_3, date_4, + altitude_3, + altitude_4, ), ( "p2_af2", @@ -623,6 +655,8 @@ def synthese_data(app, users, datasets, source, sources_modules): source, date_1, date_2, + altitude_1, + altitude_2, ), ( "p2_af1", @@ -633,6 +667,8 @@ def synthese_data(app, users, datasets, source, sources_modules): source, date_1, date_1, + altitude_1, + altitude_1, ), ( "p3_af3", @@ -643,6 +679,8 @@ def synthese_data(app, users, datasets, source, sources_modules): source, date_2, date_2, + altitude_2, + altitude_2, ), ]: unique_id_sinp = ( @@ -662,6 +700,8 @@ def synthese_data(app, users, datasets, source, sources_modules): [users["admin_user"], users["user"]], date_min, date_max, + altitude_min, + altitude_max, **kwargs, ) db.session.add(s) From a231ed5adc42f42621991f89a2332328bd7f1381 Mon Sep 17 00:00:00 2001 From: Etienne Delclaux Date: Fri, 4 Oct 2024 18:05:12 +0200 Subject: [PATCH 5/8] test: add taxon_stats route test --- backend/geonature/tests/test_synthese.py | 57 ++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/backend/geonature/tests/test_synthese.py b/backend/geonature/tests/test_synthese.py index f39df755a4..84f604d07b 100644 --- a/backend/geonature/tests/test_synthese.py +++ b/backend/geonature/tests/test_synthese.py @@ -1114,6 +1114,63 @@ def test_general_stat(self, users): assert response.status_code == 200 + def test_taxon_stats(self, synthese_data, users): + set_logged_user(self.client, users["stranger_user"]) + + AREA_TYPE_VALID = "COM" + AREA_TYPE_INVALID = "UNDEFINED" + CD_REF_INVALID = 987654321 + CD_REF_INVALID_STATS = { + "altitude_max": None, + "altitude_min": None, + "area_count": 0, + "cd_ref": CD_REF_INVALID, + "date_max": None, + "date_min": None, + "observation_count": 0, + "observer_count": 0, + } + CD_REF_VALID = 2497 + CD_REF_VALID_STATS = { + "altitude_max": 900, + "altitude_min": 800, + "area_count": 2, + "cd_ref": CD_REF_VALID, + "date_max": "Thu, 03 Oct 2024 08:09:10 GMT", + "date_min": "Wed, 02 Oct 2024 11:22:33 GMT", + "observation_count": 3, + "observer_count": 1, + } + + # Missing area_type parameter + response = self.client.get( + url_for("gn_synthese.taxon_stats", cd_ref=CD_REF_VALID), + ) + assert response.status_code == 400 + assert response.json["description"] == "Missing area_type parameter" + + # Invalid area_type parameter + response = self.client.get( + url_for("gn_synthese.taxon_stats", cd_ref=CD_REF_VALID, area_type=AREA_TYPE_INVALID), + ) + assert response.status_code == 400 + assert response.json["description"] == "Invalid area_type" + + # Invalid cd_ref parameter + response = self.client.get( + url_for("gn_synthese.taxon_stats", cd_ref=CD_REF_INVALID, area_type=AREA_TYPE_VALID), + ) + assert response.status_code == 200 + assert response.get_json() == CD_REF_INVALID_STATS + + # Invalid cd_ref parameter + response = self.client.get( + url_for("gn_synthese.taxon_stats", cd_ref=CD_REF_VALID, area_type=AREA_TYPE_VALID), + ) + response_json = response.get_json() + assert response.status_code == 200 + assert response.get_json() == CD_REF_VALID_STATS + def test_get_one_synthese_record(self, app, users, synthese_data): response = self.client.get( url_for("gn_synthese.get_one_synthese", id_synthese=synthese_data["obs1"].id_synthese) From 6b28d6cd6d8fe5ef1d2ca524cff10b076bcbdc2d Mon Sep 17 00:00:00 2001 From: Etienne Delclaux <150020787+edelclaux@users.noreply.github.com> Date: Mon, 7 Oct 2024 16:29:11 +0200 Subject: [PATCH 6/8] fix: adjust comment Co-authored-by: Jacques Fize <4259846+jacquesfize@users.noreply.github.com> --- backend/geonature/core/gn_synthese/routes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/geonature/core/gn_synthese/routes.py b/backend/geonature/core/gn_synthese/routes.py index 8e9883f6fe..9f543633b1 100644 --- a/backend/geonature/core/gn_synthese/routes.py +++ b/backend/geonature/core/gn_synthese/routes.py @@ -961,7 +961,7 @@ def general_stats(permissions): @permissions.check_cruved_scope("R", get_scope=True, module_code="SYNTHESE") @json_resp def taxon_stats(scope, cd_ref): - """Return stats about distinct taxon""" + """Return stats for a specific taxon""" area_type = request.args.get("area_type") From a3c082d2bd8c026a48a649f720372dcb9531ccdf Mon Sep 17 00:00:00 2001 From: Etienne Delclaux Date: Mon, 7 Oct 2024 16:29:49 +0200 Subject: [PATCH 7/8] fix: computeIndicatorFromDecsription --> computeIndicatorFromDescription --- .../src/app/syntheseModule/taxon-sheet/indicator/indicator.ts | 2 +- .../taxon-sheet/tab-profile/tab-profile.component.ts | 4 ++-- .../app/syntheseModule/taxon-sheet/taxon-sheet.component.ts | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/src/app/syntheseModule/taxon-sheet/indicator/indicator.ts b/frontend/src/app/syntheseModule/taxon-sheet/indicator/indicator.ts index 441fb02c80..40b1679f2a 100644 --- a/frontend/src/app/syntheseModule/taxon-sheet/indicator/indicator.ts +++ b/frontend/src/app/syntheseModule/taxon-sheet/indicator/indicator.ts @@ -38,7 +38,7 @@ function getValue(field: string, indicatorConfig: IndicatorDescription, stats?: return DEFAULT_VALUE; } -export function computeIndicatorFromDecsription( +export function computeIndicatorFromDescription( indicatorDescription: IndicatorDescription, stats?: Stats ): Indicator { diff --git a/frontend/src/app/syntheseModule/taxon-sheet/tab-profile/tab-profile.component.ts b/frontend/src/app/syntheseModule/taxon-sheet/tab-profile/tab-profile.component.ts index 0e0f51d908..b8e0eeb828 100644 --- a/frontend/src/app/syntheseModule/taxon-sheet/tab-profile/tab-profile.component.ts +++ b/frontend/src/app/syntheseModule/taxon-sheet/tab-profile/tab-profile.component.ts @@ -5,7 +5,7 @@ import { DataFormService, Profile } from '@geonature_common/form/data-form.servi import { Taxon } from '@geonature_common/form/taxonomy/taxonomy.component'; import { CommonService } from '@geonature_common/service/common.service'; import { - computeIndicatorFromDecsription, + computeIndicatorFromDescription, Indicator, IndicatorDescription, } from '../indicator/indicator'; @@ -85,7 +85,7 @@ export class TabProfileComponent implements OnInit { set profile(profile: Profile | null) { this._profile = profile; this.indicators = INDICATORS.map((indicatorRaw: IndicatorDescription) => - computeIndicatorFromDecsription(indicatorRaw, profile?.properties) + computeIndicatorFromDescription(indicatorRaw, profile?.properties) ); } } diff --git a/frontend/src/app/syntheseModule/taxon-sheet/taxon-sheet.component.ts b/frontend/src/app/syntheseModule/taxon-sheet/taxon-sheet.component.ts index e665ee7aef..6710ecd0b4 100644 --- a/frontend/src/app/syntheseModule/taxon-sheet/taxon-sheet.component.ts +++ b/frontend/src/app/syntheseModule/taxon-sheet/taxon-sheet.component.ts @@ -10,7 +10,7 @@ import { GN2CommonModule } from '@geonature_common/GN2Common.module'; import { InfosComponent } from './infos/infos.component'; import { LayoutComponent } from './layout/layout.component'; import { - computeIndicatorFromDecsription, + computeIndicatorFromDescription, Indicator, IndicatorDescription, } from './indicator/indicator'; @@ -100,7 +100,7 @@ export class TaxonSheetComponent implements OnInit { setIndicators(stats: any) { this.indicators = INDICATORS.map((indicatorConfig: IndicatorDescription) => - computeIndicatorFromDecsription(indicatorConfig, stats) + computeIndicatorFromDescription(indicatorConfig, stats) ); } From 4ccaccf5ee8394e2ee82633376d7bba55b48afa2 Mon Sep 17 00:00:00 2001 From: Etienne Delclaux <150020787+edelclaux@users.noreply.github.com> Date: Mon, 7 Oct 2024 16:33:04 +0200 Subject: [PATCH 8/8] fix: boolean miscase in config.toml.example Co-authored-by: Jacques Fize <4259846+jacquesfize@users.noreply.github.com> --- config/default_config.toml.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/default_config.toml.example b/config/default_config.toml.example index 1a3ca728d3..a291ec0c9e 100644 --- a/config/default_config.toml.example +++ b/config/default_config.toml.example @@ -444,7 +444,7 @@ MEDIA_CLEAN_CRONTAB = "0 1 * * *" [SYNTHESE.TAXON_SHEET] # Options dédiées à la fiche taxon # Permet d'activer ou non la section "Profile" - ENABLE_PROFILE = True + ENABLE_PROFILE = true # Gestion des demandes d'inscription [ACCOUNT_MANAGEMENT]