From b64d9e4402b722a4f640062e97d69f4d004dd21b Mon Sep 17 00:00:00 2001
From: anonymous <anonymous>
Date: Tue, 25 Jun 2019 10:03:15 +0200
Subject: [PATCH] ODPSH-31: update layout

---
 .../odsh/i18n/de/LC_MESSAGES/ckanext-odsh.mo  | Bin 8305 -> 8386 bytes
 .../odsh/i18n/de/LC_MESSAGES/ckanext-odsh.po  |  11 +-
 ckanext/odsh/plugin.py                        | 142 ++++++++++--------
 ckanext/odsh/public/odsh.css                  |  51 +++++++
 ckanext/odsh/templates/datarequests/base.html |  55 +++++++
 .../odsh/templates/datarequests/comment.html  |  41 +++++
 ckanext/odsh/templates/datarequests/show.html | 101 +++++++++++++
 .../datarequests/snippets/comment_form.html   |  29 ++++
 .../datarequests/snippets/comment_item.html   |  47 ++++++
 .../datarequests/snippets/comments.html       |  19 +++
 ckanext/odsh/tests/test_datarequest.py        |  71 +++++++++
 11 files changed, 505 insertions(+), 62 deletions(-)
 create mode 100644 ckanext/odsh/templates/datarequests/base.html
 create mode 100644 ckanext/odsh/templates/datarequests/comment.html
 create mode 100644 ckanext/odsh/templates/datarequests/show.html
 create mode 100644 ckanext/odsh/templates/datarequests/snippets/comment_form.html
 create mode 100644 ckanext/odsh/templates/datarequests/snippets/comment_item.html
 create mode 100644 ckanext/odsh/templates/datarequests/snippets/comments.html
 create mode 100644 ckanext/odsh/tests/test_datarequest.py

diff --git a/ckanext/odsh/i18n/de/LC_MESSAGES/ckanext-odsh.mo b/ckanext/odsh/i18n/de/LC_MESSAGES/ckanext-odsh.mo
index ab0a6177c3cc3782e3ad5f9f36bd1a9e3e1f5af1..98510aaaa8b8077e816b5e300aabaeb12c971117 100644
GIT binary patch
delta 2293
zcmez9aLBR#o)F7a1_lO(4h9AW83qQ17#0u@f#+~AFfcPPFwBM03!(H<4h9C0(v?vD
zIt~T~AqED9O&kmi{0s~XM>!Z6xEUB2u0rXD91!#0axgHkGB7Z3aWXKlGceRM2yikm
zh%+!SNN_?l7(i(oPKZGsoD2+H3=9kroD2+t3=9m(oD2*K3=9mVoD2+-3=9m@pyC^#
z>JCBmpXG#DbdwX}u=|`03~USx44*g|7&sXi7=CgxFi0@eGcYi7K_q0j7#IW?7#Nhf
zAQqW$F))ZRFfh1rfnCZF%LQ>rDi_4y0;s++E(QiZ1_p*&E(Qis1_p*6E(Qiw1_p*j
zTnr4n3=9k>xEL5z7#J9?aWOE6FfcIu=3-!wWnf?s;AUXpt!H3h(Bp>q#D<%JfrEj8
zA&eVhU_3X(qC6<Sgd1W&GdIKsGobV$Zb-<j;f93R4sMXo7#L1K<sWi0FjzA%Fudkw
zV31;9U{L3QM4b~414BIz0|SFQ55&R{9tH+oP!RJ#d@_*-;(+Bm5QnYhfrQjHsQSH7
z@iRP-D7*?a_$?2_A)lapCSHj7?7R?hF<yuV)OZ={!9F(Ng}BHPO4~tcXI@B<dq52c
zhKeUa>2xTa!^^;+$iTo*2<1=ag+$p5UWmgNK<SlG@l8;5yFlR&N)-EfA#rt#7ZP+=
zpbFmdLW1@aF9U-%0|Uc9UPw@C@j-G$8XqLBWbr{Ps^Nopq?M0>L70Jop^Fa^lJoc=
zA+&`Ll6cSXLDI<EdOijQO$G)AVSb2!Ek8s-GCu=@90LPG3zWZ@AChQ}@I%tdBYp-3
zc?JfCKTy7m0K{T50Z3}MhVuOcAm#)MKs;6imG6ei*UuAxxO6d8;dTLti?0bl9P&m0
z5?8!}3=B*R3=HCe5R0S)Ar6rjWMGhHU|_HmWMI%^U|<LpWMJ?DIb4u|!GeK-;k+ON
z!vsbK1`Z)e2+tITq=Dta;51RsupPo+I3^4U>Z`&Kg%5=p7(j{hy)eW<e}o}E5E6le
zl(q;&U7!d=UAzdyAvsWfhX~jLhN&VD{Yyk3*?AX~e^G>iK@F7upNl|(l1~(3pq?m1
zgN-P}fFMywVoDT+ln<GrkX%qD3Q3%eqL3i(f%4~zGB7AJFfgnVWnfTZU|={W3W<V`
zP<;Yo5dGp}3=FKG{4WO;P!WUVZ*4J%PaMP`i7rMA;*bt8h(XiEAVujmF^I!2i$Ox}
zvlt|e{1<}+v8*^Gaq5ag5}&;|#AD%5ey%tJLp`X(YZZt1e402Uu9t{I;&vTW!wIPT
zJ#hwx5C#T@H{y^)>nH)ymn{KFLzNN`3wk9O7>pPg7#2%F9C%FvQjUC+fSCVB0ulmj
zk_`3WvY1yAlBmQbL2<&sAS21ZV9UV3;4BFVkw!^K6ikzZ_-LLaBq}yYLJT|zrB6#j
ze0o(9;(@nNee6;Yhw@87^vg;?9H1`+D*qW67|f&~ab+dNz@Wpxzz{11iQ6tINWPvb
z1+i!zlwJX)H%UQ!yhjQW<d>u%CFmn51_pCb886MiV8Fn@;2{lhaHTXP3cI8sxn*U&
zG$d$lNJCt9Pa2Y#K1f4c$SecV$R)$TpuxbvAT0y2z(odPL6Ho^z*RC315e68a>oxD
zNLrASg+zsoEF@&}Wg+^f$wJar{SjFP215o0h7Yn3pD4*eg4{w5VzI9r#Gp7i1_mt#
z28J3rNKw074&u<0a*)J$M-F1}PdSLg)a4=O#>+!W%!%@lkUT37DLJpngQHZ7;g&q4
zR(d24G3c*61A`?41A~+T1A{XI14D=cB(crdT*<MYi7h8Ju{bqlvMF~nXK-nHdTMb=
zYSHHH+{WDeVW~xjx22_}=9T1RCTEmPekrzzH!Q!XI5{IHF+DXeC3W*G@#`D_YQEnr

delta 2214
zcmX@)_|c*Mo)F7a1_lO(HU<U;83qOh9~KY~fonJz7?>Ft7;2$(Bb09CU|;|#?S%6C
zI2ag&7#J8PaWF9OGcYhL<zQgoW?*303Z)NnK+HeO!N9=Ez`*c|gMopafuWw^0|x_x
zI0FO252!)`PKZVsPKZGooD2+H3=9kwoD2+t3=9m;oD2*K3=9mRoD2+-3=9lqQ1J;+
zb&H_-*K$HE+Q|uV*nUn11~vu;hD)3b44e!M3^zF$7$g|#85kZzCH`<SFbFU(FfemL
zEE3^jU=U+qU{K=%yOhC}3*r!0E{MSaTo41pxEL7t7#J90xfmEk85kIHxEL5z85kIv
zxEL6C85kH=a4|5bFfcG|<6>YCVPIgm&BeeV%fP_!0VH3~z`(%A4e^N#H^d+_Ziof0
zP`)=eL}L^;#O3)=x{@0bQqA0ukm~0K`G|pGJ~sn{H3I{~4sHepDFz0HFWiu*;^$#t
z;9+235aEH?qsYU+P_N6tz+k`wad{jM#NrYj1_llW28Jpgh!2~g8oHq3GkG9UwulE}
z@Gc&R1NKAt=b`GaK*b;PKpg%Vs{b!k9UCu0Jvb3@^Fj;~;DrRSC@;i?3Q%!<C~X3z
zEqNIj6d4#8?4bMvUPu(B@<JS*3#H4T;`LB<?Ys~V_3%QXW-@O*B*+#*73|`L1nGWW
z1_o^g28PqTkf8j|3&{n>e2}zZ&Ihr`lMmwXAU*~LVFm_<a6U*#X7fQpsF4qnSZDG<
z(!?%41_n(A28IVvK36?IM1cW61A`m`14AH`U%(GZBop}|X=6P<1A{yR1H(xu|0O@f
zVrBtI>gEuD@TCPH<|qh2JZ2A-kATW&3qTxN099AtA^>smVgZOlb_zh^>XrZl0}}%S
z!xN~+=K>Iiyb)kvkY-?DU=w6u&|_d=P!eQd@B%qpkb%L1fq`L;AOph$Mh1qff{-9i
z6NaRX5@AS`wm|90!r+juXILZ*QMgVRk~;SYLmYHc7~+Hb!jPc*0aYg_0#T<U0&$2X
zlpiVru^>qVqQ6iCl6~8u{COe_3~CGv3|mAXA$eN_WN$qK!*8epPEm*f@}iK$q$dg~
z56na%iN;+Nk~sZDAweDq<>!bpFeo!HFqDfjFeouFFw7Q(M8Q6&zPnKRi6|s@y@v8X
ziZU>Wfb#zjQHW1?#UP1JQw-veP%(%>DPoYKvsnz{@&#hxpkp{721z4l#2|_Cl^7(^
z{SkwNfQUGRuO$xgh?6+T#|#X?;*cm#6lY+l2Nj{&;t&n3;tUKSARmZBQt1b%1}O<h
zqS26msI!q^U@&4}U<i_cIH*+u;<Kd^5QA4sKtgOQl)py;k~oh^K%(HZ1OtOD0|Ub+
zQ27fg5)C9FLGCIE@rkb_#KrNF5CaRKbd@B;M=g?&#5f14Z<{2<XZxV~&qzWXa8DAF
zZJ$a)qUNO}1A`6&0|So~Bq}YX>LK~qMG9h(FO&|4(uq<KA7@KJQgNdc0|ThUoG8V>
zV9vn6uwROS!GM8*;ky*X!5Y$#D6^D?<c<hwNXWEHLp;_i4M{umr6CU7R4)zDxKo;e
zL4$#T;gmGQg3r<r3lwD_21d$246KlW<c8%kkhni91Br^)GLWE^lZEJam4&3GVp#?T
zLk0$h`LYm?T$F{>CC_Cc_SXNAg&4#u$H1V)z`&p_2Ps;^<RC7skb@+?ZaIj-E94*!
zyCw%Qm`@&3LORGpLb66466CG&kSOhxhg3on<ss&*k!N7A1QlfR3=GboSYTFwBsTZW
cGdcD%O-|%)*!-H?h<mcG_{Pm|#IJDx0JYu5S^xk5

diff --git a/ckanext/odsh/i18n/de/LC_MESSAGES/ckanext-odsh.po b/ckanext/odsh/i18n/de/LC_MESSAGES/ckanext-odsh.po
index b09176f7..4a07dff1 100644
--- a/ckanext/odsh/i18n/de/LC_MESSAGES/ckanext-odsh.po
+++ b/ckanext/odsh/i18n/de/LC_MESSAGES/ckanext-odsh.po
@@ -419,4 +419,13 @@ msgid "Create New Data Request"
 msgstr "Neuen Datensatz vorschlagen"
 
 msgid "Suggest New Data Request"
-msgstr "Datensatz vorschlagen"
\ No newline at end of file
+msgstr "Datensatz vorschlagen"
+
+msgid "Add New Comment"
+msgstr "Kommentieren"
+
+msgid "Released"
+msgstr "Veröffentlicht"
+
+msgid "Suggester"
+msgstr "Vorschlagende"
\ No newline at end of file
diff --git a/ckanext/odsh/plugin.py b/ckanext/odsh/plugin.py
index b8f211af..182ab96d 100644
--- a/ckanext/odsh/plugin.py
+++ b/ckanext/odsh/plugin.py
@@ -1,3 +1,4 @@
+from multiline_formatter.formatter import MultilineMessagesFormatter
 import datetime
 import json
 import ckan.plugins as plugins
@@ -24,7 +25,7 @@ import ckan.plugins as p
 import logging
 import validation
 import precondition
- 
+
 import sys
 
 log = logging.getLogger(__name__)
@@ -39,7 +40,7 @@ log = logging.getLogger(__name__)
 #     @wraps(handle_http_exception)
 #     def ret_val(exception):
 #         print("HEHREHR")
-#         exc = handle_http_exception(exception)    
+#         exc = handle_http_exception(exception)
 #         return jsonify({'code':exc.code, 'message':exc.description}), exc.code
 #     return ret_val
 
@@ -56,7 +57,7 @@ log = logging.getLogger(__name__)
 
 _ = toolkit._
 
-from multiline_formatter.formatter import MultilineMessagesFormatter
+
 class OdshLogger(MultilineMessagesFormatter):
     multiline_marker = '...'
     multiline_fmt = multiline_marker + ' : %(message)s'
@@ -79,7 +80,7 @@ class OdshLogger(MultilineMessagesFormatter):
                 self.multiline_fmt % dict(record.__dict__, message=line)
                 for line in splitted
             )
-            output = output.replace('"','\\"')
+            output = output.replace('"', '\\"')
             output += endl_marker
         else:
             output = self._fmt % record.__dict__
@@ -96,7 +97,7 @@ class OdshLogger(MultilineMessagesFormatter):
                     self.multiline_fmt % dict(record.__dict__, message=line)
                     for index, line in enumerate(record.exc_text.splitlines())
                 )
-                output = output.replace('"','\\"')
+                output = output.replace('"', '\\"')
                 output += endl_marker
             except UnicodeError:
                 output += '\n'.join(
@@ -141,9 +142,10 @@ def odsh_group_id_selected(selected, group_id):
 
     return False
 
-def remove_route(map,routename):
+
+def remove_route(map, routename):
     route = None
-    for i,r in enumerate(map.matchlist):
+    for i, r in enumerate(map.matchlist):
 
         if r.name == routename:
             route = r
@@ -178,27 +180,39 @@ class OdshHarvestPlugin(plugins.SingletonPlugin):
     def update_config(self, config_):
         toolkit.add_template_directory(config_, 'harvest_templates')
     plugins.implements(plugins.IRoutes, inherit=True)
+
     def before_map(self, map):
-        DATASET_TYPE_NAME='harvest'
+        DATASET_TYPE_NAME = 'harvest'
         controller = 'ckanext.odsh.controller:OdshHarvestController'
 
-        map.connect('{0}_delete'.format(DATASET_TYPE_NAME), '/' + DATASET_TYPE_NAME + '/delete/:id',controller=controller, action='delete')
-        map.connect('{0}_refresh'.format(DATASET_TYPE_NAME), '/' + DATASET_TYPE_NAME + '/refresh/:id',controller=controller,
-                action='refresh')
-        map.connect('{0}_admin'.format(DATASET_TYPE_NAME), '/' + DATASET_TYPE_NAME + '/admin/:id', controller=controller, action='admin')
-        map.connect('{0}_about'.format(DATASET_TYPE_NAME), '/' + DATASET_TYPE_NAME + '/about/:id', controller=controller, action='about')
-        map.connect('{0}_clear'.format(DATASET_TYPE_NAME), '/' + DATASET_TYPE_NAME + '/clear/:id', controller=controller, action='clear')
-
-        map.connect('harvest_job_list', '/' + DATASET_TYPE_NAME + '/{source}/job', controller=controller, action='list_jobs')
-        map.connect('harvest_job_show_last', '/' + DATASET_TYPE_NAME + '/{source}/job/last', controller=controller, action='show_last_job')
-        map.connect('harvest_job_show', '/' + DATASET_TYPE_NAME + '/{source}/job/{id}', controller=controller, action='show_job')
-        map.connect('harvest_job_abort', '/' + DATASET_TYPE_NAME + '/{source}/job/{id}/abort', controller=controller, action='abort_job')
-
-        map.connect('harvest_object_show', '/' + DATASET_TYPE_NAME + '/object/:id', controller=controller, action='show_object')
-        map.connect('harvest_object_for_dataset_show', '/dataset/harvest_object/:id', controller=controller, action='show_object', ref_type='dataset')
+        map.connect('{0}_delete'.format(DATASET_TYPE_NAME), '/' +
+                    DATASET_TYPE_NAME + '/delete/:id', controller=controller, action='delete')
+        map.connect('{0}_refresh'.format(DATASET_TYPE_NAME), '/' + DATASET_TYPE_NAME + '/refresh/:id', controller=controller,
+                    action='refresh')
+        map.connect('{0}_admin'.format(DATASET_TYPE_NAME), '/' +
+                    DATASET_TYPE_NAME + '/admin/:id', controller=controller, action='admin')
+        map.connect('{0}_about'.format(DATASET_TYPE_NAME), '/' +
+                    DATASET_TYPE_NAME + '/about/:id', controller=controller, action='about')
+        map.connect('{0}_clear'.format(DATASET_TYPE_NAME), '/' +
+                    DATASET_TYPE_NAME + '/clear/:id', controller=controller, action='clear')
+
+        map.connect('harvest_job_list', '/' + DATASET_TYPE_NAME +
+                    '/{source}/job', controller=controller, action='list_jobs')
+        map.connect('harvest_job_show_last', '/' + DATASET_TYPE_NAME +
+                    '/{source}/job/last', controller=controller, action='show_last_job')
+        map.connect('harvest_job_show', '/' + DATASET_TYPE_NAME +
+                    '/{source}/job/{id}', controller=controller, action='show_job')
+        map.connect('harvest_job_abort', '/' + DATASET_TYPE_NAME +
+                    '/{source}/job/{id}/abort', controller=controller, action='abort_job')
+
+        map.connect('harvest_object_show', '/' + DATASET_TYPE_NAME +
+                    '/object/:id', controller=controller, action='show_object')
+        map.connect('harvest_object_for_dataset_show', '/dataset/harvest_object/:id',
+                    controller=controller, action='show_object', ref_type='dataset')
 
         org_controller = 'ckanext.harvest.controllers.organization:OrganizationController'
-        map.connect('{0}_org_list'.format(DATASET_TYPE_NAME), '/organization/' + DATASET_TYPE_NAME + '/' + '{id}', controller=org_controller, action='source_list')
+        map.connect('{0}_org_list'.format(DATASET_TYPE_NAME), '/organization/' +
+                    DATASET_TYPE_NAME + '/' + '{id}', controller=org_controller, action='source_list')
         return map
 
     def after_map(self, map):
@@ -209,13 +223,13 @@ class OdshDCATHarvestPlugin(plugins.SingletonPlugin):
     plugins.implements(IDCATRDFHarvester, inherit=True)
 
     def before_update(self, harvest_object, dataset_dict, temp_dict):
-        
+
         existing_package_dict = self._get_existing_dataset(harvest_object.guid)
         new_dataset_extras = Extras(dataset_dict['extras'])
         if new_dataset_extras.key('modified') and \
-          new_dataset_extras.value('modified') < existing_package_dict.get('metadata_modified'):
+                new_dataset_extras.value('modified') < existing_package_dict.get('metadata_modified'):
             log.info("Modified date of new dataset is not newer than "
-            + "the already exisiting dataset, ignoring new one.") 
+                     + "the already exisiting dataset, ignoring new one.")
             dataset_dict.clear()
 
     def _get_existing_dataset(self, guid):
@@ -311,25 +325,26 @@ class OdshPlugin(plugins.SingletonPlugin, DefaultTranslation, DefaultDatasetForm
         if p.toolkit.asbool(config.get('ckanext.dcat.enable_rdf_endpoints', True)):
             remove_route(map, 'dcat_catalog')
             map.connect('dcat_catalog',
-                            config.get('ckanext.dcat.catalog_endpoint', '/catalog.{_format}'),
-                            controller='ckanext.odsh.controller:OdshDCATController', action='read_catalog',
-                            requirements={'_format': 'xml|rdf|n3|ttl|jsonld'})
+                        config.get('ckanext.dcat.catalog_endpoint',
+                                   '/catalog.{_format}'),
+                        controller='ckanext.odsh.controller:OdshDCATController', action='read_catalog',
+                        requirements={'_format': 'xml|rdf|n3|ttl|jsonld'})
 
         # with SubMapper(map, controller='ckanext.odsh.controller:OdshApiController') as m:
         #     m.connect('/catalog2', action='read_catalog')
 
-
-
         # /api ver 3 or none with matomo
         GET_POST = dict(method=['GET', 'POST'])
         with SubMapper(map, controller='ckanext.odsh.controller:OdshApiController', path_prefix='/api{ver:/3|}', ver='/3') as m:
-            m.connect('/action/{logic_function}', action='action', conditions=GET_POST)
+            m.connect('/action/{logic_function}',
+                      action='action', conditions=GET_POST)
 
         with SubMapper(map, controller='ckanext.odsh.controller:OdshFeedController') as m:
             m.connect('/feeds/custom.atom', action='custom')
 
         with SubMapper(map, controller='ckanext.odsh.controller:OdshPackageController') as m:
-            m.connect('new_view', '/dataset/{id}/resource/{resource_id}/new_view', action='edit_view', ckan_icon='pencil-square-o')
+            m.connect('new_view', '/dataset/{id}/resource/{resource_id}/new_view',
+                      action='edit_view', ckan_icon='pencil-square-o')
 
         with SubMapper(map, controller='ckanext.odsh.controller:OdshGroupController') as m:
             m.connect('organizations_index', '/organization', action='index')
@@ -338,7 +353,8 @@ class OdshPlugin(plugins.SingletonPlugin, DefaultTranslation, DefaultDatasetForm
         with SubMapper(map, controller='ckanext.odsh.controller:OdshUserController') as m:
             m.connect('user_index', '/user', action='index')
             m.connect('/user/edit', action='edit')
-            m.connect('user_edit', '/user/edit/{id:.*}', action='edit', ckan_icon='cog')
+            m.connect(
+                'user_edit', '/user/edit/{id:.*}', action='edit', ckan_icon='cog')
             m.connect('user_delete', '/user/delete/{id}', action='delete')
             m.connect('/user/reset/{id:.*}', action='perform_reset')
             m.connect('/user/reset', action='request_reset')
@@ -350,6 +366,10 @@ class OdshPlugin(plugins.SingletonPlugin, DefaultTranslation, DefaultDatasetForm
             m.connect('/user/logged_out_redirect', action='logged_out_page')
             m.connect('user_datasets', '/user/{id:.*}', action='read',
                       ckan_icon='sitemap')
+
+        map.connect('comment_datarequest', '/datarequest/{id}',
+                    controller='ckanext.datarequests.controllers.ui_controller:DataRequestsUI',
+                    action='comment', conditions=dict(method=['GET', 'POST']), ckan_icon='comment')
         return map
 
     def dataset_facets(self, facets_dict, package_type):
@@ -372,7 +392,7 @@ class OdshPlugin(plugins.SingletonPlugin, DefaultTranslation, DefaultDatasetForm
                             'groups': _('Kategorie')})
 
     def _update_schema(self, schema):
-        for field in ['title', 'notes','license_id']:
+        for field in ['title', 'notes', 'license_id']:
             schema.update({field: [toolkit.get_converter('not_empty')]})
 
         for i, item in enumerate(schema['tags']['name']):
@@ -394,10 +414,11 @@ class OdshPlugin(plugins.SingletonPlugin, DefaultTranslation, DefaultDatasetForm
                 toolkit.get_converter('validate_licenseAttributionByText'),
             ]
         })
-        schema.update({'__extras':  [toolkit.get_converter('odsh_validate_extras')] })
+        schema.update(
+            {'__extras':  [toolkit.get_converter('odsh_validate_extras')]})
 
-        ## only to make sure the spatial field is there for validation
-        # schema.update({'spatial':  [toolkit.get_converter('convert_from_extras')]}) 
+        # only to make sure the spatial field is there for validation
+        # schema.update({'spatial':  [toolkit.get_converter('convert_from_extras')]})
 
     def create_package_schema(self):
         schema = super(OdshPlugin, self).create_package_schema()
@@ -430,7 +451,7 @@ class OdshPlugin(plugins.SingletonPlugin, DefaultTranslation, DefaultDatasetForm
     # use several daterange queries agains temporal_start and temporal_end field
     # TODO: use field of type date_range in solr index instead
     def before_search(self, search_params):
-        search_params['facet.mincount']=0
+        search_params['facet.mincount'] = 0
         extras = search_params.get('extras')
         print(search_params)
         if not extras:
@@ -439,8 +460,8 @@ class OdshPlugin(plugins.SingletonPlugin, DefaultTranslation, DefaultDatasetForm
 
         fq = search_params['fq']
 
-        start_date=None
-        end_date=None
+        start_date = None
+        end_date = None
         try:
             start_date = odsh_helpers.extend_search_convert_local_to_utc_timestamp(
                 extras.get('ext_startdate'))
@@ -462,7 +483,6 @@ class OdshPlugin(plugins.SingletonPlugin, DefaultTranslation, DefaultDatasetForm
             do_enclosing_query = False
             end_date = '*'
 
-
         start_query = '+extras_temporal_start:[{start_date} TO {end_date}]'.format(
             start_date=start_date, end_date=end_date)
 
@@ -480,37 +500,37 @@ class OdshPlugin(plugins.SingletonPlugin, DefaultTranslation, DefaultDatasetForm
             enclosing_query = ' OR ({enclosing_query_start} AND {enclosing_query_end})'.format(
                 enclosing_query_start=enclosing_query_start, enclosing_query_end=enclosing_query_end)
 
-        
         if end_date is '*':
             open_end_query = '(*:* NOT extras_temporal_end:[* TO *])'
         else:
             open_end_query = '((*:* NOT extras_temporal_end:[* TO *]) AND extras_temporal_start:[* TO {end_date}])'.format(
-                    end_date=end_date)
+                end_date=end_date)
 
         fq = u'{fq} ({start_query} OR {end_query} {enclosing_query} OR {open_end_query})'.format(
             fq=fq, start_query=start_query, end_query=end_query, enclosing_query=enclosing_query, open_end_query=open_end_query)
-        
 
         search_params['fq'] = fq
 
         return search_params
 
-    scores = [ ['0OL'], ['0OL','1RE'], ['0OL','1RE','2OF'], ['0OL','1RE','2OF','3URI'], ['0OL','1RE','2OF','3URI','4LD']]
+    scores = [['0OL'], ['0OL', '1RE'], ['0OL', '1RE', '2OF'], [
+        '0OL', '1RE', '2OF', '3URI'], ['0OL', '1RE', '2OF', '3URI', '4LD']]
+
     def map_qa_score(self, dict_pkg):
         if 'validated_data_dict' in dict_pkg and 'openness_score' in dict_pkg['validated_data_dict']:
-                d = json.loads(dict_pkg['validated_data_dict'])
-                score = -1
-                for r in d['resources']:
-                    if 'qa' in r:
-                        i = r['qa'].find('openness_score')
-                        s = int(r['qa'][i+17])
-                        if s > score:
-                                score=s
-                if score > 0:
-                    dict_pkg['openness']=OdshPlugin.scores[score-1]
-
-
-    #@precondition.not_on_slave
+            d = json.loads(dict_pkg['validated_data_dict'])
+            score = -1
+            for r in d['resources']:
+                if 'qa' in r:
+                    i = r['qa'].find('openness_score')
+                    s = int(r['qa'][i+17])
+                    if s > score:
+                        score = s
+            if score > 0:
+                dict_pkg['openness'] = OdshPlugin.scores[score-1]
+
+    # @precondition.not_on_slave
+
     def before_index(self, dict_pkg):
         # make special date fields solr conform
         fields = ["issued", "temporal_start", "temporal_end"]
@@ -518,11 +538,11 @@ class OdshPlugin(plugins.SingletonPlugin, DefaultTranslation, DefaultDatasetForm
             field = 'extras_' + field
             if field in dict_pkg and dict_pkg[field]:
                 d = parse(dict_pkg[field])
-                dict_pkg[field] = '{0.year:04d}-{0.month:02d}-{0.day:02d}T00:00:00Z'.format(d)
+                dict_pkg[field] = '{0.year:04d}-{0.month:02d}-{0.day:02d}T00:00:00Z'.format(
+                    d)
         # if 'res_format' in dict_pkg:
         #     dict_pkg['res_format']=[e.lower() for e in dict_pkg['res_format']]
 
         self.map_qa_score(dict_pkg)
 
         return dict_pkg
-
diff --git a/ckanext/odsh/public/odsh.css b/ckanext/odsh/public/odsh.css
index 893d2a64..9b33a5d5 100644
--- a/ckanext/odsh/public/odsh.css
+++ b/ckanext/odsh/public/odsh.css
@@ -1979,4 +1979,55 @@ p.package-info-categorie
 .datarequest-item-autor-name
 {
     padding-left: 4px;
+}
+
+.comments-heading{
+    color: black;
+    font-weight: normal;
+    border-top: 2px solid #DBDBDB;
+    padding-top:10px;
+}
+
+.odsh-comment-wrapper{
+    padding-left: 0px;
+    max-width: 400px;
+}
+
+.odsh-comment-wrapper .controls{
+    margin-left: 0px;
+}
+
+.comment-author
+{
+    font-size:13px;
+    color: black;
+    text-decoration: underline;
+    padding-right: 4px;
+    border-right: 1px solid;
+}
+
+.comment-header-text .icon-comment{
+    font-size:13px;
+}
+
+.comment-date{
+    font-size:13px;
+    color: black;
+}
+
+.odsh-comment-content
+{
+    font-size:13px;
+}
+
+.datarequest-info.table,
+.datarequest-info.table th,
+.datarequest-info.table td
+{
+    border:none;
+}
+
+.datarequest .additional-info{
+    font-weight: normal;
+    font-size: 13px;
 }
\ No newline at end of file
diff --git a/ckanext/odsh/templates/datarequests/base.html b/ckanext/odsh/templates/datarequests/base.html
new file mode 100644
index 00000000..3bcc18e5
--- /dev/null
+++ b/ckanext/odsh/templates/datarequests/base.html
@@ -0,0 +1,55 @@
+{% extends "page.html" %}
+
+{% block styles %}
+  {{ super() }}
+  <link rel="stylesheet" href="/datarequests.css" />
+{% endblock %}
+
+{% block subtitle %}
+{% endblock %}
+
+{% block breadcrumb_content %}
+  <li class="active">{% link_for _('Data Requests'), controller='ckanext.datarequests.controllers.ui_controller:DataRequestsUI', action='index' %}</li>
+{% endblock %}
+
+    {% block main_content %}
+
+    {% block flash %}
+    {{ super() }}
+    {% endblock %}
+
+    {% block toolbar %}
+    {{ super() }}
+    {% endblock %}
+
+    <div class="datarequest row wrapper{% block wrapper_class %}{% endblock %}{% if self.secondary()|trim == '' %} no-nav{% endif %} is-table-row">
+      {#
+      The pre_primary block can be used to add content to before the
+      rendering of the main content columns of the page.
+      #}
+      {% block pre_primary %}
+      {% endblock %}
+
+
+      {% block primary %}
+      <div class="primary span9">
+        {% block primary_content %}
+        {{ super() }}
+        {% endblock %}
+      </div>
+      {% endblock %}
+
+      {% block secondary %}
+      <aside class="secondary span3">
+        {% block secondary_content %}
+        {{ super() }}
+        {% endblock %}
+      </aside>
+      {% endblock %}
+
+    </div>
+    {% block pre_wrap %}
+    {% endblock %}
+    {% endblock %}
+  </div>
+</div>
diff --git a/ckanext/odsh/templates/datarequests/comment.html b/ckanext/odsh/templates/datarequests/comment.html
new file mode 100644
index 00000000..d778bf81
--- /dev/null
+++ b/ckanext/odsh/templates/datarequests/comment.html
@@ -0,0 +1,41 @@
+{% extends "datarequests/show.html" %}
+
+{% block breadcrumb_content %}
+  {{ super() }}
+{% endblock %}
+
+{% block content_action %}
+{% endblock %}
+
+{% block content_primary_nav %}
+{% endblock %}
+
+{% block subtitle %}
+{% endblock %}
+
+{% block primary_content_inner %}
+  {% if h.check_access('update_datarequest', {'id':datarequest_id }) %}
+    {% link_for _('Manage'), controller='ckanext.datarequests.controllers.ui_controller:DataRequestsUI', action='update', id=datarequest_id, class_='btn btn-default', icon='wrench' %}
+  {% endif %}
+
+  {% if h.check_access('close_datarequest', {'id':datarequest_id }) and not c.datarequest.closed %}
+    {% link_for _('Close'), controller='ckanext.datarequests.controllers.ui_controller:DataRequestsUI', action='close', id=datarequest_id, class_='btn btn-danger', icon='lock' %}
+  {% endif %}
+
+  {% snippet "datarequests/snippets/comments.html", comments=c.comments, datarequest=c.datarequest, errors=c.errors, errors_summary=c.errors_summary, updated_comment=c.updated_comment %}
+
+  {% if h.check_access('comment_datarequest', {'id':c.datarequest.id }) %}
+    <h2 class='comments-heading'>{{_('Add New Comment')}}:</h2>
+    <div >
+
+      {% set create_comment_error = c.updated_comment is defined and c.updated_comment.id == '' %}
+      
+      {% if create_comment_error %}
+        <a name="comment_focus"></a>
+      {% endif %}
+      
+      {% snippet "datarequests/snippets/comment_form.html", datarequest=c.datarequest, errors=c.errors, errors_summary=c.errors_summary, offering=c.offering, initial_text=c.updated_comment.comment if create_comment_error, focus=create_comment_error, current_user=c.userobj %}
+    </div>
+  {% endif %}
+
+{% endblock %}
\ No newline at end of file
diff --git a/ckanext/odsh/templates/datarequests/show.html b/ckanext/odsh/templates/datarequests/show.html
new file mode 100644
index 00000000..8f6ed711
--- /dev/null
+++ b/ckanext/odsh/templates/datarequests/show.html
@@ -0,0 +1,101 @@
+{% extends "datarequests/base.html" %}
+
+{% block title %}{{_('Data Request')}} {{c.datarequest.get('title', '')}}{% endblock %}
+
+{% set datarequest_id = c.datarequest.get('id') %}
+
+{% block breadcrumb_content %}
+  <li>{% link_for _('Data Requests'), controller='ckanext.datarequests.controllers.ui_controller:DataRequestsUI', action='index' %}</li>
+  <li>{% link_for c.datarequest.get('title'), controller='ckanext.datarequests.controllers.ui_controller:DataRequestsUI', action='show', id=datarequest_id %}</li>
+{% endblock %}
+
+{% block content_action %}
+
+  {% if h.check_access('update_datarequest', {'id':datarequest_id }) %}
+    {% link_for _('Manage'), controller='ckanext.datarequests.controllers.ui_controller:DataRequestsUI', action='update', id=datarequest_id, class_='btn btn-default', icon='wrench' %}
+  {% endif %}
+
+  {% if h.check_access('close_datarequest', {'id':datarequest_id }) and not c.datarequest.closed %}
+    {% link_for _('Close'), controller='ckanext.datarequests.controllers.ui_controller:DataRequestsUI', action='close', id=datarequest_id, class_='btn btn-danger', icon='lock' %}
+  {% endif %}
+
+{% endblock %}
+
+{% block content_primary_nav %}
+  {{ h.build_nav_icon('show_datarequest', _('Data Request'), id=datarequest_id) }}
+{% endblock %}
+
+{% block secondary_content %}
+<section class="additional-info">
+      {% block package_additional_info %}
+          <div>{{ _('Released') }}: {{ h.odsh_render_datetime(c.datarequest.open_time) }}</div>
+          <div>{{ _('Suggester') }}: {{ c.datarequest.user['display_name'] if c.datarequest.user else _('None') }}</div>
+          {#<td class="dataset-details" title="{{ c.datarequest.close_time }}">{{ h.time_ago_from_timestamp(c.datarequest.close_time) if c.datarequest.close_time else _('Not closed yet') }}</td>#}
+          <div >{{ _('Status') }}:
+          <div  class="dataset-details">
+            {% if c.datarequest.get('closed', False) %}
+            <div class="datarequest-label label-closed">
+                {% trans %}Closed{% endtrans %}
+            </div>
+            {% else %}
+            <div class="label label-open">
+                {% trans %}Open{% endtrans %}
+            </div>
+            {% endif %}
+          </div>
+        {#{% if c.datarequest.closed %}
+          <tr>
+            <th scope="row" class="dataset-label">{{ _('Accepted Dataset') }}</th>
+            <td class="dataset-details">
+            {% if c.datarequest.accepted_dataset %}
+              {% link_for c.datarequest.accepted_dataset['title'], controller='package', action='read', id=c.datarequest.accepted_dataset.get('id') %}
+            {% else %}
+              {{ _('None') }}
+            {% endif %}
+            </td>
+          </tr>
+        {% endif %}
+        #}
+        <div class='comment-count-wrapper'><i class="icon-comment fa fa-comment"></i> {{ h.get_comments_number(c.datarequest.get('id', '')) }}</span> </div>
+      {% endblock %}
+</section>
+{% endblock %}
+
+{% block primary_content_inner %}
+    {% if c.datarequest.closed %}
+      <span class="uppercase label label-closed pull-right">
+        <i class="icon-lock fa fa-lock"></i>
+        {{ _('Closed') }}
+      </span>
+    {% else %}
+      <span class="uppercase label label-open pull-right">
+        <i class="icon-unlock fa fa-unlock"></i>
+        {{ _('Open') }}
+      </span>
+    {% endif %}
+
+  <h1 class="{% block page_heading_class %}page-heading{% endblock %}">{% block page_heading %}{{ c.datarequest.get('title') }}{% endblock %}</h1>
+
+  {% block datarequest_description %}
+    {% if c.datarequest.get('description') %}
+      <div class="notes embedded-content">
+        {{ h.render_markdown(c.datarequest.get('description')) }}
+      </div>
+    {% endif %}
+  {% endblock %}
+
+  {% block datarequest_additional_info %}
+    {% snippet "datarequests/snippets/additional_info.html", datarequest=c.datarequest %}
+  {% endblock %}
+
+{% if c.comments %}
+{% for comment in c.comments %}
+{% snippet "datarequests/snippets/comment_item.html", comment=c.comment, datarequest=c.datarequest, errors=c.errors, errors_summary=c.errors_summary, updated_comment=c.updated_comment %}
+{% endfor %}
+{% else %}
+<p class="empty">
+{{ _('This data request has not been commented yet') }}
+</p>
+{% endif %}
+
+{% endblock %}
\ No newline at end of file
diff --git a/ckanext/odsh/templates/datarequests/snippets/comment_form.html b/ckanext/odsh/templates/datarequests/snippets/comment_form.html
new file mode 100644
index 00000000..cbbea780
--- /dev/null
+++ b/ckanext/odsh/templates/datarequests/snippets/comment_form.html
@@ -0,0 +1,29 @@
+{% import 'macros/form.html' as form %}
+{% resource "datarequest/edit_comment.js" %}
+
+<form class="dataset-form {{ 'comment-edit-form' if comment_id else 'odsh-comment-wrapper' }} {{ 'hide' if comment_id and not (focus and errors) }} form-horizontal" id="comment-form{{ '-' + comment_id if comment_id }}" method="post" data-module="basic-form" action enctype="multipart/form-data">
+
+
+  {% if focus %}
+    {% block errors %}{{ form.errors(errors_summary) }}{% endblock %}
+  {% endif %}
+
+
+  <input type="hidden" name="datarequest-id" value="{{ datarequest.get('id', '') }}" />
+  <input type="hidden" name="comment-id" value="{{ comment_id if comment_id }}" />
+
+  <div class="controls control-full control-large control-group {{ 'error' if errors and errors.get('Comment') and focus }} editor">
+    {% set markdown_tooltip = "<pre><p>__Bold text__ or _italic text_</p><p># title<br>## secondary title<br>### etc</p><p>* list<br>* of<br>* items</p><p>http://auto.link.ed/</p></pre><p><b><a href='http://daringfireball.net/projects/markdown/syntax' target='_blank'>Full markdown syntax</a></b></p><p class='muted'><b>Please note:</b> HTML tags are stripped out for security reasons</p>" %} 
+    <textarea  name="comment" cols="20" rows="6" placeholder="{{ _('Add a new Comment') if not comment_id }}">{{ initial_text }}</textarea>
+  </div>
+
+  <div class="comment-form-actions">
+    {% if comment_id %}
+      <button id="comment-discard-{{ comment_id }}" class="btn btn-danger" name="discard">{{ _('Cancel') }}</button>
+      <button class="btn btn-primary" type="submit" name="update">{{ _('Update Comment') }}</button>
+    {% else %}
+      <button class="btn btn-primary btn-arrow-right" type="submit" name="add">{{ _('Add New Comment') }}</button>
+    {% endif %}
+  </div>
+
+</form>
\ No newline at end of file
diff --git a/ckanext/odsh/templates/datarequests/snippets/comment_item.html b/ckanext/odsh/templates/datarequests/snippets/comment_item.html
new file mode 100644
index 00000000..155e6d1d
--- /dev/null
+++ b/ckanext/odsh/templates/datarequests/snippets/comment_item.html
@@ -0,0 +1,47 @@
+{% set focus = updated_comment is not none and updated_comment.id == comment.id %}
+{% set can_update = h.check_access('update_datarequest_comment', {'id':comment.id }) %}
+
+{% if focus %}
+    <a name="comment_focus"></a>
+{% endif %}
+
+<div class="odsh-comment-wrapper">
+ {# <a href="{{ h.url_for(controller='user', action='read', id=comment.user.get('name')) }}"
+    class="comment-avatar">
+    {{ h.gravatar(comment.user.get('email_hash'), 48) }}
+  </a>
+  #}
+  <div class="">
+    <div class="odsh-comment-header">
+      <div class="comment-actions">
+      {#
+        {% if h.check_access('delete_datarequest_comment', {'id':comment.id }) %}
+          <div class="comment-action">
+            {% set locale = h.dump_json({'content': _('Are you sure you want to delete this comment?')}) %}
+            <a class="subtle-btn" id="delete-comment-{{ comment.id }}" href="{% url_for controller='ckanext.datarequests.controllers.ui_controller:DataRequestsUI', action='delete_comment', datarequest_id=datarequest.id, comment_id=comment.id %}" data-module="confirm-action" data-module-i18n="{{ locale }}"><i class="icon-remove fa fa-times"></i></a>
+          </div>
+        {% endif %}
+        {% if can_update %}
+          <div class="comment-action" id="edit-button">
+            <button class="subtle-btn" id="update-comment-{{ comment.id }}" href=""><i class="icon-pencil fa fa-pencil"></i></button>
+          </div>          
+        {% endif %}
+        #}
+        <div class="comment-header-text">
+          <i class="icon-comment fa fa-comment"></i>
+          <a href="{{ h.url_for(controller='user', action='read', id=comment.user.get('name')) }}" class="comment-author">{{ comment.user.get('display_name') }}</a>
+          <span class='comment-date' title="{{comment.time}}">{{ h.odsh_render_datetime(comment.time).lower() }}</span>
+        </div>
+      </div>
+    </div>
+    
+    <div class="odsh-comment-content {{ 'hide' if focus and errors }}" id="comment-{{ comment.id }}">
+      {{ h.render_markdown(comment.comment|safe) }}
+    </div>
+
+    {% if can_update %}
+      {% snippet "datarequests/snippets/comment_form.html", comment_id=comment.id, datarequest=datarequest, errors=errors, errors_summary=errors_summary, initial_text=updated_comment.comment if focus else comment.comment, focus=focus %}
+    {% endif %}
+
+  </div>
+</div>
\ No newline at end of file
diff --git a/ckanext/odsh/templates/datarequests/snippets/comments.html b/ckanext/odsh/templates/datarequests/snippets/comments.html
new file mode 100644
index 00000000..8ac76155
--- /dev/null
+++ b/ckanext/odsh/templates/datarequests/snippets/comments.html
@@ -0,0 +1,19 @@
+<h2 class="page-heading">
+  {% block page_heading %}
+    {{datarequest.title}}
+  {% endblock %}
+</h2>
+
+{{ h.render_markdown(h.get_translated(datarequest, 'description')) }}
+
+<h2 class='comments-heading'>{{_('Comments')}}:</h2>
+
+{% if comments %}
+  {% for comment in comments %}
+    {% snippet "datarequests/snippets/comment_item.html", comment=comment, datarequest=datarequest, errors=errors, errors_summary=errors_summary, updated_comment=updated_comment %}
+  {% endfor %}
+{% else %}
+  <p class="empty">
+    {{ _('This data request has not been commented yet') }}
+  </p>
+{% endif %}
\ No newline at end of file
diff --git a/ckanext/odsh/tests/test_datarequest.py b/ckanext/odsh/tests/test_datarequest.py
new file mode 100644
index 00000000..d94695c3
--- /dev/null
+++ b/ckanext/odsh/tests/test_datarequest.py
@@ -0,0 +1,71 @@
+
+from ckanext.odsh.tests.test_helpers import AppProxy
+import ckanext.odsh.tests.test_helpers as testhelpers
+import ckan.tests.factories as factories
+import uuid
+import pdb
+from ckanext.odsh.tests.harvest_sever_mock import HarvestServerMock
+import ckanext.odsh.tests.harvest_sever_mock as harvest_sever_mock
+import subprocess
+import re
+
+markdown = \
+    """
+Lorem markdownum supplex iniquis, nec nostram nam conde tympana, deae Mutinae
+regna sepulcro; morae arces quae, pia? Rates Circe, quandoquidem Ausoniae, me,
+cacumine apta, saevus abductas navigiis. Sacri ostendit *ad anas* amores nostras
+[currebam celeres], milia gaudet eripitur superest circumque auras, [nec]. Si
+haurit geminis agendum profana lacertis infamis?
+
+> Dedit potuit perenni nesciet flumine. Et sui **ibis** mihi supponat, flamina
+> mihi rogos, deus manum ora tenebras. Acta nec dominus aenum, haud de ripa
+> instabilemque amnis erat nam Patraeque parabat quod membra quamquam.
+"""
+
+
+class TestDatarequest:
+
+    def test_nologin_cannot_create_request(self):
+        pass
+
+    def _create_request(self):
+        guid = str(uuid.uuid4())
+        self._get_app().login()
+        response = self.app.get('/datarequest/new')
+        form = response.forms[0]
+        title = 'datarequest_' + guid
+        form['title'] = title
+        form['description'] = markdown
+        final_response = self.app.submit_form(form)
+
+        id = re.search(
+            '/datarequest/comment/([a-zA-Z0-9\-]*)">', final_response.body).group(1)
+        return id
+
+    def test_create_datarequest(self):
+        # Act
+        id = self._create_request()
+
+        # Assert
+        response = self.app.get('/datarequest')
+        assert id in response
+
+    def test_comment_datarequest(self):
+        # Arrange
+        id = self._create_request()
+        guid = str(uuid.uuid4())
+
+        # Act
+        response = self.app.get('/datarequest/comment/'+id)
+        form = response.forms[0]
+        form['comment'] = markdown + guid
+        final_response = self.app.submit_form(form)
+
+        # Assert
+        assert guid in final_response
+
+    def _get_app(self):
+        if not hasattr(self, 'app'):
+            app = AppProxy()
+            self.app = app
+        return self.app
-- 
GitLab