mirror of
https://github.com/lucaspalomodevelop/indico-plugins.git
synced 2026-03-12 23:27:22 +00:00
Remove unmaintained plugins
they are now in https://github.com/indico/indico-plugins-attic
This commit is contained in:
parent
435e54e367
commit
4b0a558b01
@ -1,6 +1,4 @@
|
||||
extras:
|
||||
indico-plugin-importer: importer
|
||||
indico-plugin-importer-invenio: importer
|
||||
extras: {}
|
||||
|
||||
skip:
|
||||
- indico-plugin-livesync-debug
|
||||
|
||||
@ -19,14 +19,11 @@ plugins_require = [
|
||||
'indico-plugin-piwik>=2.3,<2.4.dev0',
|
||||
'indico-plugin-previewer-code>=1.0,<2.4.dev0',
|
||||
'indico-plugin-previewer-jupyter>=1.0,<2.4.dev0',
|
||||
'indico-plugin-search>=2.3,<2.4.dev0',
|
||||
'indico-plugin-storage-s3>=2.3,<2.4.dev0',
|
||||
'indico-plugin-ursh>=2.3,<2.4.dev0',
|
||||
'indico-plugin-vc-vidyo>=2.3,<2.4.dev0',
|
||||
]
|
||||
extras_require = {
|
||||
'importer': ['indico-plugin-importer-invenio>=2.2,<2.4.dev0', 'indico-plugin-importer>=2.2,<2.4.dev0'],
|
||||
}
|
||||
extras_require = {}
|
||||
# END GENERATED REQUIREMENTS
|
||||
|
||||
|
||||
|
||||
@ -1,4 +0,0 @@
|
||||
graft indico_importer/static
|
||||
graft indico_importer/translations
|
||||
|
||||
global-exclude *.pyc __pycache__ .keep
|
||||
@ -1,17 +0,0 @@
|
||||
# This file is part of the Indico plugins.
|
||||
# Copyright (C) 2002 - 2020 CERN
|
||||
#
|
||||
# The Indico plugins are free software; you can redistribute
|
||||
# them and/or modify them under the terms of the MIT License;
|
||||
# see the LICENSE file for more details.
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from indico.util.i18n import make_bound_gettext
|
||||
|
||||
|
||||
_ = make_bound_gettext('importer')
|
||||
__all__ = ('ImporterSourcePluginBase', 'ImporterEngineBase')
|
||||
|
||||
|
||||
from .base import ImporterSourcePluginBase, ImporterEngineBase # isort:skip
|
||||
@ -1,67 +0,0 @@
|
||||
# This file is part of the Indico plugins.
|
||||
# Copyright (C) 2002 - 2020 CERN
|
||||
#
|
||||
# The Indico plugins are free software; you can redistribute
|
||||
# them and/or modify them under the terms of the MIT License;
|
||||
# see the LICENSE file for more details.
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from flask_pluginengine import depends
|
||||
|
||||
from indico.core.plugins import IndicoPlugin, PluginCategory
|
||||
|
||||
from indico_importer.plugin import ImporterPlugin
|
||||
|
||||
|
||||
@depends('importer')
|
||||
class ImporterSourcePluginBase(IndicoPlugin):
|
||||
"""Base class for importer engine plugins"""
|
||||
|
||||
importer_engine_classes = None
|
||||
category = PluginCategory.importers
|
||||
|
||||
def init(self):
|
||||
super(ImporterSourcePluginBase, self).init()
|
||||
for engine_class in self.importer_engine_classes:
|
||||
importer_engine = engine_class()
|
||||
ImporterPlugin.instance.register_importer_engine(importer_engine, self)
|
||||
|
||||
|
||||
class ImporterEngineBase(object):
|
||||
"""Base class for data importers"""
|
||||
|
||||
_id = ''
|
||||
name = ''
|
||||
|
||||
def import_data(self, query, size):
|
||||
"""Fetch and converts data from external source.
|
||||
|
||||
:param query: A search phrase send to the importer.
|
||||
:param size: Number of records to fetch from external source.
|
||||
:return: List of dictionaries with the following format (all keys are optional)
|
||||
[{"recordId" : idOfTheRecordInExternalSource,
|
||||
"title": eventTitle,
|
||||
"primaryAuthor": {"firstName": primaryAuthorFirstName,
|
||||
"familyName": primaryAuthorFamilyName,
|
||||
"affiliation": primaryAuthorAffiliation},
|
||||
"speaker": {"firstName": speakerFirstName,
|
||||
"familyName": speakerFamilyName,
|
||||
"affiliation": speakerAffiliation},
|
||||
"secondaryAuthor": {"firstName": secondaryAuthorFirstName,
|
||||
"familyName": secondaryAuthorFamilyName,
|
||||
"affiliation": secondaryAuthorAffiliation},
|
||||
"summary": eventSummary,
|
||||
"meetingName": nameOfTheEventMeeting,
|
||||
"materials": [{"name": nameOfTheLink,
|
||||
"url": linkDestination},
|
||||
...],
|
||||
"reportNumbers": [reportNumber, ...]
|
||||
"startDateTime": {"time" : eventStartTime,
|
||||
"date" : eventStartDate},
|
||||
"endDateTime": {"time" : eventEndTime,
|
||||
"date" : eventEndDate}
|
||||
"place": eventPlace},
|
||||
...]
|
||||
"""
|
||||
raise NotImplementedError
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,158 +0,0 @@
|
||||
/* This file is part of the Indico plugins.
|
||||
* Copyright (C) 2002 - 2020 CERN
|
||||
*
|
||||
* The Indico plugins are free software; you can redistribute
|
||||
* them and/or modify them under the terms of the MIT License;
|
||||
* see the LICENSE file for more details.
|
||||
*/
|
||||
|
||||
ul.treeList {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
div.treeListContainer {
|
||||
background: #f8f8f8;
|
||||
-moz-border-radius: 5%;
|
||||
border-radius: 5%;
|
||||
margin-top: 15px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
div.treeListHeader {
|
||||
color: #4e4c46;
|
||||
font-family: 'Times New Roman', Verdana, Arial, sans-serif;
|
||||
font-size: 20px;
|
||||
height: 20px;
|
||||
letter-spacing: 1px;
|
||||
padding: 15px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
div.treeListDescription {
|
||||
text-align: center;
|
||||
color: #777;
|
||||
font-size: 12px;
|
||||
font-style: italic;
|
||||
font-weight: normal;
|
||||
padding: 7px 0;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
.treeListDayName {
|
||||
-moz-border-radius: 20px;
|
||||
border-radius: 20px;
|
||||
border: 1px solid #ccc;
|
||||
padding: 10px;
|
||||
background-color: #fff;
|
||||
cursor: pointer;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.treeListEntry {
|
||||
-moz-border-radius: 10px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid #ccc;
|
||||
padding: 7px;
|
||||
width: 80%;
|
||||
background-color: #fff;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
div.entryListContainer {
|
||||
background: #f8f8f8;
|
||||
-moz-border-radius: 5%;
|
||||
border-radius: 5%;
|
||||
margin-top: 15px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
div.entryListHeader {
|
||||
color: #4e4c46;
|
||||
font-family: 'Times New Roman', Verdana, Arial, sans-serif;
|
||||
font-size: 20px;
|
||||
height: 20px;
|
||||
letter-spacing: 1px;
|
||||
padding: 15px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
div.entryListDesctiption {
|
||||
text-align: center;
|
||||
color: #777;
|
||||
font-size: 12px;
|
||||
font-style: italic;
|
||||
font-weight: normal;
|
||||
padding: 7px 0;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
ul.entryList li {
|
||||
-moz-border-radius: 20px;
|
||||
border-radius: 20px;
|
||||
border: 1px solid #ccc;
|
||||
padding: 7px;
|
||||
width: 90%;
|
||||
margin-bottom: 10px;
|
||||
list-style: none;
|
||||
background-color: #fff;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
ul.entryList li div {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
ul.entryList em {
|
||||
font-weight: bold;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.entryListSelected {
|
||||
background-color: #cdeb8b !important;
|
||||
box-shadow: 3px 3px 5px #000;
|
||||
-moz-box-shadow: 3px 3px 5px #000;
|
||||
-webkit-box-shadow: 3px 3px 5px #000;
|
||||
}
|
||||
|
||||
div.importDialogHeader {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
padding: 5px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
div.importDialogHeader input[type='text'] {
|
||||
width: 35%;
|
||||
min-width: 250px;
|
||||
height: 20px;
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
.entryListIndex {
|
||||
-moz-border-radius: 100% 100% 100% 100%;
|
||||
border-radius: 100% 100% 100% 100%;
|
||||
border: 1px solid #ccc;
|
||||
left: -51px;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
width: 23px;
|
||||
}
|
||||
|
||||
div.expandButtonsDiv {
|
||||
float: left;
|
||||
left: -30px;
|
||||
position: relative;
|
||||
top: 2px;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
div.presearchContainer {
|
||||
-moz-border-radius: 20px 20px 20px 20px;
|
||||
border-radius: 20px 20px 20px 20px;
|
||||
border: 1px solid #ccc;
|
||||
color: #777;
|
||||
font-size: 14px;
|
||||
margin-top: 25px;
|
||||
padding: 15px;
|
||||
text-align: center;
|
||||
}
|
||||
@ -1,103 +0,0 @@
|
||||
# This file is part of the Indico plugins.
|
||||
# Copyright (C) 2002 - 2020 CERN
|
||||
#
|
||||
# The Indico plugins are free software; you can redistribute
|
||||
# them and/or modify them under the terms of the MIT License;
|
||||
# see the LICENSE file for more details.
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from flask import jsonify, request
|
||||
from flask_pluginengine import current_plugin
|
||||
from pytz import timezone, utc
|
||||
|
||||
from indico.core.db import db
|
||||
from indico.modules.events.timetable.controllers import RHManageTimetableBase
|
||||
from indico.modules.events.timetable.models.entries import TimetableEntry, TimetableEntryType
|
||||
from indico.web.rh import RHProtected
|
||||
|
||||
|
||||
class RHGetImporters(RHProtected):
|
||||
def _process(self):
|
||||
importers = {k: importer.name for k, (importer, _) in current_plugin.importer_engines.iteritems()}
|
||||
return jsonify(importers)
|
||||
|
||||
|
||||
class RHImportData(RHProtected):
|
||||
def _process(self):
|
||||
size = request.args.get('size', 10)
|
||||
query = request.args.get('query')
|
||||
importer, plugin = current_plugin.importer_engines.get(request.view_args['importer_name'])
|
||||
with plugin.plugin_context():
|
||||
data = {'records': importer.import_data(query, size)}
|
||||
return jsonify(data)
|
||||
|
||||
|
||||
class RHEndTimeBase(RHManageTimetableBase):
|
||||
"""Base class for the importer operations"""
|
||||
normalize_url_spec = {
|
||||
'locators': {
|
||||
lambda self: self.event
|
||||
},
|
||||
'preserved_args': {
|
||||
'importer_name'
|
||||
}
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _find_latest_end_dt(entries):
|
||||
latest_dt = None
|
||||
for entry in entries:
|
||||
if latest_dt is None or entry.end_dt > latest_dt:
|
||||
latest_dt = entry.end_dt
|
||||
return latest_dt
|
||||
|
||||
def _format_date(self, date):
|
||||
return date.astimezone(timezone(self.event.timezone)).strftime('%H:%M')
|
||||
|
||||
|
||||
class RHDayEndTime(RHEndTimeBase):
|
||||
"""Get the end_dt of the latest timetable entry or the event start_dt if no entry exist on that date"""
|
||||
|
||||
def _process_args(self):
|
||||
RHEndTimeBase._process_args(self)
|
||||
self.date = self.event.tzinfo.localize(datetime.strptime(request.args['selectedDay'], '%Y/%m/%d')).date()
|
||||
|
||||
def _process(self):
|
||||
event_start_date = db.cast(TimetableEntry.start_dt.astimezone(self.event.tzinfo), db.Date)
|
||||
entries = self.event.timetable_entries.filter(event_start_date == self.date)
|
||||
latest_end_dt = self._find_latest_end_dt(entries)
|
||||
if latest_end_dt is None:
|
||||
event_start = self.event.start_dt
|
||||
latest_end_dt = utc.localize(datetime.combine(self.date, event_start.time()))
|
||||
return self._format_date(latest_end_dt)
|
||||
|
||||
|
||||
class RHBlockEndTime(RHEndTimeBase):
|
||||
"""Return the end_dt of the latest timetable entry inside the block or the block start_dt if it is empty"""
|
||||
|
||||
normalize_url_spec = {
|
||||
'locators': {
|
||||
lambda self: self.timetable_entry
|
||||
},
|
||||
'preserved_args': {
|
||||
'importer_name'
|
||||
}
|
||||
}
|
||||
|
||||
def _process_args(self):
|
||||
RHEndTimeBase._process_args(self)
|
||||
self.date = timezone(self.event.timezone).localize(datetime.strptime(request.args['selectedDay'], '%Y/%m/%d'))
|
||||
self.timetable_entry = (self.event.timetable_entries
|
||||
.filter_by(type=TimetableEntryType.SESSION_BLOCK,
|
||||
id=request.view_args['entry_id'])
|
||||
.first_or_404())
|
||||
|
||||
def _process(self):
|
||||
entries = self.timetable_entry.children
|
||||
latest_end_dt = self._find_latest_end_dt(entries)
|
||||
if latest_end_dt is None:
|
||||
latest_end_dt = self.timetable_entry.start_dt
|
||||
return self._format_date(latest_end_dt)
|
||||
@ -1,103 +0,0 @@
|
||||
# This file is part of the Indico plugins.
|
||||
# Copyright (C) 2002 - 2020 CERN
|
||||
#
|
||||
# The Indico plugins are free software; you can redistribute
|
||||
# them and/or modify them under the terms of the MIT License;
|
||||
# see the LICENSE file for more details.
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
||||
APPEND = object()
|
||||
|
||||
|
||||
class RecordConverter(object):
|
||||
"""
|
||||
Converts a dictionary or list of dictionaries into another list of dictionaries. The goal
|
||||
is to alter data fetched from connector class into a format that can be easily read by importer
|
||||
plugin. The way dictionaries are converted depends on the 'conversion' variable.
|
||||
|
||||
conversion = [ (sourceKey, destinationKey, conversionFuncion(optional), converter(optional))... ]
|
||||
|
||||
It's a list tuples in which a single element represents a translation that will be made. Every
|
||||
element of the list is a tuple that consists of from 1 to 4 entries.
|
||||
|
||||
The first one is the key name in the source dictionary, the value that applies to this key will
|
||||
be the subject of the translation. The second is the key in the destination dictionary at which
|
||||
translated value will be put. If not specified its value will be equal the value of the first
|
||||
element. If the second element is equal *append* and the converted element is a dictionary or a
|
||||
list of dictionaries, destination dictionary will be updated by the converted element.Third,
|
||||
optional, element is the function that will take the value from the source dictionary and return
|
||||
the value which will be inserted into result dictionary. If the third element is empty
|
||||
defaultConversionMethod will be called. Fourth, optional, element is a RecordConverter class
|
||||
which will be executed with converted value as an argument.
|
||||
"""
|
||||
|
||||
conversion = []
|
||||
|
||||
@staticmethod
|
||||
def default_conversion_method(attr):
|
||||
"""
|
||||
Method that will be used to convert an entry in dictionary unless other method is specified.
|
||||
"""
|
||||
return attr
|
||||
|
||||
@classmethod
|
||||
def convert(cls, record):
|
||||
"""
|
||||
Converts a single dictionary or list of dictionaries into converted list of dictionaries.
|
||||
"""
|
||||
if isinstance(record, list):
|
||||
return [cls._convert(r) for r in record]
|
||||
else:
|
||||
return [cls._convert(record)]
|
||||
|
||||
@classmethod
|
||||
def _convert_internal(cls, record):
|
||||
"""
|
||||
Converts a single dictionary into converted dictionary or list of dictionaries into converted
|
||||
list of dictionaries. Used while passing dictionaries to another converter.
|
||||
"""
|
||||
if isinstance(record, list):
|
||||
return [cls._convert(r) for r in record]
|
||||
else:
|
||||
return cls._convert(record)
|
||||
|
||||
@classmethod
|
||||
def _convert(cls, record):
|
||||
"""
|
||||
Core method of the converter. Converts a single dictionary into another dictionary.
|
||||
"""
|
||||
if not record:
|
||||
return {}
|
||||
|
||||
converted_dict = {}
|
||||
for field in cls.conversion:
|
||||
key = field[0]
|
||||
if len(field) >= 2 and field[1]:
|
||||
converted_key = field[1]
|
||||
else:
|
||||
converted_key = key
|
||||
if len(field) >= 3 and field[2]:
|
||||
conversion_method = field[2]
|
||||
else:
|
||||
conversion_method = cls.default_conversion_method
|
||||
if len(field) >= 4:
|
||||
converter = field[3]
|
||||
else:
|
||||
converter = None
|
||||
try:
|
||||
value = conversion_method(record[key])
|
||||
except KeyError:
|
||||
continue
|
||||
if converter:
|
||||
value = converter._convert_internal(value)
|
||||
if converted_key is APPEND:
|
||||
if isinstance(value, list):
|
||||
for v in value:
|
||||
converted_dict.update(v)
|
||||
else:
|
||||
converted_dict.update(value)
|
||||
else:
|
||||
converted_dict[converted_key] = value
|
||||
return converted_dict
|
||||
@ -1,63 +0,0 @@
|
||||
# This file is part of the Indico plugins.
|
||||
# Copyright (C) 2002 - 2020 CERN
|
||||
#
|
||||
# The Indico plugins are free software; you can redistribute
|
||||
# them and/or modify them under the terms of the MIT License;
|
||||
# see the LICENSE file for more details.
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from indico.core import signals
|
||||
from indico.core.plugins import IndicoPlugin, IndicoPluginBlueprint, PluginCategory, plugin_url_rule_to_js
|
||||
from indico.modules.events.timetable.views import WPManageTimetable
|
||||
from indico.web.flask.util import url_rule_to_js
|
||||
|
||||
from indico_importer import _
|
||||
from indico_importer.controllers import RHBlockEndTime, RHDayEndTime, RHGetImporters, RHImportData
|
||||
|
||||
|
||||
class ImporterPlugin(IndicoPlugin):
|
||||
"""Importer
|
||||
|
||||
Extends Indico for other plugins to import data from external sources to
|
||||
the timetable.
|
||||
"""
|
||||
category = PluginCategory.importers
|
||||
|
||||
def init(self):
|
||||
super(ImporterPlugin, self).init()
|
||||
self.inject_bundle('main.js', WPManageTimetable)
|
||||
self.inject_bundle('main.css', WPManageTimetable)
|
||||
self.connect(signals.event.timetable_buttons, self.get_timetable_buttons)
|
||||
self.importer_engines = {}
|
||||
|
||||
def get_blueprints(self):
|
||||
return blueprint
|
||||
|
||||
def get_timetable_buttons(self, *args, **kwargs):
|
||||
if self.importer_engines:
|
||||
yield _('Importer'), 'create-importer-dialog'
|
||||
|
||||
def get_vars_js(self):
|
||||
return {'urls': {'import_data': plugin_url_rule_to_js('importer.import_data'),
|
||||
'importers': plugin_url_rule_to_js('importer.importers'),
|
||||
'day_end_date': plugin_url_rule_to_js('importer.day_end_date'),
|
||||
'block_end_date': plugin_url_rule_to_js('importer.block_end_date'),
|
||||
'add_contrib': url_rule_to_js('timetable.add_contribution'),
|
||||
'create_subcontrib_rest': url_rule_to_js('contributions.create_subcontrib_rest'),
|
||||
'create_contrib_reference_rest': url_rule_to_js('contributions.create_contrib_reference_rest'),
|
||||
'create_subcontrib_reference_rest': url_rule_to_js('contributions'
|
||||
'.create_subcontrib_reference_rest'),
|
||||
'add_link': url_rule_to_js('attachments.add_link')}}
|
||||
|
||||
def register_importer_engine(self, importer_engine, plugin):
|
||||
self.importer_engines[importer_engine._id] = (importer_engine, plugin)
|
||||
|
||||
|
||||
blueprint = IndicoPluginBlueprint('importer', __name__)
|
||||
blueprint.add_url_rule('/importers/<importer_name>/search', 'import_data', RHImportData, methods=('POST',))
|
||||
blueprint.add_url_rule('/importers/', 'importers', RHGetImporters)
|
||||
|
||||
blueprint.add_url_rule('/importers/<importer_name>/event/<confId>/day-end-date', 'day_end_date', RHDayEndTime)
|
||||
blueprint.add_url_rule('/importers/<importer_name>/event/<confId>/entry/<entry_id>/block-end-date', 'block_end_date',
|
||||
RHBlockEndTime)
|
||||
@ -1,228 +0,0 @@
|
||||
# Translations template for PROJECT.
|
||||
# Copyright (C) 2017 ORGANIZATION
|
||||
# This file is distributed under the same license as the PROJECT project.
|
||||
#
|
||||
# Translators:
|
||||
# Thomas Baron <thomas.baron@cern.ch>, 2015,2017
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Indico\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2017-10-18 11:55+0200\n"
|
||||
"PO-Revision-Date: 2017-10-27 14:56+0000\n"
|
||||
"Last-Translator: Thomas Baron <thomas.baron@cern.ch>\n"
|
||||
"Language-Team: French (France) (http://www.transifex.com/indico/indico/language/fr_FR/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.5.1\n"
|
||||
"Language: fr_FR\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
|
||||
#: indico_importer/static/js/importer.js:31
|
||||
msgid "Jan"
|
||||
msgstr "Jan"
|
||||
|
||||
#: indico_importer/static/js/importer.js:32
|
||||
msgid "Feb"
|
||||
msgstr "Fév"
|
||||
|
||||
#: indico_importer/static/js/importer.js:33
|
||||
msgid "Mar"
|
||||
msgstr "Mar"
|
||||
|
||||
#: indico_importer/static/js/importer.js:34
|
||||
msgid "Apr"
|
||||
msgstr "Avr"
|
||||
|
||||
#: indico_importer/static/js/importer.js:35
|
||||
msgid "May"
|
||||
msgstr "Mai"
|
||||
|
||||
#: indico_importer/static/js/importer.js:36
|
||||
msgid "Jun"
|
||||
msgstr "Juin"
|
||||
|
||||
#: indico_importer/static/js/importer.js:37
|
||||
msgid "Jul"
|
||||
msgstr "Juil"
|
||||
|
||||
#: indico_importer/static/js/importer.js:38
|
||||
msgid "Aug"
|
||||
msgstr "Aoû"
|
||||
|
||||
#: indico_importer/static/js/importer.js:39
|
||||
msgid "Sep"
|
||||
msgstr "Sep"
|
||||
|
||||
#: indico_importer/static/js/importer.js:40
|
||||
msgid "Oct"
|
||||
msgstr "Oct"
|
||||
|
||||
#: indico_importer/static/js/importer.js:41
|
||||
msgid "Nov"
|
||||
msgstr "Nov"
|
||||
|
||||
#: indico_importer/static/js/importer.js:42
|
||||
msgid "Dec"
|
||||
msgstr "Déc"
|
||||
|
||||
#: indico_importer/static/js/importer.js:111
|
||||
msgid "Something went wrong"
|
||||
msgstr "Une erreur est survenue"
|
||||
|
||||
#: indico_importer/static/js/importer.js:270
|
||||
msgid "search"
|
||||
msgstr "rechercher"
|
||||
|
||||
#: indico_importer/static/js/importer.js:301
|
||||
msgid "in"
|
||||
msgstr "dans"
|
||||
|
||||
#: indico_importer/static/js/importer.js:308
|
||||
msgid "Proceed..."
|
||||
msgstr "Continuer..."
|
||||
|
||||
#: indico_importer/static/js/importer.js:322
|
||||
msgid "Close"
|
||||
msgstr "Fermer"
|
||||
|
||||
#: indico_importer/static/js/importer.js:331
|
||||
msgid "Please select contributions to be added and their destination."
|
||||
msgstr "Veuillez sélectionner les contributions à ajouter et leur destination."
|
||||
|
||||
#: indico_importer/static/js/importer.js:357
|
||||
msgid "Import Entries"
|
||||
msgstr "Importer les entrées"
|
||||
|
||||
#: indico_importer/static/js/importer.js:397
|
||||
msgid "Please type your search phrase and press 'search'."
|
||||
msgstr "Veuillez entrer votre phrase de recherche et appuyer sur 'rechercher'."
|
||||
|
||||
#: indico_importer/static/js/importer.js:398
|
||||
msgid "here"
|
||||
msgstr "ici"
|
||||
|
||||
#: indico_importer/static/js/importer.js:402
|
||||
msgid ""
|
||||
"Your entries were inserted successfully. Please specify a new query or click"
|
||||
msgstr "Vos entrées ont été insérées. Veuillez indiquer une nouvelle recherche ou cliquer sur "
|
||||
|
||||
#: indico_importer/static/js/importer.js:402
|
||||
msgid "to see the previous results."
|
||||
msgstr "pour voir les résultats précédents."
|
||||
|
||||
#: indico_importer/static/js/importer.js:477
|
||||
msgid "Duration time of every inserted contribution:"
|
||||
msgstr "Durée de chaque contribution insérée: "
|
||||
|
||||
#: indico_importer/static/js/importer.js:478
|
||||
msgid "Start time of the first contribution:"
|
||||
msgstr "Heure de début de la première contribution: "
|
||||
|
||||
#: indico_importer/static/js/importer.js:479
|
||||
msgid "Show me the destination:"
|
||||
msgstr "Afficher la destination: "
|
||||
|
||||
#: indico_importer/static/js/importer.js:604
|
||||
msgid "Insert"
|
||||
msgstr "Insérer"
|
||||
|
||||
#: indico_importer/static/js/importer.js:686
|
||||
msgid "Cancel"
|
||||
msgstr "Annuler"
|
||||
|
||||
#: indico_importer/static/js/importer.js:708
|
||||
msgid "Adjust entries"
|
||||
msgstr "Ajuster les entrées"
|
||||
|
||||
#: indico_importer/static/js/importer.js:944
|
||||
msgid "Report number(s)"
|
||||
msgstr "Numéros de rapports"
|
||||
|
||||
#: indico_importer/static/js/importer.js:951
|
||||
msgid "Title"
|
||||
msgstr "Titre"
|
||||
|
||||
#: indico_importer/static/js/importer.js:954
|
||||
msgid "Meeting"
|
||||
msgstr "Réunion"
|
||||
|
||||
#: indico_importer/static/js/importer.js:958
|
||||
msgid "Primary author"
|
||||
msgstr "Auteur principal"
|
||||
|
||||
#: indico_importer/static/js/importer.js:961
|
||||
msgid "Secondary author"
|
||||
msgstr "Auteur secondaire"
|
||||
|
||||
#: indico_importer/static/js/importer.js:964
|
||||
msgid "Speaker"
|
||||
msgstr "Orateur"
|
||||
|
||||
#: indico_importer/static/js/importer.js:970
|
||||
#: indico_importer/static/js/importer.js:994
|
||||
msgid "Summary"
|
||||
msgstr "Récapitulatif"
|
||||
|
||||
#: indico_importer/static/js/importer.js:974
|
||||
msgid " (show all)"
|
||||
msgstr "(afficher tout)"
|
||||
|
||||
#: indico_importer/static/js/importer.js:984
|
||||
msgid " (hide)"
|
||||
msgstr "(cacher)"
|
||||
|
||||
#: indico_importer/static/js/importer.js:999
|
||||
msgid "Place"
|
||||
msgstr "Lieu"
|
||||
|
||||
#: indico_importer/static/js/importer.js:1003
|
||||
msgid "Materials"
|
||||
msgstr "Documents"
|
||||
|
||||
#: indico_importer/static/js/importer.js:1044
|
||||
msgid "st"
|
||||
msgstr "er"
|
||||
|
||||
#: indico_importer/static/js/importer.js:1047
|
||||
msgid "nd"
|
||||
msgstr "ème"
|
||||
|
||||
#: indico_importer/static/js/importer.js:1050
|
||||
msgid "rd"
|
||||
msgstr "ème"
|
||||
|
||||
#: indico_importer/static/js/importer.js:1053
|
||||
msgid "th"
|
||||
msgstr "ème"
|
||||
|
||||
#: indico_importer/static/js/importer.js:1136
|
||||
msgid "One entry was found. "
|
||||
msgid_plural "{0} entries were found. "
|
||||
msgstr[0] "Une entrée trouvée."
|
||||
msgstr[1] "{0} entrées trouvées."
|
||||
|
||||
#: indico_importer/static/js/importer.js:1201
|
||||
msgid "Please select the results you want to insert."
|
||||
msgstr "Veuillez sélectionner les résultats que vous souhaitez insérer."
|
||||
|
||||
#: indico_importer/static/js/importer.js:1202
|
||||
msgid "No results were found. Please change the search phrase."
|
||||
msgstr "Aucun résultat. Veuillez modifier la phrase de recherche."
|
||||
|
||||
#: indico_importer/static/js/importer.js:1203
|
||||
msgid "Step 1: Search results:"
|
||||
msgstr "Étape 1: Sélectionnez les entrées à insérer:"
|
||||
|
||||
#: indico_importer/static/js/importer.js:1208
|
||||
msgid "more results"
|
||||
msgstr "plus de résultats"
|
||||
|
||||
#: indico_importer/static/js/importer.js:1445
|
||||
msgid "Step 2: Choose destination:"
|
||||
msgstr "Étape 2: Choisissez la destination:"
|
||||
|
||||
#: indico_importer/static/js/importer.js:1446
|
||||
msgid "Please select the place in which the contributions will be inserted."
|
||||
msgstr "Veuillez sélectionner l'endroit où les contributions seront insérées."
|
||||
@ -1,24 +0,0 @@
|
||||
# Translations template for PROJECT.
|
||||
# Copyright (C) 2017 ORGANIZATION
|
||||
# This file is distributed under the same license as the PROJECT project.
|
||||
#
|
||||
# Translators:
|
||||
# Thomas Baron <thomas.baron@cern.ch>, 2015
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Indico\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2017-10-18 11:55+0200\n"
|
||||
"PO-Revision-Date: 2017-09-23 20:55+0000\n"
|
||||
"Last-Translator: Thomas Baron <thomas.baron@cern.ch>\n"
|
||||
"Language-Team: French (France) (http://www.transifex.com/indico/indico/language/fr_FR/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.5.1\n"
|
||||
"Language: fr_FR\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
|
||||
#: indico_importer/plugin.py:48
|
||||
msgid "Importer"
|
||||
msgstr "Importateur"
|
||||
@ -1,227 +0,0 @@
|
||||
# Translations template for PROJECT.
|
||||
# Copyright (C) 2020 ORGANIZATION
|
||||
# This file is distributed under the same license as the PROJECT project.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2020.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PROJECT VERSION\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2020-08-19 20:40+0200\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.8.0\n"
|
||||
|
||||
#: indico_importer/client/index.js:23
|
||||
msgid "Jan"
|
||||
msgstr ""
|
||||
|
||||
#: indico_importer/client/index.js:24
|
||||
msgid "Feb"
|
||||
msgstr ""
|
||||
|
||||
#: indico_importer/client/index.js:25
|
||||
msgid "Mar"
|
||||
msgstr ""
|
||||
|
||||
#: indico_importer/client/index.js:26
|
||||
msgid "Apr"
|
||||
msgstr ""
|
||||
|
||||
#: indico_importer/client/index.js:27
|
||||
msgid "May"
|
||||
msgstr ""
|
||||
|
||||
#: indico_importer/client/index.js:28
|
||||
msgid "Jun"
|
||||
msgstr ""
|
||||
|
||||
#: indico_importer/client/index.js:29
|
||||
msgid "Jul"
|
||||
msgstr ""
|
||||
|
||||
#: indico_importer/client/index.js:30
|
||||
msgid "Aug"
|
||||
msgstr ""
|
||||
|
||||
#: indico_importer/client/index.js:31
|
||||
msgid "Sep"
|
||||
msgstr ""
|
||||
|
||||
#: indico_importer/client/index.js:32
|
||||
msgid "Oct"
|
||||
msgstr ""
|
||||
|
||||
#: indico_importer/client/index.js:33
|
||||
msgid "Nov"
|
||||
msgstr ""
|
||||
|
||||
#: indico_importer/client/index.js:34
|
||||
msgid "Dec"
|
||||
msgstr ""
|
||||
|
||||
#: indico_importer/client/index.js:107
|
||||
msgid "Something went wrong"
|
||||
msgstr ""
|
||||
|
||||
#: indico_importer/client/index.js:270
|
||||
msgid "search"
|
||||
msgstr ""
|
||||
|
||||
#: indico_importer/client/index.js:321
|
||||
msgid "in"
|
||||
msgstr ""
|
||||
|
||||
#: indico_importer/client/index.js:335
|
||||
msgid "Proceed..."
|
||||
msgstr ""
|
||||
|
||||
#: indico_importer/client/index.js:359
|
||||
msgid "Close"
|
||||
msgstr ""
|
||||
|
||||
#: indico_importer/client/index.js:370
|
||||
msgid "Please select contributions to be added and their destination."
|
||||
msgstr ""
|
||||
|
||||
#: indico_importer/client/index.js:396
|
||||
msgid "Import Entries"
|
||||
msgstr ""
|
||||
|
||||
#: indico_importer/client/index.js:440
|
||||
msgid "Please type your search phrase and press 'search'."
|
||||
msgstr ""
|
||||
|
||||
#: indico_importer/client/index.js:442
|
||||
msgid "here"
|
||||
msgstr ""
|
||||
|
||||
#: indico_importer/client/index.js:446
|
||||
msgid ""
|
||||
"Your entries were inserted successfully. Please specify a new query or "
|
||||
"click"
|
||||
msgstr ""
|
||||
|
||||
#: indico_importer/client/index.js:452
|
||||
msgid "to see the previous results."
|
||||
msgstr ""
|
||||
|
||||
#: indico_importer/client/index.js:537
|
||||
msgid "Duration time of every inserted contribution:"
|
||||
msgstr ""
|
||||
|
||||
#: indico_importer/client/index.js:538
|
||||
msgid "Start time of the first contribution:"
|
||||
msgstr ""
|
||||
|
||||
#: indico_importer/client/index.js:539
|
||||
msgid "Show me the destination:"
|
||||
msgstr ""
|
||||
|
||||
#: indico_importer/client/index.js:668
|
||||
msgid "Insert"
|
||||
msgstr ""
|
||||
|
||||
#: indico_importer/client/index.js:777
|
||||
msgid "Cancel"
|
||||
msgstr ""
|
||||
|
||||
#: indico_importer/client/index.js:801
|
||||
msgid "Adjust entries"
|
||||
msgstr ""
|
||||
|
||||
#: indico_importer/client/index.js:1052
|
||||
msgid "Report number(s)"
|
||||
msgstr ""
|
||||
|
||||
#: indico_importer/client/index.js:1060
|
||||
msgid "Title"
|
||||
msgstr ""
|
||||
|
||||
#: indico_importer/client/index.js:1065
|
||||
msgid "Meeting"
|
||||
msgstr ""
|
||||
|
||||
#: indico_importer/client/index.js:1073
|
||||
msgid "Primary author"
|
||||
msgstr ""
|
||||
|
||||
#: indico_importer/client/index.js:1083
|
||||
msgid "Secondary author"
|
||||
msgstr ""
|
||||
|
||||
#: indico_importer/client/index.js:1093
|
||||
msgid "Speaker"
|
||||
msgstr ""
|
||||
|
||||
#: indico_importer/client/index.js:1103 indico_importer/client/index.js:1132
|
||||
msgid "Summary"
|
||||
msgstr ""
|
||||
|
||||
#: indico_importer/client/index.js:1107
|
||||
msgid " (show all)"
|
||||
msgstr ""
|
||||
|
||||
#: indico_importer/client/index.js:1119
|
||||
msgid " (hide)"
|
||||
msgstr ""
|
||||
|
||||
#: indico_importer/client/index.js:1144
|
||||
msgid "Place"
|
||||
msgstr ""
|
||||
|
||||
#: indico_importer/client/index.js:1149
|
||||
msgid "Materials"
|
||||
msgstr ""
|
||||
|
||||
#: indico_importer/client/index.js:1193
|
||||
msgid "st"
|
||||
msgstr ""
|
||||
|
||||
#: indico_importer/client/index.js:1196
|
||||
msgid "nd"
|
||||
msgstr ""
|
||||
|
||||
#: indico_importer/client/index.js:1199
|
||||
msgid "rd"
|
||||
msgstr ""
|
||||
|
||||
#: indico_importer/client/index.js:1202
|
||||
msgid "th"
|
||||
msgstr ""
|
||||
|
||||
#: indico_importer/client/index.js:1291
|
||||
msgid "One entry was found. "
|
||||
msgid_plural "{0} entries were found. "
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
#: indico_importer/client/index.js:1367
|
||||
msgid "Please select the results you want to insert."
|
||||
msgstr ""
|
||||
|
||||
#: indico_importer/client/index.js:1371
|
||||
msgid "No results were found. Please change the search phrase."
|
||||
msgstr ""
|
||||
|
||||
#: indico_importer/client/index.js:1375
|
||||
msgid "Step 1: Search results:"
|
||||
msgstr ""
|
||||
|
||||
#: indico_importer/client/index.js:1393
|
||||
msgid "more results"
|
||||
msgstr ""
|
||||
|
||||
#: indico_importer/client/index.js:1667
|
||||
msgid "Step 2: Choose destination:"
|
||||
msgstr ""
|
||||
|
||||
#: indico_importer/client/index.js:1670
|
||||
msgid "Please select the place in which the contributions will be inserted."
|
||||
msgstr ""
|
||||
|
||||
@ -1,23 +0,0 @@
|
||||
# Translations template for PROJECT.
|
||||
# Copyright (C) 2020 ORGANIZATION
|
||||
# This file is distributed under the same license as the PROJECT project.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2020.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PROJECT VERSION\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2020-08-19 20:40+0200\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.8.0\n"
|
||||
|
||||
#: indico_importer/plugin.py:39
|
||||
msgid "Importer"
|
||||
msgstr ""
|
||||
|
||||
@ -1,18 +0,0 @@
|
||||
# This file is part of the Indico plugins.
|
||||
# Copyright (C) 2002 - 2020 CERN
|
||||
#
|
||||
# The Indico plugins are free software; you can redistribute
|
||||
# them and/or modify them under the terms of the MIT License;
|
||||
# see the LICENSE file for more details.
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
||||
def convert_dt_tuple(dt_tuple):
|
||||
split_datetime = dt_tuple[0].split('T')
|
||||
if len(split_datetime) > 1:
|
||||
return {'date': dt_tuple[0].split('T')[0],
|
||||
'time': dt_tuple[0].split('T')[1]}
|
||||
else:
|
||||
return {'date': dt_tuple[0].split('T')[0],
|
||||
'time': '00:00'}
|
||||
@ -1,34 +0,0 @@
|
||||
# This file is part of the Indico plugins.
|
||||
# Copyright (C) 2002 - 2020 CERN
|
||||
#
|
||||
# The Indico plugins are free software; you can redistribute
|
||||
# them and/or modify them under the terms of the MIT License;
|
||||
# see the LICENSE file for more details.
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from setuptools import find_packages, setup
|
||||
|
||||
|
||||
setup(
|
||||
name='indico-plugin-importer',
|
||||
version='2.2',
|
||||
description='Framework for importing timetable data from external sources into Indico',
|
||||
url='https://github.com/indico/indico-plugins',
|
||||
license='MIT',
|
||||
author='Indico Team',
|
||||
author_email='indico-team@cern.ch',
|
||||
packages=find_packages(),
|
||||
zip_safe=False,
|
||||
include_package_data=True,
|
||||
install_requires=[
|
||||
'indico>=2.2.dev0'
|
||||
],
|
||||
classifiers=[
|
||||
'Environment :: Plugins',
|
||||
'Environment :: Web Environment',
|
||||
'License :: OSI Approved :: MIT License',
|
||||
'Programming Language :: Python :: 2.7'
|
||||
],
|
||||
entry_points={'indico.plugins': {'importer = indico_importer.plugin:ImporterPlugin'}}
|
||||
)
|
||||
@ -1,5 +0,0 @@
|
||||
{
|
||||
"entry": {
|
||||
"main": "./index.js"
|
||||
}
|
||||
}
|
||||
@ -1,3 +0,0 @@
|
||||
graft indico_importer_invenio/translations
|
||||
|
||||
global-exclude *.pyc __pycache__ .keep
|
||||
@ -1,13 +0,0 @@
|
||||
# This file is part of the Indico plugins.
|
||||
# Copyright (C) 2002 - 2020 CERN
|
||||
#
|
||||
# The Indico plugins are free software; you can redistribute
|
||||
# them and/or modify them under the terms of the MIT License;
|
||||
# see the LICENSE file for more details.
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from indico.util.i18n import make_bound_gettext
|
||||
|
||||
|
||||
_ = make_bound_gettext('importer_invenio')
|
||||
@ -1,643 +0,0 @@
|
||||
# This file is part of the Indico plugins.
|
||||
# Copyright (C) 2002 - 2020 CERN
|
||||
#
|
||||
# The Indico plugins are free software; you can redistribute
|
||||
# them and/or modify them under the terms of the MIT License;
|
||||
# see the LICENSE file for more details.
|
||||
|
||||
# flake8: noqa
|
||||
|
||||
"""
|
||||
Tools to connect to distant Invenio servers using Invenio APIs.
|
||||
|
||||
|
||||
Example of use:
|
||||
|
||||
from InvenioConnector import *
|
||||
cds = InvenioConnector("http://cds.cern.ch")
|
||||
|
||||
results = cds.search("higgs")
|
||||
|
||||
for record in results:
|
||||
print record["245__a"][0]
|
||||
print record["520__b"][0]
|
||||
for author in record["100__"]:
|
||||
print author["a"][0], author["u"][0]
|
||||
|
||||
FIXME:
|
||||
- implement cache expiration
|
||||
- exceptions handling
|
||||
- parsing of <!-- Search-Engine-Total-Number-Of-Results: N -->
|
||||
- better checking of input parameters
|
||||
- improve behaviour when running locally
|
||||
(perform_request_search *requiring* "req" object)
|
||||
"""
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
import urllib
|
||||
import urllib2
|
||||
import xml.sax
|
||||
|
||||
import requests
|
||||
from requests.exceptions import ConnectionError, InvalidSchema, InvalidURL, MissingSchema, RequestException
|
||||
|
||||
|
||||
MECHANIZE_CLIENTFORM_VERSION_CHANGE = (0, 2, 0)
|
||||
try:
|
||||
import mechanize
|
||||
if mechanize.__version__ < MECHANIZE_CLIENTFORM_VERSION_CHANGE:
|
||||
OLD_MECHANIZE_VERSION = True
|
||||
import ClientForm
|
||||
else:
|
||||
OLD_MECHANIZE_VERSION = False
|
||||
MECHANIZE_AVAILABLE = True
|
||||
except ImportError:
|
||||
MECHANIZE_AVAILABLE = False
|
||||
|
||||
try:
|
||||
# if we are running locally, we can optimize :-)
|
||||
from invenio.config import CFG_SITE_URL, CFG_SITE_SECURE_URL, CFG_SITE_RECORD, CFG_CERN_SITE
|
||||
from invenio.legacy.bibsched.bibtask import task_low_level_submission
|
||||
from invenio.legacy.search_engine import perform_request_search, collection_restricted_p
|
||||
from invenio.modules.formatter import format_records
|
||||
from invenio.utils.url import make_user_agent_string
|
||||
LOCAL_SITE_URLS = [CFG_SITE_URL, CFG_SITE_SECURE_URL]
|
||||
CFG_USER_AGENT = make_user_agent_string("invenio_connector")
|
||||
except ImportError:
|
||||
LOCAL_SITE_URLS = None
|
||||
CFG_CERN_SITE = 0
|
||||
CFG_USER_AGENT = "invenio_connector"
|
||||
|
||||
CFG_CDS_URL = "http://cds.cern.ch/"
|
||||
|
||||
class InvenioConnectorAuthError(Exception):
|
||||
"""
|
||||
This exception is called by InvenioConnector when authentication fails during
|
||||
remote or local connections.
|
||||
"""
|
||||
def __init__(self, value):
|
||||
"""
|
||||
Set the internal "value" attribute to that of the passed "value" parameter.
|
||||
@param value: an error string to display to the user.
|
||||
@type value: string
|
||||
"""
|
||||
Exception.__init__(self)
|
||||
self.value = value
|
||||
def __str__(self):
|
||||
"""
|
||||
Return oneself as a string (actually, return the contents of self.value).
|
||||
@return: representation of error
|
||||
@rtype: string
|
||||
"""
|
||||
return str(self.value)
|
||||
|
||||
class InvenioConnectorServerError(Exception):
|
||||
"""
|
||||
This exception is called by InvenioConnector when using it on a machine with no
|
||||
Invenio installed and no remote server URL is given during instantiation.
|
||||
"""
|
||||
def __init__(self, value):
|
||||
"""
|
||||
Set the internal "value" attribute to that of the passed "value" parameter.
|
||||
@param value: an error string to display to the user.
|
||||
@type value: string
|
||||
"""
|
||||
Exception.__init__(self)
|
||||
self.value = value
|
||||
def __str__(self):
|
||||
"""
|
||||
Return oneself as a string (actually, return the contents of self.value).
|
||||
@return: representation of error
|
||||
@rtype: string
|
||||
"""
|
||||
return str(self.value)
|
||||
|
||||
class InvenioConnector(object):
|
||||
"""
|
||||
Creates an connector to a server running Invenio
|
||||
"""
|
||||
|
||||
def __init__(self, url=None, user="", password="", login_method="Local",
|
||||
local_import_path="invenio", insecure_login=False):
|
||||
"""
|
||||
Initialize a new instance of the server at given URL.
|
||||
|
||||
If the server happens to be running on the local machine, the
|
||||
access will be done directly using the Python APIs. In that case
|
||||
you can choose from which base path to import the necessary file
|
||||
specifying the local_import_path parameter.
|
||||
|
||||
@param url: the url to which this instance will be connected.
|
||||
Defaults to CFG_SITE_URL, if available.
|
||||
@type url: string
|
||||
@param user: the optional username for interacting with the Invenio
|
||||
instance in an authenticated way.
|
||||
@type user: string
|
||||
@param password: the corresponding password.
|
||||
@type password: string
|
||||
@param login_method: the name of the login method the Invenio instance
|
||||
is expecting for this user (in case there is more than one).
|
||||
@type login_method: string
|
||||
@param local_import_path: the base path from which the connector should
|
||||
try to load the local connector, if available. Eg "invenio" will
|
||||
lead to "import invenio.dbquery"
|
||||
@type local_import_path: string
|
||||
|
||||
@raise InvenioConnectorAuthError: if no secure URL is given for authentication
|
||||
@raise InvenioConnectorServerError: if no URL is given on a machine without Invenio installed
|
||||
"""
|
||||
if url == None and LOCAL_SITE_URLS != None:
|
||||
self.server_url = LOCAL_SITE_URLS[0] # Default to CFG_SITE_URL
|
||||
elif url == None:
|
||||
raise InvenioConnectorServerError("You do not seem to have Invenio installed and no remote URL is given")
|
||||
else:
|
||||
self.server_url = url
|
||||
self._validate_server_url()
|
||||
|
||||
self.local = LOCAL_SITE_URLS and self.server_url in LOCAL_SITE_URLS
|
||||
self.cached_queries = {}
|
||||
self.cached_records = {}
|
||||
self.cached_baskets = {}
|
||||
self.user = user
|
||||
self.password = password
|
||||
self.login_method = login_method
|
||||
self.browser = None
|
||||
if self.user:
|
||||
if not insecure_login and not self.server_url.startswith('https://'):
|
||||
raise InvenioConnectorAuthError("You have to use a secure URL (HTTPS) to login")
|
||||
if MECHANIZE_AVAILABLE:
|
||||
self._init_browser()
|
||||
self._check_credentials()
|
||||
else:
|
||||
self.user = None
|
||||
raise InvenioConnectorAuthError("The Python module Mechanize (and ClientForm" \
|
||||
" if Mechanize version < 0.2.0) must" \
|
||||
" be installed to perform authenticated requests.")
|
||||
|
||||
def _init_browser(self):
|
||||
"""
|
||||
Ovveride this method with the appropriate way to prepare a logged in
|
||||
browser.
|
||||
"""
|
||||
self.browser = mechanize.Browser()
|
||||
self.browser.set_handle_robots(False)
|
||||
self.browser.open(self.server_url + "/youraccount/login")
|
||||
self.browser.select_form(nr=0)
|
||||
try:
|
||||
self.browser['nickname'] = self.user
|
||||
self.browser['password'] = self.password
|
||||
except:
|
||||
self.browser['p_un'] = self.user
|
||||
self.browser['p_pw'] = self.password
|
||||
# Set login_method to be writable
|
||||
self.browser.form.find_control('login_method').readonly = False
|
||||
self.browser['login_method'] = self.login_method
|
||||
self.browser.submit()
|
||||
|
||||
def _check_credentials(self):
|
||||
out = self.browser.response().read()
|
||||
if not 'youraccount/logout' in out:
|
||||
raise InvenioConnectorAuthError("It was not possible to successfully login with the provided credentials" + out)
|
||||
|
||||
def search(self, read_cache=True, **kwparams):
|
||||
"""
|
||||
Returns records corresponding to the given search query.
|
||||
|
||||
See docstring of invenio.legacy.search_engine.perform_request_search()
|
||||
for an overview of available parameters.
|
||||
|
||||
@raise InvenioConnectorAuthError: if authentication fails
|
||||
"""
|
||||
parse_results = False
|
||||
of = kwparams.get('of', "")
|
||||
if of == "":
|
||||
parse_results = True
|
||||
of = "xm"
|
||||
kwparams['of'] = of
|
||||
params = urllib.urlencode(kwparams, doseq=1)
|
||||
|
||||
# Are we running locally? If so, better directly access the
|
||||
# search engine directly
|
||||
if self.local and of != 't':
|
||||
# See if user tries to search any restricted collection
|
||||
c = kwparams.get('c', "")
|
||||
if c != "":
|
||||
if type(c) is list:
|
||||
colls = c
|
||||
else:
|
||||
colls = [c]
|
||||
for collection in colls:
|
||||
if collection_restricted_p(collection):
|
||||
if self.user:
|
||||
self._check_credentials()
|
||||
continue
|
||||
raise InvenioConnectorAuthError("You are trying to search a restricted collection. Please authenticate yourself.\n")
|
||||
kwparams['of'] = 'id'
|
||||
results = perform_request_search(**kwparams)
|
||||
if of.lower() != 'id':
|
||||
results = format_records(results, of)
|
||||
else:
|
||||
if params + str(parse_results) not in self.cached_queries or not read_cache:
|
||||
if self.user:
|
||||
results = self.browser.open(self.server_url + "/search?" + params)
|
||||
else:
|
||||
results = urllib2.urlopen(self.server_url + "/search?" + params)
|
||||
if 'youraccount/login' in results.geturl():
|
||||
# Current user not able to search collection
|
||||
raise InvenioConnectorAuthError("You are trying to search a restricted collection. Please authenticate yourself.\n")
|
||||
else:
|
||||
return self.cached_queries[params + str(parse_results)]
|
||||
|
||||
if parse_results:
|
||||
# FIXME: we should not try to parse if results is string
|
||||
parsed_records = self._parse_results(results, self.cached_records)
|
||||
self.cached_queries[params + str(parse_results)] = parsed_records
|
||||
return parsed_records
|
||||
else:
|
||||
# pylint: disable=E1103
|
||||
# The whole point of the following code is to make sure we can
|
||||
# handle two types of variable.
|
||||
try:
|
||||
res = results.read()
|
||||
except AttributeError:
|
||||
res = results
|
||||
# pylint: enable=E1103
|
||||
|
||||
if of == "id":
|
||||
try:
|
||||
if type(res) is str:
|
||||
# Transform to list
|
||||
res = [int(recid.strip()) for recid in \
|
||||
res.strip("[]").split(",") if recid.strip() != ""]
|
||||
res.reverse()
|
||||
except (ValueError, AttributeError):
|
||||
res = []
|
||||
self.cached_queries[params + str(parse_results)] = res
|
||||
return self.cached_queries[params + str(parse_results)]
|
||||
|
||||
def search_with_retry(self, sleeptime=3.0, retrycount=3, **params):
|
||||
"""
|
||||
This function performs a search given a dictionary of search(..)
|
||||
parameters. It accounts for server timeouts as necessary and
|
||||
will retry some number of times.
|
||||
|
||||
@param sleeptime: number of seconds to sleep between retries
|
||||
@type sleeptime: float
|
||||
|
||||
@param retrycount: number of times to retry given search
|
||||
@type retrycount: int
|
||||
|
||||
@param params: search parameters
|
||||
@type params: **kwds
|
||||
|
||||
@rtype: list
|
||||
@return: returns records in given format
|
||||
"""
|
||||
results = []
|
||||
count = 0
|
||||
while count < retrycount:
|
||||
try:
|
||||
results = self.search(**params)
|
||||
break
|
||||
except urllib2.URLError:
|
||||
sys.stderr.write("Timeout while searching...Retrying\n")
|
||||
time.sleep(sleeptime)
|
||||
count += 1
|
||||
else:
|
||||
sys.stderr.write("Aborting search after %d attempts.\n" % (retrycount,))
|
||||
return results
|
||||
|
||||
def search_similar_records(self, recid):
|
||||
"""
|
||||
Returns the records similar to the given one
|
||||
"""
|
||||
return self.search(p="recid:" + str(recid), rm="wrd")
|
||||
|
||||
def search_records_cited_by(self, recid):
|
||||
"""
|
||||
Returns records cited by the given one
|
||||
"""
|
||||
return self.search(p="recid:" + str(recid), rm="citation")
|
||||
|
||||
def get_records_from_basket(self, bskid, group_basket=False, read_cache=True):
|
||||
"""
|
||||
Returns the records from the (public) basket with given bskid
|
||||
"""
|
||||
if bskid not in self.cached_baskets or not read_cache:
|
||||
if self.user:
|
||||
if group_basket:
|
||||
group_basket = '&category=G'
|
||||
else:
|
||||
group_basket = ''
|
||||
results = self.browser.open(self.server_url + \
|
||||
"/yourbaskets/display?of=xm&bskid=" + str(bskid) + group_basket)
|
||||
else:
|
||||
results = urllib2.urlopen(self.server_url + \
|
||||
"/yourbaskets/display_public?of=xm&bskid=" + str(bskid))
|
||||
else:
|
||||
return self.cached_baskets[bskid]
|
||||
|
||||
parsed_records = self._parse_results(results, self.cached_records)
|
||||
self.cached_baskets[bskid] = parsed_records
|
||||
return parsed_records
|
||||
|
||||
def get_record(self, recid, read_cache=True):
|
||||
"""
|
||||
Returns the record with given recid
|
||||
"""
|
||||
if recid in self.cached_records or not read_cache:
|
||||
return self.cached_records[recid]
|
||||
else:
|
||||
return self.search(p="recid:" + str(recid))
|
||||
|
||||
def upload_marcxml(self, marcxml, mode):
|
||||
"""
|
||||
Uploads a record to the server
|
||||
|
||||
Parameters:
|
||||
marcxml - *str* the XML to upload.
|
||||
mode - *str* the mode to use for the upload.
|
||||
"-i" insert new records
|
||||
"-r" replace existing records
|
||||
"-c" correct fields of records
|
||||
"-a" append fields to records
|
||||
"-ir" insert record or replace if it exists
|
||||
"""
|
||||
if mode not in ["-i", "-r", "-c", "-a", "-ir"]:
|
||||
raise NameError, "Incorrect mode " + str(mode)
|
||||
|
||||
# Are we running locally? If so, submit directly
|
||||
if self.local:
|
||||
(code, marcxml_filepath) = tempfile.mkstemp(prefix="upload_%s" % \
|
||||
time.strftime("%Y%m%d_%H%M%S_",
|
||||
time.localtime()))
|
||||
marcxml_file_d = os.fdopen(code, "w")
|
||||
marcxml_file_d.write(marcxml)
|
||||
marcxml_file_d.close()
|
||||
return task_low_level_submission("bibupload", "", mode, marcxml_filepath)
|
||||
else:
|
||||
params = urllib.urlencode({'file': marcxml,
|
||||
'mode': mode})
|
||||
## We don't use self.browser as batchuploader is protected by IP
|
||||
opener = urllib2.build_opener()
|
||||
opener.addheaders = [('User-Agent', CFG_USER_AGENT)]
|
||||
return opener.open(self.server_url + "/batchuploader/robotupload", params,)
|
||||
|
||||
def _parse_results(self, results, cached_records):
|
||||
"""
|
||||
Parses the given results (in MARCXML format).
|
||||
|
||||
The given "cached_records" list is a pool of
|
||||
already existing parsed records (in order to
|
||||
avoid keeping several times the same records in memory)
|
||||
"""
|
||||
parser = xml.sax.make_parser()
|
||||
handler = RecordsHandler(cached_records)
|
||||
parser.setContentHandler(handler)
|
||||
parser.parse(results)
|
||||
return handler.records
|
||||
|
||||
def _validate_server_url(self):
|
||||
"""Validates self.server_url"""
|
||||
try:
|
||||
request = requests.head(self.server_url)
|
||||
if request.status_code >= 400:
|
||||
raise InvenioConnectorServerError(
|
||||
"Unexpected status code '%d' accessing URL: %s"
|
||||
% (request.status_code, self.server_url))
|
||||
except (InvalidSchema, MissingSchema) as err:
|
||||
raise InvenioConnectorServerError(
|
||||
"Bad schema, expecting http:// or https://:\n %s" % (err,))
|
||||
except ConnectionError as err:
|
||||
raise InvenioConnectorServerError(
|
||||
"Couldn't establish connection to '%s':\n %s"
|
||||
% (self.server_url, err))
|
||||
except InvalidURL as err:
|
||||
raise InvenioConnectorServerError(
|
||||
"Invalid URL '%s':\n %s"
|
||||
% (self.server_url, err))
|
||||
except RequestException as err:
|
||||
raise InvenioConnectorServerError(
|
||||
"Unknown error connecting to '%s':\n %s"
|
||||
% (self.server_url, err))
|
||||
|
||||
|
||||
class Record(dict):
|
||||
"""
|
||||
Represents a Invenio record
|
||||
"""
|
||||
def __init__(self, recid=None, marcxml=None, server_url=None):
|
||||
#dict.__init__(self)
|
||||
self.recid = recid
|
||||
self.marcxml = ""
|
||||
if marcxml is not None:
|
||||
self.marcxml = marcxml
|
||||
#self.record = {}
|
||||
self.server_url = server_url
|
||||
|
||||
def __setitem__(self, item, value):
|
||||
tag, ind1, ind2, subcode = decompose_code(item)
|
||||
if subcode is not None:
|
||||
#if not dict.has_key(self, tag + ind1 + ind2):
|
||||
# dict.__setitem__(self, tag + ind1 + ind2, [])
|
||||
dict.__setitem__(self, tag + ind1 + ind2, [{subcode: [value]}])
|
||||
else:
|
||||
dict.__setitem__(self, tag + ind1 + ind2, value)
|
||||
|
||||
def __getitem__(self, item):
|
||||
tag, ind1, ind2, subcode = decompose_code(item)
|
||||
|
||||
datafields = dict.__getitem__(self, tag + ind1 + ind2)
|
||||
if subcode is not None:
|
||||
subfields = []
|
||||
for datafield in datafields:
|
||||
if subcode in datafield:
|
||||
subfields.extend(datafield[subcode])
|
||||
return subfields
|
||||
else:
|
||||
return datafields
|
||||
|
||||
def __repr__(self):
|
||||
return "Record(" + dict.__repr__(self) + ")"
|
||||
|
||||
def __str__(self):
|
||||
return self.marcxml
|
||||
|
||||
def export(self, of="marcxml"):
|
||||
"""
|
||||
Returns the record in chosen format
|
||||
"""
|
||||
return self.marcxml
|
||||
|
||||
def url(self):
|
||||
"""
|
||||
Returns the URL to this record.
|
||||
Returns None if not known
|
||||
"""
|
||||
if self.server_url is not None and \
|
||||
self.recid is not None:
|
||||
return self.server_url + "/"+ CFG_SITE_RECORD +"/" + str(self.recid)
|
||||
else:
|
||||
return None
|
||||
if MECHANIZE_AVAILABLE:
|
||||
class _SGMLParserFactory(mechanize.DefaultFactory):
|
||||
"""
|
||||
Black magic to be able to interact with CERN SSO forms.
|
||||
"""
|
||||
def __init__(self, i_want_broken_xhtml_support=False):
|
||||
if OLD_MECHANIZE_VERSION:
|
||||
forms_factory = mechanize.FormsFactory(
|
||||
form_parser_class=ClientForm.XHTMLCompatibleFormParser)
|
||||
else:
|
||||
forms_factory = mechanize.FormsFactory(
|
||||
form_parser_class=mechanize.XHTMLCompatibleFormParser)
|
||||
mechanize.Factory.__init__(
|
||||
self,
|
||||
forms_factory=forms_factory,
|
||||
links_factory=mechanize.LinksFactory(),
|
||||
title_factory=mechanize.TitleFactory(),
|
||||
response_type_finder=mechanize._html.ResponseTypeFinder(
|
||||
allow_xhtml=i_want_broken_xhtml_support),
|
||||
)
|
||||
|
||||
class CDSInvenioConnector(InvenioConnector):
|
||||
def __init__(self, user="", password="", local_import_path="invenio"):
|
||||
"""
|
||||
This is a specialized InvenioConnector class suitable to connect
|
||||
to the CERN Document Server (CDS), which uses centralized SSO.
|
||||
"""
|
||||
cds_url = CFG_CDS_URL
|
||||
if user:
|
||||
cds_url = cds_url.replace('http', 'https')
|
||||
super(CDSInvenioConnector, self).__init__(cds_url, user, password, local_import_path=local_import_path)
|
||||
|
||||
def _init_browser(self):
|
||||
"""
|
||||
@note: update this everytime the CERN SSO login form is refactored.
|
||||
"""
|
||||
self.browser = mechanize.Browser(factory=_SGMLParserFactory(i_want_broken_xhtml_support=True))
|
||||
self.browser.set_handle_robots(False)
|
||||
self.browser.open(self.server_url)
|
||||
self.browser.follow_link(text_regex="Sign in")
|
||||
self.browser.select_form(nr=0)
|
||||
self.browser.form['ctl00$ctl00$NICEMasterPageBodyContent$SiteContentPlaceholder$txtFormsLogin'] = self.user
|
||||
self.browser.form['ctl00$ctl00$NICEMasterPageBodyContent$SiteContentPlaceholder$txtFormsPassword'] = self.password
|
||||
self.browser.submit()
|
||||
self.browser.select_form(nr=0)
|
||||
self.browser.submit()
|
||||
|
||||
class RecordsHandler(xml.sax.handler.ContentHandler):
|
||||
"MARCXML Parser"
|
||||
|
||||
def __init__(self, records):
|
||||
"""
|
||||
Parameters:
|
||||
records - *dict* a dictionary with an already existing cache of records
|
||||
"""
|
||||
self.cached_records = records
|
||||
self.records = []
|
||||
self.in_record = False
|
||||
self.in_controlfield = False
|
||||
self.in_datafield = False
|
||||
self.in_subfield = False
|
||||
self.cur_tag = None
|
||||
self.cur_subfield = None
|
||||
self.cur_controlfield = None
|
||||
self.cur_datafield = None
|
||||
self.cur_record = None
|
||||
self.recid = 0
|
||||
self.buffer = ""
|
||||
self.counts = 0
|
||||
|
||||
def startElement(self, name, attributes):
|
||||
if name == "record":
|
||||
self.cur_record = Record()
|
||||
self.in_record = True
|
||||
|
||||
elif name == "controlfield":
|
||||
tag = attributes["tag"]
|
||||
self.cur_datafield = ""
|
||||
self.cur_tag = tag
|
||||
self.cur_controlfield = []
|
||||
if tag not in self.cur_record:
|
||||
self.cur_record[tag] = self.cur_controlfield
|
||||
self.in_controlfield = True
|
||||
|
||||
elif name == "datafield":
|
||||
tag = attributes["tag"]
|
||||
self.cur_tag = tag
|
||||
ind1 = attributes["ind1"]
|
||||
if ind1 == " ": ind1 = "_"
|
||||
ind2 = attributes["ind2"]
|
||||
if ind2 == " ": ind2 = "_"
|
||||
if tag + ind1 + ind2 not in self.cur_record:
|
||||
self.cur_record[tag + ind1 + ind2] = []
|
||||
self.cur_datafield = {}
|
||||
self.cur_record[tag + ind1 + ind2].append(self.cur_datafield)
|
||||
self.in_datafield = True
|
||||
|
||||
elif name == "subfield":
|
||||
subcode = attributes["code"]
|
||||
if subcode not in self.cur_datafield:
|
||||
self.cur_subfield = []
|
||||
self.cur_datafield[subcode] = self.cur_subfield
|
||||
else:
|
||||
self.cur_subfield = self.cur_datafield[subcode]
|
||||
self.in_subfield = True
|
||||
|
||||
def characters(self, data):
|
||||
if self.in_subfield:
|
||||
self.buffer += data
|
||||
elif self.in_controlfield:
|
||||
self.buffer += data
|
||||
elif "Search-Engine-Total-Number-Of-Results:" in data:
|
||||
print(data)
|
||||
match_obj = re.search("\d+", data)
|
||||
if match_obj:
|
||||
print(int(match_obj.group()))
|
||||
self.counts = int(match_obj.group())
|
||||
|
||||
def endElement(self, name):
|
||||
if name == "record":
|
||||
self.in_record = False
|
||||
elif name == "controlfield":
|
||||
if self.cur_tag == "001":
|
||||
self.recid = int(self.buffer)
|
||||
if self.recid in self.cached_records:
|
||||
# Record has already been parsed, no need to add
|
||||
pass
|
||||
else:
|
||||
# Add record to the global cache
|
||||
self.cached_records[self.recid] = self.cur_record
|
||||
# Add record to the ordered list of results
|
||||
self.records.append(self.cached_records[self.recid])
|
||||
|
||||
self.cur_controlfield.append(self.buffer)
|
||||
self.in_controlfield = False
|
||||
self.buffer = ""
|
||||
elif name == "datafield":
|
||||
self.in_datafield = False
|
||||
elif name == "subfield":
|
||||
self.in_subfield = False
|
||||
self.cur_subfield.append(self.buffer)
|
||||
self.buffer = ""
|
||||
|
||||
|
||||
def decompose_code(code):
|
||||
"""
|
||||
Decomposes a MARC "code" into tag, ind1, ind2, subcode
|
||||
"""
|
||||
code = "%-6s" % code
|
||||
ind1 = code[3:4]
|
||||
if ind1 == " ": ind1 = "_"
|
||||
ind2 = code[4:5]
|
||||
if ind2 == " ": ind2 = "_"
|
||||
subcode = code[5:6]
|
||||
if subcode == " ": subcode = None
|
||||
return (code[0:3], ind1, ind2, subcode)
|
||||
@ -1,80 +0,0 @@
|
||||
# This file is part of the Indico plugins.
|
||||
# Copyright (C) 2002 - 2020 CERN
|
||||
#
|
||||
# The Indico plugins are free software; you can redistribute
|
||||
# them and/or modify them under the terms of the MIT License;
|
||||
# see the LICENSE file for more details.
|
||||
|
||||
from indico.util.string import strip_tags
|
||||
|
||||
from indico_importer.converter import APPEND, RecordConverter
|
||||
from indico_importer.util import convert_dt_tuple
|
||||
|
||||
|
||||
class InvenioRecordConverterBase(RecordConverter):
|
||||
"""
|
||||
Base class of every Invenio record converter. Data in Invenio records is always stored
|
||||
in the list (e.g. author's name is stored as ["Joe Doe"]), so defaultConversion method
|
||||
simply takes the first element form a list.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def default_conversion_method(attr):
|
||||
return attr[0]
|
||||
|
||||
|
||||
class InvenioAuthorConverter(InvenioRecordConverterBase):
|
||||
"""
|
||||
Converts author name, surname and affiliation.
|
||||
"""
|
||||
|
||||
conversion = [('a', 'firstName', lambda x: x[0].split(' ')[0]),
|
||||
('a', 'familyName', lambda x: ' '.join(x[0].split(' ')[1:])),
|
||||
('u', 'affiliation'),
|
||||
('e', APPEND, lambda x: {'isSpeaker': 'speaker' in x})]
|
||||
|
||||
|
||||
class InvenioPlaceTimeConverter111(InvenioRecordConverterBase):
|
||||
"""
|
||||
Extracts event's place and start/end time. Used for entry 111 in MARC 21.
|
||||
"""
|
||||
|
||||
conversion = [('9', 'startDateTime', convert_dt_tuple),
|
||||
('z', 'endDateTime', convert_dt_tuple),
|
||||
('c', 'place')]
|
||||
|
||||
|
||||
class InvenioPlaceTimeConverter518(InvenioRecordConverterBase):
|
||||
"""
|
||||
Extracts event's place and start/end time. Used for entry 518 in MARC 21.
|
||||
"""
|
||||
|
||||
conversion = [('d', 'startDateTime', convert_dt_tuple),
|
||||
('h', 'endDateTime', convert_dt_tuple),
|
||||
('r', 'place')]
|
||||
|
||||
|
||||
class InvenioLinkConverter(InvenioRecordConverterBase):
|
||||
"""
|
||||
Extracts link to the event.
|
||||
"""
|
||||
|
||||
conversion = [('y', 'name'),
|
||||
('u', 'url')]
|
||||
|
||||
|
||||
class InvenioRecordConverter(InvenioRecordConverterBase):
|
||||
"""
|
||||
Main converter class. Converts record from InvenioConverter in format readable by a plugin.
|
||||
"""
|
||||
|
||||
conversion = [('088', 'reportNumbers', lambda x: [number for number in x[0]['a'][0].split(' ') if number != '(Confidential)']),
|
||||
('100', 'primaryAuthor', lambda x: x[0] if 'Primary Author' in x[0].get('e', []) else {}, InvenioAuthorConverter),
|
||||
('100', 'speaker', lambda x: x[0] if 'Speaker' in x[0].get('e', []) else {}, InvenioAuthorConverter),
|
||||
('111', APPEND, None, InvenioPlaceTimeConverter111),
|
||||
('245', 'title', lambda x: x[0]['a'][0]),
|
||||
('518', APPEND, None, InvenioPlaceTimeConverter518),
|
||||
('520', 'summary', lambda x: strip_tags(x[0]['a'][0])),
|
||||
('700', 'secondaryAuthor', None, InvenioAuthorConverter),
|
||||
('61124', 'meetingName', lambda x: str(x[0]['a'][0])),
|
||||
('8564', 'materials', lambda x: x, InvenioLinkConverter)]
|
||||
@ -1,19 +0,0 @@
|
||||
# This file is part of the Indico plugins.
|
||||
# Copyright (C) 2002 - 2020 CERN
|
||||
#
|
||||
# The Indico plugins are free software; you can redistribute
|
||||
# them and/or modify them under the terms of the MIT License;
|
||||
# see the LICENSE file for more details.
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from wtforms.fields.html5 import URLField
|
||||
from wtforms.validators import URL
|
||||
|
||||
from indico.web.forms.base import IndicoForm
|
||||
|
||||
from indico_importer_invenio import _
|
||||
|
||||
|
||||
class SettingsForm(IndicoForm):
|
||||
server_url = URLField(_("Invenio server URL"), validators=[URL()])
|
||||
@ -1,25 +0,0 @@
|
||||
# This file is part of the Indico plugins.
|
||||
# Copyright (C) 2002 - 2020 CERN
|
||||
#
|
||||
# The Indico plugins are free software; you can redistribute
|
||||
# them and/or modify them under the terms of the MIT License;
|
||||
# see the LICENSE file for more details.
|
||||
|
||||
from flask_pluginengine import current_plugin
|
||||
|
||||
from indico_importer import ImporterEngineBase
|
||||
|
||||
from .connector import InvenioConnector
|
||||
from .converters import InvenioRecordConverter
|
||||
|
||||
|
||||
class InvenioImporter(ImporterEngineBase):
|
||||
"""Fetches and converts data from CDS Invenio"""
|
||||
|
||||
_id = 'invenio'
|
||||
name = 'CDS Invenio'
|
||||
|
||||
def import_data(self, query, size):
|
||||
url = current_plugin.settings.get('server_url')
|
||||
registers = InvenioConnector(url).search(p=query, rg=size)
|
||||
return InvenioRecordConverter.convert(registers)
|
||||
@ -1,24 +0,0 @@
|
||||
# This file is part of the Indico plugins.
|
||||
# Copyright (C) 2002 - 2020 CERN
|
||||
#
|
||||
# The Indico plugins are free software; you can redistribute
|
||||
# them and/or modify them under the terms of the MIT License;
|
||||
# see the LICENSE file for more details.
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from indico_importer import ImporterSourcePluginBase
|
||||
|
||||
from .forms import SettingsForm
|
||||
from .importer import InvenioImporter
|
||||
|
||||
|
||||
class ImporterInvenioPlugin(ImporterSourcePluginBase):
|
||||
"""Importer for Invenio
|
||||
|
||||
Adds Invenio importer to Indico timetable import sources.
|
||||
"""
|
||||
configurable = True
|
||||
settings_form = SettingsForm
|
||||
default_settings = {'server_url': ''}
|
||||
importer_engine_classes = (InvenioImporter,)
|
||||
@ -1,23 +0,0 @@
|
||||
# Translations template for PROJECT.
|
||||
# Copyright (C) 2015 ORGANIZATION
|
||||
# This file is distributed under the same license as the PROJECT project.
|
||||
#
|
||||
# Translators:
|
||||
# Thomas Baron <thomas.baron@cern.ch>, 2015
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Indico\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2015-03-11 16:21+0100\n"
|
||||
"PO-Revision-Date: 2015-03-12 09:51+0000\n"
|
||||
"Last-Translator: Thomas Baron <thomas.baron@cern.ch>\n"
|
||||
"Language-Team: French (France) (http://www.transifex.com/projects/p/indico/language/fr_FR/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 1.3\n"
|
||||
"Language: fr_FR\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
|
||||
msgid "Indico"
|
||||
msgstr "Indico"
|
||||
@ -1,24 +0,0 @@
|
||||
# Translations template for PROJECT.
|
||||
# Copyright (C) 2017 ORGANIZATION
|
||||
# This file is distributed under the same license as the PROJECT project.
|
||||
#
|
||||
# Translators:
|
||||
# Thomas Baron <thomas.baron@cern.ch>, 2015
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Indico\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2017-10-18 11:55+0200\n"
|
||||
"PO-Revision-Date: 2017-09-23 20:55+0000\n"
|
||||
"Last-Translator: Thomas Baron <thomas.baron@cern.ch>\n"
|
||||
"Language-Team: French (France) (http://www.transifex.com/indico/indico/language/fr_FR/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.5.1\n"
|
||||
"Language: fr_FR\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
|
||||
#: indico_importer_invenio/forms.py:28
|
||||
msgid "Invenio server URL"
|
||||
msgstr "URL du serveur Invenio"
|
||||
@ -1,23 +0,0 @@
|
||||
# Translations template for PROJECT.
|
||||
# Copyright (C) 2020 ORGANIZATION
|
||||
# This file is distributed under the same license as the PROJECT project.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2020.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PROJECT VERSION\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2020-08-19 20:40+0200\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.8.0\n"
|
||||
|
||||
#: indico_importer_invenio/forms.py:19
|
||||
msgid "Invenio server URL"
|
||||
msgstr ""
|
||||
|
||||
@ -1,35 +0,0 @@
|
||||
# This file is part of the Indico plugins.
|
||||
# Copyright (C) 2002 - 2020 CERN
|
||||
#
|
||||
# The Indico plugins are free software; you can redistribute
|
||||
# them and/or modify them under the terms of the MIT License;
|
||||
# see the LICENSE file for more details.
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from setuptools import find_packages, setup
|
||||
|
||||
|
||||
setup(
|
||||
name='indico-plugin-importer-invenio',
|
||||
version='2.2',
|
||||
description='Invenio data source for the Indico Importer plugin',
|
||||
url='https://github.com/indico/indico-plugins',
|
||||
license='MIT',
|
||||
author='Indico Team',
|
||||
author_email='indico-team@cern.ch',
|
||||
packages=find_packages(),
|
||||
zip_safe=False,
|
||||
include_package_data=True,
|
||||
install_requires=[
|
||||
'indico>=2.2.dev0',
|
||||
'indico-plugin-importer>=2.2'
|
||||
],
|
||||
classifiers=[
|
||||
'Environment :: Plugins',
|
||||
'Environment :: Web Environment',
|
||||
'License :: OSI Approved :: MIT License',
|
||||
'Programming Language :: Python :: 2.7'
|
||||
],
|
||||
entry_points={'indico.plugins': {'importer_invenio = indico_importer_invenio.plugin:ImporterInvenioPlugin'}}
|
||||
)
|
||||
@ -1,5 +0,0 @@
|
||||
graft indico_search/static
|
||||
graft indico_search/templates
|
||||
graft indico_search/translations
|
||||
|
||||
global-exclude *.pyc __pycache__ .keep
|
||||
@ -1,17 +0,0 @@
|
||||
# This file is part of the Indico plugins.
|
||||
# Copyright (C) 2002 - 2020 CERN
|
||||
#
|
||||
# The Indico plugins are free software; you can redistribute
|
||||
# them and/or modify them under the terms of the MIT License;
|
||||
# see the LICENSE file for more details.
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from indico.util.i18n import make_bound_gettext
|
||||
|
||||
|
||||
_ = make_bound_gettext('search')
|
||||
__all__ = ('SearchPluginBase', 'SearchEngine', 'SearchForm')
|
||||
|
||||
from .base import SearchPluginBase, SearchEngine # isort:skip
|
||||
from .forms import SearchForm # isort:skip
|
||||
@ -1,65 +0,0 @@
|
||||
# This file is part of the Indico plugins.
|
||||
# Copyright (C) 2002 - 2020 CERN
|
||||
#
|
||||
# The Indico plugins are free software; you can redistribute
|
||||
# them and/or modify them under the terms of the MIT License;
|
||||
# see the LICENSE file for more details.
|
||||
|
||||
from flask import session
|
||||
from flask_pluginengine import depends
|
||||
|
||||
from indico.core.plugins import IndicoPlugin, PluginCategory
|
||||
|
||||
from indico_search.forms import SearchForm
|
||||
from indico_search.plugin import SearchPlugin
|
||||
|
||||
|
||||
@depends('search')
|
||||
class SearchPluginBase(IndicoPlugin):
|
||||
"""Base class for search engine plugins"""
|
||||
|
||||
#: the SearchEngine subclass to use
|
||||
engine_class = None
|
||||
#: the SearchForm subclass to use
|
||||
search_form = SearchForm
|
||||
category = PluginCategory.search
|
||||
|
||||
def init(self):
|
||||
super(SearchPluginBase, self).init()
|
||||
SearchPlugin.instance.engine_plugin = self
|
||||
|
||||
@property
|
||||
def only_public(self):
|
||||
"""If the search engine only returns public events"""
|
||||
return session.user is None
|
||||
|
||||
def perform_search(self, values, obj=None, obj_type=None):
|
||||
"""Performs the search.
|
||||
|
||||
For documentation on the parameters and return value, see
|
||||
the documentation of the :class:`SearchEngine` class.
|
||||
"""
|
||||
return self.engine_class(values, obj, obj_type).process()
|
||||
|
||||
|
||||
class SearchEngine(object):
|
||||
"""Base class for a search engine"""
|
||||
|
||||
def __init__(self, values, obj, obj_type):
|
||||
"""
|
||||
:param values: the values sent by the user
|
||||
:param obj: object to search in (a `Category` or `Conference`)
|
||||
"""
|
||||
self.values = values
|
||||
self.obj = obj
|
||||
self.obj_type = obj_type
|
||||
self.user = session.user
|
||||
|
||||
def process(self):
|
||||
"""Executes the search
|
||||
|
||||
:return: an object that's passed directly to the result template.
|
||||
if a flask response is returned, it is sent to the client
|
||||
instead (useful to redirect to an external page)
|
||||
"""
|
||||
raise NotImplementedError
|
||||
@ -1,21 +0,0 @@
|
||||
# This file is part of the Indico plugins.
|
||||
# Copyright (C) 2002 - 2020 CERN
|
||||
#
|
||||
# The Indico plugins are free software; you can redistribute
|
||||
# them and/or modify them under the terms of the MIT License;
|
||||
# see the LICENSE file for more details.
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from indico.core.plugins import IndicoPluginBlueprint
|
||||
|
||||
from indico_search.controllers import RHSearch, RHSearchCategoryTitles
|
||||
|
||||
|
||||
blueprint = IndicoPluginBlueprint('search', 'indico_search')
|
||||
|
||||
blueprint.add_url_rule('/search', 'search', RHSearch)
|
||||
blueprint.add_url_rule('/category/<int:category_id>/search', 'search', RHSearch)
|
||||
blueprint.add_url_rule('/event/<confId>/search', 'search', RHSearch)
|
||||
|
||||
blueprint.add_url_rule('/category/search-titles', 'category_names', RHSearchCategoryTitles)
|
||||
@ -1,532 +0,0 @@
|
||||
// This file is part of the Indico plugins.
|
||||
// Copyright (C) 2002 - 2020 CERN
|
||||
//
|
||||
// The Indico plugins are free software; you can redistribute
|
||||
// them and/or modify them under the terms of the MIT License;
|
||||
// see the LICENSE file for more details.
|
||||
|
||||
import './main.scss';
|
||||
|
||||
(function(global) {
|
||||
const $t = $T.domain('search');
|
||||
|
||||
type(
|
||||
'IntelligentSearchBox',
|
||||
['RealtimeTextBox'],
|
||||
{
|
||||
_highlight(elem, token) {
|
||||
// Code for highlighting the matched part of the string
|
||||
const idx = elem.toLowerCase().indexOf(token.toLowerCase());
|
||||
if (idx >= 0) {
|
||||
const before = elem.slice(0, idx);
|
||||
const after = elem.slice(idx + token.length, elem.length);
|
||||
return [before, Html.span('highlight', elem.slice(idx, idx + token.length)), after];
|
||||
}
|
||||
},
|
||||
|
||||
_truncateTitle(title) {
|
||||
const max = 27;
|
||||
if (title.length > max) {
|
||||
return `${title.slice(0, max / 2)} ... ${title.slice(-max / 2)}`;
|
||||
} else {
|
||||
return title;
|
||||
}
|
||||
},
|
||||
|
||||
_showSuggestions(text, suggList, totalNumber) {
|
||||
const self = this;
|
||||
if (!this.suggestionBox) {
|
||||
// Create the suggestion box only once
|
||||
|
||||
// totalNumber will change during time
|
||||
this.totalNumber = new WatchValue(totalNumber);
|
||||
this.suggestionList = Html.ul();
|
||||
|
||||
const infoBox = Html.div('help', $t.gettext('Some category suggestions...'));
|
||||
|
||||
// Create the suggestion box
|
||||
this.suggestionBox = Html.div(
|
||||
{
|
||||
style: {
|
||||
position: 'absolute',
|
||||
top: pixels(23),
|
||||
left: 0,
|
||||
},
|
||||
className: 'suggestion-box',
|
||||
},
|
||||
infoBox,
|
||||
this.suggestionList,
|
||||
$B(Html.div('other-results'), this.totalNumber, function(number) {
|
||||
return number === 0
|
||||
? ''
|
||||
: `${number} ${$t.gettext('other results - please write more...')}`;
|
||||
})
|
||||
);
|
||||
|
||||
this.container.append(this.suggestionBox);
|
||||
}
|
||||
|
||||
// Prepare regular expression for highlighting
|
||||
const tokens = _.filter(escapeHTML(text).split(' '), function(t) {
|
||||
return t !== '';
|
||||
});
|
||||
const tokenRE = new RegExp(`(${tokens.join('|')})`, 'gi');
|
||||
|
||||
// Bind the list to the DOM element
|
||||
let counter = 0;
|
||||
|
||||
$B(this.suggestionList, suggList, function(elem) {
|
||||
const titleHtml = escapeHTML(self._truncateTitle(elem.title)).replace(
|
||||
tokenRE,
|
||||
'<span class="highlight">$1</span>'
|
||||
);
|
||||
const index = counter;
|
||||
const title = Html.span('title');
|
||||
title.dom.innerHTML = titleHtml;
|
||||
const pathText = Util.truncateCategPath(elem.path).join(' >> ');
|
||||
const path = Html.div('path', pathText);
|
||||
const liItem = Html.li({}, title, path);
|
||||
|
||||
// Use mouse to control selector as well
|
||||
|
||||
liItem.observeEvent('mouseover', function() {
|
||||
if (self.selectorPos != index) {
|
||||
self._clearSelector();
|
||||
self.selectorPos = index;
|
||||
self._setSelector();
|
||||
}
|
||||
});
|
||||
|
||||
liItem.observeClick(function() {
|
||||
window.location = elem.url;
|
||||
});
|
||||
|
||||
counter++;
|
||||
return liItem;
|
||||
});
|
||||
|
||||
this.suggestionList.observeEvent('mouseout', function() {
|
||||
self._clearSelector();
|
||||
self.selectorPos = -1;
|
||||
});
|
||||
|
||||
// Update
|
||||
this.totalNumber.set(totalNumber);
|
||||
},
|
||||
|
||||
_hideSuggestions() {
|
||||
this.container.remove(this.suggestionBox);
|
||||
this.suggestionBox = null;
|
||||
this.selectorPos = -1;
|
||||
this.suggestions = null;
|
||||
},
|
||||
|
||||
_retrieveOptions(expression) {
|
||||
const self = this;
|
||||
this.querying = true;
|
||||
|
||||
$.ajax({
|
||||
url: self.searchUrl,
|
||||
type: 'GET',
|
||||
data: {
|
||||
term: expression,
|
||||
},
|
||||
dataType: 'json',
|
||||
complete() {
|
||||
self.querying = false;
|
||||
self.timeOfLastQuery = new Date().getTime();
|
||||
},
|
||||
success(data) {
|
||||
if (handleAjaxError(data)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.results.length) {
|
||||
self.suggestions = data.results;
|
||||
self._showSuggestions(expression, data.results, data.count - data.results.length);
|
||||
} else {
|
||||
self._hideSuggestions();
|
||||
}
|
||||
|
||||
const currentText = trim(self.get());
|
||||
|
||||
// if the text changed meanwhile and it
|
||||
// is still long enough
|
||||
if (currentText != expression && currentText.length > 1) {
|
||||
// request
|
||||
self._textTyped();
|
||||
} else if (currentText.length <= 1) {
|
||||
// if it is not long enough
|
||||
self._hideSuggestions();
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
_getTimeSinceLastQuery() {
|
||||
const now = new Date();
|
||||
return now.getTime() - this.timeOfLastQuery;
|
||||
},
|
||||
|
||||
_waitForRequestTime() {
|
||||
const self = this;
|
||||
if (!this.queuedRequest) {
|
||||
// This should never happen...
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._getTimeSinceLastQuery() > 1000) {
|
||||
this._textTyped();
|
||||
this.queuedRequest = false;
|
||||
} else {
|
||||
setTimeout(function() {
|
||||
self._waitForRequestTime();
|
||||
}, 300);
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
* Called each time a new character is typed
|
||||
* strips white spaces, and calls for a request if needed
|
||||
*/
|
||||
_textTyped(key) {
|
||||
const self = this;
|
||||
const text = trim(this.get());
|
||||
|
||||
if (text.length > 1) {
|
||||
// if we're not already querying and enough time has passed
|
||||
// since the last request
|
||||
if (!this.querying && this._getTimeSinceLastQuery() > 1000) {
|
||||
this._retrieveOptions(text);
|
||||
} else if (!this.queuedRequest) {
|
||||
// otherwise, if we can't do a request right now
|
||||
// and no other request is queued
|
||||
this.queuedRequest = true;
|
||||
|
||||
setTimeout(function() {
|
||||
self._waitForRequestTime();
|
||||
}, 300);
|
||||
}
|
||||
} else if (this.suggestionBox) {
|
||||
this._hideSuggestions();
|
||||
}
|
||||
},
|
||||
|
||||
_openSelection(event) {
|
||||
if (this.selectorPos >= 0) {
|
||||
window.location = this.suggestions[this.selectorPos].url;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
/*
|
||||
* Move the selector (gray area) up or down
|
||||
*/
|
||||
_moveSelector(direction) {
|
||||
if (this.suggestionBox) {
|
||||
const suggNum = this.suggestionList.length.get();
|
||||
|
||||
if (this.selectorPos < 0) {
|
||||
this.selectorPos = direction == 'down' ? 0 : suggNum - 1;
|
||||
} else {
|
||||
this._clearSelector();
|
||||
this.selectorPos += direction == 'up' ? -1 : 1;
|
||||
|
||||
if (this.selectorPos >= suggNum) {
|
||||
this.selectorPos = -1;
|
||||
} else if (this.selectorPos < 0) {
|
||||
this.selectorPos = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this._setSelector();
|
||||
},
|
||||
|
||||
_setSelector() {
|
||||
if (this.selectorPos >= 0) {
|
||||
this.suggestionList.item(this.selectorPos).dom.className = 'selected';
|
||||
}
|
||||
},
|
||||
|
||||
_clearSelector() {
|
||||
if (this.selectorPos >= 0) {
|
||||
this.suggestionList.item(this.selectorPos).dom.className = '';
|
||||
}
|
||||
},
|
||||
|
||||
isAnyItemSelected() {
|
||||
return this.selectorPos > 0;
|
||||
},
|
||||
},
|
||||
function(args, container, searchUrl) {
|
||||
args.autocomplete = 'off';
|
||||
this.RealtimeTextBox(args);
|
||||
this.selectorPos = -1;
|
||||
this.querying = false;
|
||||
this.container = container;
|
||||
this.timeOfLastQuery = 0;
|
||||
this.searchUrl = searchUrl;
|
||||
|
||||
const self = this;
|
||||
|
||||
this.observe(function(key, event) {
|
||||
self._textTyped(key);
|
||||
return true;
|
||||
});
|
||||
|
||||
this.observeOtherKeys(function(text, key, event) {
|
||||
if (key == 38 || key == 40) {
|
||||
self._moveSelector(key == 38 ? 'up' : 'down');
|
||||
return false;
|
||||
} else if (key == 27) {
|
||||
self._hideSuggestions();
|
||||
return false;
|
||||
} else if (key == 13) {
|
||||
return self._openSelection(event);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
$E(document.body).observeClick(function(event) {
|
||||
// Close box if a click is done outside of it
|
||||
|
||||
/* for some unknown reason, onclick is called on the submit button,
|
||||
* as soon as the return key is pressed in any of the textfields.
|
||||
* To make it even better, onclick is called before onkeyup,
|
||||
* which forces us to do the last two checks.
|
||||
*/
|
||||
|
||||
if (
|
||||
self.suggestionBox &&
|
||||
!self.suggestionList.ancestorOf($E(eventTarget(event))) &&
|
||||
$E(eventTarget(event)) != self.input
|
||||
) {
|
||||
self._hideSuggestions();
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
$.widget('indico.search_tag', {
|
||||
options: {
|
||||
categ_title: 'Home',
|
||||
everywhere: true,
|
||||
search_category_url: '#',
|
||||
search_url: '#',
|
||||
form: null,
|
||||
},
|
||||
|
||||
_transition(title, no_check) {
|
||||
const $tag = this.$tag;
|
||||
|
||||
$tag
|
||||
.fadeTo('fast', 0.3)
|
||||
.find('.where')
|
||||
.html(title);
|
||||
|
||||
$tag
|
||||
.fadeTo('fast', 0.5)
|
||||
},
|
||||
|
||||
_create() {
|
||||
const self = this;
|
||||
|
||||
const tag_template = _.template(
|
||||
'<div class="search-tag">' +
|
||||
'<div class="where"><%= categ_title %></div>' +
|
||||
'<div class="cross">x</div>' +
|
||||
'</div>'
|
||||
);
|
||||
|
||||
const $tag = (this.$tag = $(
|
||||
tag_template({
|
||||
categ_title: this.options.everywhere
|
||||
? $t.gettext('Everywhere')
|
||||
: this.options.categ_title,
|
||||
})
|
||||
));
|
||||
|
||||
$(this.element).replaceWith($tag);
|
||||
const $where = $('.where', $tag);
|
||||
|
||||
if (this.options.everywhere) {
|
||||
$tag.addClass('everywhere');
|
||||
$('.cross', this.$tag).hide();
|
||||
} else {
|
||||
$tag.addClass('in-category');
|
||||
$('.cross', this.$tag).show();
|
||||
}
|
||||
|
||||
$('.cross', $tag).on('click', function() {
|
||||
self.search_everywhere();
|
||||
});
|
||||
|
||||
const $parent = $tag.parent();
|
||||
|
||||
$parent.on('mouseenter', '.search-tag.everywhere', function() {
|
||||
self.show_categ();
|
||||
});
|
||||
$parent.on('mouseover', '.search-tag.everywhere', function(e) {
|
||||
self.show_tip(e);
|
||||
});
|
||||
$parent.on('mouseleave', '.search-tag.everywhere', function() {
|
||||
$where.removeData('hasFocus');
|
||||
});
|
||||
$parent.on('click', '.search-tag.in-category-over', function() {
|
||||
self.confirm_tip();
|
||||
});
|
||||
$parent.on('mouseleave', '.search-tag.in-category-over', function() {
|
||||
self.back_to_everywhere();
|
||||
});
|
||||
},
|
||||
|
||||
confirm_tip() {
|
||||
const $where = $('.where', this.$tag);
|
||||
const $tag = this.$tag;
|
||||
|
||||
$tag.qtip('destroy');
|
||||
$where.fadeOut('fast', function() {
|
||||
$(this).fadeIn('fast');
|
||||
});
|
||||
|
||||
this.$tag
|
||||
.addClass('in-category')
|
||||
.removeClass('everywhere')
|
||||
.removeClass('in-category-over');
|
||||
this.options.form.attr('action', this.options.search_category_url);
|
||||
|
||||
$tag.animate(
|
||||
200,
|
||||
'swing',
|
||||
function() {
|
||||
$('.cross', $tag).fadeIn('fast');
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
search_everywhere() {
|
||||
$('.cross', this.$tag).hide();
|
||||
this.$tag.addClass('everywhere').removeClass('in-category');
|
||||
this._transition($t.gettext('Everywhere'), true);
|
||||
this.options.form.attr('action', this.options.search_url);
|
||||
},
|
||||
|
||||
show_categ() {
|
||||
const $tag = this.$tag;
|
||||
const $where = $('.where', $tag);
|
||||
const self = this;
|
||||
$where.data('hasFocus', true);
|
||||
setTimeout(function() {
|
||||
if ($where.data('hasFocus')) {
|
||||
self._transition(self.options.categ_title);
|
||||
$tag.addClass('in-category-over');
|
||||
}
|
||||
}, 200);
|
||||
},
|
||||
|
||||
show_tip(event) {
|
||||
this.$tag.qtip(
|
||||
{
|
||||
content: format($t.gettext('Click to search inside <span class="label">{title}</span>'), {
|
||||
title: this.options.categ_title,
|
||||
}),
|
||||
position: {
|
||||
at: 'bottom center',
|
||||
my: 'top center',
|
||||
},
|
||||
show: {
|
||||
event: event.type,
|
||||
ready: true,
|
||||
},
|
||||
},
|
||||
event
|
||||
);
|
||||
},
|
||||
|
||||
back_to_everywhere() {
|
||||
const $where = $('.where', this.$tag);
|
||||
const self = this;
|
||||
|
||||
this.$tag.removeClass('in-category-over');
|
||||
$where.removeData('hasFocus');
|
||||
setTimeout(function() {
|
||||
if (!$where.data('hasFocus')) {
|
||||
self._transition($t.gettext('Everywhere'), true);
|
||||
self.$tag.addClass('everywhere');
|
||||
}
|
||||
}, 200);
|
||||
},
|
||||
});
|
||||
|
||||
global.categorySearchBox = function categorySearchBox(options) {
|
||||
// expected options: categoryNamesUrl, searchUrl, searchCategoryUrl, categoryName, isRoot
|
||||
const form = $('#category-search-form');
|
||||
const extra = form.find('.extra-options');
|
||||
const controls = form.find('.search-controls');
|
||||
let extraConfigured = false;
|
||||
$('#category-search-expand').on('click', function() {
|
||||
if (extra.is(':visible')) {
|
||||
extra.slideUp('fast');
|
||||
} else {
|
||||
extra.slideDown('fast');
|
||||
if (!extraConfigured) {
|
||||
extraConfigured = true;
|
||||
extra.css('display', 'table').position({
|
||||
of: controls,
|
||||
my: 'right top',
|
||||
at: 'right bottom',
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function verifyForm() {
|
||||
const startDate = $('#search-start_date').val();
|
||||
const endDate = $('#search-end_date').val();
|
||||
return (
|
||||
(!startDate || Util.parseDateTime(startDate, IndicoDateTimeFormats.DefaultHourless)) &&
|
||||
(!endDate || Util.parseDateTime(endDate, IndicoDateTimeFormats.DefaultHourless))
|
||||
);
|
||||
}
|
||||
|
||||
const intelligentSearchBox = new IntelligentSearchBox(
|
||||
{
|
||||
name: 'search-phrase',
|
||||
id: 'search-phrase',
|
||||
style: {backgroundColor: 'transparent', outline: 'none'},
|
||||
},
|
||||
$E('category-search-box'),
|
||||
options.categoryNamesUrl
|
||||
);
|
||||
|
||||
$E('search-phrase').replaceWith(intelligentSearchBox.draw());
|
||||
|
||||
$('.search-button').on('click', function() {
|
||||
if (verifyForm()) {
|
||||
$('#category-search-form').submit();
|
||||
}
|
||||
});
|
||||
|
||||
$('#search-phrase').on('keypress', function(e) {
|
||||
if (e.which == 13 && !intelligentSearchBox.isAnyItemSelected()) {
|
||||
if (verifyForm()) {
|
||||
$('#category-search-form').submit();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!options.isRoot) {
|
||||
$('#category-search-form .search-button').before($('<div class="search-tag">'));
|
||||
$('.search-tag').search_tag({
|
||||
everywhere: true,
|
||||
categ_title: options.categoryName,
|
||||
form: $('#category-search-form'),
|
||||
search_url: options.searchUrl,
|
||||
search_category_url: options.searchCategoryUrl,
|
||||
});
|
||||
}
|
||||
};
|
||||
})(window);
|
||||
@ -1,281 +0,0 @@
|
||||
// This file is part of the Indico plugins.
|
||||
// Copyright (C) 2002 - 2020 CERN
|
||||
//
|
||||
// The Indico plugins are free software; you can redistribute
|
||||
// them and/or modify them under the terms of the MIT License;
|
||||
// see the LICENSE file for more details.
|
||||
|
||||
#search-advanced-help-tooltip {
|
||||
display: none;
|
||||
|
||||
code {
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.field {
|
||||
color: #8a8a45;
|
||||
}
|
||||
|
||||
.negation {
|
||||
color: #b00;
|
||||
}
|
||||
}
|
||||
|
||||
.search-container {
|
||||
.search-public-warning {
|
||||
float: right;
|
||||
padding: 10px;
|
||||
color: #f44;
|
||||
}
|
||||
|
||||
.search-banner {
|
||||
float: right;
|
||||
margin-top: 10px;
|
||||
|
||||
& > span {
|
||||
color: #777;
|
||||
}
|
||||
|
||||
img {
|
||||
vertical-align: middle;
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
form {
|
||||
width: 400px;
|
||||
|
||||
#search-phrase {
|
||||
width: 300px;
|
||||
height: 20px;
|
||||
font-size: 17px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
input[type='submit'] {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.toggle-advanced-options-container {
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
.advanced-options > table {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
color: #b14300;
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
#category-search-form {
|
||||
.search-box {
|
||||
position: absolute;
|
||||
right: 1.5em;
|
||||
top: 50px;
|
||||
border: none;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.search-button {
|
||||
float: right;
|
||||
border-left: 1px solid #003042;
|
||||
width: 28px;
|
||||
height: 21px;
|
||||
line-height: 21px;
|
||||
cursor: pointer;
|
||||
background: #444;
|
||||
text-align: center;
|
||||
color: #fafafa;
|
||||
}
|
||||
|
||||
.search-controls {
|
||||
width: auto;
|
||||
border: 1px solid #003042;
|
||||
height: 21px;
|
||||
overflow: hidden;
|
||||
background-color: white;
|
||||
|
||||
input {
|
||||
border: 0;
|
||||
font-weight: normal;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
.search-field {
|
||||
margin-left: 3px;
|
||||
min-width: 150px;
|
||||
width: auto;
|
||||
height: 19px;
|
||||
}
|
||||
|
||||
.extra-options {
|
||||
border: 1px solid #888;
|
||||
border-radius: 6px;
|
||||
min-width: 275px;
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 23px;
|
||||
right: 0;
|
||||
background-color: white;
|
||||
overflow: visible;
|
||||
|
||||
table {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
table td:first-child {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
input {
|
||||
margin-left: 8px;
|
||||
font-size: 12px;
|
||||
width: 148px;
|
||||
}
|
||||
|
||||
select {
|
||||
margin-left: 8px;
|
||||
font-size: 12px;
|
||||
width: 148px;
|
||||
}
|
||||
|
||||
td {
|
||||
font-size: 12px;
|
||||
color: #444;
|
||||
}
|
||||
|
||||
.label {
|
||||
text-align: center;
|
||||
padding: 5px;
|
||||
border-radius: 6px 6px 0 0;
|
||||
background-color: #ececec;
|
||||
font-style: italic;
|
||||
border-bottom: 1px solid #aaa;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.search-tag {
|
||||
max-width: 112px;
|
||||
display: flex;
|
||||
line-height: 13px;
|
||||
vertical-align: middle;
|
||||
padding: 2px 5px;
|
||||
float: left;
|
||||
-ms-user-select: none;
|
||||
-o-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: -moz-none;
|
||||
user-select: none;
|
||||
font-size: 11px;
|
||||
overflow: hidden;
|
||||
opacity: 0.6;
|
||||
color: #000;
|
||||
|
||||
.where {
|
||||
max-width: 100px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.cross {
|
||||
font-weight: bold;
|
||||
color: #566e89;
|
||||
float: right;
|
||||
cursor: pointer;
|
||||
font-size: 10px;
|
||||
margin-left: 4px;
|
||||
line-height: 12px;
|
||||
}
|
||||
|
||||
&.everywhere {
|
||||
border: 1px solid #566e89;
|
||||
text-shadow: 0 1px 0 #666;
|
||||
background-color: #aaa;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
&.in-category {
|
||||
border: 1px solid #566e89;
|
||||
background-color: #8ea1b7;
|
||||
text-shadow: 0 1px 0 #666;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
&.in-category-over {
|
||||
border: 1px solid #566e89;
|
||||
background-color: #8ea1b7;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.suggestion-box {
|
||||
border: 1px solid #888;
|
||||
width: 220px;
|
||||
overflow: hidden;
|
||||
background-color: white;
|
||||
|
||||
.help {
|
||||
background-color: #fff;
|
||||
color: #555;
|
||||
font-size: 12px;
|
||||
font-style: italic;
|
||||
margin: 0 0 0;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.other-results {
|
||||
background-color: #fff;
|
||||
color: #777;
|
||||
font-size: 12px;
|
||||
font-style: italic;
|
||||
text-align: center;
|
||||
margin-top: 5px;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
ul {
|
||||
background-color: #fff;
|
||||
padding: 3px;
|
||||
margin: 0 0 0 0;
|
||||
|
||||
li {
|
||||
list-style-type: none;
|
||||
padding: 3px;
|
||||
border-top: 1px solid #ddd;
|
||||
cursor: pointer;
|
||||
|
||||
&.selected {
|
||||
background-color: #ececec;
|
||||
}
|
||||
|
||||
.title {
|
||||
color: #0b63a5;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.highlight {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.path {
|
||||
color: #444;
|
||||
font-size: 9px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,62 +0,0 @@
|
||||
# This file is part of the Indico plugins.
|
||||
# Copyright (C) 2002 - 2020 CERN
|
||||
#
|
||||
# The Indico plugins are free software; you can redistribute
|
||||
# them and/or modify them under the terms of the MIT License;
|
||||
# see the LICENSE file for more details.
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from flask import jsonify, request
|
||||
from flask_pluginengine import current_plugin
|
||||
from sqlalchemy.orm import undefer
|
||||
from werkzeug.wrappers import Response
|
||||
|
||||
from indico.modules.categories import Category
|
||||
from indico.modules.events import Event
|
||||
from indico.web.rh import RH
|
||||
|
||||
from indico_search.views import WPSearchCategory, WPSearchConference
|
||||
|
||||
|
||||
class RHSearch(RH):
|
||||
"""Performs a search using the search engine plugin"""
|
||||
|
||||
def _process_args(self):
|
||||
if 'confId' in request.view_args:
|
||||
self.obj = Event.get_or_404(request.view_args['confId'], is_deleted=False)
|
||||
self.obj_type = 'event'
|
||||
elif 'category_id' in request.view_args:
|
||||
self.obj = Category.get_or_404(request.view_args['category_id'], is_deleted=False)
|
||||
self.obj_type = 'category' if not self.obj.is_root else None
|
||||
else:
|
||||
self.obj = Category.get_root()
|
||||
self.obj_type = None
|
||||
|
||||
def _process(self):
|
||||
with current_plugin.engine_plugin.plugin_context():
|
||||
form = current_plugin.search_form(formdata=request.args, prefix='search-', csrf_enabled=False)
|
||||
result = None
|
||||
if form.validate_on_submit():
|
||||
result = current_plugin.perform_search(form.data, self.obj, self.obj_type)
|
||||
if isinstance(result, Response): # probably a redirect or a json response
|
||||
return result
|
||||
view_class = WPSearchConference if isinstance(self.obj, Event) else WPSearchCategory
|
||||
return view_class.render_template('results.html', self.obj, only_public=current_plugin.only_public,
|
||||
form=form, obj_type=self.obj_type, result=result)
|
||||
|
||||
|
||||
class RHSearchCategoryTitles(RH):
|
||||
"""Searches for categories with matching titles"""
|
||||
def _process(self):
|
||||
query = (Category.query
|
||||
.filter(Category.title_matches(request.args['term']),
|
||||
~Category.is_deleted)
|
||||
.options(undefer('chain_titles'))
|
||||
.order_by(Category.title))
|
||||
results = [{
|
||||
'title': category.title,
|
||||
'path': category.chain_titles[1:-1],
|
||||
'url': unicode(category.url)
|
||||
} for category in query.limit(7)]
|
||||
return jsonify(success=True, results=results, count=query.count())
|
||||
@ -1,35 +0,0 @@
|
||||
# This file is part of the Indico plugins.
|
||||
# Copyright (C) 2002 - 2020 CERN
|
||||
#
|
||||
# The Indico plugins are free software; you can redistribute
|
||||
# them and/or modify them under the terms of the MIT License;
|
||||
# see the LICENSE file for more details.
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from flask import request
|
||||
from wtforms.fields.core import SelectField, StringField
|
||||
from wtforms.validators import Optional
|
||||
|
||||
from indico.web.forms.base import IndicoForm
|
||||
from indico.web.forms.fields import IndicoDateField
|
||||
|
||||
from indico_search import _
|
||||
|
||||
|
||||
FIELD_CHOICES = [('', _('Anywhere')),
|
||||
('title', _('Title')),
|
||||
('abstract', _('Description/Abstract')),
|
||||
('author', _('Author/Speaker')),
|
||||
('affiliation', _('Affiliation')),
|
||||
('keyword', _('Keyword'))]
|
||||
|
||||
|
||||
class SearchForm(IndicoForm):
|
||||
phrase = StringField(_('Phrase'))
|
||||
field = SelectField(_('Search in'), choices=FIELD_CHOICES, default='')
|
||||
start_date = IndicoDateField('Start Date', [Optional()])
|
||||
end_date = IndicoDateField('End Date', [Optional()])
|
||||
|
||||
def is_submitted(self):
|
||||
return 'search-phrase' in request.args
|
||||
@ -1,64 +0,0 @@
|
||||
# This file is part of the Indico plugins.
|
||||
# Copyright (C) 2002 - 2020 CERN
|
||||
#
|
||||
# The Indico plugins are free software; you can redistribute
|
||||
# them and/or modify them under the terms of the MIT License;
|
||||
# see the LICENSE file for more details.
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
|
||||
from flask import g, request
|
||||
from flask_pluginengine import plugins_loaded
|
||||
|
||||
from indico.core.plugins import IndicoPlugin, PluginCategory
|
||||
from indico.modules.events.layout import layout_settings
|
||||
from indico.web.views import WPBase
|
||||
|
||||
from indico_search.blueprint import blueprint
|
||||
from indico_search.util import render_engine_or_search_template
|
||||
|
||||
|
||||
class SearchPlugin(IndicoPlugin):
|
||||
"""Search
|
||||
|
||||
Provides a base for search engine plugins.
|
||||
"""
|
||||
category = PluginCategory.search
|
||||
_engine_plugin = None # the search engine plugin
|
||||
|
||||
def init(self):
|
||||
super(SearchPlugin, self).init()
|
||||
self.connect(plugins_loaded, self._plugins_loaded, sender=self.app)
|
||||
self.template_hook('conference-header-right-column', self._add_conference_search_box)
|
||||
self.template_hook('page-header', self._add_category_search_box)
|
||||
self.inject_bundle('main.js', WPBase)
|
||||
self.inject_bundle('main.css', WPBase)
|
||||
|
||||
def _plugins_loaded(self, sender, **kwargs):
|
||||
if not self.engine_plugin and 'INDICO_DUMPING_URLS' not in os.environ:
|
||||
raise RuntimeError('Search plugin active but no search engine plugin loaded')
|
||||
|
||||
@property
|
||||
def engine_plugin(self):
|
||||
return self._engine_plugin
|
||||
|
||||
@engine_plugin.setter
|
||||
def engine_plugin(self, value):
|
||||
if self._engine_plugin is not None:
|
||||
raise RuntimeError('Another search engine plugin is active: {}'.format(self._engine_plugin.name))
|
||||
self._engine_plugin = value
|
||||
|
||||
def get_blueprints(self):
|
||||
return blueprint
|
||||
|
||||
def _add_conference_search_box(self, event, **kwargs):
|
||||
if layout_settings.get(event, 'is_searchable') and not g.get('static_site'):
|
||||
form = self.engine_plugin.search_form(prefix='search-', csrf_enabled=False)
|
||||
return render_engine_or_search_template('searchbox_conference.html', event=event, form=form)
|
||||
|
||||
def _add_category_search_box(self, category, **kwargs):
|
||||
if request.blueprint != 'plugin_search':
|
||||
form = self.engine_plugin.search_form(prefix='search-', csrf_enabled=False)
|
||||
return render_engine_or_search_template('searchbox_category.html', category=category, form=form)
|
||||
@ -1,99 +0,0 @@
|
||||
{% if obj_type == 'event' %}
|
||||
{% extends 'events/display/conference/base.html' %}
|
||||
{% else %}
|
||||
{% extends 'layout/base.html' %}
|
||||
{% endif %}
|
||||
|
||||
{% from 'forms/_form.html' import form_header, form_footer %}
|
||||
|
||||
{% block page_class %}
|
||||
{{ super() if obj_type == 'event' else 'fixed-width-standalone-page' }}
|
||||
{% endblock %}
|
||||
|
||||
{% block title %}
|
||||
{% trans %}Search{% endtrans %}
|
||||
{% if obj_type == 'event' %}
|
||||
{% trans %}Event{% endtrans %}
|
||||
{% elif obj_type == 'category' %}
|
||||
{% trans %}Category{% endtrans %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container search-container">
|
||||
{% if only_public %}
|
||||
<div class="search-public-warning">
|
||||
{% trans %}Warning: since you are not logged in, only results from public events will appear.{% endtrans %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="topBar">
|
||||
<div class="content">
|
||||
<div>
|
||||
<div class="search-banner">
|
||||
<span>Search powered by</span>
|
||||
{% block banner %}{% endblock %}
|
||||
</div>
|
||||
|
||||
{{ form_header(form, method='get', i_form=false, disable_if_locked=false) }}
|
||||
<div>
|
||||
{{ form.phrase() }}
|
||||
<input type="submit" value="{% trans %}Search{% endtrans %}">
|
||||
{% block tooltip %}{% endblock %}
|
||||
</div>
|
||||
|
||||
<div class="toggle-advanced-options-container">
|
||||
<a id="toggle-advanced-options" href="#"
|
||||
data-msg-show="{% trans %}Show advanced options{% endtrans %}"
|
||||
data-msg-hide="{% trans %}Hide advanced options{% endtrans %}">
|
||||
{%- trans %}Show advanced options{% endtrans -%}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div id="advanced-options" class="advanced-options" style="display: none;">
|
||||
<table>
|
||||
<tr>
|
||||
<td>{{ form.field.label() }}</td>
|
||||
<td>{{ form.field() }}</td>
|
||||
</tr>
|
||||
{% block criteria_fields %}{% endblock %}
|
||||
<tr>
|
||||
<td>{{ form.start_date.label() }}</td>
|
||||
<td>{{ form.start_date(style='width: 180px;') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ form.end_date.label() }}</td>
|
||||
<td>{{ form.end_date(style='width: 180px;') }}</td>
|
||||
</tr>
|
||||
{% block sort_fields %}{% endblock %}
|
||||
</table>
|
||||
</div>
|
||||
{{ form_footer(form, i_form=false) }}
|
||||
</div>
|
||||
</div>
|
||||
{% set errors = form.error_list %}
|
||||
{% if errors %}
|
||||
<ul>
|
||||
{% for error in errors %}
|
||||
<li>{{ error }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% if form.validate_on_submit() %}
|
||||
{% block results %}{% endblock %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$('#toggle-advanced-options').on('click', function(e) {
|
||||
e.preventDefault();
|
||||
var $this = $(this);
|
||||
var container = $('#advanced-options').toggle();
|
||||
var text = container.is(':hidden') ? $this.data('msgShow') : $this.data('msgHide');
|
||||
$('#toggle-advanced-options').text(text);
|
||||
});
|
||||
</script>
|
||||
{% block scripts %}{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
@ -1,47 +0,0 @@
|
||||
{% from 'forms/_form.html' import form_header, form_footer %}
|
||||
|
||||
{{ form_header(form, method='get', action=url_for_plugin('search.search'), id='category-search-form', i_form=false,
|
||||
disable_if_locked=false) }}
|
||||
<div class="search-box" id="category-search-box">
|
||||
<div class="search-controls">
|
||||
<div class="search-button icon-search"></div>
|
||||
<div id="category-search-expand" class="arrowExpandIcon"></div>
|
||||
{{ form.phrase(style='background-color: transparent;') }}
|
||||
</div>
|
||||
|
||||
<div class="extra-options">
|
||||
<div class="label">
|
||||
{% trans %}Advanced options{% endtrans %}
|
||||
{% block search_syntax_tooltip %}{% endblock %}
|
||||
</div>
|
||||
<table>
|
||||
<tr>
|
||||
<td style="text-align: right; white-space: nowrap;">{{ form.field.label() }}</td>
|
||||
<td>{{ form.field(class_='search-field') }}</td>
|
||||
</tr>
|
||||
{% block extra_fields %}{% endblock %}
|
||||
<tr>
|
||||
<td style="text-align: right; white-space: nowrap;">{{ form.start_date.label() }}</td>
|
||||
<td style="white-space: nowrap;">{{ form.start_date() }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="text-align: right; white-space: nowrap;">{{ form.end_date.label() }}</td>
|
||||
<td style="white-space: nowrap;">{{ form.end_date() }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{{ form_footer(form, i_form=false) }}
|
||||
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
'use strict';
|
||||
categorySearchBox({
|
||||
categoryNamesUrl: {{ url_for_plugin('search.category_names') | tojson }},
|
||||
searchUrl: {{ url_for_plugin('search.search') | tojson }},
|
||||
searchCategoryUrl: {{ url_for_plugin('search.search', category) | tojson }},
|
||||
categoryName: {{ (category.title if category else None) | tojson }},
|
||||
isRoot: {{ (not category or category.is_root) | tojson }}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@ -1,12 +0,0 @@
|
||||
{% from 'forms/_form.html' import form_header, form_footer %}
|
||||
|
||||
{{ form_header(form, method='get', action=url_for_plugin('search.search', event), i_form=false,
|
||||
disable_if_locked=false) }}
|
||||
<div class="toolbar thin f-j-end">
|
||||
<div class="group">
|
||||
{{ form.phrase(id='conference-search-phrase', placeholder=_("Search...")) }}
|
||||
<button class="i-button highlight text-color color-on-hover icon-search" type="submit"></button>
|
||||
{% block extra_fields %}{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
{{ form_footer(form, i_form=false) }}
|
||||
@ -1,41 +0,0 @@
|
||||
# Translations template for PROJECT.
|
||||
# Copyright (C) 2017 ORGANIZATION
|
||||
# This file is distributed under the same license as the PROJECT project.
|
||||
#
|
||||
# Translators:
|
||||
# Thomas Baron <thomas.baron@cern.ch>, 2015,2017
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Indico\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2017-10-18 11:55+0200\n"
|
||||
"PO-Revision-Date: 2017-09-23 20:55+0000\n"
|
||||
"Last-Translator: Thomas Baron <thomas.baron@cern.ch>\n"
|
||||
"Language-Team: French (France) (http://www.transifex.com/indico/indico/language/fr_FR/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.5.1\n"
|
||||
"Language: fr_FR\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
|
||||
#: indico_search/static/js/search.js:50
|
||||
msgid "Some category suggestions..."
|
||||
msgstr "Quelques suggestions de catégories..."
|
||||
|
||||
#: indico_search/static/js/search.js:64
|
||||
msgid "other results - please write more..."
|
||||
msgstr "autres résultats - veuillez entrer plus de texte..."
|
||||
|
||||
#: indico_search/static/js/search.js:345 indico_search/static/js/search.js:405
|
||||
#: indico_search/static/js/search.js:444
|
||||
msgid "Everywhere"
|
||||
msgstr "Partout"
|
||||
|
||||
#: indico_search/static/js/search.js:424
|
||||
msgid "Click to search inside <span class=\"label\">{title}</span>"
|
||||
msgstr "Veuillez cliquer pour chercher dans <span class=\"label\">{title}</span>"
|
||||
|
||||
#: indico_search/templates/searchbox_conference.html:6
|
||||
msgid "Search..."
|
||||
msgstr "Chercher..."
|
||||
@ -1,88 +0,0 @@
|
||||
# Translations template for PROJECT.
|
||||
# Copyright (C) 2017 ORGANIZATION
|
||||
# This file is distributed under the same license as the PROJECT project.
|
||||
#
|
||||
# Translators:
|
||||
# Thomas Baron <thomas.baron@cern.ch>, 2015,2017
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Indico\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2017-10-18 11:55+0200\n"
|
||||
"PO-Revision-Date: 2017-09-23 20:55+0000\n"
|
||||
"Last-Translator: Thomas Baron <thomas.baron@cern.ch>\n"
|
||||
"Language-Team: French (France) (http://www.transifex.com/indico/indico/language/fr_FR/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.5.1\n"
|
||||
"Language: fr_FR\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
|
||||
#: indico_search/forms.py:29
|
||||
msgid "Anywhere"
|
||||
msgstr "Partout"
|
||||
|
||||
#: indico_search/forms.py:30
|
||||
msgid "Title"
|
||||
msgstr "Titre"
|
||||
|
||||
#: indico_search/forms.py:31
|
||||
msgid "Description/Abstract"
|
||||
msgstr "Description/résumé"
|
||||
|
||||
#: indico_search/forms.py:32
|
||||
msgid "Author/Speaker"
|
||||
msgstr "Auteur/orateur"
|
||||
|
||||
#: indico_search/forms.py:33
|
||||
msgid "Affiliation"
|
||||
msgstr "Affiliation"
|
||||
|
||||
#: indico_search/forms.py:34
|
||||
msgid "Keyword"
|
||||
msgstr "Mots-clés"
|
||||
|
||||
#: indico_search/forms.py:38
|
||||
msgid "Phrase"
|
||||
msgstr "Phrase"
|
||||
|
||||
#: indico_search/forms.py:39
|
||||
msgid "Search in"
|
||||
msgstr "Chercher dans"
|
||||
|
||||
#: indico_search/templates/results.html:14
|
||||
#: indico_search/templates/results.html:41
|
||||
msgid "Search"
|
||||
msgstr "Chercher"
|
||||
|
||||
#: indico_search/templates/results.html:16
|
||||
msgid "Event"
|
||||
msgstr "Événement"
|
||||
|
||||
#: indico_search/templates/results.html:18
|
||||
msgid "Category"
|
||||
msgstr "Categorie"
|
||||
|
||||
#: indico_search/templates/results.html:26
|
||||
msgid ""
|
||||
"Warning: since you are not logged in, only results from public events will "
|
||||
"appear."
|
||||
msgstr "Attention: puisque vous n'êtes pas authentifié, seuls des résultats publics seront affichés."
|
||||
|
||||
#: indico_search/templates/results.html:47
|
||||
#: indico_search/templates/results.html:49
|
||||
msgid "Show advanced options"
|
||||
msgstr "Afficher les options avancées"
|
||||
|
||||
#: indico_search/templates/results.html:48
|
||||
msgid "Hide advanced options"
|
||||
msgstr "Cacher les options avancées"
|
||||
|
||||
#: indico_search/templates/searchbox_category.html:13
|
||||
msgid "Advanced options"
|
||||
msgstr "Options avancées"
|
||||
|
||||
#: indico_search/templates/searchbox_conference.html:6
|
||||
msgid "Search..."
|
||||
msgstr "Rechercher..."
|
||||
@ -1,36 +0,0 @@
|
||||
# Translations template for PROJECT.
|
||||
# Copyright (C) 2020 ORGANIZATION
|
||||
# This file is distributed under the same license as the PROJECT project.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2020.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PROJECT VERSION\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2020-08-19 20:40+0200\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.8.0\n"
|
||||
|
||||
#: indico_search/client/index.js:45
|
||||
msgid "Some category suggestions..."
|
||||
msgstr ""
|
||||
|
||||
#: indico_search/client/index.js:347 indico_search/client/index.js:413
|
||||
#: indico_search/client/index.js:457
|
||||
msgid "Everywhere"
|
||||
msgstr ""
|
||||
|
||||
#: indico_search/client/index.js:433
|
||||
msgid "Click to search inside <span class=\"label\">{title}</span>"
|
||||
msgstr ""
|
||||
|
||||
#: indico_search/templates/searchbox_conference.html:7
|
||||
msgid "Search..."
|
||||
msgstr ""
|
||||
|
||||
@ -1,87 +0,0 @@
|
||||
# Translations template for PROJECT.
|
||||
# Copyright (C) 2020 ORGANIZATION
|
||||
# This file is distributed under the same license as the PROJECT project.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2020.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PROJECT VERSION\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2020-08-19 20:40+0200\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.8.0\n"
|
||||
|
||||
#: indico_search/forms.py:20
|
||||
msgid "Anywhere"
|
||||
msgstr ""
|
||||
|
||||
#: indico_search/forms.py:21
|
||||
msgid "Title"
|
||||
msgstr ""
|
||||
|
||||
#: indico_search/forms.py:22
|
||||
msgid "Description/Abstract"
|
||||
msgstr ""
|
||||
|
||||
#: indico_search/forms.py:23
|
||||
msgid "Author/Speaker"
|
||||
msgstr ""
|
||||
|
||||
#: indico_search/forms.py:24
|
||||
msgid "Affiliation"
|
||||
msgstr ""
|
||||
|
||||
#: indico_search/forms.py:25
|
||||
msgid "Keyword"
|
||||
msgstr ""
|
||||
|
||||
#: indico_search/forms.py:29
|
||||
msgid "Phrase"
|
||||
msgstr ""
|
||||
|
||||
#: indico_search/forms.py:30
|
||||
msgid "Search in"
|
||||
msgstr ""
|
||||
|
||||
#: indico_search/templates/results.html:14
|
||||
#: indico_search/templates/results.html:41
|
||||
msgid "Search"
|
||||
msgstr ""
|
||||
|
||||
#: indico_search/templates/results.html:16
|
||||
msgid "Event"
|
||||
msgstr ""
|
||||
|
||||
#: indico_search/templates/results.html:18
|
||||
msgid "Category"
|
||||
msgstr ""
|
||||
|
||||
#: indico_search/templates/results.html:26
|
||||
msgid ""
|
||||
"Warning: since you are not logged in, only results from public events "
|
||||
"will appear."
|
||||
msgstr ""
|
||||
|
||||
#: indico_search/templates/results.html:47
|
||||
#: indico_search/templates/results.html:49
|
||||
msgid "Show advanced options"
|
||||
msgstr ""
|
||||
|
||||
#: indico_search/templates/results.html:48
|
||||
msgid "Hide advanced options"
|
||||
msgstr ""
|
||||
|
||||
#: indico_search/templates/searchbox_category.html:14
|
||||
msgid "Advanced options"
|
||||
msgstr ""
|
||||
|
||||
#: indico_search/templates/searchbox_conference.html:7
|
||||
msgid "Search..."
|
||||
msgstr ""
|
||||
|
||||
@ -1,28 +0,0 @@
|
||||
# This file is part of the Indico plugins.
|
||||
# Copyright (C) 2002 - 2020 CERN
|
||||
#
|
||||
# The Indico plugins are free software; you can redistribute
|
||||
# them and/or modify them under the terms of the MIT License;
|
||||
# see the LICENSE file for more details.
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from flask_pluginengine import current_plugin, render_plugin_template
|
||||
|
||||
|
||||
def render_engine_or_search_template(template_name, **context):
|
||||
"""Renders a template from the engine plugin or the search plugin
|
||||
|
||||
If the template is available in the engine plugin, it's taken
|
||||
from there, otherwise the template from this plugin is used.
|
||||
|
||||
:param template_name: name of the template
|
||||
:param context: the variables that should be available in the
|
||||
context of the template.
|
||||
"""
|
||||
from indico_search.plugin import SearchPlugin
|
||||
assert current_plugin == SearchPlugin.instance
|
||||
|
||||
templates = ('{}:{}'.format(SearchPlugin.instance.engine_plugin.name, template_name),
|
||||
template_name)
|
||||
return render_plugin_template(templates, **context)
|
||||
@ -1,20 +0,0 @@
|
||||
# This file is part of the Indico plugins.
|
||||
# Copyright (C) 2002 - 2020 CERN
|
||||
#
|
||||
# The Indico plugins are free software; you can redistribute
|
||||
# them and/or modify them under the terms of the MIT License;
|
||||
# see the LICENSE file for more details.
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from indico.core.plugins import WPJinjaMixinPlugin
|
||||
from indico.modules.categories.views import WPCategory
|
||||
from indico.modules.events.views import WPConferenceDisplayBase
|
||||
|
||||
|
||||
class WPSearchCategory(WPJinjaMixinPlugin, WPCategory):
|
||||
pass
|
||||
|
||||
|
||||
class WPSearchConference(WPJinjaMixinPlugin, WPConferenceDisplayBase):
|
||||
pass
|
||||
@ -1,34 +0,0 @@
|
||||
# This file is part of the Indico plugins.
|
||||
# Copyright (C) 2002 - 2020 CERN
|
||||
#
|
||||
# The Indico plugins are free software; you can redistribute
|
||||
# them and/or modify them under the terms of the MIT License;
|
||||
# see the LICENSE file for more details.
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from setuptools import find_packages, setup
|
||||
|
||||
|
||||
setup(
|
||||
name='indico-plugin-search',
|
||||
version='2.3',
|
||||
description='Framework for searching content in Indico',
|
||||
url='https://github.com/indico/indico-plugins',
|
||||
license='MIT',
|
||||
author='Indico Team',
|
||||
author_email='indico-team@cern.ch',
|
||||
packages=find_packages(),
|
||||
zip_safe=False,
|
||||
include_package_data=True,
|
||||
install_requires=[
|
||||
'indico>=2.3.dev0'
|
||||
],
|
||||
classifiers=[
|
||||
'Environment :: Plugins',
|
||||
'Environment :: Web Environment',
|
||||
'License :: OSI Approved :: MIT License',
|
||||
'Programming Language :: Python :: 2.7'
|
||||
],
|
||||
entry_points={'indico.plugins': {'search = indico_search.plugin:SearchPlugin'}}
|
||||
)
|
||||
@ -1,5 +0,0 @@
|
||||
{
|
||||
"entry": {
|
||||
"main": "./index.js"
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user