diff --git a/search_invenio/.gitignore b/search_invenio/.gitignore new file mode 100644 index 0000000..3b7caa1 --- /dev/null +++ b/search_invenio/.gitignore @@ -0,0 +1,7 @@ +.idea/ +.*.swp +*.pyc +*.egg-info +.webassets-cache/ +*.min.css +*.min.js diff --git a/search_invenio/MANIFEST.in b/search_invenio/MANIFEST.in new file mode 100644 index 0000000..764608e --- /dev/null +++ b/search_invenio/MANIFEST.in @@ -0,0 +1,3 @@ +graft indico_search_invenio/static +graft indico_search_invenio/templates +include indico_search_invenio/marc2short.xsl diff --git a/search_invenio/indico_search_invenio/__init__.py b/search_invenio/indico_search_invenio/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/search_invenio/indico_search_invenio/engine.py b/search_invenio/indico_search_invenio/engine.py new file mode 100644 index 0000000..031215f --- /dev/null +++ b/search_invenio/indico_search_invenio/engine.py @@ -0,0 +1,287 @@ +# This file is part of Indico. +# Copyright (C) 2002 - 2014 European Organization for Nuclear Research (CERN). +# +# Indico is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# Indico is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Indico; if not, see . + +from __future__ import unicode_literals + +import os +import re +from datetime import timedelta, datetime + +import requests +from flask import redirect, request, jsonify +from flask_pluginengine import current_plugin +from lxml import etree +from werkzeug.urls import url_encode +from xml.dom import minidom + +from indico.core.plugins import get_plugin_template_module +from indico.util.string import to_unicode +from indico_search import SearchEngine +from MaKaC.conference import Category, Conference + +from indico_search_invenio.entries import Author, SubContributionEntry, ContributionEntry, EventEntry + + +class InvenioRemoteSearch(object): + # In general it would be nice if some of the functionality here was provided by a base class, but + # since most likely not all search backends distinguish between event/contribution collections, + # the base class would have to be very generic. + # But if someone ever integrates another search engine and uses this as a base, he is very welcome + # to extract parts from here and move them into the indico_search plugins instead of copying it to + # his own plugin! + + def __init__(self, engine): + self.engine = engine + + @property + def results_per_page(self): + return current_plugin.settings.get('results_per_page') + + def process(self): + if request.is_xhr: + # AJAX request loading additional results + return self.process_xhr() + + collections = set() + if self.engine.values['collection'] in {'', 'events'}: + collections.add('events') + if self.engine.values['collection'] in {'', 'contributions'}: + collections.add('contributions') + + result_data = {'query': {k: v for k, v in request.form.iterlists() if k != 'search-collection'}, + 'events': None, + 'contributions': None} + for collection in collections: + result_data[collection] = self._query_results(collection, 1) + + if request.is_xhr: + return jsonify(events=self._make_json_response(result_data['events'], True), + contributions=self._make_json_response(result_data['contributions'])) + + return result_data + + def process_xhr(self): + offset = int(request.form['offset']) + collection = self.engine.values['collection'] + assert collection in {'events', 'contributions'} + return jsonify(self._make_json_response(self._query_results(collection, offset), collection == 'events')) + + def _make_json_response(self, data, is_event): + mod = get_plugin_template_module('_results.html') + return {'html': '\n'.join(mod.render_result(r, is_event) for r in data['results']), + 'has_more': data['has_more'], + 'offset': data['offset']} + + def _query_results(self, collection, offset): + num = self.results_per_page + if self.engine.user: + # Querying private data so we might have to skip some results - fetching more may avoid an extra query + num *= 2 + params = self.engine.make_query(collection) + results = [] + has_more = True + while len(results) < self.results_per_page: + data = self._process_data(self._fetch_data(of='xm', rg=num, jrec=offset, **params)) + if not data: + # No results returned => stop searching, and we know for sure increasing the offset won't return more + has_more = False + break + offset += len(data) + for r in data: + if not r.is_visible(self.engine.user): + continue + results.append(r) + # We might have more data than we want to show - remove those and also make sure they are found again in + # the next batch of items + extra = len(results) - self.results_per_page + if extra > 0: + del results[-extra:] + offset -= extra + break + if len(data) < num: + has_more = False + break + if not results: + return None + return {'has_more': has_more, 'offset': offset, 'results': results} + + def _fetch_data(self, **params): + return requests.get(current_plugin.settings.get('search_url'), params=params).content + + def _process_data(self, data): + xsl = etree.XSLT(etree.parse(os.path.join(current_plugin.root_path, 'marc2short.xsl'))) + xml = unicode(xsl(etree.fromstring(data))) + doc = minidom.parseString(xml) + return [self._process_record(e) for e in doc.getElementsByTagName('record') + if e.getElementsByTagName('identifier')] + + def _process_record(self, elem): + authors = [] + materials = [] + element_id = elem.getElementsByTagName('identifier')[0].firstChild.data + + try: + title = elem.getElementsByTagName('title')[0].firstChild.data + except (IndexError, AttributeError): + title = None + + try: + location = elem.getElementsByTagName('location')[0].firstChild.data + except (IndexError, AttributeError): + location = None + + try: + start_date = elem.getElementsByTagName('start_date')[0].firstChild.data + start_date = datetime.strptime(start_date, '%Y-%m-%dT%H:%M') + except (IndexError, AttributeError): + start_date = None + + try: + description = elem.getElementsByTagName('description')[0].firstChild.data + except (IndexError, AttributeError): + description = None + + for author in elem.getElementsByTagName('author'): + name = author.getElementsByTagName('name')[0].firstChild.data + try: + role = author.getElementsByTagName('role')[0].firstChild.data + except (IndexError, AttributeError): + role = None + + try: + affiliation = author.getElementsByTagName('affiliation')[0].firstChild.data + except (IndexError, AttributeError): + affiliation = None + authors.append(Author(name, role, affiliation)) + + for material in elem.getElementsByTagName('material'): + try: + material_url = material.getElementsByTagName('url')[0].firstChild.data + except (IndexError, AttributeError): + material_url = None + + try: + material_description = material.getElementsByTagName('description')[0].firstChild.data + except (IndexError, AttributeError): + material_description = None + materials.append((material_url, material_description)) + + return self._make_result(element_id, title, location, start_date, materials, authors, description) + + def _make_result(self, entry_id, title, location, start_date, materials, authors, description): + match = re.match(r'^INDICO\.(\w+)(\.(\w+)(\.(\w)+)?)?$', entry_id) + if not match: + raise Exception('unrecognized entry id: {}'.format(entry_id)) + + if match.group(4): + return SubContributionEntry(match.group(5), title, location, start_date, materials, authors, description, + match.group(3), match.group(1)) + elif match.group(2): + return ContributionEntry(match.group(3), title, location, start_date, materials, authors, description, + match.group(1)) + else: + return EventEntry(match.group(1), title, location, start_date, materials, authors, description) + + +class InvenioSearchEngine(SearchEngine): + @property + def use_redirect(self): + return current_plugin.settings.get('display_mode') == 'redirect' + + @property + def only_public(self): + return current_plugin.settings.get('display_mode') != 'api_private' + + def process(self): + query = self.make_query() + if not any(query.viewvalues()): + return None + elif self.use_redirect: + return redirect(self._build_url(**query)) + else: + rs = InvenioRemoteSearch(self) + return rs.process() + + def _build_url(self, **query_params): + return '{}?{}'.format(current_plugin.settings.get('search_url'), url_encode(query_params)) + + def make_query(self, collection=None): + query = {} + search_items = [] + # Main search term + field = self.values['field'] + phrase = self.values['phrase'] + if phrase: + if field == 'author': + query['f'] = '' + search_items += self._make_field_query(phrase, field) + else: + query['f'] = field + search_items.append(phrase) + # Collection + if self.only_public: + search_items.append(self._make_public_collection_query(collection)) + else: + query['c'] = self._make_private_collection_query(collection) + # Category/Event + search_items.append(self._make_obj_query()) + # Date + search_items.append(self._make_date_query()) + # Build query + query['p'] = ' '.join(filter(None, search_items)) + # Sorting + query['sf'] = 'date' + query['so'] = self.values['sort_order'] + return query + + def _make_field_query(self, phrase, field): + if phrase[0] == '"' and phrase[-1] == '"': + return ['{}:"{}"'.format(field, phrase.replace('"', ''))] + return ['{}:{}'.format(field, word) for word in phrase.split()] + + def _make_date_query(self): + start_date = self.values['start_date'] + end_date = self.values['end_date'] + if not start_date and not end_date: + return None + start_date = start_date.strftime('%Y-%m-%d') if start_date else '1950' + end_date = (end_date + timedelta(days=1)).strftime('%Y-%m-%d') if end_date else '2100' + return '518__d:"{}"->"{}"'.format(start_date, end_date) + + def _make_public_collection_query(self, collection=None): + if collection is None: + collection = self.values['collection'] + if collection == 'events': + return """970__a:"INDICO.*" -970__a:'.*.'""" + elif collection == 'contributions': + return """970__a:"INDICO.*" 970__a:'.*.'""" + else: + # XXX: This was missing in the old plugin, but I think it's necessary to get only Indico results + return '970__a:"INDICO.*"' + + def _make_private_collection_query(self, collection=None): + if collection is None: + collection = self.values['collection'] + suffix = 'CONTRIBS' if collection == 'contributions' else 'EVENTS' + prefix = 'INDICOSEARCH' if self.user else 'INDICOSEARCH.PUBLIC' + return '{}.{}'.format(prefix, suffix) + + def _make_obj_query(self): + if isinstance(self.obj, Category) and not self.obj.isRoot(): + return '650_7:"{}*"'.format(':'.join(map(to_unicode, self.obj.getCategoryPath()))) + elif isinstance(self.obj, Conference): + # XXX: The old plugin prefixed this with 'AND ' but according to the invenio docs, AND is implied + return '970__a:"INDICO.{}.*"'.format(self.obj.id) diff --git a/search_invenio/indico_search_invenio/entries.py b/search_invenio/indico_search_invenio/entries.py new file mode 100644 index 0000000..f10cfad --- /dev/null +++ b/search_invenio/indico_search_invenio/entries.py @@ -0,0 +1,151 @@ +# This file is part of Indico. +# Copyright (C) 2002 - 2014 European Organization for Nuclear Research (CERN). +# +# Indico is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# Indico is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Indico; if not, see . + +from __future__ import unicode_literals + +from flask_pluginengine import current_plugin +from pytz import timezone + +from indico.util.date_time import as_utc +from indico.web.flask.util import url_for +from MaKaC.accessControl import AccessWrapper +from MaKaC.common.timezoneUtils import DisplayTZ +from MaKaC.conference import ConferenceHolder + + +class Author(object): + def __init__(self, name, role, affiliation): + self.name = name + self.role = role + self.affiliation = affiliation + + +class SearchResult(object): + def __init__(self, result_id, title, location, start_date, materials, authors, description): + self.id = result_id + self.title = title + self.location = location + self._start_date = as_utc(start_date) if start_date else None + self.materials = materials + self.authors = authors + self.description = description + + @property + def event(self): + return ConferenceHolder().getById(self.event_id, quiet=True) + + @property + def start_date(self): + if not self._start_date: + return None + tz = DisplayTZ(conf=self.event).getDisplayTZ() + return self._start_date.astimezone(timezone(tz)) + + @property + def object(self): + raise NotImplementedError + + @property + def event_id(self): + raise NotImplementedError + + @property + def compound_id(self): + raise NotImplementedError + + def is_visible(self, user): + obj = self.object + if not obj: + current_plugin.logger.warning('referenced element {} does not exist'.format(self.compound_id)) + return False + return obj.canView(AccessWrapper(user)) + + def __repr__(self): + return '<{}({})>'.format(type(self).__name__, self.compound_id) + + +class ContributionEntry(SearchResult): + def __init__(self, entry_id, title, location, start_date, materials, authors, description, parent): + super(ContributionEntry, self).__init__(entry_id, title, location, start_date, materials, authors, description) + self.parent = parent + + @property + def event_id(self): + return self.parent + + @property + def url(self): + return url_for('event.contributionDisplay', confId=self.event_id, contribId=self.id) + + @property + def compound_id(self): + return '{}:{}'.format(self.event_id, self.id) + + @property + def object(self): + event = self.event + if not event: + return None + return event.getContributionById(self.id) + + +class SubContributionEntry(SearchResult): + def __init__(self, entry_id, title, location, start_date, materials, authors, description, parent, event): + super(SubContributionEntry, self).__init__(entry_id, title, location, start_date, materials, authors, + description) + self.parent = parent + self._event_id = event + materials.append((url_for('event.conferenceDisplay', confId=event), 'Event details')) + + @property + def event_id(self): + return self._event_id + + @property + def url(self): + return url_for('event.subContributionDisplay', confId=self.event_id, contribId=self.parent, subContId=self.id) + + @property + def compound_id(self): + return '{}:{}:{}'.format(self.event_id, self.parent, self.id) + + @property + def object(self): + event = self.event + if not event: + return None + contribution = event.getContributionById(self.parent) + if not contribution: + return None + return contribution.getSubContributionById(self.id) + + +class EventEntry(SearchResult): + @property + def url(self): + return url_for('event.conferenceDisplay', confId=self.id) + + @property + def compound_id(self): + return self.id + + @property + def event_id(self): + return self.id + + @property + def object(self): + return self.event diff --git a/search_invenio/indico_search_invenio/forms.py b/search_invenio/indico_search_invenio/forms.py new file mode 100644 index 0000000..244717c --- /dev/null +++ b/search_invenio/indico_search_invenio/forms.py @@ -0,0 +1,34 @@ +# This file is part of Indico. +# Copyright (C) 2002 - 2014 European Organization for Nuclear Research (CERN). +# +# Indico is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# Indico is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Indico; if not, see . + +from __future__ import unicode_literals + +from wtforms.fields.core import SelectField + +from indico.util.i18n import _ +from indico_search import SearchForm + + +COLLECTION_CHOICES = [('', _('Both (Events + Contributions)')), + ('events', _('Events')), + ('contributions', _('Contributions'))] +SORT_ORDER_CHOICES = [('a', _('Oldest first')), + ('d', _('Newest first'))] + + +class InvenioSearchForm(SearchForm): + collection = SelectField(_('Search for'), choices=COLLECTION_CHOICES, default='') + sort_order = SelectField(_('Sort order'), choices=SORT_ORDER_CHOICES, default='d') diff --git a/search_invenio/indico_search_invenio/marc2short.xsl b/search_invenio/indico_search_invenio/marc2short.xsl new file mode 100644 index 0000000..a819b32 --- /dev/null +++ b/search_invenio/indico_search_invenio/marc2short.xsl @@ -0,0 +1,145 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <xsl:value-of select="text()"/> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <xsl:value-of select="text()"/> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/search_invenio/indico_search_invenio/plugin.py b/search_invenio/indico_search_invenio/plugin.py new file mode 100644 index 0000000..d691fec --- /dev/null +++ b/search_invenio/indico_search_invenio/plugin.py @@ -0,0 +1,68 @@ +# This file is part of Indico. +# Copyright (C) 2002 - 2014 European Organization for Nuclear Research (CERN). +# +# Indico is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# Indico is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Indico; if not, see . + +from __future__ import unicode_literals + +from wtforms.fields.core import SelectField +from wtforms.fields.html5 import URLField, IntegerField +from wtforms.validators import URL, NumberRange + +from indico.core.plugins import IndicoPluginBlueprint +from indico.util.i18n import _ +from indico.web.forms.base import IndicoForm +from indico_search import SearchPluginBase +from indico_search.views import WPSearchCategory, WPSearchConference + +from indico_search_invenio.engine import InvenioSearchEngine +from indico_search_invenio.forms import InvenioSearchForm + + +class SettingsForm(IndicoForm): + search_url = URLField(_('Invenio URL'), [URL()]) + display_mode = SelectField(_('Display mode'), choices=[('api_public', _('Embedded (public data)')), + ('api_private', _('Embedded (private data)')), + ('redirect', _('External (Redirect)'))]) + results_per_page = IntegerField(_('Results per page'), [NumberRange(min=5)], + description=_("Number of results to show per page (only in embedded mode)")) + + +class InvenioSearchPlugin(SearchPluginBase): + """Invenio Search + + Uses Invenio as Indico's search engine + """ + + settings_form = SettingsForm + default_settings = { + 'search_url': None, + 'display_mode': 'redirect', + 'results_per_page': 20 + } + engine_class = InvenioSearchEngine + search_form = InvenioSearchForm + + def init(self): + super(InvenioSearchPlugin, self).init() + for wp in (WPSearchCategory, WPSearchConference): + self.inject_css('search_invenio_css', wp) + self.inject_js('search_invenio_js', wp) + + def register_assets(self): + self.register_css_bundle('search_invenio_css', 'css/search_invenio.scss') + self.register_js_bundle('search_invenio_js', 'js/search_invenio.js') + + def get_blueprints(self): + return IndicoPluginBlueprint('search_invenio', 'indico_search_invenio') diff --git a/search_invenio/indico_search_invenio/static/css/search_invenio.scss b/search_invenio/indico_search_invenio/static/css/search_invenio.scss new file mode 100644 index 0000000..022a45b --- /dev/null +++ b/search_invenio/indico_search_invenio/static/css/search_invenio.scss @@ -0,0 +1,67 @@ +#result-tabs { + clear: both; + + .no-results { + margin-top: 20px; + color: #f00; + } +} + +.results-container { + .load-more-container { + overflow: auto; + text-align: center; + + a { + font-weight: bold; + } + } + + ul.result-list { + padding: 0; + list-style-type: none; + + & > li { + border-bottom: 1px solid #ddd; + padding: 5px; + + .title { + font-size: 14px; + } + + .date { + display: block; + } + + .parent-event { + display: block; + font-style: italic; + } + + .link { + color: #76A2C4; + } + + ul.authors { + margin-bottom: 5px; + margin-top: 5px; + } + + ul.material { + margin-top: 5px; + margin-left: 20px; + } + + .description { + margin: 5px 5px 5px 10px; + padding: 5px; + font-size: 13px; + color: #666; + + a.show-full-desc { + margin-bottom: 10px; + } + } + } + } +} diff --git a/search_invenio/indico_search_invenio/static/images/logo.png b/search_invenio/indico_search_invenio/static/images/logo.png new file mode 100644 index 0000000..683871f Binary files /dev/null and b/search_invenio/indico_search_invenio/static/images/logo.png differ diff --git a/search_invenio/indico_search_invenio/static/js/search_invenio.js b/search_invenio/indico_search_invenio/static/js/search_invenio.js new file mode 100644 index 0000000..4f37922 --- /dev/null +++ b/search_invenio/indico_search_invenio/static/js/search_invenio.js @@ -0,0 +1,75 @@ +/* + * This file is part of Indico. + * Copyright (C) 2002 - 2014 European Organization for Nuclear Research (CERN). + * + * Indico is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Indico is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Indico; if not, see . + */ + +(function(global) { + 'use strict'; + + global.invenioSearchResults = function invenioSearchResults(options) { + // expected options: queryData, url, hasEvents, hasContribs + + var tabList = []; + if (options.hasEvents) { + tabList.push([$T('Events'), $E('results-events')]); + } + if (options.hasContribs) { + tabList.push([$T('Contributions'), $E('results-contribs')]); + } + var tabCtrl = new JTabWidget(tabList); + $E('result-tabs').set(tabCtrl.draw()); + $('#result-tabs > div').css({display: 'table', width: '100%'}); + + $('#result-tabs').on('click', '.js-show-full-desc', function(e) { + e.preventDefault(); + var desc = $(this).next().remove().text(); + $(this).parent().html(desc); + }); + + $('.js-load-more').on('click', function(e) { + e.preventDefault(); + var $this = $(this); + var collection = $this.data('collection'); + $.ajax({ + url: options.url, + type: 'POST', + traditional: true, // we don't want foo[] for list data! + data: $.extend({}, options.queryData, { + 'offset': $this.data('offset'), + 'search-collection': collection + }), + dataType: 'json', + complete: IndicoUI.Dialogs.Util.progress(), + success: function(data) { + if (handleAjaxError(data)) { + return; + } + var resultList = $this.closest('.js-results-container').find('.result-list'); + var lastOld = resultList.children().last(); + resultList.append(data.html); + $('html, body').animate({ + scrollTop: lastOld.next().offset().top + }, 250); + lastOld.nextAll().effect('highlight', 1000); + $this.data('offset', data.offset); + if (!data.has_more) { + $this.parent().hide(); + } + } + }); + }); + }; +})(window); diff --git a/search_invenio/indico_search_invenio/templates/_results.html b/search_invenio/indico_search_invenio/templates/_results.html new file mode 100644 index 0000000..db58c3f --- /dev/null +++ b/search_invenio/indico_search_invenio/templates/_results.html @@ -0,0 +1,39 @@ +{# This macro is used by results.html and also in the InvenioRemoteSearch class! #} +{% macro render_result(result, is_event) %} +
  • + {% if result.title %} + {{ result.title }} + {% endif %} + {% if result.start_date %} + {{ result.start_date.strftime('%Y-%m-%d %H:%M:%S (%Z)') }} + {% endif %} + {% if not is_event %} + {{ result.event.title }} + {% endif %} + {% if result.authors %} +
      + {% for author in result.authors %} +
    • + {{ author.name }} + ({{ author.role }}{% if author.affiliation %}, {{ author.affiliation }}{% endif %}) +
    • + {% endfor %} +
    + {% endif %} + {% if result.description %} + {% set truncated = result.description | truncate(100) %} +
    + {{ truncated }} + {% if truncated != result.description %} + more +
    {{ result.description }}
    + {% endif %} +
    + {% endif %} +
      + {% for link, title in result.materials %} +
    • {{ title }}
    • + {% endfor %} +
    +
  • +{% endmacro %} diff --git a/search_invenio/indico_search_invenio/templates/results.html b/search_invenio/indico_search_invenio/templates/results.html new file mode 100644 index 0000000..d68043b --- /dev/null +++ b/search_invenio/indico_search_invenio/templates/results.html @@ -0,0 +1,76 @@ +{% extends 'search:results.html' %} +{% from 'search_invenio:_results.html' import render_result %} + +{% block banner %} + Invenio +{% endblock %} + +{% block criteria_fields %} + + {{ form.collection.label() }} + {{ form.collection() }} + +{% endblock %} + +{% block sort_fields %} + + {{ form.sort_order.label() }} + {{ form.sort_order() }} + +{% endblock %} + +{% block results %} +
    + {% if not result.events and not result.contributions %} +
    {% trans %}No results found{% endtrans %}
    + {% else %} + {% if result.events %} +
    +
      + {% for event in result.events.results %} + {{ render_result(event, is_event=true) }} + {% endfor %} +
    + + {% if result.events.has_more %} + + {% endif %} +
    + {% endif %} + + {% if result.contributions %} +
    +
      + {% for contrib in result.contributions.results %} + {{ render_result(contrib, is_event=false) }} + {% endfor %} +
    + + {% if result.contributions.has_more %} + + {% endif %} +
    + {% endif %} + {% endif %} +
    +{% endblock %} + +{% block scripts %} + {% if result.events or result.contributions %} + + {% endif %} +{% endblock %} diff --git a/search_invenio/indico_search_invenio/templates/searchbox_category.html b/search_invenio/indico_search_invenio/templates/searchbox_category.html new file mode 100644 index 0000000..97c5d1a --- /dev/null +++ b/search_invenio/indico_search_invenio/templates/searchbox_category.html @@ -0,0 +1,8 @@ +{% extends 'search:searchbox_category.html' %} + +{% block extra_fields %} + + {{ form.collection.label() }} + {{ form.collection() }} + +{% endblock %} diff --git a/search_invenio/indico_search_invenio/zodbimport.py b/search_invenio/indico_search_invenio/zodbimport.py new file mode 100644 index 0000000..96b99e7 --- /dev/null +++ b/search_invenio/indico_search_invenio/zodbimport.py @@ -0,0 +1,42 @@ +# This file is part of Indico. +# Copyright (C) 2002 - 2014 European Organization for Nuclear Research (CERN). +# +# Indico is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# Indico is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Indico; if not, see . + +from __future__ import unicode_literals + +from indico.core.db import db +from indico.util.console import cformat +from indico_zodbimport import Importer, convert_to_unicode + +from indico_search_invenio.plugin import InvenioSearchPlugin + + +class InvenioSearchImporter(Importer): + plugins = {'search', 'search_invenio'} + + def migrate(self): + self.migrate_settings() + + def migrate_settings(self): + print cformat('%{white!}migrating settings') + InvenioSearchPlugin.settings.delete_all() + opts = self.zodb_root['plugins']['search']._PluginType__plugins['invenio']._PluginBase__options + InvenioSearchPlugin.settings.set('search_url', convert_to_unicode(opts['serverUrl'].getValue()).strip()) + type_map = {'public': 'api_public', + 'private': 'api_private', + 'redirect': 'redirect'} + display_mode = type_map[opts['type'].getValue()] + InvenioSearchPlugin.settings.set('display_mode', display_mode) + db.session.commit() diff --git a/search_invenio/setup.py b/search_invenio/setup.py new file mode 100644 index 0000000..613d8bb --- /dev/null +++ b/search_invenio/setup.py @@ -0,0 +1,45 @@ +# This file is part of Indico. +# Copyright (C) 2002 - 2014 European Organization for Nuclear Research (CERN). +# +# Indico is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# Indico is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Indico; if not, see . + +from __future__ import unicode_literals + +from setuptools import setup, find_packages + + +setup( + name='indico_search_invenio', + version='0.1', + url='https://github.com/indico/indico-plugin-search-invenio', + license='https://www.gnu.org/licenses/gpl-3.0.txt', + author='Indico Team', + author_email='indico-team@cern.ch', + packages=find_packages(), + zip_safe=False, + include_package_data=True, + platforms='any', + install_requires=[ + 'indico>=1.9.1', + 'indico_search' + ], + classifiers=[ + 'Environment :: Plugins', + 'Environment :: Web Environment', + 'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)', + 'Programming Language :: Python :: 2.7' + ], + entry_points={'indico.plugins': {'search_invenio = indico_search_invenio.plugin:InvenioSearchPlugin'}, + 'indico.zodb_importers': {'search_invenio = indico_search_invenio.zodbimport:InvenioSearchImporter'}} +)