diff --git a/ckanext/odsh/controller.py b/ckanext/odsh/controller.py index 312c7960ec4ca19032e84fa31ce537752112f3af..5913c930e1889ee836e72ec8faac9499309d8f76 100644 --- a/ckanext/odsh/controller.py +++ b/ckanext/odsh/controller.py @@ -4,12 +4,14 @@ from ckan.controllers.user import UserController from ckan.controllers.api import ApiController from ckan.controllers.feed import FeedController from ckan.controllers.package import PackageController +from ckan.controllers.feed import FeedController, ITEMS_LIMIT, _package_search, _create_atom_id import ckan.lib.helpers as h import ckan.authz as authz from ckan.common import c import logging import matomo import ckan.logic as logic +from ckan.common import c, request, config abort = base.abort log = logging.getLogger(__name__) @@ -130,4 +132,61 @@ class MamotoApiController(ApiController): class MatomoFeedController(FeedController): def custom(self): matomo.create_matomo_request() - return FeedController.custom(self) \ No newline at end of file + return FeedController.custom(self) + +class OdshFeedController(FeedController): + def custom(self): + extra_fields=['ext_startdate', 'ext_enddate', 'ext_bbox', 'ext_prev_extent'] + q = request.params.get('q', u'') + fq = '' + search_params = {} + extras = {} + for (param, value) in request.params.items(): + if param not in ['q', 'page', 'sort'] + extra_fields \ + and len(value) and not param.startswith('_'): + search_params[param] = value + fq += ' %s:"%s"' % (param, value) + if param in extra_fields: + extras[param]=value + search_params['extras']=extras + + page = h.get_page_number(request.params) + + limit = ITEMS_LIMIT + data_dict = { + 'q': q, + 'fq': fq, + 'start': (page - 1) * limit, + 'rows': limit, + 'sort': request.params.get('sort', None), + 'extras': extras + } + + item_count, results = _package_search(data_dict) + + navigation_urls = self._navigation_urls(request.params, + item_count=item_count, + limit=data_dict['rows'], + controller='feed', + action='custom') + + feed_url = self._feed_url(request.params, + controller='feed', + action='custom') + + atom_url = h._url_with_params('/feeds/custom.atom', + search_params.items()) + + alternate_url = self._alternate_url(request.params) + + site_title = config.get('ckan.site_title', 'CKAN') + + return self.output_feed(results, + feed_title=u'%s - Custom query' % site_title, + feed_description=u'Recently created or updated' + ' datasets on %s. Custom query: \'%s\'' % + (site_title, q), + feed_link=alternate_url, + feed_guid=_create_atom_id(atom_url), + feed_url=feed_url, + navigation_urls=navigation_urls) \ No newline at end of file diff --git a/ckanext/odsh/fanstatic/odsh_datepicker.js b/ckanext/odsh/fanstatic/odsh_datepicker.js index c8f40c7bce7a01ddd6fb4884938b885e07b9b58a..8a6b26010275fd3309c0ce0cb3ffe1f78de31fcb 100644 --- a/ckanext/odsh/fanstatic/odsh_datepicker.js +++ b/ckanext/odsh/fanstatic/odsh_datepicker.js @@ -3,6 +3,8 @@ this.ckan.module('odsh_datepicker', function ($, _) return { initialize: function () { + var showFormat = "dd.mm.yyyy"; + var serverFormat = 'YYYY-MM-DD'; if (!$.fn.datepicker.dates['de']) $.fn.datepicker.dates['de'] = { days: ["Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag"], @@ -14,10 +16,9 @@ this.ckan.module('odsh_datepicker', function ($, _) monthsTitle: "Monate", clear: "Löschen", weekStart: 1, - format: "dd.mm.yyyy" + format: showFormat }; var target = $('#' + this.options.target) - var showFormat = "dd.mm.yyyy" var opts = { format: showFormat, autoclose: true, @@ -27,22 +28,27 @@ this.ckan.module('odsh_datepicker', function ($, _) } var onChange = function (ev) { - var v = moment(ev.date); - var fs = 'YYYY-MM-DD'; - - - if (ev.date) + var dateString = $(ev.target).val() + var date = moment(dateString, "DD.MM.YYYY", true) + var isValid = date.isValid() && (dateString.length == 10) + if (isValid) { - target.val(v.format(fs)); + target.val(date.format(serverFormat)); } else { target.val(''); } } + var onClear = function (ev) + { + target.val(''); + } this.el.datepicker(opts) .on('changeDate', onChange); + this.el.datepicker(opts) + .on('clearDate', onClear); } } }); diff --git a/ckanext/odsh/plugin.py b/ckanext/odsh/plugin.py index e9e58fa7706625252a1cb3232eeda292b60f80f6..0381c327a2605ac066a0658b7bde63bebc9d713d 100644 --- a/ckanext/odsh/plugin.py +++ b/ckanext/odsh/plugin.py @@ -130,7 +130,7 @@ class OdshPlugin(plugins.SingletonPlugin, DefaultTranslation, DefaultDatasetForm with SubMapper(map, controller='ckanext.odsh.controller:MamotoApiController', path_prefix='/api{ver:/3|}', ver='/3') as m: m.connect('/action2/{logic_function}', action='action', conditions=GET_POST) - with SubMapper(map, controller='ckanext.odsh.controller:MatomoFeedController') as m: + with SubMapper(map, controller='ckanext.odsh.controller:OdshFeedController') as m: m.connect('/feeds/custom.atom', action='custom') # redirect all user routes to custom controller diff --git a/ckanext/odsh/public/odsh.css b/ckanext/odsh/public/odsh.css index 192233767ddac21ef9b1e05142ce4b63d3788819..a950af9f4c085363d9ce4e6f53fbfb87a1252015 100644 --- a/ckanext/odsh/public/odsh.css +++ b/ckanext/odsh/public/odsh.css @@ -1818,6 +1818,14 @@ p.package-info-categorie float: right; margin-right: 5px; } +.control-group.error .multiselect-container>li>a>label +{ + color: #003064; +} +.control-group.error .multiselect-container>li>a>label:hover +{ + color: white; +} .select2-container .select2-choice .select2-arrow { @@ -1868,11 +1876,12 @@ p.package-info-categorie float:none; } +.select2-results .select2-highlighted, .dropdown-menu > .active > a, .dropdown-menu > .active > a:hover, .dropdown-menu > .active > a:focus, .dropdown-menu > li.active a, .dropdown-menu > li > a:hover, .dropdown-menu > li > a:focus, .dropdown-submenu:hover > a, .dropdown-submenu:focus > a { - background-color: #3875d7; + background-color: #003064; background-image: none; } diff --git a/ckanext/odsh/public/odsh_header.css b/ckanext/odsh/public/odsh_header.css index 7c6801bc56ed1864e969dc98dd81c9f684bdc5bf..a20e89cde5b543d172e2d3d8974b1b906574445f 100644 --- a/ckanext/odsh/public/odsh_header.css +++ b/ckanext/odsh/public/odsh_header.css @@ -114,13 +114,13 @@ margin-right: auto; } -.masthead .nav-collapse { +.navbar.masthead .nav-collapse { float: left; padding-top: 0px; width: 100%; } -.masthead .navigation { +.navbar.masthead .navigation { margin-right: 0px; } .navbar .nav @@ -128,8 +128,8 @@ margin: 0px; } -.masthead .navigation .nav-pills, -.masthead .navigation { +.navbar.masthead .navigation .nav-pills, +.navbar.masthead .navigation { width: 100%; } @@ -139,29 +139,29 @@ background-color: #003064 !important; } -.masthead .navigation .nav-pills li:not(:first-child) { +.navbar.masthead .navigation .nav-pills li:not(:first-child) { margin-left: 22px; } /* Default border to align font vertically to active navigation tab */ -.masthead .navigation .nav-pills li { +.navbar.masthead .navigation .nav-pills li { border-top: 2px solid #f2f2f2; } /* Change default border color on hover to background color (align font vertically to active navigation tab) */ -.masthead .navigation .nav-pills li.open, -.masthead .navigation .nav-pills li:hover { +.navbar.masthead .navigation .nav-pills li.open, +.navbar.masthead .navigation .nav-pills li:hover { border-top: 2px solid #003064; } /* The active navigation tab gets a differently coloured top-border */ -.masthead .navigation .nav-pills li.active { +.navbar.masthead .navigation .nav-pills li.active { border-top: 2px solid #003064; } /* Remove borders and get background color from parent (.navigation-container) */ -.masthead .navigation .nav-pills li.active a { +.navbar.masthead .navigation .nav-pills li.active a { background-color: inherit; -webkit-box-shadow: none !important; -moz-box-shadow: none !important; @@ -170,44 +170,41 @@ } /* Font color of navigation links (default) */ -.masthead .nav>li>a:focus, -.masthead .nav>li>a, .masthead .nav>.active>a { +.navbar.masthead .nav>li>a:focus, +.navbar.masthead .nav>li>a, .navbar.masthead .nav>.active>a { padding: 16px 10px 19px; color: #003064; font-size: 1.125rem; } /* Font color of navigation links (hover) */ -.masthead .nav>li:hover a { +.navbar.masthead .nav>li:hover a { color: #ffffff; text-decoration: underline; } -.masthead .navigation .nav-pills li.disabled a:hover, -.masthead .navigation .nav-pills li.disabled +.navbar.masthead .navigation .nav-pills li.disabled a:hover, +.navbar.masthead .navigation .nav-pills li.disabled { pointer-events: none; cursor: default; background-color: rgb(242, 242, 242)!important; border-top: 2px solid rgb(242, 242, 242) ; } -.masthead .navigation .nav-pills li.disabled span{ +.navbar.masthead .navigation .nav-pills li.disabled span{ color:rgba(0, 49, 102, 0.44); } -.masthead .navigation .nav-pills li.disabled:hover{ +.navbar.masthead .navigation .nav-pills li.disabled:hover{ background-color: rgb(242, 242, 242)!important; } -.masthead .navigation .nav-pills .dropdown -{ -} -.masthead .navigation .nav-pills .dropdown .dropdown-menu +.navbar.masthead .navigation .nav-pills .dropdown .dropdown-menu { width: 190px; } -.masthead .navigation .nav-pills .dropdown .dropdown-menu > li +.navbar.masthead .navigation .nav-pills .dropdown .dropdown-menu > li { color:black; margin-left: 0px; @@ -215,16 +212,16 @@ border: none; } -.masthead .navigation .nav-pills .dropdown .dropdown-menu > li > a:hover, -.masthead .navigation .nav-pills .dropdown .dropdown-menu > li:hover, -.masthead .navigation .nav-pills .dropdown .dropdown-menu > li:hover a +.navbar.masthead .navigation .nav-pills .dropdown .dropdown-menu > li > a:hover, +.navbar.masthead .navigation .nav-pills .dropdown .dropdown-menu > li:hover, +.navbar.masthead .navigation .nav-pills .dropdown .dropdown-menu > li:hover a { color:white; background-color: #003064; cursor: pointer; } -.masthead .navigation .nav-pills .dropdown .dropdown-menu > li > a +.navbar.masthead .navigation .nav-pills .dropdown .dropdown-menu > li > a { color:white; color: #003064; @@ -235,26 +232,26 @@ display: inline-block; } -.masthead .navigation .nav-pills li.dropdown.open, -.masthead .navigation .nav-pills li.dropdown.open > a.dropdown-toggle:hover, +.navbar.masthead .navigation .nav-pills li.dropdown.open, +.navbar.masthead .navigation .nav-pills li.dropdown.open > a.dropdown-toggle:hover, .navbar .nav li.dropdown.open > .dropdown-toggle { background-color: #003064; color: white; } -.masthead .navigation .nav-pills .dropdown img +.navbar.masthead .navigation .nav-pills .dropdown img { height: 2.5em; vertical-align: middle; } -.masthead .navigation .nav-pills li.dropdown +.navbar.masthead .navigation .nav-pills li.dropdown { float: right; } -.masthead .navigation .nav-pills li.dropdown > .dropdown-toggle +.navbar.masthead .navigation .nav-pills li.dropdown > .dropdown-toggle { padding-bottom: 12px; padding-top: 12px; @@ -279,13 +276,13 @@ position: relative; } -.masthead .navigation .nav-pills .dropdown .dropdown-menu > li:hover .user-icon-small, +.navbar.masthead .navigation .nav-pills .dropdown .dropdown-menu > li:hover .user-icon-small, .user-icon-small:hover{ fill:white; } -.masthead .navigation .nav-pills li.open .user-icon, -.masthead .navigation .nav-pills li a:hover .user-icon +.navbar.masthead .navigation .nav-pills li.open .user-icon, +.navbar.masthead .navigation .nav-pills li a:hover .user-icon { fill:white; } \ No newline at end of file diff --git a/ckanext/odsh/templates/macros/form.html b/ckanext/odsh/templates/macros/form.html index f5b95d288c1e7cf6cd9f23199e33bd5338dde49c..cc01867dab57de4dc4fd41785ab9df68579b6791 100644 --- a/ckanext/odsh/templates/macros/form.html +++ b/ckanext/odsh/templates/macros/form.html @@ -156,7 +156,7 @@ options - A list/tuple of fields to be used as <options>. #} {% macro markdown(name, id='', label='', value='', placeholder='', error="", classes=[], attrs={}, - is_required=false) %} + is_required=false, cols=20, rows=5) %} {% set classes = (classes|list) %} {% do classes.append('control-full') %} {% set markdown_tooltip = " @@ -169,7 +169,7 @@ options - A list/tuple of fields to be used as <options>. extra_html=extra_html, is_required=is_required) %} <div class="row-fluid"> <div class="span6"> - <textarea id="{{ id or name }}" name="{{ name }}" cols="20" rows="5" placeholder="{{ placeholder }}" + <textarea id="{{ id or name }}" name="{{ name }}" cols="{{cols}}" rows="{{rows}}" placeholder="{{ placeholder }}" {{ attributes(attrs) }}>{{ value | empty_and_escape }}</textarea> </div> <div class="span6 inline-error"> diff --git a/ckanext/odsh/templates/organization/snippets/organization_form.html b/ckanext/odsh/templates/organization/snippets/organization_form.html new file mode 100644 index 0000000000000000000000000000000000000000..4028d04852a5a92661fad71deaa3ed069504c2fd --- /dev/null +++ b/ckanext/odsh/templates/organization/snippets/organization_form.html @@ -0,0 +1,65 @@ +{% import 'macros/form.html' as form %} + +<form id='organization-edit-form' class="dataset-form form-horizontal" method="post" data-module="basic-form" enctype="multipart/form-data"> + {% block error_summary %} + {{ form.errors(error_summary) }} + {% endblock %} + + {% block basic_fields %} + {% set attrs = {'data-module': 'slug-preview-target'} %} + {{ form.input('title', label=_('Name'), id='field-name', placeholder=_('My Organization'), value=data.title, error=errors.title, classes=['control-full'], attrs=attrs) }} + + {# Perhaps these should be moved into the controller? #} + {% set prefix = h.url_for(controller='organization', action='read', id='') %} + {% set domain = h.url_for(controller='organization', action='read', id='', qualified=true) %} + {% set domain = domain|replace("http://", "")|replace("https://", "") %} + {% set attrs = {'data-module': 'slug-preview-slug', 'data-module-prefix': domain, 'data-module-placeholder': '<organization>'} %} + + {{ form.prepend('name', label=_('URL'), prepend=prefix, id='field-url', placeholder=_('my-organization'), value=data.name, error=errors.name, attrs=attrs, is_required=true) }} + + {{ form.markdown('description', label=_('Description'), id='field-description', placeholder=_('A little information about my organization...'), value=data.description, error=errors.description, rows=30) }} + + {% set is_upload = data.image_url and not data.image_url.startswith('http') %} + {% set is_url = data.image_url and data.image_url.startswith('http') %} + + {{ form.image_upload(data, errors, is_upload_enabled=h.uploads_enabled(), is_url=is_url, is_upload=is_upload) }} + + {% endblock %} + + {% block custom_fields %} + {% for extra in data.extras %} + {% set prefix = 'extras__%d__' % loop.index0 %} + {{ form.custom( + names=(prefix ~ 'key', prefix ~ 'value', prefix ~ 'deleted'), + id='field-extras-%d' % loop.index, + label=_('Custom Field'), + values=(extra.key, extra.value, extra.deleted), + error=errors[prefix ~ 'key'] or errors[prefix ~ 'value'] + ) }} + {% endfor %} + + {# Add a max if 3 empty columns #} + {% for extra in range(data.extras|count, 3) %} + {% set index = (loop.index0 + data.extras|count) %} + {% set prefix = 'extras__%d__' % index %} + {{ form.custom( + names=(prefix ~ 'key', prefix ~ 'value', prefix ~ 'deleted'), + id='field-extras-%d' % index, + label=_('Custom Field'), + values=(extra.key, extra.value, extra.deleted), + error=errors[prefix ~ 'key'] or errors[prefix ~ 'value'] + ) }} + {% endfor %} + {% endblock %} + + {{ form.required_message() }} + + <div class="form-actions"> + {% block delete_button %} + {% if h.check_access('organization_delete', {'id': data.id}) %} + <a class="btn btn-danger pull-left" href="{% url_for controller='organization', action='delete', id=data.id %}" data-module="confirm-action" data-module-content="{{ _('Are you sure you want to delete this Organization? This will delete all the public and private datasets belonging to this organization.') }}">{% block delete_button_text %}{{ _('Delete') }}{% endblock %}</a> + {% endif %} + {% endblock %} + <button class="btn btn-primary" name="save" type="submit">{% block save_text %}{{ _('Save Organization') }}{% endblock %}</button> + </div> +</form> diff --git a/ckanext/odsh/templates/snippets/search_result_text.html b/ckanext/odsh/templates/snippets/search_result_text.html index af5d22f38aef82b6be23c3c2921a70f7288d1f1c..4ff6b331605815fdf401acfb70a65ba332aefc16 100644 --- a/ckanext/odsh/templates/snippets/search_result_text.html +++ b/ckanext/odsh/templates/snippets/search_result_text.html @@ -46,4 +46,4 @@ Example: {{ text_no_query_none }} {%- endif -%} {%- endif -%} -<a href={{h.remove_url_param('ext_prev_extent', controller='feed', action='custom' )}}> <i class="fa fa-medium fa-rss"></i></a> \ No newline at end of file +<a href={{h.remove_url_param([''], controller='feed', action='custom' )}}> <i class="fa fa-medium fa-rss"></i></a> diff --git a/ckanext/odsh/validation.py b/ckanext/odsh/validation.py index 179fcab945c1b4a050a352bdcd82bdd167392f14..a49ce453f5974440d6673f454a1cb279239ef9a8 100644 --- a/ckanext/odsh/validation.py +++ b/ckanext/odsh/validation.py @@ -49,7 +49,7 @@ def validate_extras(key, data, errors, context): isStaNord = ('id',) in data and data[('id',)][:7] == 'StaNord' - validate_extra_groups(data, False, extra_errors) + validate_extra_groups(data, True, extra_errors) validate_extra_date_new(key, 'issued', data, isStaNord, extra_errors) validate_extra_date_new(key, 'temporal_start', data, isStaNord, extra_errors) validate_extra_date_new(key, 'temporal_end', data, True, extra_errors)