3939from pygeoapi .provider .base import ProviderTypeError , ProviderGenericError
4040from pygeoapi .util import (
4141 get_provider_by_type , to_json , filter_providers_by_type ,
42- filter_dict_by_key_value , get_current_datetime
42+ filter_dict_by_key_value , get_current_datetime , render_j2_template
4343)
4444
4545LOGGER = logging .getLogger (__name__ )
@@ -491,6 +491,32 @@ def _server_error(api: API, request: APIRequest, headers: dict,
491491 )
492492
493493
494+ def _render_html (api : API , request : APIRequest , dataset : str , template : str ,
495+ title : str , data : dict , ** data_kwargs ) -> str :
496+ """
497+ Render HTML content for the API response.
498+ Adds base_url, dataset_path, and collections_path URLs to the data dict
499+ before the page is rendered.
500+
501+ :param api: API instance
502+ :param request: A request object
503+ :param dataset: Dataset name (collection key)
504+ :param template: Template file path (relative)
505+ :param title: Title of the HTML page
506+ :param data: Data dictionary to be passed to the template
507+ :param data_kwargs: Additional keyword arguments to update the data dict
508+ """
509+ collections_url = api .get_collections_url ()
510+ data ['title' ] = title
511+ data ['base_url' ] = api .base_url
512+ data ['dataset_path' ] = f'{ collections_url } /{ dataset } '
513+ data ['collections_path' ] = collections_url
514+ data .update (data_kwargs )
515+ tpl_config = api .get_dataset_templates (dataset )
516+ return render_j2_template (api .tpl_config , tpl_config ,
517+ template , data , request .locale )
518+
519+
494520def list_joins (api : API , request : APIRequest ,
495521 collection : str ) -> tuple [dict , int , str ]:
496522 """
@@ -517,24 +543,24 @@ def list_joins(api: API, request: APIRequest,
517543 # Build the joins list with proper structure
518544 joins_list = []
519545 uri = f'{ api .get_collections_url ()} /{ dataset } '
520- for source_id , source_obj in sources :
546+ for source_id , source_obj in sources . items () :
521547 join_item = {
522548 'id' : source_id ,
523549 'timeStamp' : source_obj ['timeStamp' ],
524550 'links' : [
525551 {
526552 'type' : FORMAT_TYPES [F_JSON ],
527- 'rel' : request . get_linkrel ( F_JSON ) ,
553+ 'rel' : "join-source" ,
528554 'title' : l10n .translate ('Join source details as JSON' , request .locale ), # noqa
529555 'href' : f'{ uri } /joins/{ source_id } ?f={ F_JSON } '
530556 }, {
531557 'type' : FORMAT_TYPES [F_JSONLD ],
532- 'rel' : request . get_linkrel ( F_JSONLD ) ,
558+ 'rel' : "join-source" ,
533559 'title' : l10n .translate ('Join source details as RDF (JSON-LD)' , request .locale ), # noqa
534560 'href' : f'{ uri } /joins/{ source_id } ?f={ F_JSONLD } '
535561 }, {
536562 'type' : FORMAT_TYPES [F_HTML ],
537- 'rel' : request . get_linkrel ( F_HTML ) ,
563+ 'rel' : "join-source" ,
538564 'title' : l10n .translate ('Join source details as HTML' , request .locale ), # noqa
539565 'href' : f'{ uri } /joins/{ source_id } ?f={ F_HTML } '
540566 }
@@ -544,7 +570,7 @@ def list_joins(api: API, request: APIRequest,
544570
545571 # Build the response with proper structure
546572 # TODO: support pagination
547- response = {
573+ output = {
548574 'links' : [
549575 {
550576 'type' : FORMAT_TYPES [F_JSON ],
@@ -574,7 +600,25 @@ def list_joins(api: API, request: APIRequest,
574600 # locale (or fallback default locale)
575601 l10n .set_response_language (headers , request .locale )
576602
577- return headers , HTTPStatus .OK , to_json (response , api .pretty_print )
603+ if request .format == F_HTML : # render
604+ # HTML only: use provider to fetch key fields for dropdown
605+ try :
606+ provider_def = get_provider_by_type (
607+ collections [dataset ]['providers' ], 'feature'
608+ )
609+ provider = load_plugin ('provider' , provider_def )
610+ keys = provider .get_key_fields ()
611+ except ProviderTypeError :
612+ LOGGER .warning (f'Feature provider not found for collection: '
613+ f'{ dataset } ' , exc_info = True )
614+ keys = {}
615+
616+ title = f'{ collections [dataset ]['title' ]} - Join Sources'
617+ content = _render_html (api , request , dataset , 'collections/joins.html' ,
618+ title , output , key_fields = keys )
619+ return headers , HTTPStatus .OK , content
620+
621+ return headers , HTTPStatus .OK , to_json (output , api .pretty_print )
578622
579623
580624def join_details (api : API , request : APIRequest ,
@@ -599,7 +643,7 @@ def join_details(api: API, request: APIRequest,
599643 details = join_util .read_join_source (dataset , join_id )
600644
601645 uri = f'{ api .get_collections_url ()} /{ dataset } '
602- response = {
646+ output = {
603647 'id' : join_id ,
604648 'timeStamp' : get_current_datetime (),
605649 'details' : {
@@ -628,17 +672,17 @@ def join_details(api: API, request: APIRequest,
628672 'href' : f'{ uri } /joins/{ join_id } ?f={ F_HTML } '
629673 }, {
630674 'type' : 'application/geo+json' ,
631- 'rel' : 'results ' ,
675+ 'rel' : 'items ' ,
632676 'title' : 'Items with joined data as GeoJSON' ,
633677 'href' : f"{ uri } /items?f={ F_JSON } &joinId={ join_id } " ,
634678 }, {
635679 'type' : FORMAT_TYPES [F_JSONLD ],
636- 'rel' : 'results ' ,
680+ 'rel' : 'items ' ,
637681 'title' : 'Items with joined data as RDF (JSON-LD)' ,
638682 'href' : f"{ uri } /items?f={ F_JSONLD } &joinId={ join_id } " , # noqa
639683 }, {
640684 'type' : FORMAT_TYPES [F_HTML ],
641- 'rel' : 'results ' ,
685+ 'rel' : 'items ' ,
642686 'title' : 'Items with joined data items as HTML' ,
643687 'href' : f"{ uri } /items?f={ F_HTML } &joinId={ join_id } " ,
644688 }
@@ -662,7 +706,13 @@ def join_details(api: API, request: APIRequest,
662706 # locale (or fallback default locale)
663707 l10n .set_response_language (headers , request .locale )
664708
665- return headers , HTTPStatus .OK , to_json (response , api .pretty_print )
709+ if request .format == F_HTML : # render
710+ title = f'{ collections [dataset ]['title' ]} - Join Source'
711+ content = _render_html (api , request , dataset ,
712+ 'collections/joinsource.html' , title , output )
713+ return headers , HTTPStatus .OK , content
714+
715+ return headers , HTTPStatus .OK , to_json (output , api .pretty_print )
666716
667717
668718def create_join (api : API , request : APIRequest ,
@@ -711,7 +761,7 @@ def create_join(api: API, request: APIRequest,
711761
712762 uri = f'{ api .get_collections_url ()} /{ dataset } '
713763 join_id = details ['id' ]
714- response = {
764+ output = {
715765 'id' : join_id ,
716766 'timeStamp' : get_current_datetime (),
717767 'details' : {
@@ -740,17 +790,17 @@ def create_join(api: API, request: APIRequest,
740790 'href' : f'{ uri } /joins/{ join_id } ?f={ F_HTML } '
741791 }, {
742792 'type' : 'application/geo+json' ,
743- 'rel' : 'results ' ,
793+ 'rel' : 'items ' ,
744794 'title' : 'Items with joined data as GeoJSON' ,
745795 'href' : f"{ uri } /items?f={ F_JSON } &joinId={ details ['id' ]} " , # noqa
746796 }, {
747797 'type' : FORMAT_TYPES [F_JSONLD ],
748- 'rel' : 'results ' ,
798+ 'rel' : 'items ' ,
749799 'title' : 'Items with joined data as RDF (JSON-LD)' ,
750800 'href' : f"{ uri } /items?f={ F_JSONLD } &joinId={ details ['id' ]} " , # noqa
751801 }, {
752802 'type' : FORMAT_TYPES [F_HTML ],
753- 'rel' : 'results ' ,
803+ 'rel' : 'items ' ,
754804 'title' : 'Items with joined data as HTML' ,
755805 'href' : f"{ uri } /items?f={ F_HTML } &joinId={ details ['id' ]} " , # noqa
756806 }
@@ -774,7 +824,15 @@ def create_join(api: API, request: APIRequest,
774824 # locale (or fallback default locale)
775825 l10n .set_response_language (headers , prv_locale , request .locale )
776826
777- return headers , HTTPStatus .OK , to_json (response , api .pretty_print )
827+ if request .format == F_HTML :
828+ # Render same page as join details to show result of POST
829+ title = f'{ collections [dataset ]['title' ]} - Join Source'
830+ content = _render_html (api , request , dataset ,
831+ 'collections/joinsource.html' , title , output ,
832+ description = "Join source created successfully." )
833+ return headers , HTTPStatus .OK , content
834+
835+ return headers , HTTPStatus .OK , to_json (output , api .pretty_print )
778836
779837
780838def delete_join (api : API , request : APIRequest ,
@@ -855,7 +913,7 @@ def key_fields(api: API, request: APIRequest,
855913 prv_locale = l10n .get_plugin_locale (provider_def , request .raw_locale )
856914
857915 uri = f'{ api .get_collections_url ()} /{ dataset } '
858- content = {
916+ output = {
859917 'links' : [
860918 {
861919 'type' : FORMAT_TYPES [F_JSON ],
@@ -877,17 +935,23 @@ def key_fields(api: API, request: APIRequest,
877935 'keys' : []
878936 }
879937
880- for name , info in fields :
881- content ['keys' ].append ({
938+ for name , info in fields . items () :
939+ output ['keys' ].append ({
882940 'id' : name ,
883941 'type' : info .get ('type' ), # not always set (e.g. for ID)
884942 'isDefault' : info ['default' ],
885- 'language' : prv_locale .language # TODO: is this really useful?
943+ 'language' : prv_locale .language if prv_locale else request . locale . language # noqa
886944 })
887945
888946 # Set response language to requested provider locale
889947 # (if it supports language) and/or otherwise the requested pygeoapi
890948 # locale (or fallback default locale)
891- l10n .set_response_language (headers , prv_locale , request .locale )
949+ l10n .set_response_language (headers , request .locale )
950+
951+ if request .format == F_HTML : # render
952+ title = f'{ collections [dataset ]['title' ]} - Key Fields'
953+ output = _render_html (api , request , dataset , 'collections/keys.html' ,
954+ title , output )
955+ return headers , HTTPStatus .OK , output
892956
893- return headers , HTTPStatus .OK , to_json (content , api .pretty_print )
957+ return headers , HTTPStatus .OK , to_json (output , api .pretty_print )
0 commit comments