Added ckanext-datesearch
git-svn-id: https://svn.eudat.eu/EUDAT/Services/MetaData/ckanext-datesearch@1228 68e52488-0a15-44bc-a314-416658652264
This commit is contained in:
commit
c2fe606dfa
|
@ -0,0 +1,301 @@
|
|||
How to implement a temporal search widget for CKAN
|
||||
==================================================
|
||||
|
||||
CKAN supports date-range searches out of the box. For example, to search for
|
||||
datasets that were modified in August 2013, you can simply type this into the
|
||||
search bar:
|
||||
|
||||
metadata_modified:[2013-08-01T00:00:00Z TO 2013-08-31T00:00:00Z]
|
||||
|
||||
Here's the result of that search on datahub.io: <http://datahub.io/dataset?q=metadata_modified%3A[2013-08-01T00%3A00%3A00Z+TO+2013-08-31T00%3A00%3A00Z]>.
|
||||
|
||||
You can also search for `metadata_created` instead of `metadata_modified`, or
|
||||
you can search on any custom date fields you've added to datasets. You can do
|
||||
other kinds of date math besides ranges too, e.g. less than, greater than
|
||||
(it's just Solr's date query syntax).
|
||||
|
||||
So the challenge of integrating date-range searches into your CKAN site is
|
||||
mostly a user interface problem: how to present a user-friendly way to select
|
||||
a start date and an end date, and integrate the date-range facet with the rest
|
||||
of CKAN's search tools such as the query box, sorting, and other facets.
|
||||
|
||||
It's not enough just to do a date-range search, the user should be able to
|
||||
search for datasets in the *climate* group, with an open license, matching the
|
||||
term "carbon", *and* within a given date-range, and then also sort those
|
||||
results, and they should be able to do all this in a simple, user-friendly way.
|
||||
|
||||
|
||||
Create extension and plugin
|
||||
---------------------------
|
||||
|
||||
First create a CKAN extension `ckanext-datesearch` containing a plugin
|
||||
`datesearch`. The plugin doesn't need to do anything yet.
|
||||
See <http://docs.ckan.org/en/943-writing-extensions-tutorial/writing-extensions.html>.
|
||||
|
||||
|
||||
Add template directory and Fanstatic library
|
||||
--------------------------------------------
|
||||
|
||||
The plugin needs to implement `IConfigurable` and register a template
|
||||
directory and a Fanstatic library directory with CKAN.
|
||||
[ckanext/datesearch/plugin.py](ckanext/datesearch/plugin.py):
|
||||
|
||||
import ckan.plugins as plugins
|
||||
import ckan.plugins.toolkit as toolkit
|
||||
|
||||
|
||||
class DateSearchPlugin(plugins.SingletonPlugin):
|
||||
plugins.implements(plugins.IConfigurer)
|
||||
|
||||
def update_config(self, config):
|
||||
toolkit.add_template_directory(config, 'templates')
|
||||
toolkit.add_resource('fanstatic', 'ckanext-datesearch')
|
||||
|
||||
Also create the directories
|
||||
[ckanext/datesearch/templates/](ckanext/datesearch/templates/) and
|
||||
[ckanext/datesearch/fanstatic/](ckanext/datesearch/fanstatic/).
|
||||
|
||||
`templates` is a directory where we can put custom templates that will override
|
||||
the CKAN core ones.
|
||||
|
||||
`fanstatic` is a [Fanstatic](ckanext/datesearch/fanstatic/) resource directory.
|
||||
CKAN uses Fanstatic to serve static files like CSS, JavaScript or image files,
|
||||
because Fanstatic provides features like bundling, minifying, caching, etc.
|
||||
|
||||
|
||||
Add static files for the date-range picker widget
|
||||
-------------------------------------------------
|
||||
|
||||
[fanstatic/daterangepicker-bs3.css](ckanext/datesearch/fanstatic/daterangepicker-bs3.css),
|
||||
[fanstatic/daterangepicker.js](ckanext/datesearch/fanstatic/daterangepicker.js),
|
||||
and [fanstatic/moment.js](ckanext/datesearch/fanstatic/moment.js)
|
||||
are some static CSS and JavaScript files that we're
|
||||
using to get a GUI date-range picker widget. We're using Dan Grossman's
|
||||
[bootstrap-daterangepicker](https://github.com/dangrossman/bootstrap-daterangepicker).
|
||||
|
||||
Adding the files into the `fanstatic` directory in our extension just means
|
||||
that those files will be available when we need them in our templates later.
|
||||
|
||||
|
||||
Customize the dataset search page template
|
||||
------------------------------------------
|
||||
|
||||
Create the directory [ckanext/datesearch/templates/package/](ckanext/datesearch/templates/package/),
|
||||
and open the file [ckanext/datesearch/templates/package/search.html](ckanext/datesearch/templates/package/search.html):
|
||||
|
||||
{% ckan_extends %}
|
||||
|
||||
{% block secondary_content %}
|
||||
{% resource 'ckanext-datesearch/moment.js' %}
|
||||
{% resource 'ckanext-datesearch/daterangepicker.js' %}
|
||||
{% resource 'ckanext-datesearch/daterangepicker-bs3.css' %}
|
||||
{% resource 'ckanext-datesearch/daterangepicker-module.js' %}
|
||||
|
||||
{# This <section> is the date-range picker widget in the sidebar. #}
|
||||
<section class="module module-narrow module-shallow">
|
||||
<h2 class="module-heading">
|
||||
<i class="icon-medium icon-calendar"></i> {{ _('Filter by date') }}
|
||||
<a href="{{ h.remove_url_param(['ext_startdate', 'ext_enddate']) }}"
|
||||
class="action">{{ _('Clear') }}</a>
|
||||
</h2>
|
||||
<div class="module-content input-prepend" data-module="daterange-query">
|
||||
<span class="add-on"><i class="icon-calendar"></i></span>
|
||||
<input type="text" style="width: 150px" id="daterange"
|
||||
data-module="daterangepicker-module" />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{{ super() }}
|
||||
{% endblock %}
|
||||
|
||||
This is a Jinja template that overrides the [templates/package/search.html](https://github.com/okfn/ckan/blob/release-v2.0.2/ckan/templates/package/search.html)
|
||||
template in CKAN core:
|
||||
|
||||
* `{% ckan_extends %}` means this template inherits from the corresponding
|
||||
core template
|
||||
|
||||
* Look in the core template file to see what Jinja blocks are available, in
|
||||
this case we're overriding the `{% block secondary_content %}`.
|
||||
|
||||
* `{% resource 'ckanext-datesearch/moment.js' %}` tells Fanstatic to load the
|
||||
`moment.js` file from our `fanstatic` directory into this page. We load a
|
||||
number of custom resource files in our template:
|
||||
|
||||
{% resource 'ckanext-datesearch/moment.js' %}
|
||||
{% resource 'ckanext-datesearch/daterangepicker.js' %}
|
||||
{% resource 'ckanext-datesearch/daterangepicker-bs3.css' %}
|
||||
{% resource 'ckanext-datesearch/daterangepicker-module.js' %}
|
||||
|
||||
* `{{ super() }}` means to insert the contents of the block from the core
|
||||
template.
|
||||
|
||||
* The rest of the stuff inside `{% block secondary_content %}` is the custom
|
||||
stuff that we're adding to the top of the sidebar. Most of the code is just
|
||||
to make it fit into the CKAN theme. The important bit is:
|
||||
|
||||
<input type="text" style="width: 150px" id="daterange"
|
||||
data-module="daterangepicker-module" />
|
||||
|
||||
Adding a `data-module="daterangepicker-module"` attribute to an HTML element
|
||||
tells CKAN to find the `daterangepicker-module` JavaScript module and load
|
||||
it into the page after this element is rendered.
|
||||
|
||||
|
||||
Implement the `daterangepicker-module` JavaScript module
|
||||
--------------------------------------------------------
|
||||
|
||||
CKAN uses _JavaScript modules_ to load snippets of JavaScript into the page.
|
||||
When we added the `data-module="daterangepicker-module"` attribute to our
|
||||
`<input>` tag in our template earlier, we told CKAN to include the
|
||||
`daterangepicker-module` JavaScript module into our page. We now need to
|
||||
provide that module. [fanstatic/daterangepicker-module.js](ckanext/datesearch/fanstatic/daterangepicker-module.js):
|
||||
|
||||
this.ckan.module('daterangepicker-module', function($, _) {
|
||||
return {
|
||||
initialize: function() {
|
||||
|
||||
// Add hidden <input> tags #ext_startdate and #ext_enddate,
|
||||
// if they don't already exist.
|
||||
var form = $("#dataset-search");
|
||||
if ($("#ext_startdate").length === 0) {
|
||||
$('<input type="hidden" id="ext_startdate" name="ext_startdate" />').appendTo(form);
|
||||
}
|
||||
if ($("#ext_enddate").length === 0) {
|
||||
$('<input type="hidden" id="ext_enddate" name="ext_enddate" />').appendTo(form);
|
||||
}
|
||||
|
||||
// Add a date-range picker widget to the <input> with id #daterange
|
||||
$('input[id="daterange"]').daterangepicker({
|
||||
showDropdowns: true,
|
||||
timePicker: true
|
||||
},
|
||||
function(start, end) {
|
||||
|
||||
// Bootstrap-daterangepicker calls this function after the user
|
||||
// picks a start and end date.
|
||||
|
||||
// Format the start and end dates into strings in a date format
|
||||
// that Solr understands.
|
||||
start = start.format('YYYY-MM-DDTHH:mm:ss') + 'Z';
|
||||
end = end.format('YYYY-MM-DDTHH:mm:ss') + 'Z';
|
||||
|
||||
// Set the value of the hidden <input id="ext_startdate"> to
|
||||
// the chosen start date.
|
||||
$('#ext_startdate').val(start);
|
||||
|
||||
// Set the value of the hidden <input id="ext_enddate"> to
|
||||
// the chosen end date.
|
||||
$('#ext_enddate').val(end);
|
||||
|
||||
// Submit the <form id="dataset-search">.
|
||||
$("#dataset-search").submit();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
CKAN will call the `initialize` function when the page loads. This function
|
||||
uses jQuery and bootstrap-daterangepicker to add a JavaScript date-range picker
|
||||
widget to the `<input>` tag with `id="daterange"`:
|
||||
|
||||
$('input[id="daterange"]').daterangepicker({
|
||||
showDropdowns: true,
|
||||
timePicker: true
|
||||
},
|
||||
|
||||
Finally, bootstrap-daterangepicker supports a *callback function* that will
|
||||
be called after the user selects two dates. We use the callback function to add
|
||||
the selected start and end dates to the hidden `<input>` tags, and
|
||||
submit the form:
|
||||
|
||||
function(start, end) {
|
||||
|
||||
// Bootstrap-daterangepicker calls this function after the user
|
||||
// picks a start and end date.
|
||||
|
||||
// Format the start and end dates into strings in a date format
|
||||
// that Solr understands.
|
||||
start = start.format('YYYY-MM-DDTHH:mm:ss') + 'Z';
|
||||
end = end.format('YYYY-MM-DDTHH:mm:ss') + 'Z';
|
||||
|
||||
// Set the value of the hidden <input id="ext_startdate"> to
|
||||
// the chosen start date.
|
||||
$('#ext_startdate').val(start);
|
||||
|
||||
// Set the value of the hidden <input id="ext_enddate"> to
|
||||
// the chosen end date.
|
||||
$('#ext_enddate').val(end);
|
||||
|
||||
// Submit the <form id="dataset-search">.
|
||||
$("#dataset-search").submit();
|
||||
});
|
||||
|
||||
|
||||
Implement `IPackageController` to put the dates into the Solr query
|
||||
-------------------------------------------------------------------
|
||||
|
||||
Finally, when the dataset search form is submitted along with our extra start
|
||||
and end dates (via the hidden `<input>` tags), we need to catch these start
|
||||
and end dates and insert them into the Solr query. We can do this by
|
||||
implementing CKAN's `IPackageController` plugin interface and using its
|
||||
`before_search()` method to modify the Solr query parameters before the search
|
||||
is done. In `plugin.py`:
|
||||
|
||||
class DateSearchPlugin(plugins.SingletonPlugin):
|
||||
plugins.implements(plugins.IConfigurer)
|
||||
plugins.implements(plugins.IPackageController, inherit=True)
|
||||
|
||||
def update_config(self, config):
|
||||
toolkit.add_template_directory(config, 'templates')
|
||||
toolkit.add_resource('fanstatic', 'ckanext-datesearch')
|
||||
|
||||
def before_search(self, search_params):
|
||||
extras = search_params.get('extras')
|
||||
if not extras:
|
||||
# There are no extras in the search params, so do nothing.
|
||||
return search_params
|
||||
start_date = extras.get('ext_startdate')
|
||||
end_date = extras.get('ext_enddate')
|
||||
if not start_date or not end_date:
|
||||
# The user didn't select a start and end date, so do nothing.
|
||||
return search_params
|
||||
|
||||
# Add a date-range query with the selected start and end dates into the
|
||||
# Solr facet queries.
|
||||
fq = search_params['fq']
|
||||
fq = '{fq} +metadata_modified:[{start_date} TO {end_date}]'.format(
|
||||
fq=fq, start_date=start_date, end_date=end_date)
|
||||
search_params['fq'] = fq
|
||||
return search_params
|
||||
|
||||
Our `before_search()` method receives the values from our `#ext_startdate` and
|
||||
`#ext_enddate` input tags in the `extras` dict of the `search_params` dict.
|
||||
|
||||
It's important that the tag IDs begin with `ext_`, this tells CKAN to pass the
|
||||
values to our plugin.
|
||||
|
||||
We simply take these start and end date values and add them to the
|
||||
[Solr filter query](http://wiki.apache.org/solr/CommonQueryParameters#fq)
|
||||
(`fq`) in a date-range format that Solr understands:
|
||||
|
||||
# Add a date-range query with the selected start and end dates into the
|
||||
# Solr facet queries.
|
||||
fq = search_params['fq']
|
||||
fq = '{fq} +metadata_modified:[{start_date} TO {end_date}]'.format(
|
||||
fq=fq, start_date=start_date, end_date=end_date)
|
||||
search_params['fq'] = fq
|
||||
return search_params
|
||||
|
||||
|
||||
Todo
|
||||
----
|
||||
|
||||
There's still some work on the details of the user interface to be done here,
|
||||
including:
|
||||
|
||||
* After the user selects a date-range and the page reloads with the filtered
|
||||
search results, the date-range should remain filled-in in the date-range
|
||||
picker widget.
|
||||
|
||||
* If the user types something into the search box and hits return while they
|
||||
have a date-range filter on, the date-range filter should not be lost.
|
|
@ -0,0 +1,7 @@
|
|||
# this is a namespace package
|
||||
try:
|
||||
import pkg_resources
|
||||
pkg_resources.declare_namespace(__name__)
|
||||
except ImportError:
|
||||
import pkgutil
|
||||
__path__ = pkgutil.extend_path(__path__, __name__)
|
|
@ -0,0 +1,7 @@
|
|||
# this is a namespace package
|
||||
try:
|
||||
import pkg_resources
|
||||
pkg_resources.declare_namespace(__name__)
|
||||
except ImportError:
|
||||
import pkgutil
|
||||
__path__ = pkgutil.extend_path(__path__, __name__)
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,484 @@
|
|||
/*!
|
||||
* Datepicker for Bootstrap
|
||||
*
|
||||
* Copyright 2012 Stefan Petre
|
||||
* Improvements by Andrew Rowls
|
||||
* Licensed under the Apache License v2.0
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*/
|
||||
.datepicker {
|
||||
padding: 4px;
|
||||
-webkit-border-radius: 4px;
|
||||
-moz-border-radius: 4px;
|
||||
border-radius: 4px;
|
||||
direction: ltr;
|
||||
/*.dow {
|
||||
border-top: 1px solid #ddd !important;
|
||||
}*/
|
||||
|
||||
}
|
||||
.datepicker-inline {
|
||||
width: 220px;
|
||||
}
|
||||
.datepicker.datepicker-rtl {
|
||||
direction: rtl;
|
||||
}
|
||||
.datepicker.datepicker-rtl table tr td span {
|
||||
float: right;
|
||||
}
|
||||
.datepicker-dropdown {
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
.datepicker-dropdown:before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
border-left: 7px solid transparent;
|
||||
border-right: 7px solid transparent;
|
||||
border-bottom: 7px solid #ccc;
|
||||
border-top: 0;
|
||||
border-bottom-color: rgba(0, 0, 0, 0.2);
|
||||
position: absolute;
|
||||
}
|
||||
.datepicker-dropdown:after {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
border-left: 6px solid transparent;
|
||||
border-right: 6px solid transparent;
|
||||
border-bottom: 6px solid #ffffff;
|
||||
border-top: 0;
|
||||
position: absolute;
|
||||
}
|
||||
.datepicker-dropdown.datepicker-orient-left:before {
|
||||
left: 6px;
|
||||
}
|
||||
.datepicker-dropdown.datepicker-orient-left:after {
|
||||
left: 7px;
|
||||
}
|
||||
.datepicker-dropdown.datepicker-orient-right:before {
|
||||
right: 6px;
|
||||
}
|
||||
.datepicker-dropdown.datepicker-orient-right:after {
|
||||
right: 7px;
|
||||
}
|
||||
.datepicker-dropdown.datepicker-orient-top:before {
|
||||
top: -7px;
|
||||
}
|
||||
.datepicker-dropdown.datepicker-orient-top:after {
|
||||
top: -6px;
|
||||
}
|
||||
.datepicker-dropdown.datepicker-orient-bottom:before {
|
||||
bottom: -7px;
|
||||
border-bottom: 0;
|
||||
border-top: 7px solid #999;
|
||||
}
|
||||
.datepicker-dropdown.datepicker-orient-bottom:after {
|
||||
bottom: -6px;
|
||||
border-bottom: 0;
|
||||
border-top: 6px solid #ffffff;
|
||||
}
|
||||
.datepicker > div {
|
||||
display: none;
|
||||
}
|
||||
.datepicker.days div.datepicker-days {
|
||||
display: block;
|
||||
}
|
||||
.datepicker.months div.datepicker-months {
|
||||
display: block;
|
||||
}
|
||||
.datepicker.years div.datepicker-years {
|
||||
display: block;
|
||||
}
|
||||
.datepicker.decades div.datepicker-decades {
|
||||
display: block;
|
||||
}
|
||||
.datepicker table {
|
||||
margin: 0;
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
.datepicker td,
|
||||
.datepicker th {
|
||||
text-align: center;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
-webkit-border-radius: 4px;
|
||||
-moz-border-radius: 4px;
|
||||
border-radius: 4px;
|
||||
border: none;
|
||||
}
|
||||
.table-striped .datepicker table tr td,
|
||||
.table-striped .datepicker table tr th {
|
||||
background-color: transparent;
|
||||
}
|
||||
.datepicker table tr td.day:hover {
|
||||
background: #eeeeee;
|
||||
cursor: pointer;
|
||||
}
|
||||
.datepicker table tr td.old,
|
||||
.datepicker table tr td.new {
|
||||
color: #999999;
|
||||
}
|
||||
.datepicker table tr td.disabled,
|
||||
.datepicker table tr td.disabled:hover {
|
||||
background: none;
|
||||
color: #999999;
|
||||
cursor: default;
|
||||
}
|
||||
.datepicker table tr td.today,
|
||||
.datepicker table tr td.today:hover,
|
||||
.datepicker table tr td.today.disabled,
|
||||
.datepicker table tr td.today.disabled:hover {
|
||||
background-color: #fde19a;
|
||||
background-image: -moz-linear-gradient(top, #fdd49a, #fdf59a);
|
||||
background-image: -ms-linear-gradient(top, #fdd49a, #fdf59a);
|
||||
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fdd49a), to(#fdf59a));
|
||||
background-image: -webkit-linear-gradient(top, #fdd49a, #fdf59a);
|
||||
background-image: -o-linear-gradient(top, #fdd49a, #fdf59a);
|
||||
background-image: linear-gradient(top, #fdd49a, #fdf59a);
|
||||
background-repeat: repeat-x;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fdd49a', endColorstr='#fdf59a', GradientType=0);
|
||||
border-color: #fdf59a #fdf59a #fbed50;
|
||||
border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
|
||||
color: #000;
|
||||
}
|
||||
.datepicker table tr td.today:hover,
|
||||
.datepicker table tr td.today:hover:hover,
|
||||
.datepicker table tr td.today.disabled:hover,
|
||||
.datepicker table tr td.today.disabled:hover:hover,
|
||||
.datepicker table tr td.today:active,
|
||||
.datepicker table tr td.today:hover:active,
|
||||
.datepicker table tr td.today.disabled:active,
|
||||
.datepicker table tr td.today.disabled:hover:active,
|
||||
.datepicker table tr td.today.active,
|
||||
.datepicker table tr td.today:hover.active,
|
||||
.datepicker table tr td.today.disabled.active,
|
||||
.datepicker table tr td.today.disabled:hover.active,
|
||||
.datepicker table tr td.today.disabled,
|
||||
.datepicker table tr td.today:hover.disabled,
|
||||
.datepicker table tr td.today.disabled.disabled,
|
||||
.datepicker table tr td.today.disabled:hover.disabled,
|
||||
.datepicker table tr td.today[disabled],
|
||||
.datepicker table tr td.today:hover[disabled],
|
||||
.datepicker table tr td.today.disabled[disabled],
|
||||
.datepicker table tr td.today.disabled:hover[disabled] {
|
||||
background-color: #fdf59a;
|
||||
}
|
||||
.datepicker table tr td.today:active,
|
||||
.datepicker table tr td.today:hover:active,
|
||||
.datepicker table tr td.today.disabled:active,
|
||||
.datepicker table tr td.today.disabled:hover:active,
|
||||
.datepicker table tr td.today.active,
|
||||
.datepicker table tr td.today:hover.active,
|
||||
.datepicker table tr td.today.disabled.active,
|
||||
.datepicker table tr td.today.disabled:hover.active {
|
||||
background-color: #fbf069 \9;
|
||||
}
|
||||
.datepicker table tr td.today:hover:hover {
|
||||
color: #000;
|
||||
}
|
||||
.datepicker table tr td.today.active:hover {
|
||||
color: #fff;
|
||||
}
|
||||
.datepicker table tr td.range,
|
||||
.datepicker table tr td.range:hover,
|
||||
.datepicker table tr td.range.disabled,
|
||||
.datepicker table tr td.range.disabled:hover {
|
||||
background: #eeeeee;
|
||||
-webkit-border-radius: 0;
|
||||
-moz-border-radius: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
.datepicker table tr td.range.today,
|
||||
.datepicker table tr td.range.today:hover,
|
||||
.datepicker table tr td.range.today.disabled,
|
||||
.datepicker table tr td.range.today.disabled:hover {
|
||||
background-color: #f3d17a;
|
||||
background-image: -moz-linear-gradient(top, #f3c17a, #f3e97a);
|
||||
background-image: -ms-linear-gradient(top, #f3c17a, #f3e97a);
|
||||
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f3c17a), to(#f3e97a));
|
||||
background-image: -webkit-linear-gradient(top, #f3c17a, #f3e97a);
|
||||
background-image: -o-linear-gradient(top, #f3c17a, #f3e97a);
|
||||
background-image: linear-gradient(top, #f3c17a, #f3e97a);
|
||||
background-repeat: repeat-x;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#f3c17a', endColorstr='#f3e97a', GradientType=0);
|
||||
border-color: #f3e97a #f3e97a #edde34;
|
||||
border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
|
||||
-webkit-border-radius: 0;
|
||||
-moz-border-radius: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
.datepicker table tr td.range.today:hover,
|
||||
.datepicker table tr td.range.today:hover:hover,
|
||||
.datepicker table tr td.range.today.disabled:hover,
|
||||
.datepicker table tr td.range.today.disabled:hover:hover,
|
||||
.datepicker table tr td.range.today:active,
|
||||
.datepicker table tr td.range.today:hover:active,
|
||||
.datepicker table tr td.range.today.disabled:active,
|
||||
.datepicker table tr td.range.today.disabled:hover:active,
|
||||
.datepicker table tr td.range.today.active,
|
||||
.datepicker table tr td.range.today:hover.active,
|
||||
.datepicker table tr td.range.today.disabled.active,
|
||||
.datepicker table tr td.range.today.disabled:hover.active,
|
||||
.datepicker table tr td.range.today.disabled,
|
||||
.datepicker table tr td.range.today:hover.disabled,
|
||||
.datepicker table tr td.range.today.disabled.disabled,
|
||||
.datepicker table tr td.range.today.disabled:hover.disabled,
|
||||
.datepicker table tr td.range.today[disabled],
|
||||
.datepicker table tr td.range.today:hover[disabled],
|
||||
.datepicker table tr td.range.today.disabled[disabled],
|
||||
.datepicker table tr td.range.today.disabled:hover[disabled] {
|
||||
background-color: #f3e97a;
|
||||
}
|
||||
.datepicker table tr td.range.today:active,
|
||||
.datepicker table tr td.range.today:hover:active,
|
||||
.datepicker table tr td.range.today.disabled:active,
|
||||
.datepicker table tr td.range.today.disabled:hover:active,
|
||||
.datepicker table tr td.range.today.active,
|
||||
.datepicker table tr td.range.today:hover.active,
|
||||
.datepicker table tr td.range.today.disabled.active,
|
||||
.datepicker table tr td.range.today.disabled:hover.active {
|
||||
background-color: #efe24b \9;
|
||||
}
|
||||
.datepicker table tr td.selected,
|
||||
.datepicker table tr td.selected:hover,
|
||||
.datepicker table tr td.selected.disabled,
|
||||
.datepicker table tr td.selected.disabled:hover {
|
||||
background-color: #9e9e9e;
|
||||
background-image: -moz-linear-gradient(top, #b3b3b3, #808080);
|
||||
background-image: -ms-linear-gradient(top, #b3b3b3, #808080);
|
||||
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#b3b3b3), to(#808080));
|
||||
background-image: -webkit-linear-gradient(top, #b3b3b3, #808080);
|
||||
background-image: -o-linear-gradient(top, #b3b3b3, #808080);
|
||||
background-image: linear-gradient(top, #b3b3b3, #808080);
|
||||
background-repeat: repeat-x;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#b3b3b3', endColorstr='#808080', GradientType=0);
|
||||
border-color: #808080 #808080 #595959;
|
||||
border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
|
||||
color: #fff;
|
||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
.datepicker table tr td.selected:hover,
|
||||
.datepicker table tr td.selected:hover:hover,
|
||||
.datepicker table tr td.selected.disabled:hover,
|
||||
.datepicker table tr td.selected.disabled:hover:hover,
|
||||
.datepicker table tr td.selected:active,
|
||||
.datepicker table tr td.selected:hover:active,
|
||||
.datepicker table tr td.selected.disabled:active,
|
||||
.datepicker table tr td.selected.disabled:hover:active,
|
||||
.datepicker table tr td.selected.active,
|
||||
.datepicker table tr td.selected:hover.active,
|
||||
.datepicker table tr td.selected.disabled.active,
|
||||
.datepicker table tr td.selected.disabled:hover.active,
|
||||
.datepicker table tr td.selected.disabled,
|
||||
.datepicker table tr td.selected:hover.disabled,
|
||||
.datepicker table tr td.selected.disabled.disabled,
|
||||
.datepicker table tr td.selected.disabled:hover.disabled,
|
||||
.datepicker table tr td.selected[disabled],
|
||||
.datepicker table tr td.selected:hover[disabled],
|
||||
.datepicker table tr td.selected.disabled[disabled],
|
||||
.datepicker table tr td.selected.disabled:hover[disabled] {
|
||||
background-color: #808080;
|
||||
}
|
||||
.datepicker table tr td.selected:active,
|
||||
.datepicker table tr td.selected:hover:active,
|
||||
.datepicker table tr td.selected.disabled:active,
|
||||
.datepicker table tr td.selected.disabled:hover:active,
|
||||
.datepicker table tr td.selected.active,
|
||||
.datepicker table tr td.selected:hover.active,
|
||||
.datepicker table tr td.selected.disabled.active,
|
||||
.datepicker table tr td.selected.disabled:hover.active {
|
||||
background-color: #666666 \9;
|
||||
}
|
||||
.datepicker table tr td.active,
|
||||
.datepicker table tr td.active:hover,
|
||||
.datepicker table tr td.active.disabled,
|
||||
.datepicker table tr td.active.disabled:hover {
|
||||
background-color: #006dcc;
|
||||
background-image: -moz-linear-gradient(top, #0088cc, #0044cc);
|
||||
background-image: -ms-linear-gradient(top, #0088cc, #0044cc);
|
||||
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc));
|
||||
background-image: -webkit-linear-gradient(top, #0088cc, #0044cc);
|
||||
background-image: -o-linear-gradient(top, #0088cc, #0044cc);
|
||||
background-image: linear-gradient(top, #0088cc, #0044cc);
|
||||
background-repeat: repeat-x;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0);
|
||||
border-color: #0044cc #0044cc #002a80;
|
||||
border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
|
||||
color: #fff;
|
||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
.datepicker table tr td.active:hover,
|
||||
.datepicker table tr td.active:hover:hover,
|
||||
.datepicker table tr td.active.disabled:hover,
|
||||
.datepicker table tr td.active.disabled:hover:hover,
|
||||
.datepicker table tr td.active:active,
|
||||
.datepicker table tr td.active:hover:active,
|
||||
.datepicker table tr td.active.disabled:active,
|
||||
.datepicker table tr td.active.disabled:hover:active,
|
||||
.datepicker table tr td.active.active,
|
||||
.datepicker table tr td.active:hover.active,
|
||||
.datepicker table tr td.active.disabled.active,
|
||||
.datepicker table tr td.active.disabled:hover.active,
|
||||
.datepicker table tr td.active.disabled,
|
||||
.datepicker table tr td.active:hover.disabled,
|
||||
.datepicker table tr td.active.disabled.disabled,
|
||||
.datepicker table tr td.active.disabled:hover.disabled,
|
||||
.datepicker table tr td.active[disabled],
|
||||
.datepicker table tr td.active:hover[disabled],
|
||||
.datepicker table tr td.active.disabled[disabled],
|
||||
.datepicker table tr td.active.disabled:hover[disabled] {
|
||||
background-color: #0044cc;
|
||||
}
|
||||
.datepicker table tr td.active:active,
|
||||
.datepicker table tr td.active:hover:active,
|
||||
.datepicker table tr td.active.disabled:active,
|
||||
.datepicker table tr td.active.disabled:hover:active,
|
||||
.datepicker table tr td.active.active,
|
||||
.datepicker table tr td.active:hover.active,
|
||||
.datepicker table tr td.active.disabled.active,
|
||||
.datepicker table tr td.active.disabled:hover.active {
|
||||
background-color: #003399 \9;
|
||||
}
|
||||
.datepicker table tr td span {
|
||||
display: block;
|
||||
width: 23%;
|
||||
height: 54px;
|
||||
line-height: 54px;
|
||||
float: left;
|
||||
margin: 1%;
|
||||
cursor: pointer;
|
||||
-webkit-border-radius: 4px;
|
||||
-moz-border-radius: 4px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.datepicker table tr td span:hover {
|
||||
background: #eeeeee;
|
||||
}
|
||||
.datepicker table tr td span.disabled,
|
||||
.datepicker table tr td span.disabled:hover {
|
||||
background: none;
|
||||
color: #999999;
|
||||
cursor: default;
|
||||
}
|
||||
.datepicker table tr td span.active,
|
||||
.datepicker table tr td span.active:hover,
|
||||
.datepicker table tr td span.active.disabled,
|
||||
.datepicker table tr td span.active.disabled:hover {
|
||||
background-color: #006dcc;
|
||||
background-image: -moz-linear-gradient(top, #0088cc, #0044cc);
|
||||
background-image: -ms-linear-gradient(top, #0088cc, #0044cc);
|
||||
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc));
|
||||
background-image: -webkit-linear-gradient(top, #0088cc, #0044cc);
|
||||
background-image: -o-linear-gradient(top, #0088cc, #0044cc);
|
||||
background-image: linear-gradient(top, #0088cc, #0044cc);
|
||||
background-repeat: repeat-x;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0);
|
||||
border-color: #0044cc #0044cc #002a80;
|
||||
border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
|
||||
color: #fff;
|
||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
.datepicker table tr td span.active:hover,
|
||||
.datepicker table tr td span.active:hover:hover,
|
||||
.datepicker table tr td span.active.disabled:hover,
|
||||
.datepicker table tr td span.active.disabled:hover:hover,
|
||||
.datepicker table tr td span.active:active,
|
||||
.datepicker table tr td span.active:hover:active,
|
||||
.datepicker table tr td span.active.disabled:active,
|
||||
.datepicker table tr td span.active.disabled:hover:active,
|
||||
.datepicker table tr td span.active.active,
|
||||
.datepicker table tr td span.active:hover.active,
|
||||
.datepicker table tr td span.active.disabled.active,
|
||||
.datepicker table tr td span.active.disabled:hover.active,
|
||||
.datepicker table tr td span.active.disabled,
|
||||
.datepicker table tr td span.active:hover.disabled,
|
||||
.datepicker table tr td span.active.disabled.disabled,
|
||||
.datepicker table tr td span.active.disabled:hover.disabled,
|
||||
.datepicker table tr td span.active[disabled],
|
||||
.datepicker table tr td span.active:hover[disabled],
|
||||
.datepicker table tr td span.active.disabled[disabled],
|
||||
.datepicker table tr td span.active.disabled:hover[disabled] {
|
||||
background-color: #0044cc;
|
||||
}
|
||||
.datepicker table tr td span.active:active,
|
||||
.datepicker table tr td span.active:hover:active,
|
||||
.datepicker table tr td span.active.disabled:active,
|
||||
.datepicker table tr td span.active.disabled:hover:active,
|
||||
.datepicker table tr td span.active.active,
|
||||
.datepicker table tr td span.active:hover.active,
|
||||
.datepicker table tr td span.active.disabled.active,
|
||||
.datepicker table tr td span.active.disabled:hover.active {
|
||||
background-color: #003399 \9;
|
||||
}
|
||||
.datepicker table tr td span.old,
|
||||
.datepicker table tr td span.new {
|
||||
color: #999999;
|
||||
}
|
||||
.datepicker th.datepicker-switch {
|
||||
width: 145px;
|
||||
}
|
||||
.datepicker thead tr:first-child th,
|
||||
.datepicker tfoot tr th {
|
||||
cursor: pointer;
|
||||
}
|
||||
.datepicker thead tr:first-child th:hover,
|
||||
.datepicker tfoot tr th:hover {
|
||||
background: #eeeeee;
|
||||
}
|
||||
.datepicker .cw {
|
||||
font-size: 10px;
|
||||
width: 12px;
|
||||
padding: 0 2px 0 5px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.datepicker thead tr:first-child th.cw {
|
||||
cursor: default;
|
||||
background-color: transparent;
|
||||
}
|
||||
.input-append.date .add-on i,
|
||||
.input-prepend.date .add-on i {
|
||||
display: block;
|
||||
cursor: pointer;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
.input-daterange input {
|
||||
text-align: center;
|
||||
}
|
||||
.input-daterange input:first-child {
|
||||
-webkit-border-radius: 3px 0 0 3px;
|
||||
-moz-border-radius: 3px 0 0 3px;
|
||||
border-radius: 3px 0 0 3px;
|
||||
}
|
||||
.input-daterange input:last-child {
|
||||
-webkit-border-radius: 0 3px 3px 0;
|
||||
-moz-border-radius: 0 3px 3px 0;
|
||||
border-radius: 0 3px 3px 0;
|
||||
}
|
||||
.input-daterange .add-on {
|
||||
display: inline-block;
|
||||
width: auto;
|
||||
min-width: 16px;
|
||||
height: 18px;
|
||||
padding: 4px 5px;
|
||||
font-weight: normal;
|
||||
line-height: 18px;
|
||||
text-align: center;
|
||||
text-shadow: 0 1px 0 #ffffff;
|
||||
vertical-align: middle;
|
||||
background-color: #eeeeee;
|
||||
border: 1px solid #ccc;
|
||||
margin-left: -5px;
|
||||
margin-right: -5px;
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
this.ckan.module('daterangepicker-module', function ($, _) {
|
||||
return {
|
||||
initialize: function () {
|
||||
|
||||
// Define a new jQuery function to parse parameters from URL
|
||||
$.urlParam = function(name) {
|
||||
var results = new RegExp('[\\?&]' + name + '=([^&#]*)').exec(window.location.href);
|
||||
if (results == null) { return null; } else { return decodeURIComponent(results[1]) || 0; }
|
||||
};
|
||||
|
||||
// Pick out relevant parameters
|
||||
param_start = $.urlParam('ext_startdate');
|
||||
param_end = $.urlParam('ext_enddate');
|
||||
|
||||
// Populate the datepicker and hidden fields
|
||||
if (param_start) {
|
||||
$('input[name="start"]').val(moment(param_start).years());
|
||||
$('#ext_startdate').val(param_start);
|
||||
}
|
||||
if (param_end) {
|
||||
$('input[name="end"]').val(moment(param_end).years());
|
||||
$('#ext_enddate').val(param_end);
|
||||
}
|
||||
|
||||
// Add hidden <input> tags #ext_startdate and #ext_enddate,
|
||||
// if they don't already exist.
|
||||
var form = $("#dataset-search");
|
||||
if ($("#ext_startdate").length === 0) {
|
||||
$('<input type="hidden" id="ext_startdate" name="ext_startdate" />').appendTo(form);
|
||||
}
|
||||
if ($("#ext_enddate").length === 0) {
|
||||
$('<input type="hidden" id="ext_enddate" name="ext_enddate" />').appendTo(form);
|
||||
}
|
||||
|
||||
// Add a date-range picker widget to the <input> with id #daterange
|
||||
$('#datepicker.input-daterange').datepicker({
|
||||
format: "yyyy",
|
||||
startView: 3,
|
||||
minViewMode: 2,
|
||||
keyboardNavigation: false,
|
||||
autoclose: true
|
||||
}).on('hide', function (ev) {
|
||||
// Bootstrap-daterangepicker calls this function after the user
|
||||
// picks a start and end date.
|
||||
|
||||
// Format the start and end dates into strings in a date format
|
||||
// that Solr understands.
|
||||
var v = moment(ev.date).format('YYYY-MM-DDTHH:mm:ss') + 'Z';
|
||||
|
||||
switch (ev.target.name) {
|
||||
case 'start':
|
||||
// Set the value of the hidden <input id="ext_startdate"> to
|
||||
// the chosen start date.
|
||||
$('#ext_startdate').val(v);
|
||||
break;
|
||||
case 'end':
|
||||
// Set the value of the hidden <input id="ext_enddate"> to
|
||||
// the chosen end date.
|
||||
$('#ext_enddate').val(v);
|
||||
break;
|
||||
}
|
||||
|
||||
// Submit the <form id="dataset-search">.
|
||||
//$("#dataset-search").submit();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,44 @@
|
|||
import logging
|
||||
|
||||
import ckan.plugins as plugins
|
||||
import ckan.plugins.toolkit as toolkit
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DateSearchPlugin(plugins.SingletonPlugin):
|
||||
plugins.implements(plugins.IConfigurer)
|
||||
plugins.implements(plugins.IPackageController, inherit=True)
|
||||
|
||||
def update_config(self, config):
|
||||
toolkit.add_template_directory(config, 'templates')
|
||||
toolkit.add_resource('fanstatic', 'ckanext-datesearch')
|
||||
|
||||
def before_search(self, search_params):
|
||||
extras = search_params.get('extras')
|
||||
log.debug("extras: {0}".format(extras))
|
||||
if not extras:
|
||||
# There are no extras in the search params, so do nothing.
|
||||
return search_params
|
||||
|
||||
start_date = extras.get('ext_startdate')
|
||||
log.debug("start_date: {0}".format(start_date))
|
||||
|
||||
end_date = extras.get('ext_enddate')
|
||||
log.debug("end_date: {0}".format(end_date))
|
||||
|
||||
if not start_date or not end_date:
|
||||
# The user didn't select a start and end date, so do nothing.
|
||||
return search_params
|
||||
|
||||
# Add a date-range query with the selected start and end dates into the
|
||||
# Solr facet queries.
|
||||
fq = search_params['fq']
|
||||
log.debug("fq: {0}".format(fq))
|
||||
fq = '{fq} +metadata_modified:[{start_date} TO {end_date}]'.format(
|
||||
fq=fq, start_date=start_date, end_date=end_date)
|
||||
log.debug("fq: {0}".format(fq))
|
||||
search_params['fq'] = fq
|
||||
log.debug("search_params: {0}".format(search_params))
|
||||
|
||||
return search_params
|
|
@ -0,0 +1,23 @@
|
|||
{% ckan_extends %}
|
||||
|
||||
{% block secondary_content %}
|
||||
{% resource 'ckanext-datesearch/moment.js' %}
|
||||
{% resource 'ckanext-datesearch/datepicker.css' %}
|
||||
{% resource 'ckanext-datesearch/bootstrap-datepicker.js' %}
|
||||
{% resource 'ckanext-datesearch/daterangepicker-module.js' %}
|
||||
|
||||
{# This <section> is the date-range picker widget in the sidebar. #}
|
||||
<section class="module module-narrow module-shallow">
|
||||
<h2 class="module-heading">
|
||||
<i class="icon-medium icon-calendar"></i> {{ _('Filter by date') }}
|
||||
<a href="{{ h.remove_url_param(['ext_startdate', 'ext_enddate']) }}" class="action">{{ _('Clear') }}</a>
|
||||
</h2>
|
||||
<div class="module-content input-prepend input-daterange" data-module="daterange-query" id="datepicker">
|
||||
<input type="text" class="input-small" readonly="" name="start" data-module="daterangepicker-module" />
|
||||
<span class="add-on">to</span><br>
|
||||
<input type="text" class="input-small" readonly="" name="end" data-module="daterangepicker-module" />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{{ super() }}
|
||||
{% endblock %}
|
|
@ -0,0 +1,32 @@
|
|||
from setuptools import setup, find_packages
|
||||
import sys, os
|
||||
|
||||
version = '0.0'
|
||||
|
||||
setup(
|
||||
name='ckanext-datesearch',
|
||||
version=version,
|
||||
description="",
|
||||
long_description="""\
|
||||
""",
|
||||
classifiers=[], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers
|
||||
keywords='',
|
||||
author='',
|
||||
author_email='',
|
||||
url='',
|
||||
license='',
|
||||
packages=find_packages(exclude=['ez_setup', 'examples', 'tests']),
|
||||
namespace_packages=['ckanext', 'ckanext.datesearch'],
|
||||
include_package_data=True,
|
||||
zip_safe=False,
|
||||
install_requires=[
|
||||
# -*- Extra requirements: -*-
|
||||
],
|
||||
entry_points=\
|
||||
"""
|
||||
[ckan.plugins]
|
||||
# Add plugins here, eg
|
||||
# myplugin=ckanext.datesearch:PluginClass
|
||||
datesearch=ckanext.datesearch.plugin:DateSearchPlugin
|
||||
""",
|
||||
)
|
Loading…
Reference in New Issue