Skip to content

Commit a795620

Browse files
authored
support dataset specific templates (geopython#1857) (geopython#1858)
* support dataset specific templates (geopython#1857) * add example to docs
1 parent 47fc390 commit a795620

File tree

8 files changed

+71
-16
lines changed

8 files changed

+71
-16
lines changed

docs/source/configuration.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ For more information related to API design rules (the ``api_rules`` property in
5252
limit: 10 # server limit on number of items to return
5353
admin: false # whether to enable the Admin API
5454
55+
# optional configuration to specify a different set of templates for HTML pages. Recommend using absolute paths. Omit this to use the default provided templates
56+
# This property can also be defined at the resource level to override global server settings for specific datasets
5557
templates: # optional configuration to specify a different set of templates for HTML pages. Recommend using absolute paths. Omit this to use the default provided templates
5658
path: /path/to/jinja2/templates/folder # path to templates folder containing the Jinja2 template HTML files
5759
static: /path/to/static/folder # path to static folder containing css, js, images and other static files referenced by the template

docs/source/html-templating.rst

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ HTML Templating
55

66
pygeoapi uses `Jinja`_ as its templating engine to render HTML and `Flask`_ to provide route paths of the API that returns HTTP responses. For complete details on how to use these modules, refer to the `Jinja documentation`_ and the `Flask documentation`_.
77

8-
The default pygeoapi configuration has ``server.templates`` commented out and defaults to the pygeoapi ``pygeoapi/templates`` and ``pygeoapi/static`` folder. To point to a different set of template configuration, you can edit your configuration:
8+
The default pygeoapi configuration has ``server.templates`` commented out and defaults to the pygeoapi ``pygeoapi/templates`` and ``pygeoapi/static`` folder. To point to a different set of template configuration, you can edit your configuration as follows:
99

1010
.. code-block:: yaml
1111
@@ -20,9 +20,9 @@ Your templates folder should mimic the same file names and structure of the defa
2020

2121
Note that you need only copy and edit the templates you are interested in updating. For example,
2222
if you are only interested in updating the ``landing_page.html`` template, then create your own version
23-
of the only that same file. When pygeoapi detects that a custom HTML template is being used,
24-
it will look for the custom template in ``server.templates.path``. If it does not exists, pygeoapi
25-
will render the default HTML template for the given endpoint/requuest.
23+
of only that same file. When pygeoapi detects that a custom HTML template is being used,
24+
it will look for the custom template in ``server.templates.path``. If it does not exist, pygeoapi
25+
will render the default HTML template for the given endpoint/request.
2626

2727
Linking to a static file in your HTML templates can be done using Jinja syntax and the exposed ``config['server']['url']``:
2828

@@ -35,9 +35,39 @@ Linking to a static file in your HTML templates can be done using Jinja syntax a
3535
<!-- Image example with metadata -->
3636
<img src="{{ config['server']['url'] }}/static/img/logo.png" title="{{ config['metadata']['identification']['title'] }}" />
3737

38+
Dataset level templates
39+
-----------------------
40+
41+
The ``templates`` configuration directive is applied to the entire server by default. It can also be used for a dataset specific look and feel. As example use case is defining a template for a specific dataset to be able to add custom UI/UX functionality (e.g. search/filter widget).
42+
43+
.. note::
44+
45+
Dataset level templates apply to ``/collections/{collectionId}`` and below.
46+
47+
48+
Example
49+
^^^^^^^
50+
51+
The below is an example dataset specific template using pygeoapi's default theme:
52+
53+
54+
.. code-block:: html
55+
56+
{% extends "_base.html" %}
57+
58+
{% block body %}
59+
60+
<h1>My cool dataset</h1>
61+
62+
{% endblock %}
63+
64+
.. note::
65+
66+
You can choose to use pygeoapi's default base theme, or your own as desired.
67+
3868

3969
Featured themes
40-
----------------
70+
---------------
4171

4272
Community based themes can be found on the `pygeoapi Community Plugins and Themes wiki page`_.
4373

pygeoapi/api/__init__.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1287,13 +1287,15 @@ def describe_collections(self, request: Union[APIRequest, Any],
12871287
if request.format == F_HTML: # render
12881288
fcm['collections_path'] = self.get_collections_url()
12891289
if dataset is not None:
1290+
self.set_dataset_templates(dataset)
1291+
12901292
content = render_j2_template(self.tpl_config,
12911293
'collections/collection.html',
12921294
fcm, request.locale)
12931295
else:
12941296
content = render_j2_template(self.tpl_config,
1295-
'collections/index.html', fcm,
1296-
request.locale)
1297+
'collections/index.html',
1298+
fcm, request.locale)
12971299

12981300
return headers, HTTPStatus.OK, content
12991301

@@ -1379,6 +1381,8 @@ def get_collection_schema(self, request: Union[APIRequest, Any],
13791381
schema['properties'][k]['x-ogc-role'] = 'primary-instant'
13801382

13811383
if request.format == F_HTML: # render
1384+
self.set_dataset_templates(dataset)
1385+
13821386
schema['title'] = l10n.translate(
13831387
self.config['resources'][dataset]['title'], request.locale)
13841388

@@ -1450,6 +1454,10 @@ def get_format_exception(self, request) -> Tuple[dict, int, str]:
14501454
def get_collections_url(self):
14511455
return f"{self.base_url}/collections"
14521456

1457+
def set_dataset_templates(self, dataset):
1458+
if 'templates' in self.config['resources'][dataset]:
1459+
self.tpl_config['server']['templates'] = self.config['resources'][dataset]['templates'] # noqa
1460+
14531461
@staticmethod
14541462
def _create_crs_transform_spec(
14551463
config: dict,

pygeoapi/api/environmental_data_retrieval.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,8 @@ def get_collection_edr_query(api: API, request: APIRequest,
198198
err.ogc_exception_code, err.message)
199199

200200
if request.format == F_HTML: # render
201+
api.set_dataset_templates(dataset)
202+
201203
uri = f'{api.get_collections_url()}/{dataset}/{query_type}'
202204
serialized_query_params = ''
203205
for k, v in request.params.items():

pygeoapi/api/itemtypes.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,8 @@ def get_collection_queryables(api: API, request: Union[APIRequest, Any],
177177
queryables['properties'][k]['x-ogc-role'] = 'primary-instant' # noqa
178178

179179
if request.format == F_HTML: # render
180+
api.set_dataset_templates(dataset)
181+
180182
queryables['title'] = l10n.translate(
181183
api.config['resources'][dataset]['title'], request.locale)
182184

@@ -572,6 +574,7 @@ def get_collection_items(
572574
l10n.set_response_language(headers, prv_locale, request.locale)
573575

574576
if request.format == F_HTML: # render
577+
api.set_dataset_templates(dataset)
575578
# For constructing proper URIs to items
576579

577580
content['items_path'] = uri
@@ -1174,6 +1177,7 @@ def get_collection_item(api: API, request: APIRequest,
11741177
l10n.set_response_language(headers, prv_locale, request.locale)
11751178

11761179
if request.format == F_HTML: # render
1180+
api.set_dataset_templates(dataset)
11771181
content['title'] = l10n.translate(collections[dataset]['title'],
11781182
request.locale)
11791183
content['id_field'] = p.id_field

pygeoapi/api/processes.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ def describe_processes(api: API, request: APIRequest,
214214

215215
if request.format == F_HTML: # render
216216
if process is not None:
217+
api.set_dataset_templates(process)
217218
response = render_j2_template(api.tpl_config,
218219
'processes/process.html',
219220
response, request.locale)

pygeoapi/api/tiles.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ def get_collection_tiles(api: API, request: APIRequest,
171171
tiles['tilesets'].append(tile_matrix)
172172

173173
if request.format == F_HTML: # render
174+
api.set_dataset_templates(dataset)
174175
tiles['id'] = dataset
175176
tiles['title'] = l10n.translate(
176177
api.config['resources'][dataset]['title'], SYSTEM_LOCALE)
@@ -317,6 +318,7 @@ def get_collection_tiles_metadata(
317318
language=prv_locale)
318319

319320
if request.format == F_HTML: # render
321+
api.set_dataset_templates(dataset)
320322
content = render_j2_template(api.tpl_config,
321323
'collections/tiles/metadata.html',
322324
tiles_metadata, request.locale)

pygeoapi/util.py

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,15 @@
5151
import uuid
5252

5353
import dateutil.parser
54+
from babel.support import Translations
55+
from jinja2 import Environment, FileSystemLoader, select_autoescape
56+
from jinja2.exceptions import TemplateNotFound
57+
import pyproj
58+
import pygeofilter.ast
59+
import pygeofilter.values
60+
from pyproj.exceptions import CRSError
61+
from requests import Session
62+
from requests.structures import CaseInsensitiveDict
5463
from shapely import ops
5564
from shapely.geometry import (
5665
box,
@@ -66,14 +75,6 @@
6675
mapping as geom_to_geojson,
6776
)
6877
import yaml
69-
from babel.support import Translations
70-
from jinja2 import Environment, FileSystemLoader, select_autoescape
71-
import pygeofilter.ast
72-
import pygeofilter.values
73-
import pyproj
74-
from pyproj.exceptions import CRSError
75-
from requests import Session
76-
from requests.structures import CaseInsensitiveDict
7778

7879
from pygeoapi import __version__
7980
from pygeoapi import l10n
@@ -473,7 +474,12 @@ def render_j2_template(config: dict, template: Path,
473474
translations = Translations.load(locale_dir, [locale_])
474475
env.install_gettext_translations(translations)
475476

476-
template = env.get_template(template)
477+
try:
478+
template = env.get_template(template)
479+
except TemplateNotFound:
480+
LOGGER.debug(f'template {template} not found')
481+
template_paths.remove(templates)
482+
template = env.get_template(template)
477483

478484
return template.render(config=l10n.translate_struct(config, locale_, True),
479485
data=data, locale=locale_, version=__version__)

0 commit comments

Comments
 (0)