Skip to content

Commit

Permalink
[CONFIG] Taxon sheet can be disabled + ENABLE_PROFILE value now impac…
Browse files Browse the repository at this point in the history
…t ENABLE_TAB_PROFILE (#3312)

* feat: add 'ENABLE_TAXON_SHEETS' option in 'SYNTHESE'
* feat: ENABLE_XXX to ENABLE_TAB_XXX for taxon sheet tab
* feat: add a relation between 'FRONTEND.ENABLE_PROFILES' and 'SYNTHESE.TAXON_SHEET.ENABLE_TAB_PROFILE'

Co-authored-by: Jacques Fize <[email protected]>

* feat: add test for config processing

---------

Co-authored-by: jacquesfize <[email protected]>
Co-authored-by: Jacques Fize <[email protected]>
  • Loading branch information
3 people authored Jan 15, 2025
1 parent 19010fa commit ef7c9c3
Show file tree
Hide file tree
Showing 8 changed files with 182 additions and 82 deletions.
138 changes: 73 additions & 65 deletions backend/geonature/core/gn_synthese/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
VMTaxrefListForautocomplete,
)

from geonature import app

routes = Blueprint("gn_synthese", __name__)

Expand Down Expand Up @@ -966,80 +967,87 @@ def general_stats(permissions):
return data


@routes.route("/taxon_stats/<int:cd_nom>", methods=["GET"])
@permissions.check_cruved_scope("R", get_scope=True, module_code="SYNTHESE")
@json_resp
def taxon_stats(scope, cd_nom):
"""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/<int:cd_nom>", methods=["GET"])
@permissions.check_cruved_scope("R", get_scope=True, module_code="SYNTHESE")
@json_resp
def taxon_stats(scope, cd_nom):
"""Return stats for a specific taxon"""

# Ensure area_type is valid
valid_area_types = (
db.session.query(BibAreasTypes.type_code)
.distinct()
.filter(BibAreasTypes.type_code == area_type)
.scalar()
)
if not valid_area_types:
raise BadRequest("Invalid area_type")

# Subquery to fetch areas based on area_type
areas_subquery = (
select([LAreas.id_area])
.where(LAreas.id_type == BibAreasTypes.id_type)
.where(BibAreasTypes.type_code == area_type)
.alias("areas")
)
cd_ref = db.session.scalar(select(Taxref.cd_ref).where(Taxref.cd_nom == cd_nom))
taxref_cd_nom_list = db.session.scalars(select(Taxref.cd_nom).where(Taxref.cd_ref == cd_ref))
area_type = request.args.get("area_type")

# 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"),
]
if not area_type:
raise BadRequest("Missing area_type parameter")

# Ensure area_type is valid
valid_area_types = (
db.session.query(BibAreasTypes.type_code)
.distinct()
.filter(BibAreasTypes.type_code == area_type)
.scalar()
)
.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)
if not valid_area_types:
raise BadRequest("Invalid area_type")

# Subquery to fetch areas based on area_type
areas_subquery = (
select(LAreas.id_area)
.where(LAreas.id_type == BibAreasTypes.id_type, BibAreasTypes.type_code == area_type)
.alias("areas")
)
cd_ref = db.session.scalar(select(Taxref.cd_ref).where(Taxref.cd_nom == cd_nom))
taxref_cd_nom_list = db.session.scalars(
select(Taxref.cd_nom).where(Taxref.cd_ref == cd_ref)
)
.where(Synthese.cd_nom.in_(taxref_cd_nom_list))
)

synthese_query_obj = SyntheseQuery(Synthese, query, {})
synthese_query_obj.filter_query_with_cruved(g.current_user, scope)
result = DB.session.execute(synthese_query_obj.query)
synthese_stats = result.fetchone()
# 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)
)
.where(Synthese.cd_nom.in_(taxref_cd_nom_list))
)

data = {
"cd_ref": cd_nom,
"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_obj = SyntheseQuery(Synthese, query, {})
synthese_query_obj.filter_query_with_cruved(g.current_user, scope)
result = DB.session.execute(synthese_query_obj.query)
synthese_stats = result.fetchone()

data = {
"cd_ref": cd_nom,
"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


@routes.route("/taxons_tree", methods=["GET"])
Expand Down
68 changes: 67 additions & 1 deletion backend/geonature/tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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:
Expand All @@ -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
)
15 changes: 13 additions & 2 deletions backend/geonature/utils/config_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -279,8 +279,8 @@ 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_PROFILE = fields.Boolean(load_default=True)
ENABLE_TAB_TAXONOMY = fields.Boolean(load_default=True)


class Synthese(Schema):
Expand Down Expand Up @@ -439,6 +439,7 @@ class Synthese(Schema):

# --------------------------------------------------------------------
# SYNTHESE - TAXON_SHEET
ENABLE_TAXON_SHEETS = fields.Boolean(load_default=True)
TAXON_SHEET = fields.Nested(TaxonSheet, load_default=TaxonSheet().load({}))

@pre_load
Expand Down Expand Up @@ -612,3 +613,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
18 changes: 10 additions & 8 deletions config/default_config.toml.example
Original file line number Diff line number Diff line change
Expand Up @@ -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

# Gestion des demandes d'inscription
[ACCOUNT_MANAGEMENT]
Expand Down Expand Up @@ -623,8 +625,8 @@ MEDIA_CLEAN_CRONTAB = "0 1 * * *"
# Encodage des fichiers importés autorisées
ENCODAGE = ["UTF-8"]

# Bounding box des données de l'instance.
# Utilisé pour lever des warning lorsque les données sont en dehors.
# Bounding box des données de l'instance.
# Utilisé pour lever des warning lorsque les données sont en dehors.
# Format: [XMIN, YMIN, XMAX, YMAX]
# Par défaut: France métropolitaine incluant la Corse
INSTANCE_BOUNDING_BOX = [-5.0, 41.0, 10.0, 51.15]
Expand All @@ -643,7 +645,7 @@ MEDIA_CLEAN_CRONTAB = "0 1 * * *"

# SRID autorisés pour les fichiers en entrée
SRID = [
{name = "WGS84", code = 4326},
{name = "WGS84", code = 4326},
{name = "Lambert93", code = 2154}
]
# Extensions autorisées (seul le csv est accepté actuellement)
Expand All @@ -655,7 +657,7 @@ MEDIA_CLEAN_CRONTAB = "0 1 * * *"
# Si le mapping des valeurs est désactivé, specifier l'identifiant du mapping qui doit être utilisé
DEFAULT_VALUE_MAPPING_ID = 3

# rempli les valeurs de nomenclature erroné par la valeur par defaut
# rempli les valeurs de nomenclature erroné par la valeur par defaut
# Leve un warning et non une erreur sur les lignes concernées
FILL_MISSING_NOMENCLATURE_WITH_DEFAULT_VALUE = false

Expand All @@ -676,7 +678,7 @@ MEDIA_CLEAN_CRONTAB = "0 1 * * *"

# Customiser le nom du fichier de rapport de l'import
# Pour indiquer des données liés à l'import dans le nom du fichier ajouter le nom de la variable
# contenant cette dernière. Les variables suivantes sont accessibles :
# contenant cette dernière. Les variables suivantes sont accessibles :
# - date_create_import -> date de création de l'import
# - dataset.dataset_name -> nom du jeu de données de destination
# - dataset.active -> Si le jeu de données de destination est actif
Expand All @@ -703,7 +705,7 @@ MEDIA_CLEAN_CRONTAB = "0 1 * * *"


# Id d'une liste de taxons permettant de restreindre l'import d'observations de taxons comprises dans cette dernière
# Lève une exception si un taxon n'appartenant pas à liste indiquée apparaît dans les donnés importées.
# Lève une exception si un taxon n'appartenant pas à liste indiquée apparaît dans les donnés importées.
ID_LIST_TAXA_RESTRICTION = fields.Integer(load_default=None)

# URL d'accès au module d'import
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ <h4 class="mr-auto gn-color">
<a
color="primary"
class="btn btn-xs align-self-start mr-2 link-infos"
*ngIf="selectedObsTaxonDetail && config.FRONTEND['ENABLE_PROFILES']"
*ngIf="selectedObsTaxonDetail && config.SYNTHESE.ENABLE_TAXON_SHEETS"
[routerLink]="['/synthese/taxon', selectedObsTaxonDetail?.cd_ref]"
target="_blank"
mat-stroked-button
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,10 @@
<a
class="Link"
[routerLink]="['taxon/' + row.cd_nom]"
*ngIf="row.hasOwnProperty('cd_nom'); else cellDefault"
*ngIf="
config.SYNTHESE.ENABLE_TAXON_SHEETS && row.hasOwnProperty('cd_nom');
else cellDefault
"
matTooltip="Afficher la fiche du taxon"
>
<ng-container *ngTemplateOutlet="cellDefault"></ng-container>
Expand Down
1 change: 1 addition & 0 deletions frontend/src/app/syntheseModule/synthese.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ const routes: Routes = [
{
path: 'taxon/:cd_ref',
component: TaxonSheetComponent,
canActivate: [RouteService],
canActivateChild: [RouteService],
children: [
{
Expand Down
Loading

0 comments on commit ef7c9c3

Please sign in to comment.