From f839d394af4f313b66482a2b2544b556969a1f7b Mon Sep 17 00:00:00 2001 From: etj Date: Tue, 4 Jul 2023 18:27:36 +0200 Subject: [PATCH] [Fixes #11097] Faceting: resource type --- geonode/facets/models.py | 2 +- geonode/facets/providers/baseinfo.py | 147 +++++++++++++++++++++++++++ geonode/facets/tests.py | 60 +++++++---- geonode/settings.py | 2 + 4 files changed, 189 insertions(+), 22 deletions(-) create mode 100644 geonode/facets/providers/baseinfo.py diff --git a/geonode/facets/models.py b/geonode/facets/models.py index 07b1b4c7643..c7f9fbd0813 100644 --- a/geonode/facets/models.py +++ b/geonode/facets/models.py @@ -28,7 +28,7 @@ FACET_TYPE_USER = "user" FACET_TYPE_THESAURUS = "thesaurus" FACET_TYPE_CATEGORY = "category" -FACET_TYPE_RESOURCETYPE = "resourcetype" +FACET_TYPE_BASE = "base" logger = logging.getLogger(__name__) diff --git a/geonode/facets/providers/baseinfo.py b/geonode/facets/providers/baseinfo.py new file mode 100644 index 00000000000..b1f55129be3 --- /dev/null +++ b/geonode/facets/providers/baseinfo.py @@ -0,0 +1,147 @@ +######################################################################### +# +# Copyright (C) 2023 Open Source Geospatial Foundation - all rights reserved +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +######################################################################### + +import logging + +from django.db.models import Count + +from geonode.facets.models import FacetProvider, DEFAULT_FACET_PAGE_SIZE, FACET_TYPE_BASE + +logger = logging.getLogger(__name__) + + +class ResourceTypeFacetProvider(FacetProvider): + """ + Implements faceting for resources' type and subtype + """ + + @property + def name(self) -> str: + return "resourcetype" + + def get_info(self, lang="en") -> dict: + return { + "name": self.name, + "key": "filter{resource_type.in}", + "label": "Resource type", + "type": FACET_TYPE_BASE, + "hierarchical": True, + "order": 0, + } + + def get_facet_items( + self, + queryset=None, + start: int = 0, + end: int = DEFAULT_FACET_PAGE_SIZE, + lang="en", + topic_contains: str = None, + ) -> (int, list): + logger.debug("Retrieving facets for %s", self.name) + + if topic_contains: + logger.warning(f"Facet {self.name} does not support topic_contains filtering") + + q = queryset.values("resource_type", "subtype") + q = q.annotate(ctype=Count("resource_type"), csub=Count("subtype")) + q = q.order_by() + + # aggregate subtypes into rtypes + tree = {} + for r in q.all(): + res_type = r["resource_type"] + t = tree.get(res_type, {"cnt": 0, "sub": {}}) + t["cnt"] += r["ctype"] + if sub := r["subtype"]: + t["sub"][sub] = {"cnt": r["ctype"]} + tree[res_type] = t + + logger.info("Found %d main facets for %s", len(tree), self.name) + logger.debug(" ---> %s\n\n", q.query) + logger.debug(" ---> %r\n\n", q.all()) + + topics = [] + for rtype, info in tree.items(): + t = {"key": rtype, "label": rtype, "count": info["cnt"]} + if sub := info["sub"]: + children = [] + for stype, sinfo in sub.items(): + children.append({"key": stype, "label": stype, "count": sinfo["cnt"]}) + t["filter"] = "filter{subtype.in}" + t["items"] = sorted(children, reverse=True, key=lambda x: x["count"]) + topics.append(t) + + return len(topics), sorted(topics, reverse=True, key=lambda x: x["count"]) + + @classmethod + def register(cls, registry, **kwargs) -> None: + registry.register_facet_provider(ResourceTypeFacetProvider()) + + +class FeaturedFacetProvider(FacetProvider): + """ + Implements faceting for resources flagged as featured + """ + + @property + def name(self) -> str: + return "featured" + + def get_info(self, lang="en") -> dict: + return { + "name": self.name, + "key": "filter{featured}", + "label": "Featured", + "type": FACET_TYPE_BASE, + "hierarchical": False, + "order": 0, + } + + def get_facet_items( + self, + queryset=None, + start: int = 0, + end: int = DEFAULT_FACET_PAGE_SIZE, + lang="en", + topic_contains: str = None, + ) -> (int, list): + logger.debug("Retrieving facets for %s", self.name) + + if topic_contains: + logger.warning(f"Facet {self.name} does not support topic_contains filtering") + + q = queryset.values("featured").annotate(cnt=Count("featured")).order_by() + + logger.debug(" ---> %s\n\n", q.query) + logger.debug(" ---> %r\n\n", q.all()) + + topics = [ + { + "key": r["featured"], + "label": str(r["featured"]), + "count": r["cnt"], + } + for r in q[start:end] + ] + + return 2, topics + + @classmethod + def register(cls, registry, **kwargs) -> None: + registry.register_facet_provider(FeaturedFacetProvider()) diff --git a/geonode/facets/tests.py b/geonode/facets/tests.py index b5ae7f7541f..156c4908e50 100644 --- a/geonode/facets/tests.py +++ b/geonode/facets/tests.py @@ -114,49 +114,46 @@ def _create_resources(self): # These are the assigned keywords to the Resources - # RB00 -> T1K0 R0,R1 - # RB01 -> T0K0 T1K0 R0 - # RB02 -> T1K0 R1 + # RB00 -> T1K0 R0,R1 FEAT + # RB01 -> T0K0 T1K0 R0 FEAT + # RB02 -> T1K0 R1 FEAT # RB03 -> T0K0 T1K0 # RB04 -> T1K0 # RB05 -> T0K0 T1K0 - # RB06 -> T1K0 - # RB07 -> T0K0 T1K0 - # RB08 -> T1K0 T1K1 R1 + # RB06 -> T1K0 FEAT + # RB07 -> T0K0 T1K0 FEAT + # RB08 -> T1K0 T1K1 R1 FEAT # RB09 -> T0K0 T1K0 T1K1 # RB10 -> T1K1 # RB11 -> T0K0 T0K1 T1K1 - # RB12 -> T1K1 - # RB13 -> T0K0 T0K1 R1 - # RB14 -> + # RB12 -> T1K1 FEAT + # RB13 -> T0K0 T0K1 R1 FEAT + # RB14 -> FEAT # RB15 -> T0K0 T0K1 # RB16 -> # RB17 -> T0K0 T0K1 - # RB18 -> - # RB19 -> T0K0 T0K1 + # RB18 -> FEAT + # RB19 -> T0K0 T0K1 FEAT if x % 2 == 1: print(f"ADDING KEYWORDS {self.thesauri_k['0_0']} to RB {d}") d.tkeywords.add(self.thesauri_k["0_0"]) - d.save() if x % 2 == 1 and x > 10: print(f"ADDING KEYWORDS {self.thesauri_k['0_1']} to RB {d}") d.tkeywords.add(self.thesauri_k["0_1"]) - d.save() if x < 10: print(f"ADDING KEYWORDS {self.thesauri_k['1_0']} to RB {d}") d.tkeywords.add(self.thesauri_k["1_0"]) - d.save() if 7 < x < 13: d.tkeywords.add(self.thesauri_k["1_1"]) - d.save() if x in (0, 1): d.regions.add(self.regions["R0"]) - d.save() if x in (0, 2, 8, 13): d.regions.add(self.regions["R1"]) - d.save() + if (x % 6) in (0, 1, 2): + d.featured = True + d.save() d.set_permissions(public_perm_spec) @staticmethod @@ -169,9 +166,9 @@ def test_facets_base(self): obj = json.loads(res.content) self.assertIn("facets", obj) facets_list = obj["facets"] - self.assertEqual(5, len(facets_list)) + self.assertEqual(7, len(facets_list)) fmap = self._facets_to_map(facets_list) - for name in ("category", "owner", "t_0", "t_1"): + for name in ("category", "owner", "t_0", "t_1", "featured", "resourcetype"): self.assertIn(name, fmap) def test_facets_rich(self): @@ -189,7 +186,7 @@ def test_facets_rich(self): obj = json.loads(res.content) facets_list = obj["facets"] - self.assertEqual(5, len(facets_list)) + self.assertEqual(7, len(facets_list)) fmap = self._facets_to_map(facets_list) for expected in ( { @@ -233,6 +230,25 @@ def test_facets_rich(self): ], }, }, + { + "name": "featured", + "topics": { + "total": 2, + "items": [ + {"label": "True", "key": True, "count": 11}, + {"label": "False", "key": False, "count": 9}, + ], + }, + }, + { + "name": "resourcetype", + "topics": { + "total": 1, + "items": [ + {"label": "resourcebase", "key": "resourcebase", "count": 20}, + ], + }, + }, ): name = expected["name"] self.assertIn(name, fmap) @@ -254,7 +270,9 @@ def test_facets_rich(self): found = item break - self.assertIsNotNone(item, f"topic not found '{exp_label}'") + self.assertIsNotNone( + found, f"topic not found '{exp_label}' for facet '{name}' -- found items {items}" + ) for exp_field in exp_item: self.assertEqual( exp_item[exp_field], found[exp_field], f"Mismatch item key:{exp_field} facet:{name}" diff --git a/geonode/settings.py b/geonode/settings.py index d408093575b..9d6a270f138 100644 --- a/geonode/settings.py +++ b/geonode/settings.py @@ -2320,6 +2320,8 @@ def get_geonode_catalogue_service(): GEONODE_APPS += ("geonode.facets",) FACET_PROVIDERS = ( + "geonode.facets.providers.baseinfo.ResourceTypeFacetProvider", + "geonode.facets.providers.baseinfo.FeaturedFacetProvider", "geonode.facets.providers.category.CategoryFacetProvider", "geonode.facets.providers.users.OwnerFacetProvider", "geonode.facets.providers.thesaurus.ThesaurusFacetProvider",