diff --git a/backend/geonature/core/gn_synthese/routes.py b/backend/geonature/core/gn_synthese/routes.py index b8de33c4bd..e744ea9f60 100644 --- a/backend/geonature/core/gn_synthese/routes.py +++ b/backend/geonature/core/gn_synthese/routes.py @@ -965,65 +965,70 @@ def general_stats(permissions): return data -@routes.route("/taxon_stats/", methods=["GET"]) -@permissions.check_cruved_scope("R", get_scope=True, module_code="SYNTHESE") -@json_resp -def taxon_stats(scope, cd_ref): - """Return stats for a specific taxon""" +## ############################################################################ +## TAXON SHEET ROUTES +## ############################################################################ - area_type = request.args.get("area_type") +if app.config["SYNTHESE"]["ENABLE_TAXON_SHEETS"]: - if not area_type: - raise BadRequest("Missing area_type parameter") + @routes.route("/taxon_stats/", methods=["GET"]) + @permissions.check_cruved_scope("R", get_scope=True, module_code="SYNTHESE") + @json_resp + def taxon_stats(scope, cd_ref): + """Return stats for a specific taxon""" - if not TaxonSheetUtils.is_valid_area_type(area_type): - raise BadRequest("Invalid area_type parameter") + area_type = request.args.get("area_type") + if not area_type: + raise BadRequest("Missing area_type parameter") - areas_subquery = TaxonSheetUtils.get_area_subquery(area_type) - taxref_cd_nom_list = TaxonSheetUtils.get_cd_nom_list_from_cd_ref(cd_ref) + if not TaxonSheetUtils.is_valid_area_type(area_type): + raise BadRequest("Invalid area_type parameter") - # Main query to fetch stats - query = ( - select( - [ - func.count(distinct(Synthese.id_synthese)).label("observation_count"), - func.count(distinct(Synthese.observers)).label("observer_count"), - func.count(distinct(areas_subquery.c.id_area)).label("area_count"), - func.min(Synthese.altitude_min).label("altitude_min"), - func.max(Synthese.altitude_max).label("altitude_max"), - func.min(Synthese.date_min).label("date_min"), - func.max(Synthese.date_max).label("date_max"), - ] - ) - .select_from( - sa.join( - Synthese, - CorAreaSynthese, - Synthese.id_synthese == CorAreaSynthese.id_synthese, + areas_subquery = TaxonSheetUtils.get_area_subquery(area_type) + taxref_cd_nom_list = TaxonSheetUtils.get_cd_nom_list_from_cd_ref(cd_ref) + + # Main query to fetch stats + query = ( + select( + [ + func.count(distinct(Synthese.id_synthese)).label("observation_count"), + func.count(distinct(Synthese.observers)).label("observer_count"), + func.count(distinct(areas_subquery.c.id_area)).label("area_count"), + func.min(Synthese.altitude_min).label("altitude_min"), + func.max(Synthese.altitude_max).label("altitude_max"), + func.min(Synthese.date_min).label("date_min"), + func.max(Synthese.date_max).label("date_max"), + ] + ) + .select_from( + sa.join( + Synthese, + CorAreaSynthese, + Synthese.id_synthese == CorAreaSynthese.id_synthese, + ) + .join(areas_subquery, CorAreaSynthese.id_area == areas_subquery.c.id_area) + .join(LAreas, CorAreaSynthese.id_area == LAreas.id_area) + .join(BibAreasTypes, LAreas.id_type == BibAreasTypes.id_type) ) - .join(areas_subquery, CorAreaSynthese.id_area == areas_subquery.c.id_area) - .join(LAreas, CorAreaSynthese.id_area == LAreas.id_area) - .join(BibAreasTypes, LAreas.id_type == BibAreasTypes.id_type) + .where(Synthese.cd_nom.in_(taxref_cd_nom_list)) ) - .where(Synthese.cd_nom.in_(taxref_cd_nom_list)) - ) - - synthese_query = TaxonSheetUtils.get_synthese_query_with_scope(g.current_user, scope, query) - result = DB.session.execute(synthese_query) - synthese_stats = result.fetchone() - data = { - "cd_ref": cd_ref, - "observation_count": synthese_stats["observation_count"], - "observer_count": synthese_stats["observer_count"], - "area_count": synthese_stats["area_count"], - "altitude_min": synthese_stats["altitude_min"], - "altitude_max": synthese_stats["altitude_max"], - "date_min": synthese_stats["date_min"], - "date_max": synthese_stats["date_max"], - } + synthese_query = TaxonSheetUtils.get_synthese_query_with_scope(g.current_user, scope, query) + result = DB.session.execute(synthese_query) + synthese_stats = result.fetchone() + + data = { + "cd_ref": cd_ref, + "observation_count": synthese_stats["observation_count"], + "observer_count": synthese_stats["observer_count"], + "area_count": synthese_stats["area_count"], + "altitude_min": synthese_stats["altitude_min"], + "altitude_max": synthese_stats["altitude_max"], + "date_min": synthese_stats["date_min"], + "date_max": synthese_stats["date_max"], + } - return data + return data if app.config["SYNTHESE"]["TAXON_SHEET"]["ENABLE_TAB_OBSERVERS"]: diff --git a/backend/geonature/tests/test_utils.py b/backend/geonature/tests/test_utils.py index f3055e97da..0e4559d292 100644 --- a/backend/geonature/tests/test_utils.py +++ b/backend/geonature/tests/test_utils.py @@ -8,10 +8,14 @@ from marshmallow.exceptions import ValidationError +############################################################################# +# BASIC TEMPLATE CONFIG FILE +############################################################################# + TEMPLATE_CONFIG_FILE = """ SQLALCHEMY_DATABASE_URI = "postgresql://monuser:monpassachanger@localhost:5432/mabase" URL_APPLICATION = 'http://url.com/geonature' -API_ENDPOINT = 'http://url.com/geonature/api' +API_ENDPOINT = 'http://url.com/geonature/api' SECRET_KEY = 'super secret key' @@ -37,6 +41,44 @@ [MEDIAS] """ +############################################################################# +# TAXON SHEET CONFIG FILE +############################################################################# + +TEMPLATE_TAXON_SHEET_CONFIG_FILE = """ + SQLALCHEMY_DATABASE_URI = "postgresql://monuser:monpassachanger@localhost:5432/mabase" + URL_APPLICATION = 'http://url.com/geonature' + API_ENDPOINT = 'http://url.com/geonature/api' + + SECRET_KEY = 'super secret key' + + DEFAULT_LANGUAGE=fr + [HOME] + TITLE = "Bienvenue dans GeoNature" + INTRODUCTION = "Texte d'introduction, configurable pour le modifier régulièrement ou le masquer" + FOOTER = "" + + # Configuration liée aux ID de BDD + [BDD] + + # Configuration générale du frontend + [FRONTEND] + ENABLE_PROFILES={ENABLE_PROFILES} + + # Configuration de la Synthese + [SYNTHESE] + ENABLE_TAXON_SHEETS={ENABLE_TAXON_SHEETS} + [SYNTHESE.TAXON_SHEET] + ENABLE_TAB_TAXONOMY={ENABLE_TAB_TAXONOMY} + ENABLE_TAB_PROFILE={ENABLE_TAB_PROFILE} + + # Configuration cartographique + [MAPCONFIG] + + # Configuration médias + [MEDIAS] + """ + @pytest.mark.usefixtures("temporary_transaction") class TestUtils: @@ -59,3 +101,27 @@ def test_utilstoml(self): with pytest.raises(ConfigError): load_and_validate_toml(f.name, GnPySchemaConf) + + @pytest.mark.parametrize( + "enable_profiles,enable_tab_profile,expected_enable_tab_profile", + [(True, True, True), (True, False, False), (False, False, False), (False, True, False)], + ) + def test_config_profiles_consistency( + self, enable_profiles, enable_tab_profile, expected_enable_tab_profile + ): + + profiles_config = TEMPLATE_TAXON_SHEET_CONFIG_FILE.format( + ENABLE_TAXON_SHEETS=True, + ENABLE_TAB_TAXONOMY=True, + ENABLE_PROFILES=enable_profiles, + ENABLE_TAB_PROFILE=enable_tab_profile, + ) + + with tempfile.NamedTemporaryFile(mode="w") as f: + f.write(profiles_config) + with pytest.raises(ConfigError): + config = load_and_validate_toml(f.name, GnPySchemaConf) + assert ( + config["SYNTHESE"]["TAXON_SHEET"]["ENABLE_TAB_PROFILE"] + == expected_enable_tab_profile + ) diff --git a/backend/geonature/utils/config_schema.py b/backend/geonature/utils/config_schema.py index f731091d66..b90d072931 100644 --- a/backend/geonature/utils/config_schema.py +++ b/backend/geonature/utils/config_schema.py @@ -279,9 +279,9 @@ class ExportObservationSchema(Schema): class TaxonSheet(Schema): # -------------------------------------------------------------------- # SYNTHESE - TAXON_SHEET - ENABLE_PROFILE = fields.Boolean(load_default=True) - ENABLE_TAXONOMY = fields.Boolean(load_default=True) ENABLE_TAB_OBSERVERS = fields.Boolean(load_default=True) + ENABLE_TAB_PROFILE = fields.Boolean(load_default=True) + ENABLE_TAB_TAXONOMY = fields.Boolean(load_default=True) class Synthese(Schema): @@ -440,6 +440,7 @@ class Synthese(Schema): # -------------------------------------------------------------------- # SYNTHESE - TAXON_SHEET + ENABLE_TAXON_SHEETS = fields.Boolean(load_default=True) TAXON_SHEET = fields.Nested(TaxonSheet, load_default=TaxonSheet().load({})) FIELD_OBSERVERS_SEPARATOR = fields.String(load_default=",") @@ -615,3 +616,13 @@ def insert_module_config(self, data, **kwargs): continue data[module_code] = get_module_config(dist) return data + + @post_load + def profile_display_coherence(self, data, **kwargs): + if ( + data["SYNTHESE"]["TAXON_SHEET"]["ENABLE_TAB_PROFILE"] + and not data["FRONTEND"]["ENABLE_PROFILES"] + ): + data["SYNTHESE"]["TAXON_SHEET"]["ENABLE_TAB_PROFILE"] = False + + return data diff --git a/config/default_config.toml.example b/config/default_config.toml.example index 897f2a4030..f24d4bdfdf 100644 --- a/config/default_config.toml.example +++ b/config/default_config.toml.example @@ -441,12 +441,14 @@ MEDIA_CLEAN_CRONTAB = "0 1 * * *" # Seulement les données de présence cd_nomenclature_observation_status = ['Pr'] + # Activer l'affichage des informations liées à la fiche taxon dans la synthèse + ENABLE_TAXON_SHEETS = true [SYNTHESE.TAXON_SHEET] # Options dédiées à la fiche taxon # Permet d'activer ou non l'onglet "Profil" - ENABLE_PROFILE = true + ENABLE_TAB_PROFILE = true # Permet d'activer ou non l'onglet "Taxonomie" - ENABLE_TAXONOMY = true + ENABLE_TAB_TAXONOMY = true # Permet d'activer ou non la section "Observers" ENABLE_TAB_OBSERVERS = true diff --git a/frontend/src/app/shared/syntheseSharedModule/synthese-info-obs/synthese-info-obs.component.html b/frontend/src/app/shared/syntheseSharedModule/synthese-info-obs/synthese-info-obs.component.html index 5e0dfbcef3..876dfde2c8 100644 --- a/frontend/src/app/shared/syntheseSharedModule/synthese-info-obs/synthese-info-obs.component.html +++ b/frontend/src/app/shared/syntheseSharedModule/synthese-info-obs/synthese-info-obs.component.html @@ -191,7 +191,7 @@

diff --git a/frontend/src/app/syntheseModule/synthese.module.ts b/frontend/src/app/syntheseModule/synthese.module.ts index 43c2f6cded..46baba7032 100644 --- a/frontend/src/app/syntheseModule/synthese.module.ts +++ b/frontend/src/app/syntheseModule/synthese.module.ts @@ -41,6 +41,7 @@ const routes: Routes = [ { path: 'taxon/:cd_ref', component: TaxonSheetComponent, + canActivate: [RouteService], canActivateChild: [RouteService], children: [ { 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 a1b6ea14c2..db9e84a7ac 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 @@ -4,6 +4,7 @@ import { RouterStateSnapshot, Router, CanActivateChild, + CanActivate } from '@angular/router'; import { ConfigService } from '@geonature/services/config.service'; import { Observable } from 'rxjs'; @@ -29,13 +30,13 @@ export const ALL_TAXON_SHEET_ADVANCED_INFOS_ROUTES: Array = [ { label: 'Taxonomie', path: 'taxonomy', - configEnabledField: 'ENABLE_TAXONOMY', + configEnabledField: 'ENABLE_TAB_TAXONOMY', component: TabTaxonomyComponent, }, { label: 'Profil', path: 'profile', - configEnabledField: 'ENABLE_PROFILE', + configEnabledField: 'ENABLE_TAB_PROFILE', component: TabProfileComponent, }, { @@ -49,7 +50,7 @@ export const ALL_TAXON_SHEET_ADVANCED_INFOS_ROUTES: Array = [ @Injectable({ providedIn: 'root', }) -export class RouteService implements CanActivateChild { +export class RouteService implements CanActivate, CanActivateChild { readonly TAB_LINKS = []; constructor( private _config: ConfigService, @@ -62,11 +63,19 @@ export class RouteService implements CanActivateChild { ); } } + canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { + if(!this._config.SYNTHESE.ENABLE_TAXON_SHEETS){ + this._router.navigate(['/404'], { skipLocationChange: true }); + return false; + } + + return true; + } canActivateChild( childRoute: ActivatedRouteSnapshot, state: RouterStateSnapshot - ): Observable | Promise | boolean { + ): boolean { const targetedPath = childRoute.routeConfig.path; if (this.TAB_LINKS.map((tab) => tab.path).includes(targetedPath)) { return true;