Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Fiche taxon] Ajout d'un onglet "Observateurs" dans la fiche taxon #3204

Open
wants to merge 15 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 64 additions & 29 deletions backend/geonature/core/gn_synthese/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
from werkzeug.exceptions import Forbidden, NotFound, BadRequest, Conflict
from werkzeug.datastructures import MultiDict
from sqlalchemy import distinct, func, desc, asc, select, case, or_
from sqlalchemy.orm import joinedload, lazyload, selectinload, contains_eager, raiseload
from sqlalchemy.orm import joinedload, lazyload, selectinload, contains_eager, raiseload, Query
from geojson import FeatureCollection, Feature
import sqlalchemy as sa
from sqlalchemy.orm import load_only, aliased, Load, with_expression
Expand All @@ -44,6 +44,7 @@
from geonature.core.gn_synthese.models import (
BibReportsTypes,
CorAreaSynthese,
CorObserverSynthese,
DefaultsNomenclaturesValue,
Synthese,
TSources,
Expand All @@ -52,7 +53,12 @@
TReport,
SyntheseLogEntry,
)
from geonature.core.gn_commons.models import TMedias

from pypnusershub.db import User

from geonature.core.gn_synthese.synthese_config import MANDATORY_COLUMNS
from geonature.core.gn_synthese.utils.taxon_sheet import TaxonSheetUtils, SortOrder

from geonature.core.gn_synthese.utils.blurring import (
build_allowed_geom_cte,
Expand All @@ -67,7 +73,6 @@
from geonature.core.gn_permissions.decorators import login_required, permissions_required
from geonature.core.gn_permissions.tools import get_scopes_by_action, get_permissions
from geonature.core.sensitivity.models import cor_sensitivity_area_type

from ref_geo.models import LAreas, BibAreasTypes

from apptax.taxonomie.models import (
Expand Down Expand Up @@ -972,37 +977,21 @@ def general_stats(permissions):

if app.config["SYNTHESE"]["ENABLE_TAXON_SHEETS"]:

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

area_type = request.args.get("area_type")

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()
)
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)
)
if not TaxonSheetUtils.is_valid_area_type(area_type):
raise BadRequest("Invalid 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)

# Main query to fetch stats
query = (
Expand Down Expand Up @@ -1030,13 +1019,12 @@ def taxon_stats(scope, cd_nom):
.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_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_nom,
"cd_ref": cd_ref,
"observation_count": synthese_stats["observation_count"],
"observer_count": synthese_stats["observer_count"],
"area_count": synthese_stats["area_count"],
Expand All @@ -1049,6 +1037,53 @@ def taxon_stats(scope, cd_nom):
return data


if app.config["SYNTHESE"]["TAXON_SHEET"]["ENABLE_TAB_OBSERVERS"]:

@routes.route("/taxon_observers/<int:cd_ref>", methods=["GET"])
@permissions.check_cruved_scope("R", get_scope=True, module_code="SYNTHESE")
def taxon_observers(scope, cd_ref):
per_page = request.args.get("per_page", 10, int)
page = request.args.get("page", 1, int)
sort_by = request.args.get("sort_by", "observer")
sort_order = request.args.get("sort_order", SortOrder.ASC, SortOrder)
field_separator = request.args.get(
"field_separator", app.config["SYNTHESE"]["FIELD_OBSERVERS_SEPARATOR"]
)

# Handle sorting
if sort_by not in ["observer", "date_min", "date_max", "observation_count", "media_count"]:
raise BadRequest(f"The sort_by column {sort_by} is not defined")

taxref_cd_nom_list = TaxonSheetUtils.get_cd_nom_list_from_cd_ref(cd_ref)

query = (
db.session.query(
func.trim(
func.unnest(func.string_to_array(Synthese.observers, field_separator))
).label("observer"),
func.min(Synthese.date_min).label("date_min"),
func.max(Synthese.date_max).label("date_max"),
func.count(Synthese.id_synthese).label("observation_count"),
func.count(TMedias.id_media).label("media_count"),
)
.group_by("observer")
.outerjoin(Synthese.medias)
.where(Synthese.cd_nom.in_(taxref_cd_nom_list))
)
query = TaxonSheetUtils.get_synthese_query_with_scope(g.current_user, scope, query)
query = TaxonSheetUtils.update_query_with_sorting(query, sort_by, sort_order)
results = TaxonSheetUtils.paginate(query, page, per_page)

return jsonify(
{
"items": results.items,
"total": results.total,
"per_page": per_page,
"page": page,
}
)


@routes.route("/taxons_tree", methods=["GET"])
@login_required
@json_resp
Expand Down
64 changes: 64 additions & 0 deletions backend/geonature/core/gn_synthese/utils/taxon_sheet.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import typing
from geonature.utils.env import db
from ref_geo.models import LAreas, BibAreasTypes

from geonature.core.gn_synthese.models import Synthese
from sqlalchemy import select, desc, asc
from apptax.taxonomie.models import Taxref
from geonature.core.gn_synthese.utils.query_select_sqla import SyntheseQuery
from sqlalchemy.orm import Query
from werkzeug.exceptions import BadRequest
from flask_sqlalchemy.pagination import Pagination
from enum import Enum


class SortOrder(Enum):
ASC = "asc"
DESC = "desc"


class TaxonSheetUtils:

@staticmethod
def update_query_with_sorting(query: Query, sort_by: str, sort_order: SortOrder) -> Query:
if sort_order == SortOrder.ASC:
return query.order_by(asc(sort_by))

return query.order_by(desc(sort_by))

@staticmethod
def paginate(query: Query, page: int, per_page: int) -> Pagination:
return query.paginate(page=page, per_page=per_page, error_out=False)

#
@staticmethod
def get_cd_nom_list_from_cd_ref(cd_ref: int) -> typing.List[int]:
return db.session.scalars(select(Taxref.cd_nom).where(Taxref.cd_ref == cd_ref))

@staticmethod
def get_synthese_query_with_scope(current_user, scope: int, query: Query) -> SyntheseQuery:
synthese_query_obj = SyntheseQuery(Synthese, query, {})
synthese_query_obj.filter_query_with_cruved(current_user, scope)
return synthese_query_obj.query

@staticmethod
def is_valid_area_type(area_type: str) -> bool:
# Ensure area_type is valid
valid_area_types = (
db.session.query(BibAreasTypes.type_code)
.distinct()
.filter(BibAreasTypes.type_code == area_type)
.scalar()
)

return valid_area_types

@staticmethod
def get_area_subquery(area_type: str) -> Query:

# Subquery to fetch areas based on area_type
return (
select(LAreas.id_area)
.where(LAreas.id_type == BibAreasTypes.id_type, BibAreasTypes.type_code == area_type)
.alias("areas")
)
13 changes: 8 additions & 5 deletions backend/geonature/tests/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -330,12 +330,12 @@ def create_user(
db.session.add(organisme)

users_to_create = [
(("noright_user", organisme, 0), {}),
(("stranger_user", None, 2), {}),
(("associate_user", organisme, 2), {}),
(("self_user", organisme, 1), {}),
(("noright_user", organisme, 0), {"nom_role": "User", "prenom_role": "NoRight"}),
(("stranger_user", None, 2), {"nom_role": "User", "prenom_role": "Stranger"}),
(("associate_user", organisme, 2), {"nom_role": "User", "prenom_role": "Associate"}),
(("self_user", organisme, 1), {"nom_role": "User", "prenom_role": "Self"}),
(("user", organisme, 2), {"nom_role": "Bob", "prenom_role": "Bobby"}),
(("admin_user", organisme, 3), {}),
(("admin_user", organisme, 3), {"nom_role": "Administrateur", "prenom_role": "Test"}),
(("associate_user_2_exclude_sensitive", organisme, 2, True), {}),
(
(
Expand Down Expand Up @@ -546,6 +546,7 @@ def create_synthese(
source,
uuid=func.uuid_generate_v4(),
cor_observers=[],
observers=[],
date_min="",
date_max="",
altitude_min=800,
Expand Down Expand Up @@ -574,6 +575,7 @@ def create_synthese(
altitude_min=altitude_min,
altitude_max=altitude_max,
cor_observers=cor_observers,
observers=observers,
**kwargs,
)

Expand Down Expand Up @@ -743,6 +745,7 @@ def synthese_data(app, users, datasets, source, sources_modules):
source_m,
unique_id_sinp,
[users["admin_user"], users["user"]],
["Administrative Test", "Bobby Bob"],
date_min,
date_max,
altitude_min,
Expand Down
Loading
Loading