#########################################################################
#
# 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 <http://www.gnu.org/licenses/>.
#
#########################################################################
import logging
from django.conf import settings
[docs]
DEFAULT_FACET_PAGE_SIZE = 10
# Well known types of facet - not an enum bc it needs to be extensible
[docs]
FACET_TYPE_PLACE = "place"
[docs]
FACET_TYPE_USER = "user"
[docs]
FACET_TYPE_THESAURUS = "thesaurus"
[docs]
FACET_TYPE_CATEGORY = "category"
[docs]
FACET_TYPE_BASE = "base"
[docs]
FACET_TYPE_KEYWORD = "keyword"
[docs]
logger = logging.getLogger(__name__)
[docs]
class FacetProvider:
"""
Provides access to the facet information and the related topics
"""
def __init__(self, **kwargs):
[docs]
self.config = kwargs.get("config", {}).copy()
[docs]
def __str__(self):
return f"{self.__class__.__name__}[{self.name}]"
@property
[docs]
def name(self) -> str:
"""
Get the name of the facet, to be used as a key for this provider.
You may want to override this method in order to have an optimized logic
:return: The name of the provider as a str
"""
self.get_info()["name"]
[docs]
def get_info(self, lang="en", **kwargs) -> dict:
"""
Get the basic info for this provider, as a dict with these keys:
- 'name': the name of the provider (the one returned by name())
- 'filter': the filtering key to be used in a filter query
- 'label': a generic label for the facet; the client should try and localize it whenever possible
- 'localized_label': a localized label for the facet (localized according to the `lang` param)
- 'type': the facet type (e.g. user, region, thesaurus, ...)
- 'hierarchical': boolean value telling if the facet items are hierarchically organized
- "order": an optional integer suggesting the relative ordering of the facets
:param lang: lanuage for label localization
:return: a dict
"""
pass
[docs]
def get_facet_items(
self,
queryset,
start: int = 0,
end: int = DEFAULT_FACET_PAGE_SIZE,
lang="en",
topic_contains: str = None,
keys: set = {},
**kwargs,
) -> (int, list):
"""
Return the items of the facets, in a tuple:
- int, total number of items matched
- list, topic records. A topic record is a dict having these keys:
- key: the key of the items that should be used for filtering
- label: a generic label for the item; the client should try and localize it whenever possible
- localized_label: a localized label for the item
- count: the count of such topic in the current facet
- other facet specific keys
:param queryset: the prefiltered queryset (may be filtered for authorization or other filters)
:param start: int: pagination, the index of the initial returned item
:param end: int: pagination, the index of the last returned item
:param lang: the preferred language for the labels
:param topic_contains: only returns matching topics
:param keys: only returns topics with given keys, even if their count is 0
:return: a tuple int:total count of record, list of items
"""
pass
[docs]
def get_topics(self, keys: list, lang="en", **kwargs) -> list:
"""
Return the topics with the requested ids as a list
- list, topic records. A topic record is a dict having these keys:
- key: the key of the items that should be used for filtering
- label: a generic label for the item; the client should try and localize it whenever possible
- localized_label: a localized label for the item
- other facet specific keys
:param keys: the list of the keys of the topics, as returned by the get_facet_items() method
:param lang: the preferred language for the labels
:return: list of items
"""
pass
@classmethod
[docs]
def register(cls, registry, **kwargs) -> None:
"""
Perform registration of instances of this Provider
:param registry: the registry where instances shall be registered
:param kwargs: other args that may be needed by Providers
"""
pass
[docs]
class FacetsRegistry:
def __init__(self):
[docs]
self.facet_providers = None
[docs]
def _load_facets_configuration(self) -> None:
"""
Facet loading is done lazily because some FacetProvider may need to access the DB, which may not have been initialized/created yet
"""
from django.utils.module_loading import import_string
self.facet_providers = dict()
logger.info("Initializing Facets")
for providerconf in getattr(settings, "FACET_PROVIDERS", []):
clz = providerconf["class"]
provider = import_string(clz)
provider.register(self, config=providerconf.get("config", {}))
[docs]
def register_facet_provider(self, provider: FacetProvider):
logger.info(f"Registering {provider}")
self.facet_providers[provider.get_info()["name"]] = provider
[docs]
def get_providers(self):
if self.facet_providers is None:
self._load_facets_configuration()
return self.facet_providers.values()
[docs]
def get_provider(self, name):
if self.facet_providers is None:
self._load_facets_configuration()
return self.facet_providers.get(name, None)
[docs]
facet_registry = FacetsRegistry()