diff --git a/ckanext/odsh/collection/controller.py b/ckanext/odsh/collection/controller.py
index 5b049563195ed12b5a1b4f008b38fd95fc3af612..79b669afb67b03042be0c3c4b58cf5b5c1f63a0d 100644
--- a/ckanext/odsh/collection/controller.py
+++ b/ckanext/odsh/collection/controller.py
@@ -1,7 +1,7 @@
 from ckan.lib.helpers import is_url, url_for
 import ckan.plugins.toolkit as toolkit
 from ckan.controllers.package import PackageController
-from helpers import get_latest_resources_for_type, get_latest_dataset
+from helpers import get_latest_resources_for_format, get_latest_dataset
 
 
 
@@ -13,10 +13,10 @@ class LatestDatasetController(PackageController):
 
 class LatestRecourcesController(PackageController):
     
-    def latest_resource(self, id, type):
-        latest_resources = get_latest_resources_for_type(id, type)
+    def latest_resource(self, id, resource_format):
+        latest_resources = get_latest_resources_for_format(id, resource_format)
         if latest_resources is None:
-            abort(404)
+            toolkit.abort(404)
         url_type = latest_resources.get('url_type')
         if url_type is None:
             resource_url = latest_resources.get('url')
@@ -35,4 +35,4 @@ class LatestRecourcesController(PackageController):
                                     filename=pre_resource_url,
                                     qualified = True)
             toolkit.redirect_to(url_resource)
-        abort(404)
\ No newline at end of file
+        toolkit.abort(404)
\ No newline at end of file
diff --git a/ckanext/odsh/collection/helpers.py b/ckanext/odsh/collection/helpers.py
index ec0f7dcbf05e7c37afa5491501e9d54f071f11d8..9c3024337c10aabcf5723f4bea75abd4adc0de08 100644
--- a/ckanext/odsh/collection/helpers.py
+++ b/ckanext/odsh/collection/helpers.py
@@ -1,161 +1,156 @@
 from string import lower
+from operator import itemgetter
 
 import ckan.lib.helpers as helpers
 import ckan.model as model
 import ckan.plugins.toolkit as toolkit
 
 
+def get_collection(dataset_dict):
+    collection_id = get_collection_id(dataset_dict)
+    if collection_id:
+        return get_collection_info(collection_id, dataset_dict)
+    
+    return None
+
+
+def get_collection_info(collection_id, dataset_dict=None):
+    collection_dict = get_package_dict(collection_id)
+    dataset_names = get_dataset_names(collection_dict)
+    datasets_in_collection = get_datasets_from_solr(dataset_names)
+    collection_info = gather_collection_info(collection_dict, datasets_in_collection, dataset_dict)
+    return collection_info
+
+
+def get_collection_id(dataset_dict):
+    relationships_dataset = dataset_dict.get('relationships_as_subject')
+    if relationships_dataset and len(relationships_dataset):        
+        return relationships_dataset[0]['__extras']['object_package_id']
+    relationships_dataset = dataset_dict.get('relationships')
+    if relationships_dataset and len(relationships_dataset):
+        return relationships_dataset[0].get('object')
+    return None
 
-#routine functions
 
 def get_package_dict(name):
     return model.Package.get(name).as_dict()
 
-def get_relationships(name):
-    collection_dict = get_package_dict(name)
-    return collection_dict.get('relationships')
 
-def get_all_datasets_belonging_to_collection(collection_name):
-    list_rel_collection = get_relationships(collection_name)
-    name_list = list()
-    for item in list_rel_collection:
-        item_object = item.get('object') 
-        name_list.append(item_object)
-    return name_list
+def get_dataset_names(collection_dict):
+    collection_dict = get_package_dict(collection_dict.get('id')) # needed to get full package_dict
+    relationships_collection = collection_dict.get('relationships')
+    names_collection_members = [relationship.get('object') for relationship in relationships_collection]
+    return names_collection_members
 
-#for mapping latest resources and latest dataset
 
-def get_latest_dataset(collection_name):
-    collection_list_relationships = get_all_datasets_belonging_to_collection(collection_name)
-    latest_issued = latest_name = None
-    for item in collection_list_relationships:
-        item_pkt_dict =  get_package_dict(item)
-        if helpers.check_access('package_show', item_pkt_dict):
-            item_issued = item_pkt_dict.get('extras').get('issued')
-            if latest_issued < item_issued or (latest_issued == item_issued and latest_name < item):
-                latest_name=item
-                latest_issued=item_issued
-    return latest_name
+def get_datasets_from_solr(dataset_names):
+    context = None
 
+    name_expression = ' OR '.join(dataset_names)
+    fq = 'name:({})'.format(name_expression)
+    
+    sort = 'name asc,extras_issued asc'
+    
+    # maximum possible number of results is 1000, 
+    # see https://docs.ckan.org/en/ckan-2.7.3/api/index.html#ckan.logic.action.get.package_search
+    query_result = toolkit.get_action('package_search')(context, {
+        'fq': fq,
+        'sort': sort,
+        'rows': 1000, 
+    })
 
-def is_latest_resources(resource_format, type, resource_created,latest_created, resource_id, latest_id):
-    if lower(resource_format) == lower(type):
-        return (resource_created > latest_created or (resource_created == latest_created and resource_id > latest_id))
-    else:
-        return False
-
-def get_latest_resources_for_type(collection_name, type):
-    latest_dataset_name = get_latest_dataset(collection_name)
-    latest_dataset = get_package_dict(latest_dataset_name)
-    resource_list = latest_dataset.get('resources')
-    latest_resource = latest_created = latest_id = None
-    for resource in resource_list:
-        resource_format = resource.get('format')
-        resource_created = resource.get('created')
-        resource_id = resource.get('id')
-        if is_latest_resources(resource_format, type, resource_created, latest_created, resource_id, latest_id):
-            latest_id=resource_id
-            latest_created=resource_created
-            latest_resource=resource
-    return latest_resource
-
-#for predecessor and successor
-
-
-
-def get_collection_name_by_dataset(dataset_name):
-    list_rel_dataset = get_relationships(dataset_name)
-    if len(list_rel_dataset):        
-        return list_rel_dataset[0]['object']
-
-def get_collection_title_by_dataset(pkg_dict_dataset):
-    dataset_name = pkg_dict_dataset.get('name')
-    collection_name = get_collection_name_by_dataset(dataset_name)
-    if not collection_name:
-        return None
-    context = None
-    pkg_dict_collection = toolkit.get_action('package_show')(context, {'id': collection_name})
-    if not pkg_dict_collection:
-        return None
-    title_collection = pkg_dict_collection.get('title')
-    return title_collection
-
-def get_all_datasets_belonging_to_collection_by_dataset(dataset_name):
-    collection_name = get_collection_name_by_dataset(dataset_name)
-    if collection_name: 
-        return get_all_datasets_belonging_to_collection(collection_name)
-    return []
-
-def _get_siblings_dicts_with_access(pkg_dict):
-    dataset_name = pkg_dict.get('name')
-    list_of_siblings = get_all_datasets_belonging_to_collection_by_dataset(dataset_name)
-    n_siblings = len(list_of_siblings)
-    if n_siblings>0:
-        siblings_dicts = [get_package_dict(name) for name in list_of_siblings]
-        user_has_access = lambda pkg_dict:helpers.check_access('package_show', pkg_dict)
-        siblings_dicts_with_access = filter(user_has_access, siblings_dicts)
-        return siblings_dicts_with_access
-    return None
+    results = query_result.get('results')
+    datasets_found = results if results else []
+
+    return datasets_found
 
-def _sort_siblings_by_name_and_date(siblings_dicts):
-    '''
-    sort by name first and then by date to have a fallback if dates are the same
-    '''
-    _get_name = lambda pkg_dict:pkg_dict.get('name')
-    _get_issued = lambda pkg_dict:pkg_dict.get('extras').get('issued')
-    siblings_dicts_sorted_by_name = sorted(siblings_dicts, key=_get_name)
-    siblings_dicts_sorted_by_date_issued = sorted(siblings_dicts_sorted_by_name, key=_get_issued)
-    return siblings_dicts_sorted_by_date_issued
-
-def get_successor_and_predecessor_dataset(pkg_dict):
-    dataset_name = pkg_dict.get('name')
-    siblings_dicts_with_access = _get_siblings_dicts_with_access(pkg_dict)
-    if siblings_dicts_with_access:
-        n_siblings = len(siblings_dicts_with_access)
-        siblings_dicts_sorted_by_date_issued = _sort_siblings_by_name_and_date(siblings_dicts_with_access)
-        siblings_names_sorted_by_date_issued = [d['name'] for d in siblings_dicts_sorted_by_date_issued]
-        id_current_dataset = siblings_names_sorted_by_date_issued.index(dataset_name)
-        predecessor_name = (
-            siblings_names_sorted_by_date_issued[id_current_dataset-1] if (id_current_dataset > 0) 
-            else None
-        )
-        successor_name = (
-            siblings_names_sorted_by_date_issued[id_current_dataset+1] if (id_current_dataset < n_siblings-1) 
-            else None
-        )
-    else:
-        predecessor_name, successor_name = None, None
-    return successor_name, predecessor_name
-
-def get_successor_and_predecessor_urls(pkg_dict):
-    successor_name, predecessor_name = get_successor_and_predecessor_dataset(pkg_dict)
-    successor_url, predecessor_url = (
-        helpers.url_for(controller='package', action='read', id=name)
-        if name is not None
-        else None
-        for name in (successor_name, predecessor_name)
-    )
-    return successor_url, predecessor_url
 
-def get_successor(pkg_dict):
-    successor_and_predecessor = get_successor_and_predecessor_urls(pkg_dict)
-    return successor_and_predecessor[0]
+def gather_collection_info(collection_dict, datasets_in_collection, dataset_dict=None):
+    name_first_dataset = datasets_in_collection[0].get('name')
+    url_first_dataset = url_from_id(name_first_dataset)
     
-def get_predecessor(pkg_dict):
-    successor_and_predecessor = get_successor_and_predecessor_urls(pkg_dict)
-    return successor_and_predecessor[1]
-
-#link to latest collection member
-def latest_collection_member_persistent_link(pkg_dict):
-    dataset_name = pkg_dict.get('name')
-    collection_name = get_collection_name_by_dataset(
-        dataset_name=dataset_name
-    )
-    if not collection_name:
-        return None
-    url = helpers.url_for(
+    name_last_dataset = datasets_in_collection[-1].get('name')
+    url_last_dataset = url_from_id(name_last_dataset)
+
+    name_collection = collection_dict.get('name')
+    persistent_link_last_member = url_last_member(name_collection)
+
+    if dataset_dict:
+        name_current_dataset = dataset_dict.get('name')
+        dataset_names = [d.get('name') for d in datasets_in_collection]
+        
+        def get_predecessor():
+            try:
+                id_current = dataset_names.index(name_current_dataset)
+            except ValueError:
+                return None
+            if id_current > 0:
+                return dataset_names[id_current - 1]
+            return None
+        
+        def get_successor():
+            try:
+                id_current = dataset_names.index(name_current_dataset)
+            except ValueError:
+                return None
+            if id_current < len(dataset_names) - 1:
+                return dataset_names[id_current + 1]
+            return None
+        
+        name_predecessor = get_predecessor()
+        url_predecessor = url_from_id(name_predecessor) if name_predecessor else None
+        
+        name_successor = get_successor()
+        url_successor = url_from_id(name_successor) if name_successor else None
+    else:
+        url_predecessor = url_successor = None
+    
+    return {
+        'title': collection_dict.get('title'),
+        'members': datasets_in_collection,
+        'first_member': {
+            'name': name_first_dataset,
+            'url': url_first_dataset,
+        },
+        'last_member': {
+            'name': name_last_dataset,
+            'url': url_last_dataset,
+        },
+        'predecessor': {
+            'url': url_predecessor,
+        },
+        'successor': {
+            'url': url_successor,
+        },
+        'persistent_link_last_member': persistent_link_last_member,
+    }
+
+def url_from_id(package_id):
+    return helpers.url_for(controller='package', action='read', id=package_id)
+
+def url_last_member(name_collection):
+    return helpers.url_for(
         controller='ckanext.odsh.collection.controller:LatestDatasetController', 
         action='latest',
-        id=collection_name
+        id=name_collection
     )
-    return url
\ No newline at end of file
+
+
+def get_latest_dataset(collection_name):
+    collection_info = get_collection_info(collection_name)
+    latest_name = collection_info['last_member']['name']
+    return latest_name
+
+
+def get_latest_resources_for_format(collection_name, resource_format):
+    collection_info = get_collection_info(collection_name)
+    members = collection_info.get('members')
+    if not members:
+        return None
+    latest_dataset = members[-1]
+    resources = latest_dataset.get('resources')
+    if not resources:
+        return None
+    resources_with_asked_type = [r for r in resources if r.get('format').upper() == resource_format.upper()]
+    resources_sorted = sorted(resources_with_asked_type, key=itemgetter('id','created'), reverse=True)
+    return resources_sorted[-1]
diff --git a/ckanext/odsh/collection/plugin.py b/ckanext/odsh/collection/plugin.py
index 2ebcefc1af2c164045af7e861856858644885df9..27a28eccf53d86da24b79a31732bf10c37dc8ff5 100644
--- a/ckanext/odsh/collection/plugin.py
+++ b/ckanext/odsh/collection/plugin.py
@@ -1,6 +1,7 @@
 
 from ckan.lib.plugins import DefaultTranslation, DefaultDatasetForm
-import ckan.plugins as plugins 
+import ckan.plugins as plugins
+import ckan.plugins.toolkit as toolkit
 import helpers as collection_helpers
 from routes.mapper import SubMapper
 
@@ -16,6 +17,9 @@ class CollectionsPlugin(plugins.SingletonPlugin, DefaultDatasetForm):
     
     def is_fallback(self):
         return False
+    
+    def read_template(self):
+        return 'package/collection_read.html'
 
     
     # IRoutes    
@@ -28,7 +32,7 @@ class CollectionsPlugin(plugins.SingletonPlugin, DefaultDatasetForm):
         )
 
         map.connect(
-            '/collection/{id}/aktuell.{type}',
+            '/collection/{id}/aktuell.{resource_format}',
             controller='ckanext.odsh.collection.controller:LatestRecourcesController',
             action='latest_resource'
         )
@@ -39,7 +43,7 @@ class CollectionsPlugin(plugins.SingletonPlugin, DefaultDatasetForm):
             path_prefix='/collection/'
         ) as m:
             m.connect('latest', '{id}/aktuell', action='latest')
-            m.connect('latest_resource', '{id}/aktuell.{type}', action='latest_resource')  
+            m.connect('latest_resource', '{id}/aktuell.{resource_format}', action='latest_resource')  
         return map
 
     def after_map(self, map):
@@ -53,8 +57,8 @@ class CollectionsPlugin(plugins.SingletonPlugin, DefaultDatasetForm):
         # other extensions.
 
         return {
-            'collection_get_successor': collection_helpers.get_successor,
-            'collection_get_predecessor': collection_helpers.get_predecessor,
-            'collection_get_latest_member':collection_helpers.latest_collection_member_persistent_link,
-            'collection_get_title': collection_helpers.get_collection_title_by_dataset,
+            'get_collection': collection_helpers.get_collection,
+            'get_collection_info': collection_helpers.get_collection_info,
+            'url_from_id': collection_helpers.url_from_id,
         }
+    
\ No newline at end of file
diff --git a/ckanext/odsh/controller.py b/ckanext/odsh/controller.py
index 20dbbd1b770a82310889a7adf8ce246535de73ee..daf5d9efba32a5dbb132ca4877dcee7fa1464d01 100644
--- a/ckanext/odsh/controller.py
+++ b/ckanext/odsh/controller.py
@@ -147,7 +147,7 @@ class OdshApiController(ApiController):
         if toolkit.asbool(config.get('ckanext.odsh.log_api_requests', 'false')):
             try:
                 request_data = self._get_request_data(try_url_params=False)
-                log.info('POST request body: {}'.format(json.dumps(json.loads(request_data))))
+                log.info('POST request body: {}'.format(request_data))
             except Exception, e:
                 log.error(e)
         if logic_function == 'resource_qv4yAI2rgotamXGk98gJ':
diff --git a/ckanext/odsh/helper_pkg_dict.py b/ckanext/odsh/helper_pkg_dict.py
index 912b0b83db873b0ed58b98e64ef21b83c3a59e4b..487a8ff5c89933829f2711203c08171df1a31f4a 100644
--- a/ckanext/odsh/helper_pkg_dict.py
+++ b/ckanext/odsh/helper_pkg_dict.py
@@ -102,18 +102,6 @@ class HelperPgkDict(object):
         return None
     
 
-    def get_collection_id(self):
-        '''
-        construct a collection uri from the id of 
-        the containing collection
-        '''
-        package_name = self.pkg_dict.get('name')
-        collection_name = helpers_collection.get_collection_name_by_dataset(package_name)
-        collection_dict = helpers_collection.get_package_dict(collection_name)
-        collection_id = collection_dict.get('id')
-        return collection_id
-
-    
     def add_uri_to_store(self):
         '''
         store pair of uri and id for subsequent use
diff --git a/ckanext/odsh/helpers_tpsh.py b/ckanext/odsh/helpers_tpsh.py
index 608c91016c353ca9de29934c3b06a0c52dd1c4d8..927313ed03fa9623f92f4f7e2018ed425c918891 100644
--- a/ckanext/odsh/helpers_tpsh.py
+++ b/ckanext/odsh/helpers_tpsh.py
@@ -8,6 +8,8 @@ import json
 import re
 import urllib2
 from collections import OrderedDict
+import subprocess
+import os
 
 from ckan.common import config
 import ckan.lib.helpers as helpers
@@ -170,7 +172,11 @@ def get_language_for_selection():
     return dict_for_select_box
 
 def get_package_dict(name):
-    return model.Package.get(name).as_dict()
+    '''
+    raises ckan.logic.NotFound if not found
+    '''
+    package_dict = toolkit.get_action('package_show')(None, {'id': name})
+    return package_dict
 
 def size_of_fmt(num, suffix='B'):
     for unit in ['',' k',' M',' G',' T',' P',' E',' Z']:
@@ -208,4 +214,12 @@ def get_body_mail(organization, package):
     mail_document = "Dokument-ID: " +  package_name + "%0D%0A"
     mail_url = "URL: " +  url + "%0D%0A"  +  "%0D%0A" 
     message =  mail_titel + mail_document  +  mail_url + "Mein Kommentar:" +  "%0D%0A"    +  "%0D%0A"  +  "%0D%0A"  +  "%0D%0A" 
-    return anrede + message
\ No newline at end of file
+    return anrede + message
+
+def git_commit_hash():
+    current_dir = os.path.dirname(os.path.abspath(__file__))
+    try:
+        command = 'git log -n 1 --format=%H'
+    except:
+        return 'unknown'
+    return subprocess.check_output([command], shell=True, cwd=current_dir)
\ No newline at end of file
diff --git a/ckanext/odsh/logic/action.py b/ckanext/odsh/logic/action.py
index 8bca8dd62c81372c2059a43152b46cf29fd9414d..0b4402dbcc986b67f8c91964d4c15820785c0172 100644
--- a/ckanext/odsh/logic/action.py
+++ b/ckanext/odsh/logic/action.py
@@ -15,6 +15,9 @@ import logging
 log = logging.getLogger(__name__)
 
 from ckanext.odsh.setup_proxy import setup_proxy, clear_proxy
+from ckanext.odsh.collection.helpers import get_collection_id, get_package_dict
+from ckanext.odsh.helpers_tpsh import add_pkg_to_collection
+from ckanext.odsh.helpers import odsh_extract_value_from_extras
 
 
 def odsh_package_create(context, data_dict):
@@ -22,15 +25,14 @@ def odsh_package_create(context, data_dict):
     if pkg_type == 'collection':
         return package_create(context, data_dict)
     munge_increment_name(data_dict)
+    related_package_name = data_dict.get('relatedPackage')
+    if related_package_name:
+        new_package = package_create(context, data_dict)
+        add_to_same_collection(related_package_name, new_package['name'])
+        return new_package
     if pkg_type != 'dataset':
         return package_create(context, data_dict)
-    issued = False
-    for extra in data_dict.get('extras'):
-        if extra['key'] == 'issued':
-            issued = True
-            break
-    if not issued:
-        data_dict['extras'].append({'key': 'issued', 'value': datetime.datetime.utcnow().isoformat()})
+    add_issued_if_missing(data_dict)
     return package_create(context, data_dict)
 
 
@@ -47,6 +49,43 @@ def munge_increment_name(data_dict):
         log.debug('name: %s' % name)
         data_dict['name'] = name
 
+    
+def add_issued_if_missing(data_dict):
+    issued = odsh_extract_value_from_extras(data_dict.get('extras'), 'issued')
+    if issued is None:
+        data_dict['extras'].append({'key': 'issued', 'value': datetime.datetime.utcnow().isoformat()})
+    
+
+def add_to_same_collection(related_package_name, package_name):
+    related_package = get_package_dict(related_package_name)
+    collection_id = get_collection_id(related_package)
+    if not collection_id:
+        collection_dict = auto_create_collection(related_package)
+        collection_id = collection_dict.get('id')
+        add_to_collection(collection_id, related_package_name)
+    add_to_collection(collection_id, package_name)
+
+
+def auto_create_collection(related_package):
+    related_package_title = related_package.get('title')
+    owner_org = related_package.get('owner_org')
+    _collection_dict = {
+        'title': related_package_title,
+        'owner_org': owner_org,
+        'type': 'collection',
+    }
+    munge_increment_name(_collection_dict)
+    collection_dict = package_create_via_toolkit(None, _collection_dict)
+    return collection_dict
+
+
+def package_create_via_toolkit(context, package_dict):
+    return toolkit.get_action('package_create')(context, package_dict)
+
+
+def add_to_collection(collection_id, package_name):
+    add_pkg_to_collection(package_name, collection_id)
+
 
 def check_password(password):
     return (len(password) >= 8 and
diff --git a/ckanext/odsh/pdf_to_thumbnail/action.py b/ckanext/odsh/pdf_to_thumbnail/action.py
index bd5d713086311721f593ba276ea9d650f1e77f87..d77845bea62b3b0c99e3afeaeab0d7bccd8b6394 100644
--- a/ckanext/odsh/pdf_to_thumbnail/action.py
+++ b/ckanext/odsh/pdf_to_thumbnail/action.py
@@ -4,8 +4,7 @@ import ckan.lib.helpers as helpers
 from ckan.logic.action.update import package_update
 from ckan.logic.action.delete import package_delete
 
-#from thumbnail
-import thumbnail as thumbnail
+import thumbnail
 
 
 def before_package_delete(context, package_id_dict):
@@ -23,7 +22,7 @@ def before_package_update(context, pkg_dict):
         old_filename = package.get('thumbnail')
         if old_filename:
             if str(old_private) != str(new_private):
-                new_filename = thumbnail.change_filepath(old_filename)
+                new_filename = thumbnail.rename_thumbnail_to_random_name(old_filename)
                 pkg_dict['extras'].append({'key': 'thumbnail', 'value': new_filename})
             elif not pkg_dict.get('thumbnail'): 
                 pkg_dict['extras'].append({'key': 'thumbnail', 'value': old_filename})
diff --git a/ckanext/odsh/pdf_to_thumbnail/plugin.py b/ckanext/odsh/pdf_to_thumbnail/plugin.py
index 715235990b6515a9b9d86a2a4f063c8da5e2bf7e..ecd4fdefda6bdd1054ba6e90efd9910ae9b66c7b 100644
--- a/ckanext/odsh/pdf_to_thumbnail/plugin.py
+++ b/ckanext/odsh/pdf_to_thumbnail/plugin.py
@@ -1,6 +1,5 @@
 import os 
 
-
 #from ckan
 import ckan.plugins as plugins
 
@@ -21,15 +20,16 @@ class ThumbnailPlugin(plugins.SingletonPlugin):
 
 
 #IResourceController
-    def after_create(self, context, resource):        
-        _, filename = thumbnail.create_thumbnail(context, resource)
-        thumbnail.write_thumbnail_into_package(context, resource, filename)
+    def after_create(self, context, resource):
+        resources = thumbnail.resources_of_containing_package(resource)
+        thumbnail.create_thumbnail_if_none_in_package(context, resources)
         
     def after_update(self, context, resource):
-        thumbnail.check_and_create_thumbnail_after_update(context, resource)
+        resources = thumbnail.resources_of_containing_package(resource)
+        thumbnail.create_thumbnail_if_none_in_package(context, resources)
                 
     def after_delete(self, context, resources):
-        thumbnail.create_thumbnail_for_last_resource(context, resources)
+        thumbnail.create_thumbnail_if_none_in_package(context, resources)
             
 #IConfigurer 
 
diff --git a/ckanext/odsh/pdf_to_thumbnail/thumbnail.py b/ckanext/odsh/pdf_to_thumbnail/thumbnail.py
index ea0f06b32a7d8eba85a6ca9a486ebf1b7bf009b6..7a39dea04a486e5c8d3ee820a854ac0e67883c68 100644
--- a/ckanext/odsh/pdf_to_thumbnail/thumbnail.py
+++ b/ckanext/odsh/pdf_to_thumbnail/thumbnail.py
@@ -7,171 +7,221 @@ from ckan.common import config
 import urllib2
 import requests
 
-import binascii
+from binascii import b2a_hex
 import ckan.plugins.toolkit as toolkit
 import ckan.logic as logic
 #from extension
 #from ckanext.odsh.lib.uploader import raise_validation_error_if_virus_found
 
 log = logging.getLogger(__name__)
- 
 
-def get_filename_from_context(context):
+
+def create_thumbnail(context, resource):
+    '''
+    main entry point into this module
+    this function is called from pdf_to_thumbnail.plugin
+    '''
+    old_filename = _get_filename_from_context(context)
+    url_type = resource.get('url_type')
+    if url_type == 'upload':
+        is_PDF, filename = _create_thumbnail_from_memory(resource, old_filename)
+    else:
+        is_PDF, filename = (False, None)
+    return is_PDF, filename  
+
+
+def _get_filename_from_context(context):
     package = context.get('package')
     package_id = package.id
-    package= toolkit.get_action('package_show')(context, {'id': package_id})
+    package= toolkit.get_action('package_show')(None, {'id': package_id})
     thumbnail = package.get('thumbnail') 
     return  thumbnail
 
-def get_filepath_for_thumbnail(filename):
-    if filename:
-        return config.get('ckan.storage_path') + "/thumbnail/" + filename
-    return config.get('ckan.storage_path') + "/thumbnail/"
 
-def concatenate_filename(filename):
-    return filename + ".jpg"
+def _create_thumbnail_from_memory(resource, old_filename):
+    filepath = get_resource_path(resource)
+    is_PDF = _is_pdf(filepath)
+    if is_PDF:
+        with open(filepath, 'rb') as file:
+            new_filename = _create_thumbnail_from_file(file)
+        if old_filename:
+            ThumbnailPath.from_filename(old_filename).remove()
+        return is_PDF, new_filename
+    else:
+        return is_PDF, None
+
 
-def get_filepath_to_resource(resource):
+def get_resource_path(resource):
+    # see https://stackoverflow.com/questions/46572402/where-does-ckan-store-the-files-pushed-to-datastore-filestore
     resource_id = resource.get('id')
-    directory = config.get('ckan.storage_path') + '/resources/'
-    #looked up how resources are saved, by locating the keyword resources in the OS 
-    path = directory + resource_id[0:3] + '/' + resource_id[3:6] + '/' +  resource_id[6:]
-    return path
-
-def random_filename():
-    number = binascii.b2a_hex(os.urandom(15))
-    filename = 'thumbnail_picture_' + str(number)    
-    full_filename = concatenate_filename(filename)
-    filepath = get_filepath_for_thumbnail(full_filename)
-    if os.path.exists(filepath):
-        filename = random_filename()
-    return filename
-
-def change_filepath(old_filename):    
-    old_filepath = get_filepath_for_thumbnail(old_filename)
-    new_filename = concatenate_filename(random_filename())
-    new_filepath = get_filepath_for_thumbnail(new_filename)
-    try:
-        os.renames(old_filepath, new_filepath)
-        return new_filename
-    except OSError:
-        log.warning('The file path "{}"  of package was not found.'.format(old_filepath))
-     
+    filepath = os.path.join(
+        config.get('ckan.storage_path'),
+        'resources',
+        resource_id[0:3],
+        resource_id[3:6],
+        resource_id[6:]
+    )
+    return filepath
+
+
+def _is_pdf(filepath):
+    file_type = magic.from_file(filepath, mime = True)
+    return file_type == 'application/pdf'
+
 
-def create_thumbnail_from_file(file, old_filename):
+def _create_thumbnail_from_file(file):
     width = config.get('ckan.thumbnail.size.width', 410)
-    filename = random_filename()
+    new_thumbnail = ThumbnailPath.from_unique_random_name()
     file.seek(0)
     file_read = file.read()
-    directory = get_filepath_for_thumbnail('')
-    if old_filename:
-        old_filepath = get_filepath_for_thumbnail(concatenate_filename(old_filename))
-        if os.path.exists(old_filepath):
-            os.remove(old_filepath)
-    convert_from_bytes(file_read,
-                       size=(width, None),
-                       output_folder=directory,
-                       output_file=filename,
-                       single_file=True,
-                       first_page=0,
-                       last_page=0,
-                       fmt='jpg'
-                       )
-    return concatenate_filename(filename)
-
-
-def create_thumbnail_from_url(resource, old_filename):
-    return False, None
-'''
-    #  Requests does not work on a server but on a local test enviroment.
-    #  With the given configuration of a wsgi server and apache it did not work
-    #  Both libraries urllib2 and  requests (based on urllib3) returned a timeout. 
- 
-
-    resource_url = resource.get('url')
-    request = urllib2.Request(resource_url)
-    response = urllib2.urlopen(request, timeout = 100000) 
-    
-    
-    if response.code == 200:
-        filetowrite = response.read()
-        # function is set to private in ckanext.odsh.lib.uploader
-        raise_validation_error_if_virus_found(filetowrite, response.read())
-        file_type = magic.from_buffer(response.read(), mime = True)
-        header = response.headers
-        resource_size = header.get('Content-Length')
-        
-            
-        max_available_memory = config.get('ckan.max_available_memory', 250000000)  #In Bytes ca. 250 MB
-        with tempfile.SpooledTemporaryFile(max_size=max_available_memory) as file:
-            file.write(filetowrite)
-            
-            new_filename = create_thumbnail_from_file(file, old_filename)        
-            return 'is PDF', resource_size, new_filename
-'''
-
-
-def create_thumbnail_from_memory(resource, old_filename):
-    path = get_filepath_to_resource(resource)
-    file_type = magic.from_file(path, mime = True)
-    if file_type == 'application/pdf':
-        with open(path, 'rb') as file:
-            new_filename = create_thumbnail_from_file(file, old_filename)
-        is_PDF = True
-        return is_PDF, new_filename
-    else:
-        is_PDF = False
-        return is_PDF,  None
+    convert_from_bytes(
+        file_read,
+        size=(width, None),
+        output_folder=new_thumbnail.folder,
+        output_file=new_thumbnail.filename,
+        single_file=True,
+        first_page=0,
+        last_page=0,
+        fmt='jpg'
+    )
+    return new_thumbnail.filename_with_extension
+
+
+def thumbnail_folder():
+    return os.path.join(
+        config.get('ckan.storage_path'),
+        'thumbnail',
+    )
+
+
+def rename_thumbnail_to_random_name(old_filename):
+    '''
+    used by pdf_to_thumbnail.action
+    '''
+    old_filepath = ThumbnailPath.from_filename_with_extension(old_filename)
+    new_filepath = ThumbnailPath.from_unique_random_name()
+    try:
+        os.renames(old_filepath.full_filename, new_filepath.full_filename)
+        return new_filepath.filename_with_extension
+    except OSError:
+        log.warning('The file path "{}"  of package was not found.'.format(old_filepath))
+     
 
 def remove_thumbnail(context):
-    old_filename = get_filename_from_context(context)
+    '''
+    used by pdf_to_thumbnail.action
+    '''
+    old_filename = _get_filename_from_context(context)
     if old_filename:
-        old_filepath = get_filepath_for_thumbnail(old_filename)
-        if os.path.exists(old_filepath):
-            os.remove(old_filepath)
+        ThumbnailPath.from_filename_with_extension(old_filename).remove()
 
-def create_thumbnail(context, resource):
-    old_filename = get_filename_from_context(context)
-    url_type = resource.get('url_type')
-    if url_type == 'upload':
-        is_PDF,  filename = create_thumbnail_from_memory(resource, old_filename)
-    else:
-        is_PDF,  filename = create_thumbnail_from_url(resource, old_filename)
-    return is_PDF,  filename   
 
-def check_and_create_thumbnail_after_update(context, resource):
+def resources_of_containing_package(resource):
+    #todo: change arg order
+    '''
+    used by pdf_to_thumbnail.plugin
+    '''
     package_id = resource.get('package_id')
-    package = toolkit.get_action('package_show')(context, {'id': package_id})
+    package = toolkit.get_action('package_show')(None, {'id': package_id})
     resources = package.get('resources')
-    if len(resources) > 0:
-        last_resource = resources.pop()
-        last_resource_id = last_resource.get('id')
-        resource_id = resource.get('id')
-    if last_resource_id == resource_id and resource.get('url_type') != 'upload':
-        is_PDF,  filename = create_thumbnail(context, resource)
-        if is_PDF:
-            write_thumbnail_into_package(context, resource, filename)  
+    return resources
         
 
-def create_thumbnail_for_last_resource(context, resources):
-    if len(resources) > 0:
-        last_resource = resources.pop()
-        is_PDF, filename = create_thumbnail(context, last_resource)
-        if not is_PDF:
-            create_thumbnail_for_last_resource(context, resources)
+def create_thumbnail_if_none_in_package(context, resources):
+    '''
+    used by pdf_to_thumbnail.plugin
+    loops through a package's resources in the order they have been uploaded
+    and for each tries to create a thumbnail until it succeeds.
+    If the package already has a thumbnail the creation step is skipped
+    '''
+    package_dict = _get_package_dict_from_context(context)
+    if not _has_thumbnail(package_dict):
+        any(_try_create_thumbnail(context, r) for r in resources)
+
+
+def _get_package_dict_from_context(context):
+    package_id = context.get('package').id
+    package_dict = toolkit.get_action('package_show')(None, {'id': package_id})
+    return package_dict
+
+
+def _has_thumbnail(package_dict):
+    thumbnail = package_dict.get('thumbnail')
+    return bool(thumbnail)
+
+
+def _try_create_thumbnail(context, resource):
+    is_PDF, filename = create_thumbnail(context, resource)
+    success = is_PDF
+    if success:
+        _write_thumbnail_into_package(context, filename)
+    return success
+
+
+def _write_thumbnail_into_package(context, filename):
+    package_dict = _get_package_dict_from_context(context)
+    if filename:
+        package_dict.update({'thumbnail': filename})
+    toolkit.get_action('package_update')(None, package_dict)
+    
+
+class ThumbnailPath(object):
+    '''
+    utility class to manage the path of thumbnail pictures
+    '''
+
+    def __init__(self, folder, filename, extension):
+        self.folder = folder
+        self.filename = filename
+        self.extension = extension
+    
+    _EXTENSION = '.jpg'
+    
+    @staticmethod
+    def from_filename(filename):
+        '''
+        filename without extension (i.e. '.jpg')
+        '''
+        return ThumbnailPath(thumbnail_folder(), filename, ThumbnailPath._EXTENSION)
+    
+    @staticmethod
+    def from_filename_with_extension(filename_with_extension):
+        '''
+        limited to one dot in filename
+        '''
+        tokens = filename_with_extension.split('.')
+        if len(tokens) == 1:
+            filename = filename_with_extension
+            extension = ''
         else:
-            write_thumbnail_into_package(context, last_resource, filename)
-    else:
-        remove_thumbnail(context)
-        package = context.get('package')
-        package_id = package.id
-        package= toolkit.get_action('package_show')(context, {'id': package_id})
-        package.update({'thumbnail': None})
-        toolkit.get_action('package_update')(context, package)
-
-def write_thumbnail_into_package(context, resource, filename):
-        package_id = resource.get('package_id')
-        package = toolkit.get_action('package_show')(context, {'id': package_id})
-        if filename:
-            package.update({'thumbnail': filename})
-        toolkit.get_action('package_update')(context, package)
\ No newline at end of file
+            filename = '.'.join(tokens[:-1])
+            extension = '.'.join(['', tokens[-1]])
+        return ThumbnailPath(thumbnail_folder(), filename, extension)
+
+    @staticmethod
+    def from_unique_random_name():
+        thumbnail_path = ThumbnailPath._from_random_name()
+        if thumbnail_path.exists():
+            return ThumbnailPath.from_unique_random_name()
+        return thumbnail_path
+    
+    @staticmethod
+    def _from_random_name():
+        number = b2a_hex(os.urandom(15))
+        filename = 'thumbnail_picture_' + str(number)
+        return ThumbnailPath.from_filename(filename)
+    
+    @property
+    def filename_with_extension(self):
+        return self.filename + self.extension
+    
+    @property
+    def full_filename(self):
+        return os.path.join(self.folder, self.filename_with_extension)
+    
+    def exists(self):
+        return os.path.exists(self.full_filename)
+    
+    def remove(self):
+        if os.path.exists(self.full_filename):
+            os.remove(self.full_filename)
diff --git a/ckanext/odsh/plugin.py b/ckanext/odsh/plugin.py
index 4509a5982fff9a9edde8c99a9fa325839d144fe3..bd7c1688ead2feaf94a1f71f9b4ca484f5b45f08 100644
--- a/ckanext/odsh/plugin.py
+++ b/ckanext/odsh/plugin.py
@@ -133,7 +133,14 @@ class OdshPlugin(plugins.SingletonPlugin, DefaultTranslation, DefaultDatasetForm
                 toolkit.get_validator('ignore_missing'),
                 toolkit.get_converter('convert_to_extras')
             ],
-
+            'relatedPackage': [
+                toolkit.get_validator('tpsh_validate_relatedPackage'),
+                toolkit.get_converter('convert_to_extras')
+            ],
+            'accessibility': [
+                toolkit.get_validator('ignore_missing'),
+                toolkit.get_converter('convert_to_extras')
+            ]
         })
         return schema
     
@@ -151,6 +158,14 @@ class OdshPlugin(plugins.SingletonPlugin, DefaultTranslation, DefaultDatasetForm
                 toolkit.get_converter('convert_from_extras'),
                 toolkit.get_validator('ignore_missing')
             ],
+            'relatedPackage': [
+                toolkit.get_converter('convert_from_extras'),
+                toolkit.get_validator('ignore_missing')
+            ],
+            'accessibility': [
+                toolkit.get_converter('convert_from_extras'),
+                toolkit.get_validator('ignore_missing')
+            ],
         })
         return schema
 
@@ -328,44 +343,46 @@ class OdshPlugin(plugins.SingletonPlugin, DefaultTranslation, DefaultDatasetForm
         # Template helper function names should begin with the name of the
         # extension they belong to, to avoid clashing with functions from
         # other extensions.
-        return {'odsh_main_groups': odsh_helpers.odsh_main_groups,
-                'odsh_now': odsh_helpers.odsh_now,
-                'odsh_group_id_selected': odsh_helpers.odsh_group_id_selected,
-                'odsh_get_facet_items_dict': odsh_helpers.odsh_get_facet_items_dict,
-                'odsh_openness_score_dataset_html': odsh_helpers.odsh_openness_score_dataset_html,
-                'odsh_get_resource_details': odsh_helpers.odsh_get_resource_details,
-                'odsh_get_resource_views': odsh_helpers.odsh_get_resource_views,
-                'odsh_get_bounding_box': odsh_helpers.odsh_get_bounding_box,
-                'odsh_get_spatial_text': odsh_helpers.odsh_get_spatial_text,
-                'odsh_render_datetime': odsh_helpers.odsh_render_datetime,
-                'odsh_upload_known_formats': odsh_helpers.odsh_upload_known_formats,
-                'odsh_encodeurl': odsh_helpers.odsh_encodeurl,
-                'odsh_extract_error': odsh_helpers.odsh_extract_error,
-                'odsh_extract_error_new': odsh_helpers.odsh_extract_error_new,
-                'odsh_extract_value_from_extras': odsh_helpers.odsh_extract_value_from_extras,
-                'odsh_create_checksum': odsh_helpers.odsh_create_checksum,
-                'presorted_license_options': odsh_helpers.presorted_license_options,
-                'odsh_tracking_id': odsh_helpers.odsh_tracking_id,
-                'odsh_tracking_url': odsh_helpers.odsh_tracking_url,
-                'odsh_has_more_facets': odsh_helpers.odsh_has_more_facets,
-                'odsh_public_url': odsh_helpers.odsh_public_url,
-                'odsh_spatial_extends_available': odsh_helpers.spatial_extends_available,
-                'odsh_public_resource_url': odsh_helpers.odsh_public_resource_url,
-                'odsh_get_version_id': odsh_helpers.odsh_get_version_id,
-                'odsh_show_testbanner': odsh_helpers.odsh_show_testbanner,
-                'odsh_is_slave': odsh_helpers.odsh_is_slave,
-                'odsh_use_matomo': helpers_tpsh.use_matomo,
-                'tpsh_get_daterange_prettified': helper_pkg_dict.get_daterange_prettified,
-                'tpsh_get_language_of_package': helpers_tpsh.get_language_of_package,
-                'get_language_icon': helpers_tpsh.get_language_icon,
-                'short_name_for_category': odsh_helpers.short_name_for_category,
-                'get_spatial_for_selection': helpers_tpsh.get_spatial_for_selection,
-                'get_subject_for_selection': helpers_tpsh.get_subject_for_selection,
-                'get_language_for_selection': helpers_tpsh.get_language_for_selection,
-                'tpsh_get_resource_size': helpers_tpsh.get_resource_size,
-                'tpsh_get_address_org':helpers_tpsh.get_address_org,
-                'tpsh_get_body_mail':helpers_tpsh.get_body_mail,
-                }
+        return {
+            'odsh_main_groups': odsh_helpers.odsh_main_groups,
+            'odsh_now': odsh_helpers.odsh_now,
+            'odsh_group_id_selected': odsh_helpers.odsh_group_id_selected,
+            'odsh_get_facet_items_dict': odsh_helpers.odsh_get_facet_items_dict,
+            'odsh_openness_score_dataset_html': odsh_helpers.odsh_openness_score_dataset_html,
+            'odsh_get_resource_details': odsh_helpers.odsh_get_resource_details,
+            'odsh_get_resource_views': odsh_helpers.odsh_get_resource_views,
+            'odsh_get_bounding_box': odsh_helpers.odsh_get_bounding_box,
+            'odsh_get_spatial_text': odsh_helpers.odsh_get_spatial_text,
+            'odsh_render_datetime': odsh_helpers.odsh_render_datetime,
+            'odsh_upload_known_formats': odsh_helpers.odsh_upload_known_formats,
+            'odsh_encodeurl': odsh_helpers.odsh_encodeurl,
+            'odsh_extract_error': odsh_helpers.odsh_extract_error,
+            'odsh_extract_error_new': odsh_helpers.odsh_extract_error_new,
+            'odsh_extract_value_from_extras': odsh_helpers.odsh_extract_value_from_extras,
+            'odsh_create_checksum': odsh_helpers.odsh_create_checksum,
+            'presorted_license_options': odsh_helpers.presorted_license_options,
+            'odsh_tracking_id': odsh_helpers.odsh_tracking_id,
+            'odsh_tracking_url': odsh_helpers.odsh_tracking_url,
+            'odsh_has_more_facets': odsh_helpers.odsh_has_more_facets,
+            'odsh_public_url': odsh_helpers.odsh_public_url,
+            'odsh_spatial_extends_available': odsh_helpers.spatial_extends_available,
+            'odsh_public_resource_url': odsh_helpers.odsh_public_resource_url,
+            'odsh_get_version_id': odsh_helpers.odsh_get_version_id,
+            'odsh_show_testbanner': odsh_helpers.odsh_show_testbanner,
+            'odsh_is_slave': odsh_helpers.odsh_is_slave,
+            'odsh_use_matomo': helpers_tpsh.use_matomo,
+            'tpsh_get_daterange_prettified': helper_pkg_dict.get_daterange_prettified,
+            'tpsh_get_language_of_package': helpers_tpsh.get_language_of_package,
+            'get_language_icon': helpers_tpsh.get_language_icon,
+            'short_name_for_category': odsh_helpers.short_name_for_category,
+            'get_spatial_for_selection': helpers_tpsh.get_spatial_for_selection,
+            'get_subject_for_selection': helpers_tpsh.get_subject_for_selection,
+            'get_language_for_selection': helpers_tpsh.get_language_for_selection,
+            'tpsh_get_resource_size': helpers_tpsh.get_resource_size,
+            'tpsh_get_address_org':helpers_tpsh.get_address_org,
+            'tpsh_get_body_mail':helpers_tpsh.get_body_mail,
+            'tpsh_git_commit_hash': helpers_tpsh.git_commit_hash,
+        }
 
     
     # IValidators
diff --git a/ckanext/odsh/profiles/odsh_dcat_de_profile.py b/ckanext/odsh/profiles/odsh_dcat_de_profile.py
index 7daa2732f5372d2ccd3f0685c40553d94ff10111..d4db9e11a2d64da660feaa7174e5e935b20808ff 100644
--- a/ckanext/odsh/profiles/odsh_dcat_de_profile.py
+++ b/ckanext/odsh/profiles/odsh_dcat_de_profile.py
@@ -10,7 +10,6 @@ from ckanext.dcatde.profiles import DCATdeProfile, DCATDE, DCAT, DCATDE_1_0
 
 import ckanext.odsh.helpers_tpsh as helpers_tpsh
 import ckanext.odsh.collection.helpers as helpers_collection
-from ckanext.odsh.helper_pkg_dict import HelperPgkDict
 
 
 DCT = rdflib.namespace.Namespace("http://purl.org/dc/terms/")
@@ -154,9 +153,7 @@ class ODSHDCATdeProfile(DCATdeProfile):
         return is_collection
     
     def _get_dataset_refs_belonging_to_collection(self, dataset_dict):
-        dataset_names = helpers_collection.get_all_datasets_belonging_to_collection(
-            collection_name = dataset_dict.get('id')
-        )
+        dataset_names = helpers_collection.get_dataset_names(dataset_dict)
         dataset_dicts = [model.Package.get(name).as_dict() for name in dataset_names]
         dataset_ids = [dataset_dict.get('id') for dataset_dict in dataset_dicts]
         dataset_refs = [self._construct_refs(id) for id in dataset_ids]
@@ -175,12 +172,11 @@ class ODSHDCATdeProfile(DCATdeProfile):
         '''
         if dataset_dict.get('type')=='collection':
             return False
-        id_dataset = dataset_dict.get('id')
-        collection_name = helpers_collection.get_collection_name_by_dataset(id_dataset)
+        collection_name = helpers_collection.get_collection_id(dataset_dict)
         return collection_name is not None
 
     def _add_collection(self, dataset_dict, dataset_ref):
-        collection_id = HelperPgkDict(dataset_dict).get_collection_id()
+        collection_id = helpers_collection.get_collection_id(dataset_dict)
         collection_uri = self._construct_refs(collection_id)
         self.g.set(
             (dataset_ref, DCT.isVersionOf, 
diff --git a/ckanext/odsh/public/odsh.css b/ckanext/odsh/public/odsh.css
index 47a567da70b96179e763434aadc828e8d5ee3194..ddc13115e51f999a1f0ffdf7c128947a705ad147 100644
--- a/ckanext/odsh/public/odsh.css
+++ b/ckanext/odsh/public/odsh.css
@@ -1269,6 +1269,7 @@ body {
 .footer-icon{
     float: left;
     margin-right: 40px;
+    margin-bottom: 1rem;
 }
 
 .footer-icon.last{
@@ -1313,7 +1314,7 @@ body {
         box-sizing: border-box;
         padding-bottom: 15px;
         background-image: none;
-        height: 139px;
+        height: 150px;
         display: flex;
         flex-direction: column-reverse;
     }
@@ -1325,6 +1326,7 @@ body {
         display: flex;
         justify-content: space-between;
         padding-bottom: 15px;
+        flex-wrap: wrap;
     }
     .footer-icon a{
         padding: 15px 0;
@@ -1452,6 +1454,24 @@ body {
     color: #000000;
 }
 
+.hint-newer-version {
+    margin-top: 30px;
+    margin-bottom: 30px;
+    font-size: 20px;
+    color: white;
+    background-color: #D4004B;
+    padding: 0.5rem 1rem;
+}
+
+.hint-newer-version > a {
+    color: white;
+    text-decoration: underline;
+}
+
+.hint-newer-version > a:hover {
+    color: lightgray;
+}
+
 .resource-list {
     margin: 0px;
 }
@@ -2768,4 +2788,8 @@ body.filters-modal div.row > aside.secondary.span3 {
         height: auto;
         margin-right: 6px;
     }
-}
\ No newline at end of file
+}
+
+.tpsh-collection-list {
+    list-style-type: none;
+}
diff --git a/ckanext/odsh/templates/footer.html b/ckanext/odsh/templates/footer.html
index 03764a7c7b58782fb85faee0af0af365732205df..f719b26b265cfaba70b05dfc3b95888819a888b0 100644
--- a/ckanext/odsh/templates/footer.html
+++ b/ckanext/odsh/templates/footer.html
@@ -12,6 +12,7 @@
             <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'><a href='https://www.schleswig-holstein.de/DE/Serviceseiten/Barrierefreiheitserklaerung/barrierefreiheit_node.html'>Barrierefreiheit</a></div>
                 <div class='footer-icon last'><a href='http://www.schleswig-holstein.de/tpdatenschutz'>Datenschutz</a></div>
             </div>
         </div>
diff --git a/ckanext/odsh/templates/package/collection_read.html b/ckanext/odsh/templates/package/collection_read.html
new file mode 100644
index 0000000000000000000000000000000000000000..7f1ef070954d4df8b125becfd81a30213ef81134
--- /dev/null
+++ b/ckanext/odsh/templates/package/collection_read.html
@@ -0,0 +1,22 @@
+{% extends "package/read.html" %}
+
+
+{% block package_resources %}
+{% endblock package_resources %}
+
+
+{% block collection %}
+
+{% set collection_id = pkg.get('id') %}
+{% set collection = h.get_collection_info(collection_id) %}
+{% set collection_title = collection['title'] if collection else None %}
+
+{% if collection['members'] %}
+<p>
+    Dies ist die Übersichtsseite der Dokumentensammlung {{ collection_title }}. 
+    Sie enthält folgende Dokumente (neuere Dokumente zuerst):
+</p>
+{% snippet 'snippets/package_list.html', packages=collection['members'] | reverse() %}
+{% endif %}
+
+{% endblock collection %}
diff --git a/ckanext/odsh/templates/package/read.html b/ckanext/odsh/templates/package/read.html
index d057aa20f97049e6ba6a1fb00a53bbc81c7e9b70..fd94229c593a5ceb09e219f32f13b9babeff6458 100644
--- a/ckanext/odsh/templates/package/read.html
+++ b/ckanext/odsh/templates/package/read.html
@@ -1,10 +1,11 @@
 {% extends "package/read_base.html" %}
 
 {% set pkg = c.pkg_dict %}
-{% set collection_title = h.collection_get_title(pkg) %}
-{% set successor_url = h.collection_get_successor(pkg) %}
-{% set predecessor_url = h.collection_get_predecessor(pkg) %}
-{% set latest_collection_member = h.collection_get_latest_member(pkg) %}
+{% set collection = h.get_collection(pkg) %}
+{% set collection_title = collection['title'] if collection else None %}
+{% set successor_url = collection['successor']['url'] if collection else None %}
+{% set predecessor_url = collection['predecessor']['url'] if collection else None %}
+{% set latest_collection_member = collection['persistent_link_last_member'] if collection else None %}
 {% set thumbnail_url = h.thumbail_get_download_link(pkg) %}
 
 {% block breadcrumb_content %}
@@ -22,7 +23,7 @@
 <li>{% link_for _('Documents'), controller='package', action='search' %}</li>
 <li class="active"><a href="">{{ _('Create Dataset') }}</a></li>
 {% endif %}
-{% endblock %}
+{% endblock breadcrumb_content %}
 
 {% block primary_content_inner %}
 {{ super() }}
@@ -37,7 +38,7 @@
     {% if pkg.state == 'deleted' %}
     [{{ _('Deleted') }}]
     {% endif %}
-    {% endblock %}
+    {% endblock page_heading%}
     <div class="odsh-dataset-edit-button">
       {% if h.check_access('package_update', {'id':pkg.id }) %}
       <div>
@@ -61,6 +62,12 @@
     <img src="/base/images/icon_info.svg" aria-hidden="true"></img>
     {{ _('Detailinformationen') }}
 </div>
+{% if successor_url %}
+<p class="hint-newer-version">
+    Hinweis: Es ist eine <a href="{{ latest_collection_member }}">
+    neuere Version</a> dieses Dokuments verfügbar.
+</p>
+{% endif %}
 {% if pkg.notes %}
 <div class="notes embedded-content">
   {{ h.render_markdown(h.get_translated(pkg, 'notes')) }}
@@ -71,7 +78,7 @@
 
 {% block package_resources %}
 {% snippet "package/snippets/resources_list.html", pkg=pkg, resources=pkg.resources %}
-{% endblock %}
+{% endblock package_resources %}
 
 {% block thumbnail %}
 {% set thumbnail =  pkg.get('thumbnail') %}
@@ -139,5 +146,5 @@
 {% endfor %}
 </div>
 
-{% endblock %}
+{% endblock primary_content_inner %}
 
diff --git a/ckanext/odsh/templates/snippets/package_item.html b/ckanext/odsh/templates/snippets/package_item.html
index 419e685bc6eedfcb77f671a559ec19f408fbee75..6448db8f4582829b7620110bda14e20f865c414c 100644
--- a/ckanext/odsh/templates/snippets/package_item.html
+++ b/ckanext/odsh/templates/snippets/package_item.html
@@ -36,7 +36,7 @@ Example:
     <div class="preview-image-container">
             {% if thumbnail %}
             <a href={{  h.url_for(controller='package', action='read', id=package.name) }}>
-                <img src= "{{ thumbnail }}" alt= "Vorschau" />
+                <img src= "/{{ thumbnail }}" alt= "Vorschau" />
             </a>
 
             {% else %}
diff --git a/ckanext/odsh/templates/user/login.html b/ckanext/odsh/templates/user/login.html
index 874fd1bf692c79658d66e7946c0a69d8eb6f4e3e..d9fb0ad86d0f83803f17eba3fc3175d1825e733d 100644
--- a/ckanext/odsh/templates/user/login.html
+++ b/ckanext/odsh/templates/user/login.html
@@ -1,7 +1,16 @@
 {% extends "page.html" %}
 
+{% set commit_hash = h.tpsh_git_commit_hash() %}
+
 {% block subtitle %} {{ _('Login') }} {% endblock %}
 
+{% block skip %}
+<p hidden>
+    version={{ commit_hash }}
+</p>
+{{ super() }}
+{% endblock skip %}
+
 {% block breadcrumb_content %}
 <li class="active">{{ h.nav_link(_('Login'), controller='user', action='login') }}</li>
 {% endblock %}
@@ -30,4 +39,4 @@
     {% endblock %}
 </section>
 {% endblock %}
-{% endblock %}
\ No newline at end of file
+{% endblock %}
diff --git a/ckanext/odsh/tests_tpsh/_test_thumbnail_action.py b/ckanext/odsh/tests_tpsh/_test_thumbnail_action.py
new file mode 100644
index 0000000000000000000000000000000000000000..ec7165f0df6ff289047a2e13c6b901b251a37625
--- /dev/null
+++ b/ckanext/odsh/tests_tpsh/_test_thumbnail_action.py
@@ -0,0 +1,11 @@
+import ckanext.odsh.pdf_to_thumbnail.action as thumbnail_action 
+
+class TestBeforePackageDelete(object):
+    def test_deletes_if_access_granted(self):
+        # todo: implement unit test
+        assert True
+
+class TestBeforePackageUpdate(object):
+    def test_does_whatever(self):
+        # todo: implement unit test
+        assert True
\ No newline at end of file
diff --git a/ckanext/odsh/tests_tpsh/resources/ki_strategie.jpg b/ckanext/odsh/tests_tpsh/resources/ki_strategie.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..2c88dbd220cee45db52655f18ea4a6213e8a954f
Binary files /dev/null and b/ckanext/odsh/tests_tpsh/resources/ki_strategie.jpg differ
diff --git a/ckanext/odsh/tests_tpsh/resources/ki_strategie.pdf b/ckanext/odsh/tests_tpsh/resources/ki_strategie.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..e25ddb95a1e95769c32507c8fb61b5dc7eb5dd6c
Binary files /dev/null and b/ckanext/odsh/tests_tpsh/resources/ki_strategie.pdf differ
diff --git a/ckanext/odsh/tests_tpsh/resources/ki_strategie_jz.pdf b/ckanext/odsh/tests_tpsh/resources/ki_strategie_jz.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..50b4e1db2f7bd402ce2faf99b2434d328a913942
Binary files /dev/null and b/ckanext/odsh/tests_tpsh/resources/ki_strategie_jz.pdf differ
diff --git a/ckanext/odsh/tests_tpsh/resources/test_copy_pdf b/ckanext/odsh/tests_tpsh/resources/test_copy_pdf
new file mode 100644
index 0000000000000000000000000000000000000000..10451cf0fba2fea7ac87ae52cc9b350b48c67198
Binary files /dev/null and b/ckanext/odsh/tests_tpsh/resources/test_copy_pdf differ
diff --git a/ckanext/odsh/tests_tpsh/test_action.py b/ckanext/odsh/tests_tpsh/test_action.py
new file mode 100644
index 0000000000000000000000000000000000000000..a652c16a9a39cd0c0863ab5f534a893da60a9dfd
--- /dev/null
+++ b/ckanext/odsh/tests_tpsh/test_action.py
@@ -0,0 +1,207 @@
+import nose.tools as nt
+from mock import patch
+from copy import deepcopy
+
+import ckanext.odsh.logic.action as action
+
+
+patch_package_create = patch.object(
+    action,
+    'package_create',
+    return_value={'id': 'new_package_id', 'name': 'new_package_name'},
+)
+
+patch_munge_increment_name = patch.object(
+    action,
+    'munge_increment_name',
+)
+
+patch_add_to_collection = patch.object(
+    action,
+    'add_to_same_collection',
+)
+
+
+class TestOdshPackageCreate(object):
+
+    @patch_package_create
+    def test_it_calls_package_create_for_collection(self, mock):
+        action.odsh_package_create('context', {'type': 'collection'})
+        mock.assert_called_with('context', {'type': 'collection'})
+
+    @patch_package_create
+    @patch_munge_increment_name
+    def test_it_does_not_call_munge_increment_name_for_collection(self, mock_min, mock_pc):
+        action.odsh_package_create('context', {'type': 'collection'})
+        nt.assert_false(mock_min.called)
+
+    @patch_package_create
+    @patch_munge_increment_name
+    def test_it_calls_munge_increment_name_for_dataset(self, mock_min, mock_pc):
+        action.odsh_package_create(
+            'context', {'type': 'dataset', 'extras': []})
+        nt.assert_true(mock_min.called)
+
+    @patch_package_create
+    @patch_munge_increment_name
+    def test_does_not_modify_dict_if_extras_contain_issued(self, mock_min, mock_pc):
+        dataset_dict = {
+            'type': 'dataset',
+            'extras': [
+                {'key': 'issued', 'value': 'some_date'}
+            ],
+        }
+        action.odsh_package_create('context', deepcopy(dataset_dict))
+        mock_pc.assert_called_with('context', dataset_dict)
+
+    @patch_package_create
+    @patch_munge_increment_name
+    def test_does_modify_dict_if_extras_does_not_contain_issued(self, mock_min, mock_pc):
+        dataset_dict = {
+            'type': 'dataset',
+            'extras': [],
+        }
+        action.odsh_package_create('context', dataset_dict)
+        nt.assert_equal(dataset_dict['extras'][0]['key'], 'issued')
+
+    @patch_add_to_collection
+    @patch_package_create
+    @patch_munge_increment_name
+    def test_does_NOT_call_add_to_same_collection(self, mock_min, mock_pc, mock_atsc):
+        dataset_dict = {
+            'type': 'dataset',
+            'extras': [],
+        }
+        action.odsh_package_create('context', deepcopy(dataset_dict))
+        nt.assert_false(mock_atsc.called)
+
+    @patch_add_to_collection
+    @patch_package_create
+    @patch_munge_increment_name
+    def test_does_call_add_to_same_collection(self, mock_min, mock_pc, mock_atsc):
+        dataset_dict = {
+            'type': 'dataset',
+            'extras': [],
+            'relatedPackage': 'some_package_name',
+        }
+        action.odsh_package_create('context', deepcopy(dataset_dict))
+        mock_atsc.assert_called_with('some_package_name', 'new_package_name')
+
+
+patch_get_package_dict = patch.object(
+    action,
+    'get_package_dict',
+)
+
+patch_add_to_collection = patch.object(
+    action,
+    'add_to_collection',
+)
+
+patch_auto_create_collection = patch.object(
+    action,
+    'auto_create_collection',
+    return_value={
+        'id': 'auto_created_collection',
+    }
+)
+
+
+class TestAddToSameCollection(object):
+
+    @patch_auto_create_collection
+    @patch_add_to_collection
+    @patch_get_package_dict
+    def test_creates_collection_if_not_exists(self, patch_gpd, patch_atc, patch_acc):
+        patch_gpd.return_value = {
+            'relationships_as_subject': []
+        }
+        action.add_to_same_collection('related_package_name', 'package_name')
+        nt.assert_true(patch_acc.called)
+        patch_atc.assert_called_with('auto_created_collection', 'package_name')
+
+    @patch_auto_create_collection
+    @patch_add_to_collection
+    @patch_get_package_dict
+    def test_adds_new_package_to_collection(self, patch_gpd, patch_atc, patch_acc):
+        patch_gpd.return_value = {
+            'relationships': [
+                {
+                    'object': 'collection_id'
+                }
+            ]
+        }
+        action.add_to_same_collection('related_package_name', 'package_name')
+        nt.assert_false(patch_acc.called)
+        patch_atc.assert_called_with('collection_id', 'package_name')
+    
+    @patch_auto_create_collection
+    @patch_add_to_collection
+    @patch_get_package_dict
+    def test_adds_existing_package_to_new_collection(self, patch_gpd, patch_atc, patch_acc):
+        patch_gpd.return_value = {
+            'relationships_as_subject': []
+        }
+        action.add_to_same_collection('related_package_name', 'package_name')
+        patch_atc.assert_any_call('auto_created_collection', 'related_package_name')
+
+
+patch_package_create_via_toolkit = patch.object(
+    action,
+    'package_create_via_toolkit',
+)
+
+def my_munge_increment_name(data_dict):
+    data_dict['name'] = 'munged_name'
+
+patch_munge_increment_name = patch.object(
+    action,
+    'munge_increment_name',
+    side_effect=my_munge_increment_name,
+)
+class TestAutoCreateCollection(object):
+
+    @patch_munge_increment_name
+    @patch_package_create_via_toolkit
+    def test_calls_package_create_with_default_context(self, patch_pc, patch_min):
+        action.auto_create_collection({
+            'title': 'package_title', 
+            'name': 'package_name', 
+            'owner_org': 'owner_org',
+        })
+        patch_pc.assert_called_with(None, {
+            'owner_org': 'owner_org', 
+            'type': 'collection', 
+            'name': 'munged_name', 
+            'title': 'package_title',
+        })
+
+    @patch_munge_increment_name
+    @patch_package_create_via_toolkit
+    def test_returns_collection_dict(self, patch_pc, patch_min):
+        collection_dict_expected = {
+            'name': 'new_collection',
+            'type': 'collection',
+        }
+        patch_pc.return_value = collection_dict_expected
+        collection_dict = action.auto_create_collection({
+            'title': 'package_title', 
+            'name': 'package_name', 
+            'owner_org': 'owner_org',
+        })
+        nt.assert_equal(collection_dict, collection_dict_expected)
+
+
+patch_add_pkg_to_collection = patch.object(
+    action,
+    'add_pkg_to_collection',
+)
+
+
+class TestAddToCollection(object):
+
+    @patch_add_pkg_to_collection
+    def test_reminder(self, patch_aptc):
+        action.add_to_collection('collection_id', 'package_name')
+        nt.assert_true(patch_aptc.called)
+        patch_aptc.assert_called_once_with('package_name', 'collection_id')
diff --git a/ckanext/odsh/tests_tpsh/test_dcat_de_profile.py b/ckanext/odsh/tests_tpsh/test_dcat_de_profile.py
new file mode 100644
index 0000000000000000000000000000000000000000..c384ec3e03ade5131c2ea662ec32311be4e3ed49
--- /dev/null
+++ b/ckanext/odsh/tests_tpsh/test_dcat_de_profile.py
@@ -0,0 +1,160 @@
+import mock
+from copy import deepcopy
+import nose.tools as nt
+import ckanext.odsh.profiles.odsh_dcat_de_profile as odsh_profile
+
+dataset_dict_not_in_collection = {
+    u'author': None,
+    u'author_email': None,
+    u'creator_user_id': u'ff8d002d-3908-45e5-ba7b-445381860957',
+    u'extras': [{u'key': u'groups', u'value': u''},
+                {u'key': u'issued', u'value': u'2020-05-14T00:00:00'},
+                {u'key': u'licenseAttributionByText', u'value': u''},
+                {u'key': u'spatial',
+                 u'value': u'{"type": "Polygon", "coordinates": [[[9.5557, 54.6361], [9.5576, 54.6395], [9.5739, 54.6428], [9.593, 54.6508], [9.6041, 54.6497], [9.6049, 54.6533], [9.6132, 54.6556], [9.6261, 54.6632], [9.627, 54.6671], [9.6349, 54.6672], [9.6264, 54.6548], [9.6257, 54.6494], [9.6235, 54.6496], [9.624, 54.6426], [9.619, 54.6349], [9.6231, 54.6354], [9.6222, 54.6343], [9.6263, 54.6338], [9.6441, 54.6399], [9.6459, 54.6378], [9.6529, 54.6368], [9.657, 54.6327], [9.6629, 54.6312], [9.667, 54.6281], [9.6699, 54.6283], [9.6703, 54.6249], [9.6874, 54.6304], [9.6963, 54.6245], [9.6948, 54.6183], [9.7011, 54.6132], [9.6962, 54.6101], [9.6933, 54.6125], [9.6904, 54.6116], [9.6867, 54.6085], [9.6897, 54.6038], [9.6826, 54.6007], [9.6881, 54.599], [9.6857, 54.5943], [9.6911, 54.5913], [9.695, 54.591], [9.6965, 54.5932], [9.7065, 54.5957], [9.7101, 54.5935], [9.7148, 54.5931], [9.7169, 54.5948], [9.7232, 54.5928], [9.7241, 54.587], [9.718, 54.585], [9.7195, 54.5831], [9.7159, 54.5817], [9.7186, 54.5783], [9.7162, 54.5746], [9.7194, 54.5694], [9.7125, 54.5655], [9.7227, 54.5599], [9.7218, 54.5544], [9.7265, 54.5521], [9.726, 54.5492], [9.7289, 54.5497], [9.7322, 54.5478], [9.7413, 54.5402], [9.7412, 54.5375], [9.7374, 54.5357], [9.7389, 54.5321], [9.7373, 54.5303], [9.7211, 54.5299], [9.718, 54.5286], [9.714, 54.5187], [9.7119, 54.5183], [9.7024, 54.5179], [9.6932, 54.5224], [9.6731, 54.5153], [9.6439, 54.5155], [9.6344, 54.511], [9.6252, 54.5159], [9.6159, 54.5159], [9.616, 54.5236], [9.6035, 54.5269], [9.6036, 54.5282], [9.5986, 54.5282], [9.5927, 54.526], [9.5916, 54.5342], [9.5941, 54.5354], [9.5869, 54.5374], [9.5917, 54.5454], [9.5849, 54.5459], [9.576, 54.544], [9.5757, 54.5422], [9.5679, 54.5444], [9.5598, 54.5417], [9.5487, 54.5438], [9.546, 54.5413], [9.5472, 54.5402], [9.5434, 54.5391], [9.5456, 54.5394], [9.5445, 54.5367], [9.5399, 54.5357], [9.5364, 54.5393], [9.538, 54.5414], [9.5313, 54.5449], [9.5347, 54.5467], [9.5286, 54.5467], [9.5291, 54.5454], [9.5263, 54.5447], [9.5154, 54.5442], [9.5127, 54.5454], [9.5123, 54.5485], [9.5079, 54.549], [9.5054, 54.5526], [9.4992, 54.5549], [9.4941, 54.5647], [9.4899, 54.5617], [9.4798, 54.5643], [9.4824, 54.5965], [9.472, 54.5981], [9.4581, 54.5978], [9.4566, 54.6104], [9.4594, 54.6117], [9.4708, 54.6073], [9.4856, 54.6099], [9.491, 54.6094], [9.5065, 54.6153], [9.5059, 54.6205], [9.4983, 54.6206], [9.4995, 54.6246], [9.5109, 54.6234], [9.5116, 54.6258], [9.5009, 54.644], [9.4923, 54.6453], [9.4968, 54.6488], [9.4947, 54.6503], [9.493, 54.6494], [9.4844, 54.656], [9.4759, 54.6758], [9.5057, 54.6693], [9.5166, 54.6771], [9.5156, 54.6792], [9.5206, 54.6786], [9.5256, 54.6821], [9.5302, 54.683], [9.5301, 54.6842], [9.5359, 54.6846], [9.5297, 54.6703], [9.5299, 54.6676], [9.5328, 54.6672], [9.5328, 54.6652], [9.537, 54.6626], [9.5314, 54.6618], [9.5305, 54.6595], [9.5377, 54.6598], [9.5411, 54.6518], [9.5499, 54.6507], [9.5485, 54.6478], [9.5507, 54.6447], [9.5471, 54.6401], [9.5508, 54.6367], [9.5557, 54.6361]]]}'},
+                {u'key': u'spatial_text', u'value': u'Amt S\xfcdangeln'},
+                {u'key': u'spatial_uri',
+                 u'value': u'http://dcat-ap.de/def/politicalGeocoding/regionalKey/010595987'},
+                {u'key': u'subject_text', u'value': u'Brosch\xfcre'},
+                {u'key': u'temporal_end',
+                 u'value': u'2020-05-31T00:00:00'},
+                {u'key': u'temporal_start', u'value': u'2020-05-01T00:00:00'}],
+    u'groups': [],
+    u'id': u'ff55a3ee-6dcd-4f76-b5a3-08055d5241f4',
+    'is_new': True,
+    u'isopen': True,
+    u'language': u'http://publications.europa.eu/resource/authority/language/DEU',
+    u'license_id': u'http://dcat-ap.de/def/licenses/dl-zero-de/2.0',
+    u'license_title': u'Datenlizenz Deutschland \u2013 Zero \u2013 Version 2.0',
+    u'license_url': u'https://www.govdata.de/dl-de/zero-2-0',
+    u'maintainer': None,
+    u'maintainer_email': None,
+    u'metadata_created': u'2020-05-14T12:34:05.551655',
+    u'metadata_modified': u'2020-05-14T12:53:06.484131',
+    u'name': u'test-thumbnail',
+    u'notes': u'Test-Package f\xfcr Thumbnail-Gernerierung',
+    u'num_resources': 1,
+    u'num_tags': 0,
+    u'organization': {u'approval_status': u'approved',
+                      u'created': u'2019-07-29T08:11:32.697127',
+                                  u'description': u'',
+                                  u'id': u'63c87e74-60a9-4a4a-a980-d7983c47f92b',
+                                  u'image_url': u'',
+                                  u'is_organization': True,
+                                  u'name': u'test-organisation',
+                                  u'revision_id': u'3040af6c-d3f6-462d-b48a-329d63e17a28',
+                                  u'state': u'active',
+                                  u'title': u'Test-Organisation',
+                                  u'type': u'organization'},
+    u'owner_org': u'63c87e74-60a9-4a4a-a980-d7983c47f92b',
+    u'private': False,
+    u'relationships_as_object': [],
+    u'relationships_as_subject': [],
+    u'resources': [{u'cache_last_updated': None,
+                    u'cache_url': None,
+                    u'created': u'2020-05-14T12:34:19.974216',
+                                u'datastore_active': False,
+                                u'description': u'',
+                                u'format': u'PDF',
+                                u'hash': u'66123edf64fabf1c073fc45478bf4a57',
+                                u'hash_algorithm': u'http://dcat-ap.de/def/hashAlgorithms/md/5',
+                                u'id': u'3ec53c20-f038-4ad6-a9fd-265cbe6faa27',
+                                u'last_modified': u'2020-05-14T12:34:19.817460',
+                                u'mimetype': u'application/pdf',
+                                u'mimetype_inner': None,
+                                u'name': u'Checkliste-Barrierefreies-PDF.pdf',
+                                u'number_of_pages': u'3',
+                                u'package_id': u'ff55a3ee-6dcd-4f76-b5a3-08055d5241f4',
+                                u'position': 0,
+                                u'resource_type': None,
+                                u'revision_id': u'efba6d3b-6204-4872-b6d1-a9e7b6d05a35',
+                                u'size': 112437,
+                                u'state': u'active',
+                                u'url': u'http://192.168.152.133:5000/dataset/ff55a3ee-6dcd-4f76-b5a3-08055d5241f4/resource/3ec53c20-f038-4ad6-a9fd-265cbe6faa27/download/checkliste-barrierefreies-pdf.pdf',
+                                u'url_type': u'upload'}],
+    u'revision_id': u'c8cb03d3-ab84-418f-90e6-d7ec1a0a3ed9',
+    u'state': u'active',
+    u'subject': u'http://transparenz.schleswig-holstein.de/informationsgegenstand#Broschuere',
+                u'tags': [],
+                u'thumbnail': u'thumbnail_picture_67373b78b2f66339b9e2708577da52.jpg',
+                u'title': u'Test Thumbnail',
+                u'type': u'dataset',
+                u'url': None,
+                u'version': None
+}
+
+
+dataset_dict_in_collection = {
+    u'author': None,
+    u'author_email': None,
+    u'creator_user_id': u'ff8d002d-3908-45e5-ba7b-445381860957',
+    u'extras': [{u'key': u'issued', u'value': u'2019-07-07T00:00:00'},
+                {u'key': u'licenseAttributionByText', u'value': u''},
+                {u'key': u'subject_text', u'value': u'Verwaltungsvorschrift'}],
+    u'groups': [],
+    u'id': u'd1948d21-df84-4dd5-b273-1f10240c7e6b',
+    'is_new': False,
+    u'isopen': True,
+    u'language': u'http://publications.europa.eu/resource/authority/language/DEU',
+    u'license_id': u'http://dcat-ap.de/def/licenses/dl-zero-de/2.0',
+    u'license_title': u'Datenlizenz Deutschland \u2013 Zero \u2013 Version 2.0',
+    u'license_url': u'https://www.govdata.de/dl-de/zero-2-0',
+    u'maintainer': None,
+    u'maintainer_email': None,
+    u'metadata_created': u'2020-04-08T19:34:15.692996',
+    u'metadata_modified': u'2020-04-08T19:34:15.693001',
+    u'name': u'test-911',
+    u'notes': u'Testdatensatz aus Skript',
+    u'num_resources': 0,
+    u'num_tags': 0,
+    u'organization': {u'approval_status': u'approved',
+                      u'created': u'2019-07-29T08:11:32.697127',
+                      u'description': u'',
+                      u'id': u'63c87e74-60a9-4a4a-a980-d7983c47f92b',
+                      u'image_url': u'',
+                      u'is_organization': True,
+                      u'name': u'test-organisation',
+                      u'revision_id': u'3040af6c-d3f6-462d-b48a-329d63e17a28',
+                      u'state': u'active',
+                      u'title': u'Test-Organisation',
+                      u'type': u'organization'},
+    u'owner_org': u'63c87e74-60a9-4a4a-a980-d7983c47f92b',
+    u'private': False,
+    u'relationships_as_object': [],
+    u'relationships_as_subject': [{'__extras': {'object_package_id': u'299b7d18-ec59-4dc5-81c4-081c37b6e82d',
+                                                'revision_id': u'7f8c1e02-daee-4c1b-95f5-85c1e0306c1b',
+                                                'subject_package_id': u'd1948d21-df84-4dd5-b273-1f10240c7e6b'},
+                                   'comment': u'd1948d21-df84-4dd5-b273-1f10240c7e6b',
+                                   'id': u'9154e659-d069-4f9d-b7bc-a56231829eb2',
+                                   'type': u'child_of'}],
+    u'resources': [],
+    u'revision_id': u'397df935-7abe-4e72-8e8e-db2067958302',
+    u'state': u'active',
+    u'subject': u'http://transparenz.schleswig-holstein.de/informationsgegenstand#Verwaltungsvorschrift',
+    u'tags': [],
+    u'title': u'Test 91',
+    u'type': u'dataset',
+    u'url': None,
+    u'version': None
+}
+
+
+class Test_dataset_belongs_to_collection(object):
+    def setUp(self):
+        self.graph = mock.Mock()
+        self.profile = odsh_profile.ODSHDCATdeProfile(self.graph)
+
+    def test_returns_False_if_collection(self):
+        is_collection = self.profile._dataset_belongs_to_collection(
+            {'type': 'collection'})
+        nt.assert_false(is_collection)
+
+    def test_returns_False_if_no_collection_name(self):
+        is_collection = self.profile._dataset_belongs_to_collection(
+            deepcopy(dataset_dict_not_in_collection))
+        nt.assert_false(is_collection)
+
+    def test_returns_True_if_has_collection_name(self):
+        is_collection = self.profile._dataset_belongs_to_collection(
+            deepcopy(dataset_dict_in_collection))
+        nt.assert_true(is_collection)
diff --git a/ckanext/odsh/tests_tpsh/test_helpers_collection.py b/ckanext/odsh/tests_tpsh/test_helpers_collection.py
new file mode 100644
index 0000000000000000000000000000000000000000..b48bb9da2601560835e653e16005eb82646ed502
--- /dev/null
+++ b/ckanext/odsh/tests_tpsh/test_helpers_collection.py
@@ -0,0 +1,212 @@
+import nose.tools as nt
+from mock import patch
+from copy import deepcopy
+
+import ckanext.odsh.collection.helpers as helpers_collection
+
+
+class TestHelpersCollection(object):
+    def setUp(self):
+        self.datasets_in_collection = [
+            {
+                'name': 'dataset 1',
+            },
+            {
+                'name': 'dataset 2',
+            },
+            {
+                'name': 'dataset 3',
+            },
+        ]
+
+        self.collection_dict = {
+            'name': 'collection name',
+            'title': 'collection title',
+            'relationships': [
+                {
+                    'object': 'dataset 1',
+                },
+                {
+                    'object': 'dataset 2',
+                },
+                {
+                    'object': 'dataset 3',
+                },
+            ]
+        }
+
+        self.patch_get_package_dict = patch.object(
+            helpers_collection,
+            'get_package_dict',
+            return_value=self.collection_dict
+        )
+        self.patch_get_package_dict.start()
+
+        self.patch_url_from_id = patch.object(
+            helpers_collection,
+            'url_from_id',
+            new=lambda id: 'http://{}'.format(id)
+        )
+        self.patch_url_from_id.start()
+
+        self.patch_url_last_member = patch.object(
+            helpers_collection,
+            'url_last_member',
+            new=lambda collection_name: 'http://{}/last'.format(
+                collection_name)
+        )
+        self.patch_url_last_member.start()
+
+        self.patch_get_datasets_from_solr = patch.object(
+            helpers_collection,
+            'get_datasets_from_solr',
+            return_value=self.datasets_in_collection
+        )
+        self.patch_get_datasets_from_solr.start()
+
+    def tearDown(self):
+        self.patch_get_package_dict.stop()
+        self.patch_url_from_id.stop()
+        self.patch_url_last_member.stop()
+        self.patch_get_datasets_from_solr.stop()
+
+    def test_patch_get_package_dict(self):
+        collection_dict = helpers_collection.get_package_dict(
+            'collection_name')
+        nt.assert_equal(collection_dict, self.collection_dict)
+
+    def test_patch_url_from_id(self):
+        nt.assert_equal(helpers_collection.url_from_id(
+            'some_id'), 'http://some_id')
+
+    def test_patch_url_last_member(self):
+        nt.assert_equal(helpers_collection.url_last_member(
+            'some_collection'), 'http://some_collection/last')
+
+    def test_get_collection_info_WITHOUT_dataset_dict(self):
+        collection_info = helpers_collection.get_collection_info('some_id')
+        collection_info_expected = {
+            'first_member': {
+                'name': 'dataset 1',
+                'url': 'http://dataset 1'
+            },
+            'last_member': {'name': 'dataset 3', 'url': 'http://dataset 3'},
+            'members': [{'name': 'dataset 1'}, {'name': 'dataset 2'}, {'name': 'dataset 3'}],
+            'persistent_link_last_member': 'http://collection name/last',
+            'predecessor': {'url': None},
+            'successor': {'url': None},
+            'title': 'collection title'
+        }
+        nt.assert_equal(collection_info, collection_info_expected)
+    
+    def test_get_collection_info_WITH_dataset_dict(self):
+        collection_info = helpers_collection.get_collection_info('some_id', {'name': 'dataset 2'})
+        collection_info_expected = {
+            'first_member': {'name': 'dataset 1', 'url': 'http://dataset 1'},
+            'last_member': {'name': 'dataset 3', 'url': 'http://dataset 3'},
+            'members': [{'name': 'dataset 1'}, {'name': 'dataset 2'}, {'name': 'dataset 3'}],
+            'persistent_link_last_member': 'http://collection name/last',
+            'predecessor': {'url': 'http://dataset 1'},
+            'successor': {'url': 'http://dataset 3'},
+            'title': 'collection title'
+        }
+        nt.assert_equal(collection_info, collection_info_expected)
+
+    def test_get_collection_id_if_in_collection(self):
+        dataset_dict = {
+            'relationships_as_subject': [
+                {'__extras': {
+                    'object_package_id': 'some_id'
+                }}
+            ]
+        }
+        collection_id = helpers_collection.get_collection_id(dataset_dict)
+        nt.assert_equal(collection_id, 'some_id')
+    
+    def test_get_collection_id_if_in_collection_alternative_version(self):
+        dataset_dict = {
+            'relationships': [
+                {'object': 'some_id'}
+            ]
+        }
+        collection_id = helpers_collection.get_collection_id(dataset_dict)
+        nt.assert_equal(collection_id, 'some_id')
+
+    def test_get_collection_id_if_NOT_in_collection(self):
+        dataset_dict = {
+            'relationships_as_subject': []
+        }
+        collection_id = helpers_collection.get_collection_id(dataset_dict)
+        nt.assert_equal(collection_id, None)
+
+    def test_get_dataset_names(self):
+        dataset_names = helpers_collection.get_dataset_names(dict())
+        nt.assert_equal(dataset_names, ['dataset 1', 'dataset 2', 'dataset 3'])
+
+    def test_gather_collection_info_without_reference_package(self):
+        collection_info = helpers_collection.gather_collection_info(
+            self.collection_dict, self.datasets_in_collection, dataset_dict=None)
+        collection_info_expected = {
+            'first_member': {'name': 'dataset 1', 'url': 'http://dataset 1'},
+            'last_member': {'name': 'dataset 3', 'url': 'http://dataset 3'},
+            'members': [
+                {'name': 'dataset 1'},
+                {'name': 'dataset 2'},
+                {'name': 'dataset 3'}],
+            'persistent_link_last_member': 'http://collection name/last',
+            'predecessor': {'url': None},
+            'successor': {'url': None},
+            'title': 'collection title'
+        }
+        nt.assert_equal(collection_info, collection_info_expected)
+
+    def test_gather_collection_info_with_reference_package_1(self):
+        collection_info = helpers_collection.gather_collection_info(
+            self.collection_dict, self.datasets_in_collection, dataset_dict={'name': 'dataset 1'})
+        collection_info_expected = {
+            'first_member': {'name': 'dataset 1', 'url': 'http://dataset 1'},
+            'last_member': {'name': 'dataset 3', 'url': 'http://dataset 3'},
+            'members': [
+                {'name': 'dataset 1'},
+                {'name': 'dataset 2'},
+                {'name': 'dataset 3'}],
+            'persistent_link_last_member': 'http://collection name/last',
+            'predecessor': {'url': None},
+            'successor': {'url': 'http://dataset 2'},
+            'title': 'collection title'
+        }
+        nt.assert_equal(collection_info, collection_info_expected)
+
+    def test_gather_collection_info_with_reference_package_2(self):
+        collection_info = helpers_collection.gather_collection_info(
+            self.collection_dict, self.datasets_in_collection, dataset_dict={'name': 'dataset 2'})
+        collection_info_expected = {
+            'first_member': {'name': 'dataset 1', 'url': 'http://dataset 1'},
+            'last_member': {'name': 'dataset 3', 'url': 'http://dataset 3'},
+            'members': [
+                {'name': 'dataset 1'},
+                {'name': 'dataset 2'},
+                {'name': 'dataset 3'}],
+            'persistent_link_last_member': 'http://collection name/last',
+            'predecessor': {'url': 'http://dataset 1'},
+            'successor': {'url': 'http://dataset 3'},
+            'title': 'collection title'
+        }
+        nt.assert_equal(collection_info, collection_info_expected)
+
+    def test_gather_collection_info_with_reference_package_3(self):
+        collection_info = helpers_collection.gather_collection_info(
+            self.collection_dict, self.datasets_in_collection, dataset_dict={'name': 'dataset 3'})
+        collection_info_expected = {
+            'first_member': {'name': 'dataset 1', 'url': 'http://dataset 1'},
+            'last_member': {'name': 'dataset 3', 'url': 'http://dataset 3'},
+            'members': [
+                {'name': 'dataset 1'},
+                {'name': 'dataset 2'},
+                {'name': 'dataset 3'}],
+            'persistent_link_last_member': 'http://collection name/last',
+            'predecessor': {'url': 'http://dataset 2'},
+            'successor': {'url': None},
+            'title': 'collection title'
+        }
+        nt.assert_equal(collection_info, collection_info_expected)
diff --git a/ckanext/odsh/tests_tpsh/test_odsh_helpers.py b/ckanext/odsh/tests_tpsh/test_odsh_helpers.py
index a4c488230d254139ec4b3c7da1df93dd762a41dc..c11be582df29ba0bd8ebeb5390573950941c59e3 100644
--- a/ckanext/odsh/tests_tpsh/test_odsh_helpers.py
+++ b/ckanext/odsh/tests_tpsh/test_odsh_helpers.py
@@ -1,161 +1,39 @@
 import datetime
 import nose.tools as nt
-from mock import patch
 
 import ckan.lib.helpers as helpers
 
 import ckanext.odsh.helpers as odsh_helpers
 from ckanext.odsh.helpers import is_within_last_month
-import ckanext.odsh.collection.helpers as helpers_collection
 import ckan.model as model
 
 
-class Test_tpsh_get_successor_and_predecessor_dataset(object):
-    
-    def setUp(self):
-        
-        # construct datasets that shall be in following order:
-        # name      date
-        # public2   2014-07-01
-        # public3   2014-07-01
-        # public4   2014-07-02
-        # public1   2014-07-03
-
-        self.names_collection_members = [
-            u'public3', u'public2', u'public1', u'public4', 
-            u'private3', u'private2', u'private1']
-        self.dates_collection_members = [
-            u'2014-07-01T00:00:00', #public3
-            u'2014-07-01T00:00:00', #public2
-            u'2014-07-03T00:00:00', #public1
-            u'2014-07-02T00:00:00', #public4
-            u'2014-07-05T00:00:00', #private3
-            u'2014-07-06T00:00:00', #private2
-            u'2014-07-07T00:00:00', #private1
-        ]
-        self.pkg_dicts_collection_members = [
-            {
-                u'name': name,
-                u'extras': {u'issued': date}
-            }
-            for (name, date) in zip(self.names_collection_members, self.dates_collection_members)
-        ]
-
-        def fake_access_checker(access_type, pkg_dict):
-            pkg_name = pkg_dict.get('name')
-            if 'public' in pkg_name:
-                return True
-            return False
-        
-        def fake_get_package_dict(name):
-            package_list = filter(lambda pkg_dict:pkg_dict.get('name')==name, self.pkg_dicts_collection_members)
-            return package_list[0]
-
-        self.patch_get_datasets_belonging_to_collection_by_dataset = patch.object(
-            helpers_collection,
-            'get_all_datasets_belonging_to_collection_by_dataset',
-            return_value = self.names_collection_members)
-        self.patch_get_datasets_belonging_to_collection_by_dataset.start()
-
-        self.patch_check_access = patch.object(
-            helpers,
-            'check_access',
-            new=fake_access_checker, 
-        )
-        self.patch_check_access.start()
-
-        self.patch_get_package_dict = patch.object(
-            helpers_collection,
-            'get_package_dict',
-            new=fake_get_package_dict,
-        )
-        self.patch_get_package_dict.start()
-    
-    def tearDown(self):
-        self.patch_get_datasets_belonging_to_collection_by_dataset.stop()
-        self.patch_check_access.stop()
-        self.patch_get_package_dict.stop()
-
-    def test_patch_get_datasets_belonging_to_collection_by_dataset(self):
-        return_value = helpers_collection.get_all_datasets_belonging_to_collection_by_dataset()
-        nt.assert_equal(return_value, self.names_collection_members)
-    
-    def test_patch_access_checker_returns_True(self):
-        pkg_dict = {u'name': u'public1'}
-        has_access = helpers.check_access('package_show', pkg_dict)
-        nt.assert_true(has_access)
-    
-    def test_patch_access_checker_returns_False(self):
-        pkg_dict = {u'name': u'private1'}
-        has_access = helpers.check_access('package_show', pkg_dict)
-        nt.assert_false(has_access)
-    
-    def test_patch_package_get(self):
-        pkg_dict = helpers_collection.get_package_dict('public1')
-        nt.assert_equal(pkg_dict.get('name'), 'public1')
-    
-    def test_it_returns_correct_for_public2(self):
-        pkg_dict = {u'name': u'public2'}
-        successor, predecessor = helpers_collection.get_successor_and_predecessor_dataset(pkg_dict)
-        nt.assert_equal(successor, 'public3')
-        nt.assert_equal(predecessor, None)
-    
-    def test_it_returns_correct_for_public3(self):
-        pkg_dict = {u'name': u'public3'}
-        successor, predecessor = helpers_collection.get_successor_and_predecessor_dataset(pkg_dict)
-        nt.assert_equal(successor, 'public4')
-        nt.assert_equal(predecessor, 'public2')
-
-    def test_it_returns_correct_for_public4(self):
-        pkg_dict = {u'name': u'public4'}
-        successor, predecessor = helpers_collection.get_successor_and_predecessor_dataset(pkg_dict)
-        nt.assert_equal(successor, 'public1')
-        nt.assert_equal(predecessor, 'public3')
-
-    def test_it_returns_correct_for_public1(self):
-        pkg_dict = {u'name': u'public1'}
-        successor, predecessor = helpers_collection.get_successor_and_predecessor_dataset(pkg_dict)
-        nt.assert_equal(successor, None)
-        nt.assert_equal(predecessor, 'public4')
-    
-    def test_it_returns_None_if_no_siblings(self):
-        with patch.object(
-            helpers_collection,
-            'get_all_datasets_belonging_to_collection_by_dataset',
-            return_value = list()
-        ):
-            pkg_dict = {u'name': u'some_name'}
-            successor, predecessor = helpers_collection.get_successor_and_predecessor_dataset(pkg_dict)
-            nt.assert_equal(successor, None)
-            nt.assert_equal(predecessor, None)
-
-
 class Test_is_within_last_month(object):
     def test_it_returns_true_for_simple_query(self):
         date = datetime.date(2019, 4, 15)
         date_ref = datetime.date(2019, 4, 29)
         assert is_within_last_month(date, date_ref)
-    
+
     def test_it_uses_today_if_date_ref_missing(self):
         date = datetime.date.today() - datetime.timedelta(days=20)
         assert is_within_last_month(date)
-    
+
     def test_it_returns_true_for_dates_in_different_years(self):
         date = datetime.date(2018, 12, 16)
         date_ref = datetime.date(2019, 1, 15)
         assert is_within_last_month(date, date_ref)
-    
+
     def test_it_returns_false_for_dates_in_different_years(self):
         date = datetime.date(2018, 12, 15)
         date_ref = datetime.date(2019, 1, 15)
-        assert is_within_last_month(date, date_ref)==False
-    
+        assert is_within_last_month(date, date_ref) == False
+
     def test_it_returns_true_for_dates_in_differen_months(self):
         date = datetime.date(2018, 6, 16)
         date_ref = datetime.date(2018, 7, 10)
         assert is_within_last_month(date, date_ref)
-    
+
     def test_it_return_false_for_date_in_different_months(self):
         date = datetime.date(2018, 6, 8)
         date_ref = datetime.date(2018, 7, 10)
-        assert is_within_last_month(date, date_ref)==False
\ No newline at end of file
+        assert is_within_last_month(date, date_ref) == False
diff --git a/ckanext/odsh/tests_tpsh/test_thumbnail.py b/ckanext/odsh/tests_tpsh/test_thumbnail.py
new file mode 100644
index 0000000000000000000000000000000000000000..f12f0cbd06732483fce5c2ff84ebd7f2216d4eaf
--- /dev/null
+++ b/ckanext/odsh/tests_tpsh/test_thumbnail.py
@@ -0,0 +1,275 @@
+# encoding: utf-8
+
+import os
+import os.path as path
+from mock import patch, Mock, call
+import nose.tools as nt
+from ckan.common import config
+
+import ckanext.odsh.pdf_to_thumbnail.thumbnail as thumbnail
+import ckanext.odsh.tests_tpsh.thumbnail_patches as p
+
+
+class TestCreateThumbnailFromFile(p.WithTempFolder):
+    
+    def test_pass(self):
+        pass
+
+    def test_thumbnail_for_test_pdf(self):
+        with open(os.path.join(p.resources_path, 'test.pdf')) as f:
+            filename_thumbnail = thumbnail._create_thumbnail_from_file(f)
+            nt.assert_true(path.exists(
+                path.join(p.thumbnail_folder, filename_thumbnail)
+            ))
+    
+    def test_thumbnail_for_ki_strategie(self):
+        with open(os.path.join(p.resources_path, 'ki_strategie.pdf')) as f:
+            thumbnail._create_thumbnail_from_file(f)
+    
+
+class TestGetFilepathToResource(p.WithTempFolder):
+
+    def test_it_returns_correct_path(self):
+        resource = {
+            'id': '3F2504E0-4F89-11D3-9A0C-0305E82C3301'
+        }
+        filepath = thumbnail.get_resource_path(resource)
+        filepath_expected = os.path.join(
+            p.temp_path, 'resources/3F2/504/E0-4F89-11D3-9A0C-0305E82C3301')
+        nt.assert_equal(filepath, filepath_expected)
+    
+
+
+class TestChangeFilepath(p.WithTempFolder):
+
+    def test_renames_thumbnail(self):
+        open(os.path.join(p.thumbnail_folder, 'some_filename'), 'a').close()
+        thumbnail.rename_thumbnail_to_random_name('some_filename')
+        thumbnails = os.listdir(p.thumbnail_folder)
+        nt.assert_equal(len(thumbnails), 1)
+        filename = thumbnails[0]
+        nt.assert_true(filename.startswith('thumbnail_picture_'))
+        nt.assert_true(filename.endswith('.jpg'))
+
+
+class TestIsPdf(object):
+
+    def test_true_for_pdf(self):
+        filepath = os.path.join(p.resources_path, 'test.pdf')
+        is_pdf = thumbnail._is_pdf(filepath)
+        nt.assert_true(is_pdf)
+    
+    def test_false_for_text_file(self):
+        filepath = os.path.join(p.resources_path, 'transparenz.rdf')
+        is_pdf = thumbnail._is_pdf(filepath)
+        nt.assert_false(is_pdf)
+
+
+class TestCreateThumbnailFromMemory(p.WithTempFolder):
+
+    @p.patch_get_resource_path
+    def test_creates_thumbnail_for_pdf(self, _patch):
+        is_pdf, filename = thumbnail._create_thumbnail_from_memory(dict(), '')
+        nt.assert_true(filename.startswith('thumbnail_picture_'))
+        nt.assert_true(filename.endswith('.jpg'))
+        nt.assert_true(is_pdf)
+        nt.assert_true(os.path.exists(os.path.join(p.thumbnail_folder, filename)))
+
+    @p.patch_get_resource_path
+    def test_removes_old_thumbnail(self, _patch):
+        open(os.path.join(p.thumbnail_folder, 'old_thumbnail.jpg'), 'a').close()
+        thumbnail._create_thumbnail_from_memory(dict(), 'old_thumbnail')
+        thumbnails = os.listdir(p.thumbnail_folder)
+        nt.assert_equal(len(thumbnails), 1)
+        nt.assert_not_in('old_thumbnail.jpg', thumbnails)
+    
+    @p.patch_get_resource_path
+    def test_does_not_remove_old_thumbnail_for_textfile(self, _patch):
+        _patch.return_value = os.path.join(p.resources_path, 'transparenz.rdf')
+        open(os.path.join(p.thumbnail_folder, 'old_thumbnail.jpg'), 'a').close()
+        thumbnail._create_thumbnail_from_memory(dict(), 'old_thumbnail')
+        thumbnails = os.listdir(p.thumbnail_folder)
+        nt.assert_equal(len(thumbnails), 1)
+        nt.assert_in('old_thumbnail.jpg', thumbnails)
+
+    
+    @p.patch_get_resource_path
+    def test_returns_None_for_textfile(self, _patch):
+        _patch.return_value = os.path.join(p.resources_path, 'transparenz.rdf')
+        is_pdf, filename = thumbnail._create_thumbnail_from_memory(dict(), '')
+        nt.assert_false(is_pdf)
+        nt.assert_is(filename, None)
+
+
+class TestRemoveThumbnail(p.WithTempFolder):
+
+    @p.patch_get_filename_from_context
+    def test_it_removes_thumbnail(self, _patch):
+        open(os.path.join(p.thumbnail_folder, 'some_thumbnail.jpg'), 'a').close()
+        thumbnails = os.listdir(p.thumbnail_folder)
+        nt.assert_equal(len(thumbnails), 1)
+        thumbnail.remove_thumbnail(dict())
+        thumbnails = os.listdir(p.thumbnail_folder)
+        nt.assert_equal(len(thumbnails), 0)
+
+
+class TestCreateThumbnail(object):
+
+    @p.patch_create_thumbnail_from_memory
+    @p.patch_get_filename_from_context
+    def test_creates_thumbnail_from_memory(self, patch_gffc, patch_ctfm):
+        resource = {
+            'url_type': 'upload',
+        }
+        thumbnail.create_thumbnail(dict(), resource)
+        patch_ctfm.assert_called_once_with(resource, 'some_thumbnail.jpg')
+
+    @p.patch_get_filename_from_context
+    def test_returns_None_if_type_not_upload(self, patch_gffc):
+        resource = {
+            'url_type': 'something',
+        }
+        is_pdf, filename = thumbnail.create_thumbnail(dict(), resource)
+        nt.assert_false(is_pdf)
+        nt.assert_equal(filename, None)
+    
+
+class TestThumbnailPath(p.WithTempFolder):
+
+    def test_from_filename(self):
+        tp = thumbnail.ThumbnailPath.from_filename('filename')
+        nt.assert_equal(tp.folder, p.thumbnail_folder)
+        nt.assert_equal(tp.filename, 'filename')
+        nt.assert_equal(tp.extension, '.jpg')
+    
+    def test_from_filename_with_extension(self):
+        tp = thumbnail.ThumbnailPath.from_filename_with_extension('filename.jpg')
+        nt.assert_equal(tp.filename, 'filename')
+        nt.assert_equal(tp.extension, '.jpg')
+    
+    def test_from_filename_with_extension_no_extension(self):
+        tp = thumbnail.ThumbnailPath.from_filename_with_extension('filename')
+        nt.assert_equal(tp.filename, 'filename')
+        nt.assert_equal(tp.extension, '')
+    
+    def test_full_filename(self):
+        tp = thumbnail.ThumbnailPath('folder', 'filename', '.jpg')
+        nt.assert_equal(tp.full_filename, 'folder/filename.jpg')
+    
+    @p.patch_random_generator
+    def test_from_random_name(self, _patch):
+        tp = thumbnail.ThumbnailPath._from_random_name()
+        nt.assert_equal(tp.folder, p.thumbnail_folder)
+        nt.assert_equal(tp.filename, 'thumbnail_picture_some_random')
+        nt.assert_equal(tp.extension, '.jpg')
+
+
+class TestWriteThumbnailIntoPackage(object):
+
+    def setUp(self):
+        p.mock_action_handler.reset_mock()
+        p.mock_action_handler.return_value = {'id': 'foo'}
+        self.context = {
+            'package': Mock(id='foo')
+        }
+
+    @p.patch_get_action
+    def test_with_filename(self, _patch):
+        thumbnail._write_thumbnail_into_package(self.context, 'filename')
+        _patch.assert_has_calls([
+            call('package_show'), 
+            call('package_update')
+        ])
+        p.mock_action_handler.assert_has_calls([
+            call(None, {'id': 'foo'}),
+            call(None, {'id': 'foo', 'thumbnail': 'filename'})
+        ])
+    
+    @p.patch_get_action
+    def test_without_filename(self, _patch):
+        thumbnail._write_thumbnail_into_package(self.context, '')
+        _patch.assert_has_calls([
+            call('package_show'), 
+            call('package_update')
+        ])
+        p.mock_action_handler.assert_has_calls([
+            call(None, {'id': 'foo'}),
+            call(None, {'id': 'foo'})
+        ])
+    
+
+class TestCreateThumbnailIfNoneInPackage(object):
+
+    def setUp(self):
+        p.mock_action_handler.reset_mock()
+        p.mock_action_handler.return_value = {
+            'id': 'foo',
+            'resources': [
+                {'id': 'id1'},
+                {'id': 'id2'},
+            ]
+        }
+        self.context = {
+            'package': Mock(id='foo')
+        }
+    
+    @p.patch_get_action
+    @p.patch_create_thumbnail
+    @p.patch_get_package_dict_from_context
+    def test_creates_thumbnail_for_first_resource(self, patch_gpdfc, patch_ct, patch_ga):
+        patch_ct.side_effect = [(True, 'filename1'), (True, 'filename2')]
+        patch_gpdfc.return_value = {}
+        thumbnail.create_thumbnail_if_none_in_package(self.context, ['resource1', 'resource2'])
+        patch_ct.assert_called_once_with(self.context, 'resource1')
+    
+    @p.patch_get_action
+    @p.patch_create_thumbnail
+    @p.patch_get_package_dict_from_context
+    def test_creates_thumbnail_for_first_pdf(self, patch_gpdfc, patch_ct, patch_ga):
+        patch_ct.side_effect = [(False, 'filename1'), (True, 'filename2'), (True, 'filename3')]
+        patch_gpdfc.return_value = {}
+        thumbnail.create_thumbnail_if_none_in_package(self.context, ['resource1', 'resource2', 'resource3'])
+        patch_ct.assert_called_with(self.context, 'resource2')
+    
+    @p.patch_get_action
+    @p.patch_create_thumbnail
+    @p.patch_get_package_dict_from_context
+    def test_does_not_create_thumbnail_if_exists(self, patch_gpdfc, patch_ct, patch_ga):
+        patch_gpdfc.return_value = {
+            'thumbnail': 'foo.jpg',
+        }
+        thumbnail.create_thumbnail_if_none_in_package(self.context, ['resource1', 'resource2'])
+        patch_ct.assert_not_called()
+    
+    @p.patch_get_action
+    @p.patch_create_thumbnail
+    @p.patch_get_package_dict_from_context
+    def test_does_not_create_thumbnail_if_no_resources(self, patch_gpdfc, patch_ct, patch_ga):
+        patch_gpdfc.return_value = {}
+        thumbnail.create_thumbnail_if_none_in_package(self.context, [])
+        patch_ct.assert_not_called()
+    
+    @p.patch_write_thumbnail_into_package
+    @p.patch_get_action
+    @p.patch_create_thumbnail
+    @p.patch_get_package_dict_from_context
+    def test_writes_filename_into_package(self, patch_gpdfc, patch_ct, patch_ga, patch_wtip):
+        patch_ct.side_effect = [(True, 'filename1'), (True, 'filename2')]
+        patch_gpdfc.return_value = {}
+        thumbnail.create_thumbnail_if_none_in_package(self.context, ['resource1'])
+        patch_wtip.assert_called_once_with(self.context, 'filename1')
+
+
+class TestHasThumbnail(object):
+
+    def test_true_for_nonempty_string(self):
+        nt.assert_true(thumbnail._has_thumbnail({'thumbnail': 'foo'}))
+    
+    def test_false_for_None(self):
+        nt.assert_false(thumbnail._has_thumbnail({'thumbnail': None}))
+    
+    def test_false_for_empty_string(self):
+        nt.assert_false(thumbnail._has_thumbnail({'thumbnail': ''}))
+
+    def test_false_if_not_in_dict(self):
+        nt.assert_false(thumbnail._has_thumbnail({}))
\ No newline at end of file
diff --git a/ckanext/odsh/tests_tpsh/test_thumbnail_plugin.py b/ckanext/odsh/tests_tpsh/test_thumbnail_plugin.py
new file mode 100644
index 0000000000000000000000000000000000000000..59ada72ca6f995fce2b37a74df854a42fc5fe74b
--- /dev/null
+++ b/ckanext/odsh/tests_tpsh/test_thumbnail_plugin.py
@@ -0,0 +1,59 @@
+from mock import call
+import nose.tools as nt
+
+import ckanext.odsh.tests_tpsh.thumbnail_patches as p
+from ckanext.odsh.pdf_to_thumbnail.plugin import ThumbnailPlugin
+
+class TestAfterCreateFrontend(p.WithAfterCreateDataFrontend):
+
+    def setUp(self):
+        super(TestAfterCreateFrontend, self).setUp()
+        p.mock_action_handler.reset_mock()
+        p.mock_action_handler.return_value = self.package_dict
+
+    @p.patch_write_thumbnail_into_package
+    @p.patch_create_thumbnail_from_memory
+    @p.patch_get_action
+    def test_with_data_from_frontend(self, patch_ga, patch_ctfm, patch_wtip):
+        ThumbnailPlugin().after_create(self.context, self.resource)
+        patch_ctfm.assert_called_once_with(self.package_dict.get('resources')[0], None)
+        patch_ga.assert_has_calls([
+            call('package_show'),
+            call('package_show'),
+            call('package_show'),
+        ])
+        patch_wtip.assert_called_once_with(self.context, 'filename')
+
+
+class TestAfterCreateAPI(p.WithAfterCreateDataAPI):
+
+    def setUp(self):
+        super(TestAfterCreateAPI, self).setUp()
+        p.mock_action_handler.reset_mock()
+        p.mock_action_handler.return_value = self.package_dict
+
+    @p.patch_write_thumbnail_into_package
+    @p.patch_create_thumbnail_from_memory
+    @p.patch_get_action
+    def test_with_data_from_API_first_resource(self, patch_ga, patch_ctfm, patch_wtip):
+        ThumbnailPlugin().after_create(self.context, self.resource)
+        patch_ctfm.assert_called_once_with(self.package_dict.get('resources')[0], None)
+        patch_ga.assert_has_calls([
+            call('package_show'),
+            call('package_show'),
+            call('package_show'),
+        ])
+        patch_wtip.assert_called_once_with(self.context, 'filename')
+    
+    @p.patch_write_thumbnail_into_package
+    @p.patch_create_thumbnail_from_memory
+    @p.patch_get_action 
+    def test_with_data_from_API_second_resource(self, patch_ga, patch_ctfm, patch_wtip):
+        p.mock_action_handler.return_value = self.package_dict_second_call
+        ThumbnailPlugin().after_create(self.context, self.resource)
+        patch_ga.assert_has_calls([
+            call('package_show'),
+            call('package_show'),
+        ])
+        patch_ctfm.assert_not_called()
+        patch_wtip.assert_not_called()
diff --git a/ckanext/odsh/tests_tpsh/test_validation.py b/ckanext/odsh/tests_tpsh/test_validation.py
index 3c4d145405acb554fe8e4d233654d768f9fbe266..af27c9ddce151f41aff3537bfffa69021fbe3f25 100644
--- a/ckanext/odsh/tests_tpsh/test_validation.py
+++ b/ckanext/odsh/tests_tpsh/test_validation.py
@@ -1,9 +1,20 @@
 import nose.tools as nt
 from ckan.common import config
-from .test_validation_mocks import WithFrontendValidationMocks, WithAPIValidationMocks
-from ckanext.odsh.validation import validate_licenseAttributionByText, validate_extra_groups
+import ckan.logic as logic
 import ckan.plugins.toolkit as toolkit
 
+from .test_validation_mocks import (
+    WithFrontendValidationMocks, 
+    WithAPIValidationMocks,
+    patch_get_dataset,
+    patch_get_dataset_not_found,
+)
+from ckanext.odsh.validation import (
+    validate_licenseAttributionByText, 
+    validate_extra_groups, 
+    validate_relatedPackage,
+)
+
 
 class Test_validate_licenseAttributionByText(WithFrontendValidationMocks):
     def test_it_passes(self):
@@ -196,3 +207,27 @@ class Test_validate_extra_groupsAPI(WithAPIValidationMocks):
             errors=errors
         )
         nt.assert_equal(len(errors), 0)
+
+
+
+class Test_validate_relatedPackage(object):
+
+    @patch_get_dataset_not_found
+    def test_passes_if_empty(self, patch_gd):
+        validate_relatedPackage(None)
+        validate_relatedPackage('')
+    
+    
+    @patch_get_dataset
+    def test_calls_get_dataset(self, patch_gd):
+        validate_relatedPackage('some_id')
+        patch_gd.assert_called_once_with('some_id')
+
+    
+    @patch_get_dataset_not_found
+    def test_throws_error_if_package_does_not_exist(self, patch_gd):
+        with nt.assert_raises(toolkit.Invalid) as err:
+            validate_relatedPackage('some_id')
+        nt.assert_equal(
+            err.exception.error, "relatedPackage: package 'some_id' not found")
+    
\ No newline at end of file
diff --git a/ckanext/odsh/tests_tpsh/test_validation_mocks.py b/ckanext/odsh/tests_tpsh/test_validation_mocks.py
index 6d61d04e4125ac7dd352fd048bb8d9d7a216d4de..550dca4592c8c6c98e506f324ba7fc9d0a5fb2d4 100644
--- a/ckanext/odsh/tests_tpsh/test_validation_mocks.py
+++ b/ckanext/odsh/tests_tpsh/test_validation_mocks.py
@@ -1,6 +1,10 @@
 import os
 from ckan.common import config
 from ckan.lib.navl.dictization_functions import Missing
+import ckan.logic as logic
+from mock import patch
+
+import ckanext.odsh.validation as validation
 
 
 def resource_dir():
@@ -314,3 +318,16 @@ class WithAPIValidationMocks(WithConfig):
         }
 
         self.errors_mock = {}
+
+
+patch_get_dataset = patch.object(
+    validation,
+    'get_package_dict',
+)
+
+
+patch_get_dataset_not_found = patch.object(
+    validation,
+    'get_package_dict',
+    side_effect=logic.NotFound,
+)
\ No newline at end of file
diff --git a/ckanext/odsh/tests_tpsh/thumbnail_patches.py b/ckanext/odsh/tests_tpsh/thumbnail_patches.py
new file mode 100644
index 0000000000000000000000000000000000000000..ed3bbf2b7dc214ffc20a9747ad06bdf5024a9fab
--- /dev/null
+++ b/ckanext/odsh/tests_tpsh/thumbnail_patches.py
@@ -0,0 +1,531 @@
+import os
+import os.path as path
+from shutil import rmtree
+from mock import Mock, patch
+
+from ckan.common import config
+import ckan.plugins.toolkit as toolkit
+
+import ckanext.odsh.pdf_to_thumbnail.thumbnail as thumbnail
+
+
+module_path = path.abspath(__file__)
+dir_path = path.dirname(module_path)
+resources_path = path.join(dir_path, 'resources')
+temp_path = path.join(dir_path, 'temp_thumbnail')
+thumbnail_folder = path.join(temp_path, 'thumbnail')
+
+
+patch_get_resource_path = patch.object(
+    thumbnail,
+    'get_resource_path',
+    return_value=os.path.join(resources_path, 'test_copy_pdf')
+)
+
+
+patch_get_filename_from_context = patch.object(
+    thumbnail,
+    '_get_filename_from_context',
+    return_value='some_thumbnail.jpg'
+)
+
+
+patch_create_thumbnail_from_memory = patch.object(
+    thumbnail,
+    '_create_thumbnail_from_memory',
+    return_value=(True, 'filename'),
+)
+
+
+patch_random_generator = patch.object(
+    thumbnail,
+    'b2a_hex',
+    return_value='some_random',
+)
+
+
+mock_action_handler = Mock()
+patch_get_action = patch.object(
+    toolkit, 'get_action', return_value=mock_action_handler)
+
+
+patch_create_thumbnail = patch.object(
+    thumbnail, 'create_thumbnail',
+    side_effect=[(True, 'filename'), ]
+)
+
+
+patch_write_thumbnail_into_package = patch.object(
+    thumbnail, '_write_thumbnail_into_package')
+
+
+patch_remove_thumbnail = patch.object(thumbnail, 'remove_thumbnail')
+
+
+patch_get_package_dict_from_context = patch.object(thumbnail, '_get_package_dict_from_context',
+                                                   return_value={})
+
+
+patch_is_pdf = patch.object(thumbnail, '_is_pdf', return_value=True)
+
+
+class WithTempFolder(object):
+
+    def setUp(self):
+        config.update({
+            'ckan.thumbnail.size.width': 410,
+            'ckan.storage_path': temp_path,
+        })
+        if not path.exists(temp_path):
+            os.mkdir(temp_path)
+            if not path.exists(thumbnail_folder):
+                os.mkdir(thumbnail_folder)
+
+    def tearDown(self):
+        config.clear()
+        rmtree(temp_path)
+
+
+class WithAfterCreateDataFrontend(object):
+
+    def setUp(self):
+        config.update({
+            'ckan.thumbnail.size.width': 410,
+            'ckan.storage_path': temp_path,
+        })
+
+        # trapped in plugin.py
+        self.resource = {
+            u'cache_last_updated': None,
+            u'cache_url': None,
+            u'created': '2020-05-14T12:34:19.974216',
+            u'datastore_active': False,
+            u'description': u'',
+            u'format': u'PDF',
+            u'hash': u'',
+            u'id': u'3ec53c20-f038-4ad6-a9fd-265cbe6faa27',
+            u'last_modified': '2020-05-14T12:34:19.817460',
+            u'mimetype': u'application/pdf',
+            u'mimetype_inner': None,
+            u'name': u'Checkliste-Barrierefreies-PDF.pdf',
+            u'package_id': u'ff55a3ee-6dcd-4f76-b5a3-08055d5241f4',
+            u'position': 0,
+            u'resource_type': None,
+            u'revision_id': u'0c3b309e-5179-4b4d-91f6-26221bba85e1',
+            u'size': 112437L,
+            u'state': u'active',
+            u'url': u'http://192.168.152.133:5000/dataset/ff55a3ee-6dcd-4f76-b5a3-08055d5241f4/resource/3ec53c20-f038-4ad6-a9fd-265cbe6faa27/download/checkliste-barrierefreies-pdf.pdf',
+            u'url_type': u'upload'
+        }
+        self.context = {
+            'package': Mock(
+                id=u'ff55a3ee-6dcd-4f76-b5a3-08055d5241f4',
+                is_private=False,
+                private=False,
+                type=u'dataset',
+                url=None,
+            ),
+            'resources': [
+                Mock(
+                    id=u'3ec53c20-f038-4ad6-a9fd-265cbe6faa27',
+                    url_type=u'upload',
+                )
+            ]
+        }
+
+        # return value of resources_of_containing_package
+        self.resources = [
+            {
+                u'cache_last_updated': None,
+                u'cache_url': None,
+                u'created': '2020-05-14T12:34:19.974216',
+                u'datastore_active': False,
+                u'description': u'',
+                u'format': u'PDF',
+                u'hash': u'',
+                u'id': u'3ec53c20-f038-4ad6-a9fd-265cbe6faa27',
+                u'last_modified': '2020-05-14T12:34:19.817460',
+                u'mimetype': u'application/pdf',
+                u'mimetype_inner': None,
+                u'name': u'Checkliste-Barrierefreies-PDF.pdf',
+                u'package_id': u'ff55a3ee-6dcd-4f76-b5a3-08055d5241f4',
+                u'position': 0,
+                u'resource_type': None,
+                u'revision_id': u'0c3b309e-5179-4b4d-91f6-26221bba85e1',
+                u'revision_timestamp': u'2020-05-14T12:34:19.950162',
+                u'size': 112437L,
+                u'state': u'active',
+                u'url': u'http://192.168.152.133:5000/dataset/ff55a3ee-6dcd-4f76-b5a3-08055d5241f4/resource/3ec53c20-f038-4ad6-a9fd-265cbe6faa27/download/checkliste-barrierefreies-pdf.pdf',
+                u'url_type': u'upload'
+            }
+        ]
+
+        # return value of _get_package_dict_from_context
+        self.package_dict = {
+            'author': None,
+            'author_email': None,
+            'creator_user_id': u'ff8d002d-3908-45e5-ba7b-445381860957',
+            'extras': [{u'key': u'groups', u'value': u''},
+                       {u'key': u'issued', u'value': u'2020-05-14T00:00:00'},
+                       {u'key': u'licenseAttributionByText', u'value': u''},
+                       {u'key': u'spatial',
+                        u'value': u'{"type": "Polygon", "coordinates": [[[9.5557, 54.6361], [9.5576, 54.6395], [9.5739, 54.6428], [9.593, 54.6508], [9.6041, 54.6497], [9.6049, 54.6533], [9.6132, 54.6556], [9.6261, 54.6632], [9.627, 54.6671], [9.6349, 54.6672], [9.6264, 54.6548], [9.6257, 54.6494], [9.6235, 54.6496], [9.624, 54.6426], [9.619, 54.6349], [9.6231, 54.6354], [9.6222, 54.6343], [9.6263, 54.6338], [9.6441, 54.6399], [9.6459, 54.6378], [9.6529, 54.6368], [9.657, 54.6327], [9.6629, 54.6312], [9.667, 54.6281], [9.6699, 54.6283], [9.6703, 54.6249], [9.6874, 54.6304], [9.6963, 54.6245], [9.6948, 54.6183], [9.7011, 54.6132], [9.6962, 54.6101], [9.6933, 54.6125], [9.6904, 54.6116], [9.6867, 54.6085], [9.6897, 54.6038], [9.6826, 54.6007], [9.6881, 54.599], [9.6857, 54.5943], [9.6911, 54.5913], [9.695, 54.591], [9.6965, 54.5932], [9.7065, 54.5957], [9.7101, 54.5935], [9.7148, 54.5931], [9.7169, 54.5948], [9.7232, 54.5928], [9.7241, 54.587], [9.718, 54.585], [9.7195, 54.5831], [9.7159, 54.5817], [9.7186, 54.5783], [9.7162, 54.5746], [9.7194, 54.5694], [9.7125, 54.5655], [9.7227, 54.5599], [9.7218, 54.5544], [9.7265, 54.5521], [9.726, 54.5492], [9.7289, 54.5497], [9.7322, 54.5478], [9.7413, 54.5402], [9.7412, 54.5375], [9.7374, 54.5357], [9.7389, 54.5321], [9.7373, 54.5303], [9.7211, 54.5299], [9.718, 54.5286], [9.714, 54.5187], [9.7119, 54.5183], [9.7024, 54.5179], [9.6932, 54.5224], [9.6731, 54.5153], [9.6439, 54.5155], [9.6344, 54.511], [9.6252, 54.5159], [9.6159, 54.5159], [9.616, 54.5236], [9.6035, 54.5269], [9.6036, 54.5282], [9.5986, 54.5282], [9.5927, 54.526], [9.5916, 54.5342], [9.5941, 54.5354], [9.5869, 54.5374], [9.5917, 54.5454], [9.5849, 54.5459], [9.576, 54.544], [9.5757, 54.5422], [9.5679, 54.5444], [9.5598, 54.5417], [9.5487, 54.5438], [9.546, 54.5413], [9.5472, 54.5402], [9.5434, 54.5391], [9.5456, 54.5394], [9.5445, 54.5367], [9.5399, 54.5357], [9.5364, 54.5393], [9.538, 54.5414], [9.5313, 54.5449], [9.5347, 54.5467], [9.5286, 54.5467], [9.5291, 54.5454], [9.5263, 54.5447], [9.5154, 54.5442], [9.5127, 54.5454], [9.5123, 54.5485], [9.5079, 54.549], [9.5054, 54.5526], [9.4992, 54.5549], [9.4941, 54.5647], [9.4899, 54.5617], [9.4798, 54.5643], [9.4824, 54.5965], [9.472, 54.5981], [9.4581, 54.5978], [9.4566, 54.6104], [9.4594, 54.6117], [9.4708, 54.6073], [9.4856, 54.6099], [9.491, 54.6094], [9.5065, 54.6153], [9.5059, 54.6205], [9.4983, 54.6206], [9.4995, 54.6246], [9.5109, 54.6234], [9.5116, 54.6258], [9.5009, 54.644], [9.4923, 54.6453], [9.4968, 54.6488], [9.4947, 54.6503], [9.493, 54.6494], [9.4844, 54.656], [9.4759, 54.6758], [9.5057, 54.6693], [9.5166, 54.6771], [9.5156, 54.6792], [9.5206, 54.6786], [9.5256, 54.6821], [9.5302, 54.683], [9.5301, 54.6842], [9.5359, 54.6846], [9.5297, 54.6703], [9.5299, 54.6676], [9.5328, 54.6672], [9.5328, 54.6652], [9.537, 54.6626], [9.5314, 54.6618], [9.5305, 54.6595], [9.5377, 54.6598], [9.5411, 54.6518], [9.5499, 54.6507], [9.5485, 54.6478], [9.5507, 54.6447], [9.5471, 54.6401], [9.5508, 54.6367], [9.5557, 54.6361]]]}'},
+                       {u'key': u'spatial_text', u'value': u'Amt S\xfcdangeln'},
+                       {u'key': u'spatial_uri',
+                        u'value': u'http://dcat-ap.de/def/politicalGeocoding/regionalKey/010595987'},
+                       {u'key': u'subject_text', u'value': u'Brosch\xfcre'},
+                       {u'key': u'temporal_end', u'value': u'2020-05-31T00:00:00'},
+                       {u'key': u'temporal_start', u'value': u'2020-05-01T00:00:00'}],
+            'groups': [],
+            'id': u'ff55a3ee-6dcd-4f76-b5a3-08055d5241f4',
+            'is_new': True,
+            'isopen': True,
+            'language': u'http://publications.europa.eu/resource/authority/language/DEU',
+            'license_id': u'http://dcat-ap.de/def/licenses/dl-zero-de/2.0',
+            'license_title': u'Datenlizenz Deutschland \u2013 Zero \u2013 Version 2.0',
+            'license_url': u'https://www.govdata.de/dl-de/zero-2-0',
+            'maintainer': None,
+            'maintainer_email': None,
+            'metadata_created': '2020-05-14T12:34:05.551655',
+            'metadata_modified': '2020-05-14T12:34:20.551455',
+            'name': u'test-thumbnail',
+            'notes': u'Test-Package f\xfcr Thumbnail-Gernerierung',
+            'num_resources': 1,
+            'num_tags': 0,
+            'organization': {u'approval_status': u'approved',
+                             u'created': '2019-07-29T08:11:32.697127',
+                             u'description': u'',
+                             u'id': u'63c87e74-60a9-4a4a-a980-d7983c47f92b',
+                             u'image_url': u'',
+                             u'is_organization': True,
+                             u'name': u'test-organisation',
+                             u'revision_id': u'3040af6c-d3f6-462d-b48a-329d63e17a28',
+                             u'state': u'active',
+                             u'title': u'Test-Organisation',
+                             u'type': u'organization'},
+            'owner_org': u'63c87e74-60a9-4a4a-a980-d7983c47f92b',
+            'private': False,
+            'relationships_as_object': [],
+            'relationships_as_subject': [],
+            'resources': [{u'cache_last_updated': None,
+                           u'cache_url': None,
+                           u'created': '2020-05-14T12:34:19.974216',
+                           u'datastore_active': False,
+                           u'description': u'',
+                           u'format': u'PDF',
+                           u'hash': u'66123edf64fabf1c073fc45478bf4a57',
+                           u'hash_algorithm': u'http://dcat-ap.de/def/hashAlgorithms/md/5',
+                           u'id': u'3ec53c20-f038-4ad6-a9fd-265cbe6faa27',
+                           u'last_modified': '2020-05-14T12:34:19.817460',
+                           u'mimetype': u'application/pdf',
+                           u'mimetype_inner': None,
+                           u'name': u'Checkliste-Barrierefreies-PDF.pdf',
+                           u'number_of_pages': u'3',
+                           u'package_id': u'ff55a3ee-6dcd-4f76-b5a3-08055d5241f4',
+                           u'position': 0,
+                           u'resource_type': None,
+                           u'revision_id': u'efba6d3b-6204-4872-b6d1-a9e7b6d05a35',
+                           u'size': 112437L,
+                           u'state': u'active',
+                           u'url': u'http://192.168.152.133:5000/dataset/ff55a3ee-6dcd-4f76-b5a3-08055d5241f4/resource/3ec53c20-f038-4ad6-a9fd-265cbe6faa27/download/checkliste-barrierefreies-pdf.pdf',
+                           u'url_type': u'upload'}],
+            'revision_id': u'cdedc6a9-92d9-4a9a-9511-12c2458cbfe5',
+            'state': u'draft',
+            'subject': u'http://transparenz.schleswig-holstein.de/informationsgegenstand#Broschuere',
+            'tags': [],
+            'title': u'Test Thumbnail',
+            'type': u'dataset',
+            'url': None,
+            'version': None
+        }
+
+
+class WithAfterCreateDataAPI(object):
+
+    def setUp(self):
+        config.update({
+            'ckan.thumbnail.size.width': 410,
+            'ckan.storage_path': temp_path,
+        })
+
+        self.resource = {
+            u'cache_last_updated': None,
+            u'cache_url': None,
+            u'created': '2020-05-18T12:06:59.559870',
+            u'datastore_active': False,
+            u'description': u'',
+            u'format': u'PDF',
+            u'hash': u'',
+            u'id': u'341d8f18-00fc-4cd8-b6b1-0fc68f6b1c2e',
+            u'last_modified': '2020-05-18T12:06:59.487842',
+            u'mimetype': u'application/pdf',
+            u'mimetype_inner': None,
+            u'name': None,
+            u'package_id': u'18eab091-64ce-44ad-b1cb-c8801017ce90',
+            u'position': 0,
+            u'resource_type': None,
+            u'revision_id': u'5cdf9119-e88d-4a7e-a737-6b3f8c2b3877',
+            u'size': 222997L,
+            u'state': u'active',
+            u'url': u'http://192.168.152.133:5000/dataset/18eab091-64ce-44ad-b1cb-c8801017ce90/resource/341d8f18-00fc-4cd8-b6b1-0fc68f6b1c2e/download/manual.pdf',
+            u'url_type': u'upload'
+        }
+
+        self.resources = [{
+            u'cache_last_updated': None,
+            u'cache_url': None,
+            u'created': '2020-05-18T12:06:59.559870',
+            u'datastore_active': False,
+            u'description': u'',
+            u'format': u'PDF',
+            u'hash': u'',
+            u'id': u'341d8f18-00fc-4cd8-b6b1-0fc68f6b1c2e',
+            u'last_modified': '2020-05-18T12:06:59.487842',
+            u'mimetype': u'application/pdf',
+            u'mimetype_inner': None,
+            u'name': None,
+            u'package_id': u'18eab091-64ce-44ad-b1cb-c8801017ce90',
+            u'position': 0,
+            u'resource_type': None,
+            u'revision_id': u'5cdf9119-e88d-4a7e-a737-6b3f8c2b3877',
+            u'revision_timestamp': u'2020-05-18T12:06:59.538388',
+            u'size': 222997L,
+            u'state': u'active',
+            u'url': u'http://192.168.152.133:5000/dataset/18eab091-64ce-44ad-b1cb-c8801017ce90/resource/341d8f18-00fc-4cd8-b6b1-0fc68f6b1c2e/download/manual.pdf',
+            u'url_type': u'upload'
+        }]
+
+        self.resources_second_call = [
+            {
+                u'cache_last_updated': None,
+                u'cache_url': None,
+                u'created': '2020-05-18T12:24:19.871546',
+                u'datastore_active': False,
+                u'description': u'',
+                u'format': u'PDF',
+                u'hash': u'f6c4aea4feff08461c5847788ec58c16',
+                u'hash_algorithm': u'http://dcat-ap.de/def/hashAlgorithms/md/5',
+                u'id': u'ddda4166-6618-4708-96ed-edb000096b58',
+                u'last_modified': '2020-05-18T12:24:19.801966',
+                u'mimetype': u'application/pdf',
+                u'mimetype_inner': None,
+                u'name': None,
+                u'number_of_pages': u'11',
+                u'package_id': u'941e2cc8-491c-48c5-8007-8a99041e230d',
+                u'position': 0,
+                u'resource_type': None,
+                u'revision_id': u'60174ab3-419c-48dc-a56a-2bb633bbe6cb',
+                u'revision_timestamp': u'2020-05-18T12:24:20.307640',
+                u'size': 222997L,
+                u'state': u'active',
+                u'url': u'http://192.168.152.133:5000/dataset/941e2cc8-491c-48c5-8007-8a99041e230d/resource/ddda4166-6618-4708-96ed-edb000096b58/download/manual.pdf',
+                u'url_type': u'upload'},
+            {
+                u'cache_last_updated': None,
+                u'cache_url': None,
+                u'created': '2020-05-18T12:26:24.185953',
+                u'datastore_active': False,
+                u'description': u'',
+                u'format': u'PDF',
+                u'hash': u'',
+                u'id': u'8697b2f0-27b0-43ff-acfc-b7a1803db3b7',
+                u'last_modified': '2020-05-18T12:26:24.113533',
+                u'mimetype': u'application/pdf',
+                u'mimetype_inner': None,
+                u'name': None,
+                u'package_id': u'941e2cc8-491c-48c5-8007-8a99041e230d',
+                u'position': 1,
+                u'resource_type': None,
+                u'revision_id': u'3225d910-6a0d-48c9-8bb8-a362c7c94d59',
+                u'revision_timestamp': u'2020-05-18T12:26:24.163859',
+                u'size': 222997L,
+                u'state': u'active',
+                u'url': u'http://192.168.152.133:5000/dataset/941e2cc8-491c-48c5-8007-8a99041e230d/resource/8697b2f0-27b0-43ff-acfc-b7a1803db3b7/download/manual.pdf',
+                u'url_type': u'upload'
+            }
+        ]
+
+        self.context = {
+            'package': Mock(
+                id=u'18eab091-64ce-44ad-b1cb-c8801017ce90',
+                is_private=False,
+                private=False,
+                type=u'dataset',
+                url=None,
+            ),
+            'resources': [
+                Mock(
+                    id=u'341d8f18-00fc-4cd8-b6b1-0fc68f6b1c2e',
+                    url_type=u'upload',
+                )
+            ]
+        }
+
+        self.package_dict = {
+            'author': None,
+            'author_email': None,
+            'creator_user_id': u'ff8d002d-3908-45e5-ba7b-445381860957',
+            'extras': [{u'key': u'issued', u'value': u'2019-07-07T00:00:00'},
+                       {u'key': u'licenseAttributionByText', u'value': u''},
+                       {u'key': u'subject_text', u'value': u'Verwaltungsvorschrift'}],
+            'groups': [],
+            'id': u'18eab091-64ce-44ad-b1cb-c8801017ce90',
+            'is_new': False,
+            'isopen': True,
+            'language': u'http://publications.europa.eu/resource/authority/language/DEU',
+            'license_id': u'http://dcat-ap.de/def/licenses/dl-zero-de/2.0',
+            'license_title': u'Datenlizenz Deutschland \u2013 Zero \u2013 Version 2.0',
+            'license_url': u'https://www.govdata.de/dl-de/zero-2-0',
+            'maintainer': None,
+            'maintainer_email': None,
+            'metadata_created': '2020-05-18T12:06:59.137351',
+            'metadata_modified': '2020-05-18T12:07:00.060791',
+            'name': u'test-resource1',
+            'notes': u'Testdatensatz aus Skript',
+            'num_resources': 1,
+            'num_tags': 0,
+            'organization': {u'approval_status': u'approved',
+                             u'created': '2019-07-29T08:11:32.697127',
+                             u'description': u'',
+                             u'id': u'63c87e74-60a9-4a4a-a980-d7983c47f92b',
+                             u'image_url': u'',
+                             u'is_organization': True,
+                             u'name': u'test-organisation',
+                             u'revision_id': u'3040af6c-d3f6-462d-b48a-329d63e17a28',
+                             u'state': u'active',
+                             u'title': u'Test-Organisation',
+                             u'type': u'organization'},
+            'owner_org': u'63c87e74-60a9-4a4a-a980-d7983c47f92b',
+            'private': False,
+            'relationships_as_object': [],
+            'relationships_as_subject': [],
+            'resources': [{u'cache_last_updated': None,
+                           u'cache_url': None,
+                           u'created': '2020-05-18T12:06:59.559870',
+                           u'datastore_active': False,
+                           u'description': u'',
+                           u'format': u'PDF',
+                           u'hash': u'f6c4aea4feff08461c5847788ec58c16',
+                           u'hash_algorithm': u'http://dcat-ap.de/def/hashAlgorithms/md/5',
+                           u'id': u'341d8f18-00fc-4cd8-b6b1-0fc68f6b1c2e',
+                           u'last_modified': '2020-05-18T12:06:59.487842',
+                           u'mimetype': u'application/pdf',
+                           u'mimetype_inner': None,
+                           u'name': None,
+                           u'number_of_pages': u'11',
+                           u'package_id': u'18eab091-64ce-44ad-b1cb-c8801017ce90',
+                           u'position': 0,
+                           u'resource_type': None,
+                           u'revision_id': u'33c0df08-52cc-4774-b8d2-b283038b2891',
+                           u'size': 222997L,
+                           u'state': u'active',
+                           u'url': u'http://192.168.152.133:5000/dataset/18eab091-64ce-44ad-b1cb-c8801017ce90/resource/341d8f18-00fc-4cd8-b6b1-0fc68f6b1c2e/download/manual.pdf',
+                           u'url_type': u'upload'}],
+            'revision_id': u'81152f7d-b942-42db-b1f1-f9e61cdfc43f',
+            'state': u'active',
+            'subject': u'http://transparenz.schleswig-holstein.de/informationsgegenstand#Verwaltungsvorschrift',
+            'tags': [],
+            'title': u'test resource',
+            'type': u'dataset',
+            'url': None,
+            'version': None
+        }
+
+        self.package_dict_second_call = {
+            'author': None,
+            'author_email': None,
+            'creator_user_id': u'ff8d002d-3908-45e5-ba7b-445381860957',
+            'extras': [{u'key': u'issued', u'value': u'2019-07-07T00:00:00'},
+                       {u'key': u'licenseAttributionByText', u'value': u''},
+                       {u'key': u'subject_text', u'value': u'Verwaltungsvorschrift'}],
+            'groups': [],
+            'id': u'941e2cc8-491c-48c5-8007-8a99041e230d',
+            'is_new': False,
+            'isopen': True,
+            'language': u'http://publications.europa.eu/resource/authority/language/DEU',
+            'license_id': u'http://dcat-ap.de/def/licenses/dl-zero-de/2.0',
+            'license_title': u'Datenlizenz Deutschland \u2013 Zero \u2013 Version 2.0',
+            'license_url': u'https://www.govdata.de/dl-de/zero-2-0',
+            'maintainer': None,
+            'maintainer_email': None,
+            'metadata_created': '2020-05-18T12:24:19.451803',
+            'metadata_modified': '2020-05-18T12:26:24.637389',
+            'name': u'test-resource3',
+            'notes': u'Testdatensatz aus Skript',
+            'num_resources': 2,
+            'num_tags': 0,
+            'organization': {u'approval_status': u'approved',
+                             u'created': '2019-07-29T08:11:32.697127',
+                             u'description': u'',
+                             u'id': u'63c87e74-60a9-4a4a-a980-d7983c47f92b',
+                             u'image_url': u'',
+                             u'is_organization': True,
+                             u'name': u'test-organisation',
+                             u'revision_id': u'3040af6c-d3f6-462d-b48a-329d63e17a28',
+                             u'state': u'active',
+                             u'title': u'Test-Organisation',
+                             u'type': u'organization'},
+            'owner_org': u'63c87e74-60a9-4a4a-a980-d7983c47f92b',
+            'private': False,
+            'relationships_as_object': [],
+            'relationships_as_subject': [],
+            'resources': [
+                {
+                    u'cache_last_updated': None,
+                    u'cache_url': None,
+                    u'created': '2020-05-18T12:24:19.871546',
+                    u'datastore_active': False,
+                    u'description': u'',
+                    u'format': u'PDF',
+                    u'hash': u'f6c4aea4feff08461c5847788ec58c16',
+                    u'hash_algorithm': u'http://dcat-ap.de/def/hashAlgorithms/md/5',
+                    u'id': u'ddda4166-6618-4708-96ed-edb000096b58',
+                    u'last_modified': '2020-05-18T12:24:19.801966',
+                    u'mimetype': u'application/pdf',
+                    u'mimetype_inner': None,
+                    u'name': None,
+                    u'number_of_pages': u'11',
+                    u'package_id': u'941e2cc8-491c-48c5-8007-8a99041e230d',
+                    u'position': 0,
+                    u'resource_type': None,
+                    u'revision_id': u'60174ab3-419c-48dc-a56a-2bb633bbe6cb',
+                    u'size': 222997L,
+                    u'state': u'active',
+                    u'url': u'http://192.168.152.133:5000/dataset/941e2cc8-491c-48c5-8007-8a99041e230d/resource/ddda4166-6618-4708-96ed-edb000096b58/download/manual.pdf',
+                    u'url_type': u'upload'
+                },
+                {
+                    u'cache_last_updated': None,
+                    u'cache_url': None,
+                    u'created': '2020-05-18T12:26:24.185953',
+                    u'datastore_active': False,
+                    u'description': u'',
+                    u'format': u'PDF',
+                    u'hash': u'f6c4aea4feff08461c5847788ec58c16',
+                    u'hash_algorithm': u'http://dcat-ap.de/def/hashAlgorithms/md/5',
+                    u'id': u'8697b2f0-27b0-43ff-acfc-b7a1803db3b7',
+                    u'last_modified': '2020-05-18T12:26:24.113533',
+                    u'mimetype': u'application/pdf',
+                    u'mimetype_inner': None,
+                    u'name': None,
+                    u'number_of_pages': u'11',
+                    u'package_id': u'941e2cc8-491c-48c5-8007-8a99041e230d',
+                    u'position': 1,
+                    u'resource_type': None,
+                    u'revision_id': u'c9157e0f-4516-462e-9fb2-ebfe51d17b82',
+                    u'size': 222997L,
+                    u'state': u'active',
+                    u'url': u'http://192.168.152.133:5000/dataset/941e2cc8-491c-48c5-8007-8a99041e230d/resource/8697b2f0-27b0-43ff-acfc-b7a1803db3b7/download/manual.pdf',
+                    u'url_type': u'upload'
+                }
+            ],
+            'revision_id': u'68008d04-edcb-4040-bcec-b327ee39cdcc',
+            'state': u'active',
+            'subject': u'http://transparenz.schleswig-holstein.de/informationsgegenstand#Verwaltungsvorschrift',
+            'tags': [],
+            'thumbnail': u'thumbnail_picture_35c5b0113d495444a843162e72fa53.jpg',
+            'title': u'test resource',
+            'type': u'dataset',
+            'url': None,
+            'version': None
+        }
diff --git a/ckanext/odsh/tools.py b/ckanext/odsh/tools.py
index ab7c1ba60885d1a4589b1bc533f9d22e4d74d27b..d6fa856871a78fda448cd49f289be6fc024cf2c9 100644
--- a/ckanext/odsh/tools.py
+++ b/ckanext/odsh/tools.py
@@ -1,5 +1,5 @@
 import os
-from ckanext.odsh.pdf_to_thumbnail.thumbnail import get_filepath_to_resource
+from ckanext.odsh.pdf_to_thumbnail.thumbnail import get_resource_path
 from ckanext.odsh.lib.uploader import calculate_hash
 import ckan.plugins.toolkit as toolkit
 import magic
@@ -12,7 +12,7 @@ def add_attributes_resources(context, resource):
     i = 0
     for item in resources:    
         if item.get('id') == resource.get('id'):
-            path = get_filepath_to_resource(resource)
+            path = get_resource_path(resource)
             if os.path.exists(path):
                 with open(path, 'rb') as file:                  
                     
diff --git a/ckanext/odsh/validation.py b/ckanext/odsh/validation.py
index 8c2f955ebdd6664beb4a22387ee68d4f191d56f9..6b248cf3e619002b01a1ad70ad0afcb6f75b1822 100644
--- a/ckanext/odsh/validation.py
+++ b/ckanext/odsh/validation.py
@@ -6,14 +6,14 @@ import urllib2
 import json
 from itertools import count
 from dateutil.parser import parse
+from pylons import config
 
 import ckan.plugins.toolkit as toolkit
 import ckan.model as model
+import ckan.logic as logic
 from ckan.lib.navl.dictization_functions import Missing
 
-from pylons import config
-
-import pdb
+from ckanext.odsh.helpers_tpsh import get_package_dict
 
 _ = toolkit._
 
@@ -222,7 +222,6 @@ def known_spatial_uri(key, data, errors, context):
         poly = None
 
         # some harvesters might import a polygon directly...
-        # pdb.set_trace()
         poly = _extract_value(data, 'spatial')
 
         has_old_uri = False
@@ -377,6 +376,14 @@ def validate_subject(key, flattened_data, errors, context):
     flattened_data = _convert_subjectID_to_subjectText(subject_id, flattened_data)
 
 
+def validate_relatedPackage(data):
+    if data:
+        try:
+            get_package_dict(data)
+        except logic.NotFound:
+            raise toolkit.Invalid("relatedPackage: package '{}' not found".format(data))
+
+
 def get_validators():
     return {
         'known_spatial_uri': known_spatial_uri,
@@ -384,4 +391,5 @@ def get_validators():
         'odsh_validate_extras': validate_extras,
         'validate_licenseAttributionByText': validate_licenseAttributionByText,
         'tpsh_validate_subject': validate_subject,
+        'tpsh_validate_relatedPackage': validate_relatedPackage,
     }
diff --git a/test.log b/test.log
deleted file mode 100644
index 094310a6f3a6b209217cf45ba0d6ffd594e46e13..0000000000000000000000000000000000000000
--- a/test.log
+++ /dev/null
@@ -1,46 +0,0 @@
-2019-04-25 12:18:32,933 WARNI [ckan.lib.celery_app] ckan.lib.celery_app is deprecated, use ckan.lib.jobs instead.
-2019-04-25 12:18:33,115 DEBUG [ckanext.harvest.model] Harvest tables defined in memory
-2019-04-25 12:18:33,124 DEBUG [ckanext.harvest.model] Harvest tables already exist
-2019-04-25 12:18:33,154 DEBUG [ckanext.spatial.plugin] Setting up the spatial model
-2019-04-25 12:18:33,159 DEBUG [ckanext.spatial.model.package_extent] Spatial tables defined in memory
-2019-04-25 12:18:33,162 DEBUG [ckanext.spatial.model.package_extent] Spatial tables already exist
-2019-04-25 12:18:36,194 ERROR [ckanext.odsh.plugin] got exception ...
-... : Traceback (most recent call last):
-... :   File \\"/usr/lib/ckan/default/src/ckanext-odsh/ckanext/odsh/plugin.py\\", line 358, in before_search
-... :     raise BaseException('boom')
-... : BaseException: boom
-... : ";
-2019-04-25 12:18:36,743 INFO  [ckan.lib.base]  /dataset render time 0.559 seconds
-2019-04-25 12:18:37,307 INFO  [ckan.lib.base]  /api/i18n/de render time 0.007 seconds
-2019-04-25 12:18:37,664 ERROR [ckanext.odsh.plugin] got exception ...
-... : Traceback (most recent call last):
-... :   File \\"/usr/lib/ckan/default/src/ckanext-odsh/ckanext/odsh/plugin.py\\", line 358, in before_search
-... :     raise BaseException('boom')
-... : BaseException: boom
-... : ";
-2019-04-25 12:18:37,887 INFO  [ckan.lib.base]  /dataset render time 0.230 seconds
-2019-04-25 12:18:38,320 INFO  [ckan.lib.base]  /api/i18n/de render time 0.002 seconds
-2019-04-25 12:18:38,913 ERROR [ckanext.odsh.plugin] got exception ...
-... : Traceback (most recent call last):
-... :   File \\"/usr/lib/ckan/default/src/ckanext-odsh/ckanext/odsh/plugin.py\\", line 358, in before_search
-... :     raise BaseException('boom')
-... : BaseException: boom
-... : ";
-2019-04-25 12:18:39,040 INFO  [ckan.lib.base]  /dataset render time 0.132 seconds
-2019-04-25 12:18:39,518 INFO  [ckan.lib.base]  /api/i18n/de render time 0.002 seconds
-2019-04-25 12:18:40,249 ERROR [ckanext.odsh.plugin] got exception ...
-... : Traceback (most recent call last):
-... :   File \\"/usr/lib/ckan/default/src/ckanext-odsh/ckanext/odsh/plugin.py\\", line 358, in before_search
-... :     raise BaseException('boom')
-... : BaseException: boom
-... : ";
-2019-04-25 12:18:40,394 INFO  [ckan.lib.base]  /dataset render time 0.151 seconds
-2019-04-25 12:18:40,865 INFO  [ckan.lib.base]  /api/i18n/de render time 0.003 seconds
-2019-04-25 12:18:41,044 ERROR [ckanext.odsh.plugin] got exception ...
-... : Traceback (most recent call last):
-... :   File \\"/usr/lib/ckan/default/src/ckanext-odsh/ckanext/odsh/plugin.py\\", line 358, in before_search
-... :     raise BaseException('boom')
-... : BaseException: boom
-... : ";
-2019-04-25 12:18:41,194 INFO  [ckan.lib.base]  /dataset render time 0.158 seconds
-2019-04-25 12:18:41,606 INFO  [ckan.lib.base]  /api/i18n/de render time 0.002 seconds
diff --git a/test2.log b/test2.log
deleted file mode 100644
index d655cced37f21be73dda3649588a9b9572fe64ef..0000000000000000000000000000000000000000
--- a/test2.log
+++ /dev/null
@@ -1,40 +0,0 @@
-Traceback (most recent call last):
-  File "/usr/lib/ckan/default/bin/paster", line 11, in <module>
-    sys.exit(run())
-  File "/usr/lib/ckan/default/local/lib/python2.7/site-packages/paste/script/command.py", line 102, in run
-    invoke(command, command_name, options, args[1:])
-  File "/usr/lib/ckan/default/local/lib/python2.7/site-packages/paste/script/command.py", line 141, in invoke
-    exit_code = runner.run(args)
-  File "/usr/lib/ckan/default/local/lib/python2.7/site-packages/paste/script/command.py", line 236, in run
-    result = self.command()
-  File "/usr/lib/ckan/default/local/lib/python2.7/site-packages/paste/script/serve.py", line 284, in command
-    relative_to=base, global_conf=vars)
-  File "/usr/lib/ckan/default/local/lib/python2.7/site-packages/paste/script/serve.py", line 329, in loadapp
-    **kw)
-  File "/usr/lib/ckan/default/local/lib/python2.7/site-packages/paste/deploy/loadwsgi.py", line 247, in loadapp
-    return loadobj(APP, uri, name=name, **kw)
-  File "/usr/lib/ckan/default/local/lib/python2.7/site-packages/paste/deploy/loadwsgi.py", line 272, in loadobj
-    return context.create()
-  File "/usr/lib/ckan/default/local/lib/python2.7/site-packages/paste/deploy/loadwsgi.py", line 710, in create
-    return self.object_type.invoke(self)
-  File "/usr/lib/ckan/default/local/lib/python2.7/site-packages/paste/deploy/loadwsgi.py", line 146, in invoke
-    return fix_call(context.object, context.global_conf, **context.local_conf)
-  File "/usr/lib/ckan/default/local/lib/python2.7/site-packages/paste/deploy/util.py", line 55, in fix_call
-    val = callable(*args, **kw)
-  File "/usr/lib/ckan/default/src/ckan/ckan/config/middleware/__init__.py", line 46, in make_app
-    load_environment(conf, app_conf)
-  File "/usr/lib/ckan/default/src/ckan/ckan/config/environment.py", line 99, in load_environment
-    p.load_all()
-  File "/usr/lib/ckan/default/src/ckan/ckan/plugins/core.py", line 139, in load_all
-    load(*plugins)
-  File "/usr/lib/ckan/default/src/ckan/ckan/plugins/core.py", line 153, in load
-    service = _get_service(plugin)
-  File "/usr/lib/ckan/default/src/ckan/ckan/plugins/core.py", line 255, in _get_service
-    return plugin.load()(name=plugin_name)
-  File "/usr/lib/ckan/default/local/lib/python2.7/site-packages/pkg_resources/__init__.py", line 2305, in load
-    return self.resolve()
-  File "/usr/lib/ckan/default/local/lib/python2.7/site-packages/pkg_resources/__init__.py", line 2311, in resolve
-    module = __import__(self.module_name, fromlist=['__name__'], level=0)
-  File "/usr/lib/ckan/default/src/ckanext-odsh/ckanext/odsh/plugin.py", line 29, in <module>
-    class OdshLogger(multiline_formatter.formatter.MultilineMessagesFormatter):
-NameError: name 'multiline_formatter' is not defined
diff --git a/uls.conf b/uls.conf
deleted file mode 100644
index a94f2d66b6912e16f9b3121114f0b5e32a59e260..0000000000000000000000000000000000000000
--- a/uls.conf
+++ /dev/null
@@ -1,16 +0,0 @@
-inputfile = /var/log/ckan/bulk.log
-inputfile = /var/log/ckan/std/gather_consumer.log
-inputfile = /var/log/ckan/std/fetch_consumer.log
-
-#outputfile = ./logs/out.uls
-
-iformat0 = %F %T,%c%c%c ERROR [%q] got exception ...%W
-nreadformat0 =... : %W
-
-# 'got exception...' ist die erste Zeile eines Tracebacks
-reg0 = got exception ...  
-# alle forgenden Zeilen haben diesen Präfix
-nreadreg0 =^... :
-
-write0 = V;%F %T;%h;"odsh";"Exception";"traceback";"in %p
-nreadwrite0 = %W
\ No newline at end of file