From 35d9228e9fd72e9b7e68333a10ad5123ef1e16c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89lie=20Bouttier?= Date: Sat, 5 Oct 2024 16:59:45 +0200 Subject: [PATCH] feat(synthese): implement taxonomic filters --- backend/geonature/core/gn_synthese/models.py | 8 +++- .../gn_synthese/utils/query_select_sqla.py | 12 +++++- ...c722fe_permissions_add_extended_filters.py | 20 +++++++++ backend/geonature/tests/test_synthese.py | 43 +++++++++++++++++++ 4 files changed, 81 insertions(+), 2 deletions(-) diff --git a/backend/geonature/core/gn_synthese/models.py b/backend/geonature/core/gn_synthese/models.py index 6c7ebd91c3..2f5edec33d 100644 --- a/backend/geonature/core/gn_synthese/models.py +++ b/backend/geonature/core/gn_synthese/models.py @@ -459,7 +459,7 @@ def _has_permissions_grant(self, permissions) -> bool: if not permissions: return False for perm in permissions: - if perm.has_other_filters_than("SCOPE", "SENSITIVITY", "GEOGRAPHIC"): + if perm.has_other_filters_than("SCOPE", "SENSITIVITY", "GEOGRAPHIC", "TAXONOMIC"): continue # unsupported filters if perm.sensitivity_filter: if current_app.config["SYNTHESE"]["BLUR_SENSITIVE_OBSERVATIONS"]: @@ -482,6 +482,11 @@ def _has_permissions_grant(self, permissions) -> bool: if set(perm.areas_filter).isdisjoint(self.areas): # the permission does not allows any area overlapping the observation areas continue + if perm.taxons_filter: + if set(map(int, self.taxref.tree.path.split("."))).isdisjoint( + [t.cd_ref for t in perm.taxons_filter] + ): + continue return True # no filter exclude this permission return False @@ -658,6 +663,7 @@ class VSyntheseForWebApp(DB.Model): secondaryjoin=corAreaSynthese.c.id_area == LAreas.id_area, overlaps="areas,synthese_obs", ) + taxref = relationship(Taxref, primaryjoin=foreign(cd_nom) == Taxref.cd_nom) medias = relationship( TMedias, primaryjoin=(TMedias.uuid_attached_row == foreign(unique_id_sinp)), uselist=True ) diff --git a/backend/geonature/core/gn_synthese/utils/query_select_sqla.py b/backend/geonature/core/gn_synthese/utils/query_select_sqla.py index 9e89b6cd27..ed26a62cb7 100644 --- a/backend/geonature/core/gn_synthese/utils/query_select_sqla.py +++ b/backend/geonature/core/gn_synthese/utils/query_select_sqla.py @@ -147,7 +147,7 @@ def build_permissions_filter(self, user, permissions): permissions_filters = [] excluded_sensitivity = None for perm in permissions: - if perm.has_other_filters_than("SCOPE", "SENSITIVITY", "GEOGRAPHIC"): + if perm.has_other_filters_than("SCOPE", "SENSITIVITY", "GEOGRAPHIC", "TAXONOMIC"): continue perm_filters = [] if perm.sensitivity_filter: @@ -193,6 +193,16 @@ def build_permissions_filter(self, user, permissions): LAreas.id_area.in_([a.id_area for a in perm.areas_filter]) ) perm_filters.append(where_clause) + if perm.taxons_filter: + noms_lqueries = [f"'*.{t.cd_nom}.*'" for t in perm.taxons_filter] + where_clause = self.model.taxref.has( + Taxref.tree.has( + sa.text( + f"taxonomie.vm_taxref_tree.path ? array[{','.join(noms_lqueries)}]::lquery[]" + ) + ) + ) + perm_filters.append(where_clause) if perm_filters: permissions_filters.append(and_(*perm_filters)) else: diff --git a/backend/geonature/migrations/versions/707390c722fe_permissions_add_extended_filters.py b/backend/geonature/migrations/versions/707390c722fe_permissions_add_extended_filters.py index 6ce7be7545..cbfbfc56e9 100644 --- a/backend/geonature/migrations/versions/707390c722fe_permissions_add_extended_filters.py +++ b/backend/geonature/migrations/versions/707390c722fe_permissions_add_extended_filters.py @@ -88,6 +88,26 @@ def upgrade(): m.module_code = 'SYNTHESE' AND o.code_object = 'ALL' and a.code_action = 'R' """ ) + op.execute( + """ + UPDATE + gn_permissions.t_permissions_available pa + SET + taxons_filter = True + FROM + gn_commons.t_modules m, + gn_permissions.t_objects o, + gn_permissions.bib_actions a + WHERE + pa.id_module = m.id_module + AND + pa.id_object = o.id_object + AND + pa.id_action = a.id_action + AND + m.module_code = 'SYNTHESE' AND o.code_object = 'ALL' and a.code_action = 'R' + """ + ) def downgrade(): diff --git a/backend/geonature/tests/test_synthese.py b/backend/geonature/tests/test_synthese.py index bbdd17a093..8fd1011557 100644 --- a/backend/geonature/tests/test_synthese.py +++ b/backend/geonature/tests/test_synthese.py @@ -1888,3 +1888,46 @@ def test_geographic_filter_list_obs(self, synthese_data, synthese_read_permissio assert synthese_data["obs1"].id_synthese in response_ids assert synthese_data["obs2"].id_synthese not in response_ids assert synthese_data["obs3"].id_synthese in response_ids + + +@pytest.mark.usefixtures("client_class", "temporary_transaction") +class TestSyntheseTaxonomicFilter: + def test_taxonomic_filter_get_obs(self, synthese_data, synthese_read_permissions): + with db.session.begin_nested(): + user = User() + db.session.add(user) + taxon1 = synthese_data["obs1"].taxref + taxon2 = synthese_data["obs2"].taxref.parent + synthese_read_permissions(user, scope_value=None, taxons_filter=[taxon1, taxon2]) + set_logged_user(self.client, user) + response = self.client.get( + url_for("gn_synthese.get_one_synthese", id_synthese=synthese_data["obs1"].id_synthese) + ) + assert response.status_code == 200, response.data + response = self.client.get( + url_for("gn_synthese.get_one_synthese", id_synthese=synthese_data["obs2"].id_synthese) + ) + assert response.status_code == 200, response.data + response = self.client.get( + url_for("gn_synthese.get_one_synthese", id_synthese=synthese_data["obs3"].id_synthese) + ) + assert response.status_code == Forbidden.code, response.data + + def test_taxonomic_filter_list_obs(self, synthese_data, synthese_read_permissions): + with db.session.begin_nested(): + user = User() + db.session.add(user) + taxon1 = synthese_data["obs1"].taxref + taxon2 = synthese_data["obs2"].taxref.parent + synthese_read_permissions(user, scope_value=None, taxons_filter=[taxon1, taxon2]) + set_logged_user(self.client, user) + response = self.client.get( + url_for( + "gn_synthese.get_observations_for_web", + ) + ) + assert response.status_code == 200, response.data + response_ids = [f["properties"]["id_synthese"] for f in response.json["features"]] + assert synthese_data["obs1"].id_synthese in response_ids + assert synthese_data["obs2"].id_synthese in response_ids + assert synthese_data["obs3"].id_synthese not in response_ids