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:
Mikael Karlsson 2013-10-18 14:34:04 +00:00
commit c2fe606dfa
10 changed files with 2415 additions and 0 deletions

301
README.md Normal file
View File

@ -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.

7
ckanext/__init__.py Normal file
View File

@ -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__)

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -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 %}

32
setup.py Normal file
View File

@ -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
""",
)