Add plugin: search_invenio

This commit is contained in:
Adrian Moennich 2014-11-19 16:03:21 +01:00
commit b343301552
16 changed files with 1047 additions and 0 deletions

7
search_invenio/.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
.idea/
.*.swp
*.pyc
*.egg-info
.webassets-cache/
*.min.css
*.min.js

View File

@ -0,0 +1,3 @@
graft indico_search_invenio/static
graft indico_search_invenio/templates
include indico_search_invenio/marc2short.xsl

View File

@ -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 <http://www.gnu.org/licenses/>.
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)

View File

@ -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 <http://www.gnu.org/licenses/>.
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

View File

@ -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 <http://www.gnu.org/licenses/>.
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')

View File

@ -0,0 +1,145 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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 <http://www.gnu.org/licenses/>.
-->
<xsl:stylesheet version="1.0" xmlns:marc="http://www.loc.gov/MARC21/slim" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<xsl:apply-templates select="marc:collection"/>
</xsl:template>
<xsl:template match="marc:collection">
<records>
<xsl:apply-templates select="marc:record"/>
</records>
</xsl:template>
<xsl:template match="marc:record">
<record>
<xsl:if test="count(marc:datafield[@tag=611]) > 0">
<type category="contribution" />
</xsl:if>
<xsl:if test="count(marc:datafield[@tag=611]) = 0">
<type category="event" />
</xsl:if>
<xsl:apply-templates select="marc:datafield[@tag!=611 and @tag!=962]" />
<parent>
<xsl:for-each select="marc:datafield[@tag=611]">
<xsl:for-each select="marc:subfield[@code='a']">
<title>
<xsl:value-of select="text()"/>
</title>
</xsl:for-each>
</xsl:for-each>
<xsl:for-each select="marc:datafield[@tag=962]">
<xsl:for-each select="marc:subfield[@code='b']">
<code>
<xsl:value-of select="text()"/>
</code>
</xsl:for-each>
</xsl:for-each>
</parent>
</record>
</xsl:template>
<xsl:template match="marc:datafield[@tag!=611]">
<xsl:if test="@tag=035">
<xsl:for-each select="marc:subfield[@code='a']">
<identifier>
<xsl:value-of select="text()"/>
</identifier>
</xsl:for-each>
</xsl:if>
<xsl:if test="@tag=970">
<xsl:for-each select="marc:subfield[@code='a']">
<identifier>
<xsl:value-of select="text()"/>
</identifier>
</xsl:for-each>
</xsl:if>
<xsl:if test="@tag=245">
<xsl:for-each select="marc:subfield[@code='a']">
<title>
<xsl:value-of select="text()"/>
</title>
</xsl:for-each>
</xsl:if>
<xsl:if test="@tag=518">
<xsl:for-each select="marc:subfield[@code='r']">
<location>
<xsl:value-of select="text()"/>
</location>
</xsl:for-each>
<xsl:for-each select="marc:subfield[@code='d']">
<startDate>
<xsl:value-of select="text()"/>
</startDate>
</xsl:for-each>
</xsl:if>
<xsl:if test="@tag=856">
<material>
<xsl:for-each select="marc:subfield[@code='u']">
<url>
<xsl:value-of select="text()"/>
</url>
</xsl:for-each>
<xsl:for-each select="marc:subfield[@code='y']">
<description>
<xsl:value-of select="text()"/>
</description>
</xsl:for-each>
</material>
</xsl:if>
<xsl:if test="@tag=700">
<author>
<xsl:for-each select="marc:subfield[@code='a']">
<name>
<xsl:value-of select="text()"/>
</name>
</xsl:for-each>
<xsl:for-each select="marc:subfield[@code='e']">
<role>
<xsl:value-of select="text()"/>
</role>
</xsl:for-each>
<xsl:for-each select="marc:subfield[@code='u']">
<affiliation>
<xsl:value-of select="text()"/>
</affiliation>
</xsl:for-each>
</author>
</xsl:if>
<xsl:if test="@tag=520">
<xsl:for-each select="marc:subfield[@code='a']">
<description>
<xsl:value-of select="text()"/>
</description>
</xsl:for-each>
</xsl:if>
</xsl:template>
</xsl:stylesheet>

View File

@ -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 <http://www.gnu.org/licenses/>.
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')

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
(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);

View File

@ -0,0 +1,39 @@
{# This macro is used by results.html and also in the InvenioRemoteSearch class! #}
{% macro render_result(result, is_event) %}
<li>
{% if result.title %}
<a class="title" href="{{ result.url }}">{{ result.title }}</a>
{% endif %}
{% if result.start_date %}
<small class="date">{{ result.start_date.strftime('%Y-%m-%d %H:%M:%S (%Z)') }}</small>
{% endif %}
{% if not is_event %}
<small class="parent-event">{{ result.event.title }}</small>
{% endif %}
{% if result.authors %}
<ul class="authors">
{% for author in result.authors %}
<li>
{{ author.name }}
<small>({{ author.role }}{% if author.affiliation %}, {{ author.affiliation }}{% endif %})</small>
</li>
{% endfor %}
</ul>
{% endif %}
{% if result.description %}
{% set truncated = result.description | truncate(100) %}
<div class="description">
{{ truncated }}
{% if truncated != result.description %}
<a class="link show-full-desc js-show-full-desc" href="#">more</a>
<div style="display: none;">{{ result.description }}</div>
{% endif %}
</div>
{% endif %}
<ul class="nobulletsListInline material">
{% for link, title in result.materials %}
<li><a class="link" href="{{ link }}">{{ title }}</a></li>
{% endfor %}
</ul>
</li>
{% endmacro %}

View File

@ -0,0 +1,76 @@
{% extends 'search:results.html' %}
{% from 'search_invenio:_results.html' import render_result %}
{% block banner %}
<a href="http://invenio-software.org"><img src="{{ url_for_plugin('search_invenio.static', filename='images/logo.png') }}"
alt="Invenio"></a>
{% endblock %}
{% block criteria_fields %}
<tr>
<td>{{ form.collection.label() }}</td>
<td>{{ form.collection() }}</td>
</tr>
{% endblock %}
{% block sort_fields %}
<tr>
<td>{{ form.sort_order.label() }}</td>
<td>{{ form.sort_order() }}</td>
</tr>
{% endblock %}
{% block results %}
<div id="result-tabs">
{% if not result.events and not result.contributions %}
<div class="no-results">{% trans %}No results found{% endtrans %}</div>
{% else %}
{% if result.events %}
<div id="results-events" class="results-container js-results-container">
<ul class="result-list">
{% for event in result.events.results %}
{{ render_result(event, is_event=true) }}
{% endfor %}
</ul>
{% if result.events.has_more %}
<div class="load-more-container">
<a href="#" class="js-load-more" data-offset="{{ result.events.offset }}"
data-collection="events">{% trans %}Load more results{% endtrans %}</a>
</div>
{% endif %}
</div>
{% endif %}
{% if result.contributions %}
<div id="results-contribs" class="results-container js-results-container">
<ul class="result-list">
{% for contrib in result.contributions.results %}
{{ render_result(contrib, is_event=false) }}
{% endfor %}
</ul>
{% if result.contributions.has_more %}
<div class="load-more-container">
<a href="#" class="js-load-more" data-offset="{{ result.contributions.offset }}"
data-collection="contributions">{% trans %}Load more results{% endtrans %}</a>
</div>
{% endif %}
</div>
{% endif %}
{% endif %}
</div>
{% endblock %}
{% block scripts %}
{% if result.events or result.contributions %}
<script>
invenioSearchResults({
queryData: {{ result.query | tojson }},
url: {{ request.url | tojson }},
hasEvents: {{ (result.events | count > 0) | tojson }},
hasContribs: {{ (result.contributions | count > 0) | tojson }}
});
</script>
{% endif %}
{% endblock %}

View File

@ -0,0 +1,8 @@
{% extends 'search:searchbox_category.html' %}
{% block extra_fields %}
<tr>
<td style="text-align: right; white-space: nowrap;">{{ form.collection.label() }}</td>
<td style="white-space: nowrap;">{{ form.collection() }}</td>
</tr>
{% endblock %}

View File

@ -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 <http://www.gnu.org/licenses/>.
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()

45
search_invenio/setup.py Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
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'}}
)