Skip to content

Commit

Permalink
[#1547][plugins,search] Add dataset search plugin interface
Browse files Browse the repository at this point in the history
The IPackageController now also offers the `before_search` and
`after_search` methods so extensions can modify the search parameters
and the results coming from the standard Solr search.

Parameters starting with `ext_` will be added to an `extras` property
which will have no effect in the default search but will be forwarded
to the extensions as part of the search parameters.

Extensions willing to modify the standard search form can do so
implementing IGenshiStreamFilter and injecting their extra fields
in the `dataset-search-ext` div.
  • Loading branch information
amercader committed Jan 16, 2012
1 parent 97f4ce8 commit 0479996
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 12 deletions.
9 changes: 7 additions & 2 deletions ckan/controllers/package.py
Expand Up @@ -141,11 +141,15 @@ def pager_url(q=None, page=None):

try:
c.fields = []
search_extras = {}
for (param, value) in request.params.items():
if not param in ['q', 'page'] \
and len(value) and not param.startswith('_'):
c.fields.append((param, value))
q += ' %s: "%s"' % (param, value)
if not param.startswith('ext_'):
c.fields.append((param, value))
q += ' %s: "%s"' % (param, value)
else:
search_extras[param] = value

context = {'model': model, 'session': model.Session,
'user': c.user or c.author}
Expand All @@ -155,6 +159,7 @@ def pager_url(q=None, page=None):
'facet.field':g.facets,
'rows':limit,
'start':(page-1)*limit,
'extras':search_extras
}

query = get_action('package_search')(context,data_dict)
Expand Down
3 changes: 2 additions & 1 deletion ckan/lib/search/query.py
Expand Up @@ -12,7 +12,8 @@

VALID_SOLR_PARAMETERS = set([
'q', 'fl', 'fq', 'rows', 'sort', 'start', 'wt', 'qf',
'facet', 'facet.mincount', 'facet.limit', 'facet.field'
'facet', 'facet.mincount', 'facet.limit', 'facet.field',
'extras' # Not used by Solr, but useful for extensions
])

# for (solr) package searches, this specifies the fields that are searched
Expand Down
29 changes: 20 additions & 9 deletions ckan/logic/action/get.py
Expand Up @@ -52,7 +52,7 @@ def package_list(context, data_dict):
user = context["user"]
api = context.get("api_version", '1')
ref_package_by = 'id' if api == '2' else 'name'

check_access('package_list', context, data_dict)

query = model.Session.query(model.PackageRevision)
Expand Down Expand Up @@ -115,7 +115,7 @@ def group_list(context, data_dict):
if order_by not in set(('name', 'packages')):
raise ValidationError('"order_by" value %r not implemented.' % order_by)
all_fields = data_dict.get('all_fields',None)

check_access('group_list',context, data_dict)

query = model.Session.query(model.Group).join(model.GroupRevision)
Expand Down Expand Up @@ -153,7 +153,7 @@ def group_list_authz(context, data_dict):

query = Authorizer().authorized_query(user, model.Group, model.Action.EDIT)
groups = set(query.all())

if available_only:
package = context.get('package')
if package:
Expand Down Expand Up @@ -307,7 +307,7 @@ def package_relationships_list(context, data_dict):
rel = None

check_access('package_relationships_list',context, data_dict)

# TODO: How to handle this object level authz?
relationships = Authorizer().\
authorized_package_relationships(\
Expand Down Expand Up @@ -660,6 +660,10 @@ def package_search(context, data_dict):

check_access('package_search', context, data_dict)

# check if some extension needs to modify the search params
for item in PluginImplementations(IPackageController):
data_dict = item.before_search(data_dict)

# return a list of package ids
data_dict['fl'] = 'id'

Expand All @@ -672,7 +676,7 @@ def package_search(context, data_dict):
pkg_query = session.query(model.PackageRevision)\
.filter(model.PackageRevision.id == package)\
.filter(and_(
model.PackageRevision.state == u'active',
model.PackageRevision.state == u'active',
model.PackageRevision.current == True
))
pkg = pkg_query.first()
Expand All @@ -686,12 +690,19 @@ def package_search(context, data_dict):
result_dict = package_dictize(pkg,context)
results.append(result_dict)

return {

search_results = {
'count': query.count,
'facets': query.facets,
'results': results
}

# check if some extension needs to modify the search results
for item in PluginImplementations(IPackageController):
search_results = item.after_search(search_results,data_dict)

return search_results

def _extend_package_dict(package_dict,context):
model = context['model']

Expand Down Expand Up @@ -735,7 +746,7 @@ def resource_search(context, data_dict):
raise SearchError('Field "%s" not recognised in Resource search.' % field)
for term in terms:
model_attr = getattr(model.Resource, field)
if field == 'hash':
if field == 'hash':
q = q.filter(model_attr.ilike(unicode(term) + '%'))
elif field in model.Resource.get_extra_columns():
model_attr = getattr(model.Resource, 'extras')
Expand All @@ -747,15 +758,15 @@ def resource_search(context, data_dict):
q = q.filter(like)
else:
q = q.filter(model_attr.ilike('%' + unicode(term) + '%'))

if order_by is not None:
if hasattr(model.Resource, order_by):
q = q.order_by(getattr(model.Resource, order_by))

count = q.count()
q = q.offset(offset)
q = q.limit(limit)

results = []
for result in q:
if isinstance(result, tuple) and isinstance(result[0], model.DomainObject):
Expand Down
31 changes: 31 additions & 0 deletions ckan/plugins/interfaces.py
Expand Up @@ -231,6 +231,37 @@ def authz_remove_role(self, object_role):
def delete(self, entity):
pass

def before_search(self, search_params):
'''
Extensions will receive a dictionary with the query parameters,
and should return a modified (or not) version of it.
search_params will include an 'extras' dictionary with all values
from fields starting with 'ext_', so extensions can receive user
input from specific fields.
'''
return search_params

def after_search(self, search_results, search_params):
'''
Extensions will receive the search results, as well as the search
parameters, and should return a modified (or not) object with the
same structure:
{'count': '', 'results': '', 'facets': ''}
Note that count and facets may need to be adjusted if the extension
changed the results for some reason.
search_params will include an 'extras' dictionary with all values
from fields starting with 'ext_', so extensions can receive user
input from specific fields.
'''

return search_results

class IPluginObserver(Interface):
"""
Plugin to the plugin loading mechanism
Expand Down
1 change: 1 addition & 0 deletions ckan/templates/package/search_form.html
Expand Up @@ -12,6 +12,7 @@
<input type="hidden" name="${k}" value="${v}" />
</py:for>
</span>
<div id="dataset-search-ext"></div>
<input type="submit" value="${_('Search')}" class="pretty-button primary button" />
</form>

Expand Down

0 comments on commit 0479996

Please sign in to comment.