mirror of
https://github.com/lucaspalomodevelop/indico-plugins.git
synced 2026-03-16 16:54:39 +00:00
Add plugin: search_invenio
This commit is contained in:
commit
b343301552
7
search_invenio/.gitignore
vendored
Normal file
7
search_invenio/.gitignore
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
.idea/
|
||||
.*.swp
|
||||
*.pyc
|
||||
*.egg-info
|
||||
.webassets-cache/
|
||||
*.min.css
|
||||
*.min.js
|
||||
3
search_invenio/MANIFEST.in
Normal file
3
search_invenio/MANIFEST.in
Normal file
@ -0,0 +1,3 @@
|
||||
graft indico_search_invenio/static
|
||||
graft indico_search_invenio/templates
|
||||
include indico_search_invenio/marc2short.xsl
|
||||
0
search_invenio/indico_search_invenio/__init__.py
Normal file
0
search_invenio/indico_search_invenio/__init__.py
Normal file
287
search_invenio/indico_search_invenio/engine.py
Normal file
287
search_invenio/indico_search_invenio/engine.py
Normal 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)
|
||||
151
search_invenio/indico_search_invenio/entries.py
Normal file
151
search_invenio/indico_search_invenio/entries.py
Normal 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
|
||||
34
search_invenio/indico_search_invenio/forms.py
Normal file
34
search_invenio/indico_search_invenio/forms.py
Normal 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')
|
||||
145
search_invenio/indico_search_invenio/marc2short.xsl
Normal file
145
search_invenio/indico_search_invenio/marc2short.xsl
Normal 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>
|
||||
|
||||
68
search_invenio/indico_search_invenio/plugin.py
Normal file
68
search_invenio/indico_search_invenio/plugin.py
Normal 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')
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
search_invenio/indico_search_invenio/static/images/logo.png
Normal file
BIN
search_invenio/indico_search_invenio/static/images/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
@ -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);
|
||||
39
search_invenio/indico_search_invenio/templates/_results.html
Normal file
39
search_invenio/indico_search_invenio/templates/_results.html
Normal 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 %}
|
||||
76
search_invenio/indico_search_invenio/templates/results.html
Normal file
76
search_invenio/indico_search_invenio/templates/results.html
Normal 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 %}
|
||||
@ -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 %}
|
||||
42
search_invenio/indico_search_invenio/zodbimport.py
Normal file
42
search_invenio/indico_search_invenio/zodbimport.py
Normal 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
45
search_invenio/setup.py
Normal 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'}}
|
||||
)
|
||||
Loading…
x
Reference in New Issue
Block a user