diff --git a/README.md b/README.md new file mode 100644 index 0000000000000000000000000000000000000000..3e20b46d5b2c9e7b7153e2af6c9bcad183335306 --- /dev/null +++ b/README.md @@ -0,0 +1,28 @@ +# ckanext-odsh +Diese CKAN-Extension enthält die wichtigsten Features und das Layout für das Tranzparenzportal Schleswig-Holstein. +Sie ist eine Weiterentwicklung der gleichnamigen Extension, die im Zuge der Entwicklung des Open Data Portals Schleswig-Holstein entwickelt wurde. + +## Branches +### master +Dieser Branch enthält den aktuell auf den Stage- bzw. Prod-Servern laufenden Code. + +### dev +Dieser Branch enthält den aktuellsten Stand mit allen fertig entwickelten Features. + +### andere Branches +Für die Entwicklung neuer Features soll jeweils ein eigener Branch vom dev-Branch abgezweigt werden. Für den Branch soll ein sprechender Name gewählt werden. + +## Deployment auf Produktivsystem +Das Deployment auf das Produktivsystem geschieht über Ansible. Die dafür benötigten Skripte befinden sich im Repository `tpsh_deploy`. + +## Manuelle Installation + + +## Konfiguration +Die Extension benötigt Konfigurationsparameter in der CKAN-Konfigurationsdatei (z.B. `production.ini`). Die korrekten Parameter für das Produktivsystem befinden sich im Repository `tpsh_deploy` unter `resources/production.ini`. Folgende Parameter sollten für Enwicklungssysteme geändert werden: + +| Parameter | Erläuterung | Wert für Entwicklungssysteme | +|---------------------------------------|---------------------------------------------------------------|-------------------------------------------| +| ckanext.odsh.use_matomo | `true` schaltet das matomo-Tracking ein. | `false` | +| ckanext.odsh.skip_icap_virus_check | `false` schaltet den Virus-Check ein. | `true` | +| ckanext.odsh.showtestbanner | `true` schaltet das Banner "Testsystem" ein, Muss `false` für Production-Server sein. | - | \ No newline at end of file diff --git a/ckanext/odsh/controller.py b/ckanext/odsh/controller.py index 78eee8959c9ef51ad97c495e56612ec3f6552597..aecd986ecd97aa21b64d1994a37bfa354ffc8f16 100644 --- a/ckanext/odsh/controller.py +++ b/ckanext/odsh/controller.py @@ -11,7 +11,6 @@ from ckan.controllers.package import PackageController from ckan.controllers.feed import FeedController, ITEMS_LIMIT, _package_search, _create_atom_id import ckan.lib.helpers as h import ckan.authz as authz -from ckan.common import c import logging import matomo import ckan.logic as logic diff --git a/ckanext/odsh/lib/odsh_icap_client.cfg b/ckanext/odsh/lib/odsh_icap_client.cfg deleted file mode 100644 index 654b57a83fc705ebff6769c89035df98f583432a..0000000000000000000000000000000000000000 --- a/ckanext/odsh/lib/odsh_icap_client.cfg +++ /dev/null @@ -1,9 +0,0 @@ -[DEFAULT] -# the IP of the ICAP-Server -host = 10.61.127.77 - -# The port of the ICAP-Server -port = 1344 - -# the IP of the client-machine -clientip = 127.0.0.1 diff --git a/ckanext/odsh/lib/odsh_icap_client.py b/ckanext/odsh/lib/odsh_icap_client.py index 703ac49eb3a7f1699e862433cca04a1704fbcdb3..91f04a432ea91f6378744b4eb3c0b5a8b69c52f7 100644 --- a/ckanext/odsh/lib/odsh_icap_client.py +++ b/ckanext/odsh/lib/odsh_icap_client.py @@ -1,20 +1,34 @@ import socket import sys import time -import ConfigParser +import logging +from ckan.common import config +import ckan.plugins.toolkit as toolkit + +log = logging.getLogger(__name__) + +def _read_from_config(key): + value = config.get(key, None) + if value is None: + _raise_KeyError_if_not_in_config(key) + return value + +def _raise_KeyError_if_not_in_config(key): + raise KeyError('key {} is not defined in ckan config file.'.format(key)) class ODSHICAPRequest(object): - def __init__(self, FILENAME, FILEBUFF, cfg_file='odsh_icap_client.cfg'): - config = ConfigParser.ConfigParser() - config.read(cfg_file) - self.HOST = '10.61.127.77' - self.PORT = 1344 - self.CLIENTIP = '127.0.0.1' + def __init__(self, FILENAME, FILEBUFF): + try: + self.HOST = _read_from_config('ckanext.odsh.icap.host') + self.PORT = toolkit.asint(_read_from_config('ckanext.odsh.icap.port')) + self.CLIENTIP = _read_from_config('ckanext.odsh.icap.clientip') + except KeyError, e: + log.error(e) self.FILENAME = FILENAME self.FILEBUFF = FILEBUFF - + def send(self): print("----- Starting ICAP-Request via RESPMOD -----") @@ -82,21 +96,6 @@ class ODSHICAPRequest(object): sock.send(l) sock.send("\r\n".encode()) l = fileBuffer.read(PACK_SIZE) - - def _sendfile_old(self, fileName, sock): - print('start sending file') - PACK_SIZE = 1024 # in bytes - - with open(fileName) as f: - l = f.read(PACK_SIZE) - while(l): - print('sending %d bytes of data...' % len(l)) - sock.send('{:02X}'.format(len(l)).encode()) - sock.send("\r\n".encode()) - sock.send(l) - sock.send("\r\n".encode()) - l = f.read(PACK_SIZE) - print('done sending') def _recvall(self, sock): print('receiving response from icap server') diff --git a/ckanext/odsh/public/odsh.css b/ckanext/odsh/public/odsh.css index 650475d89b4035c3ade61b4a828ff4deaacf309a..47a567da70b96179e763434aadc828e8d5ee3194 100644 --- a/ckanext/odsh/public/odsh.css +++ b/ckanext/odsh/public/odsh.css @@ -79,6 +79,18 @@ h3{ font-size:20px; } +.skip-link { + position: fixed; + top: -200px; + text-decoration: none; + padding: 10px; + display: inline-block; + } + +.skip-link:focus { + top: 0; +} + .secondary h1.heading { color:black; font-weight: normal ; @@ -639,7 +651,8 @@ label.rangesearch.disabled { .controls input.rangesearch{ box-sizing: border-box; width: 100%; - padding: 12px 2px; + height: 1.5rem; + padding: 2px; font-style: normal; font-size: 12px; line-height: 1.16666667em; @@ -930,6 +943,11 @@ label:after { font-style: normal; } +input#field-username { + margin-bottom: 30px; + vertical-align: super; +} + .control-group.error input, .control-group.error select, .control-group.error .select2-choices, diff --git a/ckanext/odsh/templates/footer.html b/ckanext/odsh/templates/footer.html index b1212dba15b04d860702804730bf8ee107168b81..03764a7c7b58782fb85faee0af0af365732205df 100644 --- a/ckanext/odsh/templates/footer.html +++ b/ckanext/odsh/templates/footer.html @@ -8,11 +8,11 @@ <div class='footer-line'></div> <div class="footer-left"> <div class="footer-icon">© {{g.site_title}}</div> - <div class="footer-right"> - <div class='footer-icon'><a href='http://www.schleswig-holstein.de/tpkontakt'>Kontakt</a></div> - <div class='footer-icon'><a href='http://www.schleswig-holstein.de/tpimpressum'>Impressum</a></div> - <div class='footer-icon last'><a href='http://www.schleswig-holstein.de/tpdatenschutz'>Datenschutz</a></div> - </div> + </div> + <div class="footer-right"> + <div class='footer-icon'><a href='http://www.schleswig-holstein.de/tpkontakt'>Kontakt</a></div> + <div class='footer-icon'><a href='http://www.schleswig-holstein.de/tpimpressum'>Impressum</a></div> + <div class='footer-icon last'><a href='http://www.schleswig-holstein.de/tpdatenschutz'>Datenschutz</a></div> </div> </div> </div> diff --git a/ckanext/odsh/templates/header.html b/ckanext/odsh/templates/header.html index 662acfe5ab63466e0c799db0464492c5dfb44d00..f7419ab93400a1b6850cd432045dcd65e1d3e823 100644 --- a/ckanext/odsh/templates/header.html +++ b/ckanext/odsh/templates/header.html @@ -39,7 +39,7 @@ <ul class="nav nav-pills"> <li class="header-menu-mobile" data-module="tpsh_toggle_menu"> <a>Menü</a> - <img src="/base/images/icon_close_white.svg"> + <img src="/base/images/icon_close_white.svg" alt="Menü schließen" aria-label="Menü schließen"> </li> {% block header_site_navigation_tabs %} {{ @@ -66,21 +66,21 @@ d="M42.5,44.5h-31v-6.4c0-4.7,3.9-8.6,8.6-8.6h13.9c4.7,0,8.6,3.9,8.6,8.6V44.5z" /> </svg> {{name}}</a> - <ul class="dropdown-menu" role="menu" aria-labelledby="dLabel"> + <ul class="dropdown-menu" role="menu"> <li> <svg class='user-icon-small' viewBox="0 0 54 54"> <circle cx="27" cy="17" r="7.5" /> <path d="M42.5,44.5h-31v-6.4c0-4.7,3.9-8.6,8.6-8.6h13.9c4.7,0,8.6,3.9,8.6,8.6V44.5z" /> </svg> - <a href="{{ h.url_for('/user/') }}" title="logout"> + <a href="{{ h.url_for('/user/') }}" title="logout" role="menuitem"> <span class="text"> Mein Profil einsehen </span> </a> </li> <li> - <a href="{{ h.url_for('/user/edit') }}" title="logout"> + <a href="{{ h.url_for('/user/edit') }}" title="logout" role="menuitem"> <i class='fa fa-edit'></i> <span class="text"> Mein Profil bearbeiten @@ -88,7 +88,7 @@ </a> </li> <li> - <a href="{{ h.url_for('/user/_logout') }}" title="logout"> + <a href="{{ h.url_for('/user/_logout') }}" title="logout" role="menuitem"> <i class='fa fa-sign-out'></i> <span class="text">Logout</span> </a> diff --git a/ckanext/odsh/templates/package/snippets/close_mobile_sidebar_button.html b/ckanext/odsh/templates/package/snippets/close_mobile_sidebar_button.html index 87a19511442d1923bfef6b2c4f2e36a2f05dbadf..5e30ade9262d6db85bd7ed8792b3f92091950b7a 100644 --- a/ckanext/odsh/templates/package/snippets/close_mobile_sidebar_button.html +++ b/ckanext/odsh/templates/package/snippets/close_mobile_sidebar_button.html @@ -1,3 +1,3 @@ <div class="container-fluid hide-filters-style"> - <a class="no-text hide-filters"><img src="/base/images/icon_close.svg"><span class="text">close</span></a> + <a class="no-text hide-filters"><img src="/base/images/icon_close.svg" alt="Filterdialog schließen"><span class="text">close</span></a> </div> \ No newline at end of file diff --git a/ckanext/odsh/templates/page.html b/ckanext/odsh/templates/page.html index 6e958cacd443657ef4eb249533197cc046b8e413..134079dd6ced8e52b3e01a7eec0fcef0a3db1a22 100644 --- a/ckanext/odsh/templates/page.html +++ b/ckanext/odsh/templates/page.html @@ -1,5 +1,10 @@ {% ckan_extends %} +{% block skip %} + <a class="skip-link" href="#content"> + Zum Inhalt springen. + </a> +{% endblock skip %} {%- block content %} <div class="blur-layer"></div> diff --git a/ckanext/odsh/templates/snippets/facet_list.html b/ckanext/odsh/templates/snippets/facet_list.html index ab0d8af05fdd028f4697ff60895f02d88120ddd2..d2b8521998aa412d9b20b7e92d7443c2bfa84bff 100644 --- a/ckanext/odsh/templates/snippets/facet_list.html +++ b/ckanext/odsh/templates/snippets/facet_list.html @@ -38,8 +38,8 @@ > </span> {% if name=='groups' %} - <span class='group-icon-container'> - <img src="/base/images/icon_kat_{{item.name}}.svg"/> + <span class="group-icon-container" aria-hidden="true"> + <img src="/base/images/icon_kat_{{item.name}}.svg" alt=""/> </span> {% endif %} diff --git a/ckanext/odsh/templates/snippets/package_item.html b/ckanext/odsh/templates/snippets/package_item.html index b31095c43ffbcff6b8aa792784da7c9c6685c7b4..419e685bc6eedfcb77f671a559ec19f408fbee75 100644 --- a/ckanext/odsh/templates/snippets/package_item.html +++ b/ckanext/odsh/templates/snippets/package_item.html @@ -109,8 +109,8 @@ Example: </span> {% for category in package.groups %} <span class='category-with-icon'> - <span class='category-icon-container'> - <img src="/base/images/icon_kat_{{category.name}}.svg" /> + <span class='category-icon-container' aria-hidden='true'> + <img src="/base/images/icon_kat_{{category.name}}.svg" alt=""/> </span> <span class="category-name"> {{ category.display_name }} diff --git a/ckanext/odsh/tests_tpsh/test_icap.py b/ckanext/odsh/tests_tpsh/test_icap.py new file mode 100644 index 0000000000000000000000000000000000000000..f97bbc74249eadeeb2e36ebdf07aae53191d0d82 --- /dev/null +++ b/ckanext/odsh/tests_tpsh/test_icap.py @@ -0,0 +1,67 @@ +import nose.tools as nt +from testfixtures import log_capture + +from ckan.common import config +from ckanext.odsh.lib.odsh_icap_client import ODSHICAPRequest, _read_from_config + +class Test_ODSHICAPRequest(object): + def setUp(self): + config.update({ + 'ckanext.odsh.icap.host': 'some_host', + 'ckanext.odsh.icap.port': '123', + 'ckanext.odsh.icap.clientip': 'some_ip', + }) + + def tearDown(self): + config.clear() + + def test_it_initializes_with_parameters_set_in_config(self): + request = ODSHICAPRequest('some_filename', 'some_filebuffer') + nt.assert_equal(request.HOST, 'some_host') + nt.assert_equal(request.PORT, 123) + nt.assert_equal(request.CLIENTIP, 'some_ip') + + @log_capture() + def test_it_logs_missing_parameter_host_to_error_log(self, capture): + del config['ckanext.odsh.icap.host'] + ODSHICAPRequest('some_filename', 'some_filebuffer') + capture.check(( + 'ckanext.odsh.lib.odsh_icap_client', + 'ERROR', + "'key ckanext.odsh.icap.host is not defined in ckan config file.'" + )) + + @log_capture() + def test_it_logs_missing_parameter_port_to_error_log(self, capture): + del config['ckanext.odsh.icap.port'] + ODSHICAPRequest('some_filename', 'some_filebuffer') + capture.check(( + 'ckanext.odsh.lib.odsh_icap_client', + 'ERROR', + "'key ckanext.odsh.icap.port is not defined in ckan config file.'" + )) + + @log_capture() + def test_it_logs_missing_parameter_clientip_to_error_log(self, capture): + del config['ckanext.odsh.icap.clientip'] + ODSHICAPRequest('some_filename', 'some_filebuffer') + capture.check(( + 'ckanext.odsh.lib.odsh_icap_client', + 'ERROR', + "'key ckanext.odsh.icap.clientip is not defined in ckan config file.'" + )) + + def test_read_from_config_raises_KeyError_if_host_not_defined_in_config(self): + del config['ckanext.odsh.icap.host'] + with nt.assert_raises(KeyError): + _read_from_config('ckanext.odsh.icap.host') + + def test_read_from_config_raises_KeyError_if_port_not_defined_in_config(self): + del config['ckanext.odsh.icap.port'] + with nt.assert_raises(KeyError): + _read_from_config('ckanext.odsh.icap.port') + + def test_read_from_config_raises_KeyError_if_clientip_not_defined_in_config(self): + del config['ckanext.odsh.icap.clientip'] + with nt.assert_raises(KeyError): + _read_from_config('ckanext.odsh.icap.clientip') diff --git a/dev-requirements.txt b/dev-requirements.txt index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..c574e4eaf013c487e6c63e0048eb9132b71c2627 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -0,0 +1,185 @@ +alabaster==0.7.11 +amqp==1.4.9 +anyjson==0.3.3 +arrow==0.13.1 +asn1crypto==0.24.0 +astroid==1.6.6 +atomicwrites==1.3.0 +attrs==19.3.0 +Babel==2.3.4 +backports.functools-lru-cache==1.5 +Beaker==1.8.1 +beautifulsoup4==4.5.1 +billiard==3.3.0.23 +bleach==1.5.0 +blinker==1.4 +celery==3.1.25 +certifi==2018.8.13 +cffi==1.11.5 +chardet==3.0.4 +click==6.7 +configparser==3.7.4 +contextlib2==0.6.0.post1 +cookies==2.2.1 +coverage==4.5.1 +coveralls==1.3.0 +cryptography==2.3 +decorator==4.0.6 +defusedxml==0.6.0 +dnspython==1.16.0 +docopt==0.6.2 +docutils==0.12 +entrypoints==0.3 +enum34==1.1.6 +eventlet==0.24.1 +extras==1.0.0 +factory-boy==2.1.1 +fanstatic==0.12 +filehash==0.1.dev3 +first==2.0.1 +fixtures==3.0.0 +flake8==3.7.8 +Flask==0.11.1 +Flask-DebugToolbar==0.10.0 +FormEncode==1.3.0 +funcsigs==1.0.2 +functools32==3.2.3.post2 +futures==3.3.0 +GeoAlchemy==0.7.2 +GeoAlchemy2==0.4.2 +geolinks==0.2.0 +geomet==0.2.0.post2 +greenlet==0.4.15 +html5lib==0.9999999 +httpretty==0.8.3 +hupper==1.6.1 +idna==2.7 +imagesize==1.0.0 +importlib-metadata==0.23 +ipaddress==1.0.22 +isodate==0.6.0 +isort==4.3.21 +itsdangerous==0.24 +Jinja2==2.8 +json-table-schema==0.2.1 +kombu==3.0.37 +lazy-object-proxy==1.4.1 +linecache2==1.0.0 +lxml==3.6.2 +Mako==1.0.4 +Markdown==2.6.7 +MarkupSafe==0.23 +mccabe==0.6.1 +messytables==0.15.2 +mock==2.0.0 +monotonic==1.5 +more-itertools==5.0.0 +mox3==0.26.0 +multiline-log-formatter==0.1.8 +nose==1.3.7 +ofs==0.4.2 +ordereddict==1.1 +OWSLib==0.16.0 +packaging==17.1 +Pairtree===0.7.1-T +passlib==1.6.5 +Paste==1.7.5.1 +PasteDeploy==2.0.1 +PasteScript==2.0.2 +pathlib==1.0.1 +pathlib2==2.3.5 +pbr==1.10.0 +pdf2image==1.9.0 +pdftotext==2.1.2 +pika==0.12.0 +Pillow==6.1.0 +pip-tools==1.7.0 +piwikapi==0.3 +pkg-resources==0.0.0 +plaster==1.0 +plaster-pastedeploy==0.7 +pluggy==0.13.0 +polib==1.0.7 +progressbar==2.3 +psycopg2==2.7.3.2 +py==1.8.0 +pyasn1==0.4.8 +pycodestyle==2.5.0 +pycountry==18.5.26 +pycparser==2.18 +pycrypto==2.6.1 +pycryptodome==3.9.0 +pycsw==2.2.0 +pyfakefs==2.9 +pyflakes==2.1.1 +Pygments==2.1.3 +pylint==1.9.5 +Pylons==0.9.7 +pymongo==3.9.0 +pyOpenSSL==18.0.0 +pyparsing==2.2.0 +pyproj==1.9.5.1 +pyramid==1.10.4 +pyramid-exclog==0.1 +-e git+https://github.com/IdentityPython/pysaml2.git@247e2f642b37de90a61c4f5ca49d8403840b6ea2#egg=pysaml2 +pysolr==3.6.0 +pytest==4.6.6 +pytest-cov==2.8.1 +python-dateutil==2.8.0 +python-json-logger==0.1.11 +python-magic==0.4.12 +python-memcached==1.48 +python-mimeparse==1.6.0 +pytz==2016.7 +pyutilib.component.core==4.6.4 +rdflib==4.2.1 +rdflib-jsonld==0.4.0 +redis==2.10.1 +repoze.lru==0.6 +repoze.who==2.3 +repoze.who-friendlyform==1.0.8 +requests==2.11.1 +responses==0.10.6 +rope==0.14.0 +Routes==1.13 +rq==0.6.0 +rq-dashboard==0.4.0 +scandir==1.10.0 +selenium==3.141.0 +Shapely==1.5.17 +simplejson==3.10.0 +singledispatch==3.4.0.3 +six==1.10.0 +snowballstemmer==1.2.1 +SPARQLWrapper==1.8.2 +Sphinx==1.7.1 +sphinx-rtd-theme==0.3.1 +sphinxcontrib-websupport==1.1.0 +SQLAlchemy==0.9.6 +sqlalchemy-migrate==0.10.0 +sqlparse==0.2.2 +Tempita==0.5.2 +testfixtures==6.10.3 +testtools==2.3.0 +traceback2==1.4.0 +translationstring==1.3 +typing==3.6.4 +tzlocal==1.3 +unicodecsv==0.14.1 +unittest2==1.1.0 +urllib3==1.23 +vdm==0.13 +venusian==1.2.0 +vine==1.1.4 +wcwidth==0.1.7 +WebError==0.13.1 +WebHelpers==1.3 +WebOb==1.0.8 +WebTest==1.4.3 +Werkzeug==0.11.10 +wrapt==1.11.2 +xlrd==1.0.0 +xmltodict==0.10.2 +zipp==0.6.0 +zope.deprecation==4.4.0 +zope.interface==4.3.2