diff --git a/dev/config/pyramid_oereb.yml.mako b/dev/config/pyramid_oereb.yml.mako
index aace2f21ec..8dd0a58270 100644
--- a/dev/config/pyramid_oereb.yml.mako
+++ b/dev/config/pyramid_oereb.yml.mako
@@ -358,6 +358,9 @@ pyramid_oereb:
db_connection: *main_db_connection
# The model which maps the logo images database table.
model: pyramid_oereb.contrib.data_sources.standard.models.main.Logo
+ hooks:
+ get_logo_ref: pyramid_oereb.core.hook_methods.get_logo_ref
+ get_qr_code_ref: pyramid_oereb.core.hook_methods.get_qr_code_ref
# The processor of the oereb project joins the document type labels. In the standard configuration this
# is assumed to be read from a database. Hint: If you want to read the values out of an existing database
diff --git a/pyramid_oereb/core/config.py b/pyramid_oereb/core/config.py
index 862e5f8120..e564e1ffed 100644
--- a/pyramid_oereb/core/config.py
+++ b/pyramid_oereb/core/config.py
@@ -729,6 +729,25 @@ def get_logo_lookup_confederation():
return Config.get_logo_lookup('confederation')
+ @staticmethod
+ def get_logo_hooks():
+ """
+ Returns the hook methods specified in config file.
+
+ Returns:
+ list: list of hook methods provided in config in dotted names.
+
+ Raises:
+ ConfigurationError
+ """
+
+ logo_config = Config.get_logo_config()
+ if logo_config is None:
+ raise ConfigurationError("Missing configuration for logos")
+ if logo_config.get('hooks') is None:
+ raise ConfigurationError("Missing configuration for logos hook methods")
+ return logo_config.get('hooks')
+
@staticmethod
def init_document_types():
"""
diff --git a/pyramid_oereb/core/hook_methods.py b/pyramid_oereb/core/hook_methods.py
index d11de52279..a2993e4d43 100644
--- a/pyramid_oereb/core/hook_methods.py
+++ b/pyramid_oereb/core/hook_methods.py
@@ -5,6 +5,7 @@
from functools import cmp_to_key
from pyramid_oereb import route_prefix
+from pyramid_oereb.core import get_multilingual_element
from pyramid_oereb.core.records.office import OfficeRecord
@@ -48,6 +49,46 @@ def get_symbol_ref(request, record):
)
+def get_logo_ref(request, logo_code, language, image_dict):
+ """
+ Returns the link to the logos.
+
+ Args:
+ request (pyramid.request.Request): The current request instance.
+ logo_code (str): Code of logo, eg. bs or ch.
+ language (str): language of extract.
+ image_dict (dict): dict of image
+
+ Returns:
+ uri: the link to the logos.
+ """
+
+ return request.route_url(
+ '{0}/image/logo'.format(route_prefix),
+ logo=logo_code,
+ language=language,
+ extension=get_multilingual_element(
+ image_dict,
+ language
+ ).extension
+ )
+
+
+def get_qr_code_ref(request, qr_code_ref):
+ """
+ Returns the link for the qr_code.
+
+ Args:
+ request (pyramid.request.Request): The current request instance.
+ qr_code_ref (str): The string of qr-code url.
+
+ Returns:
+ uri: the link to the qr_code.
+ """
+
+ return qr_code_ref
+
+
def get_surveying_data_provider(real_estate):
"""
diff --git a/pyramid_oereb/core/renderer/__init__.py b/pyramid_oereb/core/renderer/__init__.py
index 4b5d73e700..8ffb433d13 100644
--- a/pyramid_oereb/core/renderer/__init__.py
+++ b/pyramid_oereb/core/renderer/__init__.py
@@ -52,6 +52,46 @@ def get_symbol_ref(cls, request, record):
log.error('No "get_symbol_ref" method found for theme {}'.format(record.theme.code))
raise HTTPServerError()
+ @classmethod
+ def get_logo_ref(cls, request, logo_code, language, image_dict):
+ """
+ Returns the link to the symbol of the specified logo.
+
+ Args:
+ request (pyramid.request.Request): The current request instance.
+ logo_code (str): Code of logo, eg. bs or ch.
+ language (str): language of extract.
+ image_dict (dict): dict of image
+
+ Returns:
+ uri: The link to the symbol for the specified logo.
+ """
+ method = None
+ method = DottedNameResolver().resolve(Config.get_logo_hooks().get('get_logo_ref'))
+ if callable(method):
+ return method(request, logo_code, language, image_dict)
+ log.error('No "get_logo_ref" method found for logos')
+ raise HTTPServerError()
+
+ @classmethod
+ def get_qr_code_ref(cls, request, qr_code_ref):
+ """
+ Returns the link for the qr_code.
+
+ Args:
+ request (pyramid.request.Request): The current request instance.
+ qr_code_ref (str): The string of qr-code url.
+
+ Returns:
+ uri: the link to the qr_code.
+ """
+ method = None
+ method = DottedNameResolver().resolve(Config.get_logo_hooks().get('get_qr_code_ref'))
+ if callable(method):
+ return method(request, qr_code_ref)
+ log.error('No "get_qr_code_ref" method found for logos')
+ raise HTTPServerError()
+
@classmethod
def get_response(cls, system):
"""
diff --git a/pyramid_oereb/core/renderer/extract/json_.py b/pyramid_oereb/core/renderer/extract/json_.py
index 518a6e57fe..82e4331b18 100644
--- a/pyramid_oereb/core/renderer/extract/json_.py
+++ b/pyramid_oereb/core/renderer/extract/json_.py
@@ -7,7 +7,7 @@
from pyramid.response import Response
from pyramid.testing import DummyRequest
-from pyramid_oereb import Config, route_prefix
+from pyramid_oereb import Config
from pyramid_oereb.core import get_multilingual_element
from pyramid_oereb.core.records.documents import DocumentRecord
from pyramid_oereb.core.records.theme import ThemeRecord
@@ -119,44 +119,35 @@ def _render(self, extract, param):
})
else:
extract_dict.update({
- 'LogoPLRCadastreRef': self._request.route_url(
- '{0}/image/logo'.format(route_prefix),
- logo='oereb',
- language=self._language,
- extension=get_multilingual_element(
- extract.logo_plr_cadastre.image_dict,
- self._language
- ).extension
- ),
- 'FederalLogoRef': self._request.route_url(
- '{0}/image/logo'.format(route_prefix),
- logo='confederation',
- language=self._language,
- extension=get_multilingual_element(
- extract.federal_logo.image_dict,
- self._language
- ).extension
- ),
- 'CantonalLogoRef': self._request.route_url(
- '{0}/image/logo'.format(route_prefix),
- logo='canton',
- language=self._language,
- extension=get_multilingual_element(
- extract.cantonal_logo.image_dict,
- self._language
- ).extension
- ),
- 'MunicipalityLogoRef': self._request.route_url(
- '{0}/image/logo'.format(route_prefix),
- logo='municipality',
- language=self._language,
- extension=get_multilingual_element(
- extract.municipality_logo.image_dict,
- self._language
- ).extension
- ) + '?fosnr={}'.format(extract.real_estate.fosnr),
- 'QRCodeRef': extract.qr_code_ref
- })
+ 'LogoPLRCadastreRef': self.get_logo_ref(
+ self._request,
+ 'oereb',
+ self._language,
+ extract.logo_plr_cadastre.image_dict
+ ),
+ 'FederalLogoRef': self.get_logo_ref(
+ self._request,
+ 'confederation',
+ self._language,
+ extract.federal_logo.image_dict
+ ),
+ 'CantonalLogoRef': self.get_logo_ref(
+ self._request,
+ 'canton',
+ self._language,
+ extract.cantonal_logo.image_dict
+ ),
+ 'MunicipalityLogoRef': self.get_logo_ref(
+ self._request,
+ 'municipality',
+ self._language,
+ extract.municipality_logo.image_dict
+ ) + '?fosnr={}'.format(extract.real_estate.fosnr),
+ 'QRCodeRef': self.get_qr_code_ref(
+ self._request,
+ extract.qr_code_ref
+ )
+ })
if extract.electronic_signature is not None:
extract_dict['ElectronicSignature'] = extract.electronic_signature
diff --git a/pyramid_oereb/core/renderer/extract/templates/xml/extract.xml b/pyramid_oereb/core/renderer/extract/templates/xml/extract.xml
index 7941212dcf..2b76efcb47 100644
--- a/pyramid_oereb/core/renderer/extract/templates/xml/extract.xml
+++ b/pyramid_oereb/core/renderer/extract/templates/xml/extract.xml
@@ -65,12 +65,12 @@
${extract.extract_identifier}
${extract.qr_code.encode()}
%else:
- ${request.route_url('{0}/image/logo'.format(route_prefix), logo='oereb', language=language, extension=get_multilingual_element(extract.logo_plr_cadastre.image_dict, language).extension) | x}
- ${request.route_url('{0}/image/logo'.format(route_prefix), logo='confederation', language=language, extension=get_multilingual_element(extract.federal_logo.image_dict, language).extension) | x}
- ${request.route_url('{0}/image/logo'.format(route_prefix), logo='canton', language=language, extension=get_multilingual_element(extract.cantonal_logo.image_dict, language).extension) | x}
- ${request.route_url('{0}/image/logo'.format(route_prefix), logo='municipality', language=language, extension=get_multilingual_element(extract.municipality_logo.image_dict, language).extension) + '?fosnr={}'.format(extract.real_estate.fosnr) | x}
+ ${get_logo_ref(request=request, logo_code='oereb', language=language, image_dict=extract.logo_plr_cadastre.image_dict) | x}
+ ${get_logo_ref(request=request, logo_code='confederation', language=language, image_dict=extract.logo_plr_cadastre.image_dict) | x}
+ ${get_logo_ref(request=request, logo_code='canton', language=language, image_dict=extract.logo_plr_cadastre.image_dict) | x}
+ ${get_logo_ref(request=request, logo_code='municipality', language=language, image_dict=extract.logo_plr_cadastre.image_dict) + '?fosnr={}'.format(extract.real_estate.fosnr) | x}
${extract.extract_identifier}
- ${extract.qr_code_ref}
+ ${get_qr_code_ref(request=request, qr_code_ref=extract.qr_code_ref) | x}
%endif
%for general_information in extract.general_information:
<%include file="general_information.xml" args="general_information=general_information"/>
diff --git a/pyramid_oereb/core/renderer/extract/xml_.py b/pyramid_oereb/core/renderer/extract/xml_.py
index afa75bee93..83851c0b2a 100644
--- a/pyramid_oereb/core/renderer/extract/xml_.py
+++ b/pyramid_oereb/core/renderer/extract/xml_.py
@@ -83,6 +83,8 @@ def _render(self, extract, params):
'get_localized_image': self.get_localized_image,
'request': self._request,
'get_symbol_ref': self.get_symbol_ref,
+ 'get_logo_ref': self.get_logo_ref,
+ 'get_qr_code_ref': self.get_qr_code_ref,
'date_format': '%Y-%m-%dT%H:%M:%S'
})
return content
diff --git a/tests/contrib.print_proxy.mapfish_print/resources/test_config.yml b/tests/contrib.print_proxy.mapfish_print/resources/test_config.yml
index b9b2134422..3b1dd144ee 100644
--- a/tests/contrib.print_proxy.mapfish_print/resources/test_config.yml
+++ b/tests/contrib.print_proxy.mapfish_print/resources/test_config.yml
@@ -22,6 +22,9 @@ pyramid_oereb:
params:
db_connection: postgresql://postgres:postgres@oereb-db:5432/pyramid_oereb_test
model: pyramid_oereb.contrib.data_sources.standard.models.main.Logo
+ hooks:
+ get_logo_ref: pyramid_oereb.core.hook_methods.get_logo_ref
+ get_qr_code_ref: pyramid_oereb.core.hook_methods.get_qr_code_ref
document_types:
source:
diff --git a/tests/contrib.print_proxy.mapfish_print/resources/test_custom_config.yml b/tests/contrib.print_proxy.mapfish_print/resources/test_custom_config.yml
index a967257dd6..c253e36031 100644
--- a/tests/contrib.print_proxy.mapfish_print/resources/test_custom_config.yml
+++ b/tests/contrib.print_proxy.mapfish_print/resources/test_custom_config.yml
@@ -22,6 +22,9 @@ pyramid_oereb:
params:
db_connection: postgresql://postgres:postgres@oereb-db:5432/pyramid_oereb_test
model: pyramid_oereb.standard.models.main.Logo
+ hooks:
+ get_logo_ref: pyramid_oereb.core.hook_methods.get_logo_ref
+ get_qr_code_ref: pyramid_oereb.core.hook_methods.get_qr_code_ref
document_types:
source:
diff --git a/tests/contrib.print_proxy.mapfish_print/test_mapfish_print.py b/tests/contrib.print_proxy.mapfish_print/test_mapfish_print.py
index c6c3c56c55..732f3cfafc 100644
--- a/tests/contrib.print_proxy.mapfish_print/test_mapfish_print.py
+++ b/tests/contrib.print_proxy.mapfish_print/test_mapfish_print.py
@@ -847,7 +847,6 @@ def dummy_pdf():
@patch.object(pyramid_oereb.core.views.webservice, 'route_prefix', 'oereb')
-@patch.object(pyramid_oereb.core.renderer.extract.json_, 'route_prefix', 'oereb')
@patch.object(pyramid_oereb.core.config.Config, 'municipalities', [MunicipalityRecord(1234, 'test', True)])
def test_mfp_service(mock_responses, pyramid_test_config,
real_estate_data,
diff --git a/tests/core/renderer/test_base.py b/tests/core/renderer/test_base.py
index 9b5322eee5..e1f4bb9a7f 100644
--- a/tests/core/renderer/test_base.py
+++ b/tests/core/renderer/test_base.py
@@ -1,8 +1,11 @@
# -*- coding: utf-8 -*-
+import io
+from urllib.parse import urlparse
import pytest
from unittest.mock import patch
import datetime
+from PIL import Image
from pyramid.httpexceptions import HTTPServerError, HTTPInternalServerError
from pyramid.response import Response
@@ -19,6 +22,18 @@
from tests.mockrequest import MockRequest
+@pytest.fixture
+def png_image():
+ yield Image.new("RGB", (72, 36), (128, 128, 128))
+
+
+@pytest.fixture
+def png_binary(png_image):
+ output = io.BytesIO()
+ png_image.save(output, format='PNG')
+ yield output.getvalue()
+
+
def test_call(DummyRenderInfo, pyramid_oereb_test_config):
renderer = Base(DummyRenderInfo())
assert isinstance(renderer.info, DummyRenderInfo)
@@ -206,3 +221,69 @@ def test_get_symbol_ref(theme_code, pyramid_test_config, pyramid_oereb_test_conf
assert ref == 'http://example.com/image/symbol/{}/legend_entry.svg?identifier=1'.format(
theme_code
)
+
+
+@pytest.mark.parametrize('test_value, expected_results', [
+ ({
+ 'logo_code': 'ch',
+ 'language': 'de',
+ }, '/image/logo/ch/de.png'),
+ ({
+ 'logo_code': 'bs',
+ 'language': 'fr',
+ }, '/image/logo/bs/fr.png')
+ ])
+def test_get_logo_ref(test_value, expected_results, png_binary):
+ with patch.object(Config, 'get_logo_hooks',
+ return_value={"get_logo_ref": "pyramid_oereb.core.hook_methods.get_logo_ref"}):
+ request = DummyRequest()
+ url = urlparse(Base.get_logo_ref(request,
+ test_value.get('logo_code'),
+ test_value.get('language'),
+ {test_value.get('language'): ImageRecord(png_binary)}))
+ assert url.path == expected_results
+
+
+@pytest.mark.parametrize('test_value, expected_results', [
+ ({
+ 'logo_code': 'ch',
+ 'language': 'de',
+ }, '/image/logo/ch/de.png'),
+ ({
+ 'logo_code': 'bs',
+ 'language': 'fr',
+ }, '/image/logo/bs/fr.png')
+ ])
+def test_get_logo_ref_no_method(test_value, expected_results, png_binary):
+ with patch.object(Config, 'get_logo_hooks',
+ return_value={"get_logo_ref": "pyramid_oereb.core.hook_methods.get_logo_ref"}):
+ with patch.object(pyramid_oereb.core.hook_methods, 'get_logo_ref', {}):
+ with pytest.raises(HTTPServerError):
+ Base.get_logo_ref(DummyRequest(),
+ test_value.get('logo_code'),
+ test_value.get('language'),
+ {test_value.get('language'): ImageRecord(png_binary)})
+
+
+@pytest.mark.parametrize('test_value, expected_results', [
+ ('', ''),
+ (None, None)
+ ])
+def test_get_qr_code_ref(test_value, expected_results):
+ with patch.object(Config, 'get_logo_hooks',
+ return_value={"get_qr_code_ref": "pyramid_oereb.core.hook_methods.get_qr_code_ref"}):
+ request = DummyRequest()
+ assert Base.get_qr_code_ref(request, test_value) == expected_results
+
+
+@pytest.mark.parametrize('test_value, expected_results', [
+ ('', '')
+ ])
+def test_get_qr_code_ref_no_method(test_value, expected_results):
+ with patch.object(Config, 'get_logo_hooks',
+ return_value={
+ "get_qr_code_ref": "pyramid_oereb.core.hook_methods.get_qr_code_ref"
+ }):
+ with patch.object(pyramid_oereb.core.hook_methods, 'get_qr_code_ref', {}):
+ with pytest.raises(HTTPServerError):
+ Base.get_qr_code_ref(DummyRequest(), test_value)
diff --git a/tests/core/renderer/test_json.py b/tests/core/renderer/test_json.py
index 181d62f30f..790706f34c 100644
--- a/tests/core/renderer/test_json.py
+++ b/tests/core/renderer/test_json.py
@@ -3,7 +3,6 @@
import datetime
import pytest
-from unittest.mock import patch
from shapely.geometry import MultiPolygon, Polygon, Point, LineString
from pyramid.path import DottedNameResolver
@@ -30,9 +29,6 @@
from tests.mockrequest import MockRequest
from pyramid_oereb.core.views.webservice import Parameter
-import pyramid_oereb.core.renderer.extract.json_
-import pyramid_oereb.core.hook_methods
-
def law_status():
return LawStatusRecord(u'inKraft', {u'de': u'Rechtskräftig'})
@@ -58,7 +54,6 @@ def glossary_expected():
}]
-@patch.object(pyramid_oereb.core.renderer.extract.json_, 'route_prefix', 'oereb')
@pytest.mark.parametrize('parameter, glossaries_input, glossaries_expected', [
(default_param(), glossary_input(), glossary_expected()),
(default_param(), [], []),
@@ -230,7 +225,6 @@ def test_format_real_estate(DummyRenderInfo, real_estate_test_data):
}
-@patch.object(pyramid_oereb.core.hook_methods, 'route_prefix', 'oereb')
@pytest.mark.parametrize('parameter', [
default_param(),
Parameter('json', False, True, False, 'BL0200002829', '1000', 'CH775979211712', 'de'),
@@ -504,7 +498,6 @@ def test_format_theme(DummyRenderInfo, params):
}
-@patch.object(pyramid_oereb.core.hook_methods, 'route_prefix', 'oereb')
@pytest.mark.parametrize('parameter', [
default_param(),
Parameter('json', 'reduced', False, True, 'BL0200002829', '1000', 'CH775979211712', 'de')
diff --git a/tests/core/test_config.py b/tests/core/test_config.py
index 296c2fa814..afb1a2794b 100644
--- a/tests/core/test_config.py
+++ b/tests/core/test_config.py
@@ -96,6 +96,26 @@ def mock_read_logos():
assert Config.logos is None
+@pytest.mark.run(order=-1)
+def test_get_logo_hooks():
+ with patch.object(Config, 'get_logo_config', return_value={"hooks": []}):
+ Config._config = None
+ assert Config.get_logo_hooks() == []
+
+
+@pytest.mark.parametrize('test_value', [
+ ({"logos": [{"hooks": []}]}),
+ ({"logos": [{"not_expecting_key": []}]}),
+ (None)
+])
+@pytest.mark.run(order=-1)
+def test_get_logo_hooks_none(test_value):
+ with patch.object(Config, 'get_logo_config', return_value=test_value):
+ Config._config = None
+ with pytest.raises(ConfigurationError):
+ Config.get_logo_hooks()
+
+
@pytest.mark.run(order=-1)
def test_get_all_federal(config_path):
Config._config = None
diff --git a/tests/core/test_hook_methods.py b/tests/core/test_hook_methods.py
index bdb75786a0..5dc317c733 100644
--- a/tests/core/test_hook_methods.py
+++ b/tests/core/test_hook_methods.py
@@ -1,3 +1,4 @@
+import io
from pyramid_oereb.core.records.extract import ExtractRecord
from pyramid_oereb.core.records.law_status import LawStatusRecord
import pytest
@@ -5,6 +6,7 @@
from shapely.wkt import loads
from unittest.mock import patch
+from PIL import Image
from pyramid.testing import DummyRequest
@@ -15,7 +17,8 @@
from pyramid_oereb.core.records.view_service import LegendEntryRecord
from pyramid_oereb.core.records.real_estate import RealEstateRecord
from pyramid_oereb.core.hook_methods import compare, get_symbol, get_symbol_ref, \
- get_surveying_data_update_date, plr_sort_within_themes
+ get_logo_ref, get_qr_code_ref, get_surveying_data_update_date,\
+ plr_sort_within_themes
from pyramid_oereb.contrib.data_sources.standard.sources.plr import StandardThemeConfigParser
import pyramid_oereb.contrib.data_sources.standard.hook_methods
from tests.core.records.test_extract import create_dummy_extract
@@ -61,6 +64,18 @@ def legend_entry_data(pyramid_oereb_test_config, dbsession, transact, file_adapt
yield legend_entries
+@pytest.fixture
+def png_image():
+ yield Image.new("RGB", (72, 36), (128, 128, 128))
+
+
+@pytest.fixture
+def png_binary(png_image):
+ output = io.BytesIO()
+ png_image.save(output, format='PNG')
+ yield output.getvalue()
+
+
def test_get_symbol():
with pytest.raises(NotImplementedError):
binary_image, content_type = get_symbol({'identifier': "1"}, {})
@@ -82,6 +97,36 @@ def test_get_symbol_ref(pyramid_test_config):
assert url.path == '/image/symbol/ch.BelasteteStandorte/legend_entry.png'
+@pytest.mark.parametrize('test_value, expected_results', [
+ ({
+ 'logo_code': 'ch',
+ 'language': 'de',
+ }, '/image/logo/ch/de.png'),
+ ({
+ 'logo_code': 'bs',
+ 'language': 'fr',
+ }, '/image/logo/bs/fr.png')
+ ])
+def test_get_logo_ref(test_value, expected_results, png_binary):
+ request = DummyRequest()
+ url = urlparse(get_logo_ref(request,
+ test_value.get('logo_code'),
+ test_value.get('language'),
+ {test_value.get('language'): ImageRecord(png_binary)}
+ ))
+ assert url.path == expected_results
+
+
+@pytest.mark.parametrize('test_value, expected_results', [
+ ('', ''),
+ ({}, {}),
+ (None, None)
+ ])
+def test_get_qr_code_ref(test_value, expected_results):
+ request = DummyRequest()
+ assert get_qr_code_ref(request, test_value) == expected_results
+
+
def test_get_surveying_data_date():
real_estate = RealEstateRecord('test_type', 'BL', 'Nusshof', 1, 100,
loads('POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))'))
diff --git a/tests/core/webservice/test_getextractbyid.py b/tests/core/webservice/test_getextractbyid.py
index 54a5340a49..26f0199cb1 100644
--- a/tests/core/webservice/test_getextractbyid.py
+++ b/tests/core/webservice/test_getextractbyid.py
@@ -148,7 +148,6 @@ def test_return_no_content():
@patch.object(pyramid_oereb.core.hook_methods, 'route_prefix', 'oereb')
-@patch.object(pyramid_oereb.core.renderer.extract.json_, 'route_prefix', 'oereb')
@patch.object(pyramid_oereb.core.views.webservice, 'route_prefix', 'oereb')
@patch.object(MockRequest, 'route_url', lambda *args, **kwargs: '')
@pytest.mark.parametrize('egrid,topics', [
diff --git a/tests/resources/test_config.yml b/tests/resources/test_config.yml
index af25f1937a..ad896d48a6 100644
--- a/tests/resources/test_config.yml
+++ b/tests/resources/test_config.yml
@@ -178,6 +178,9 @@ pyramid_oereb:
params:
db_connection: *main_db_connection
model: pyramid_oereb.contrib.data_sources.standard.models.main.Logo
+ hooks:
+ get_logo_ref: pyramid_oereb.core.hook_methods.get_logo_ref
+ get_qr_code_ref: pyramid_oereb.core.hook_methods.get_qr_code_ref
document_types:
source: