Remove unmaintained plugins

they are now in https://github.com/indico/indico-plugins-attic
This commit is contained in:
Indico Team 2020-11-10 11:45:49 +01:00 committed by Adrian Moennich
parent 435e54e367
commit 4b0a558b01
48 changed files with 2 additions and 5289 deletions

View File

@ -1,6 +1,4 @@
extras:
indico-plugin-importer: importer
indico-plugin-importer-invenio: importer
extras: {}
skip:
- indico-plugin-livesync-debug

View File

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

View File

@ -1,4 +0,0 @@
graft indico_importer/static
graft indico_importer/translations
global-exclude *.pyc __pycache__ .keep

View File

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

View File

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

View File

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

View File

@ -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)

View File

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

View File

@ -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)

View File

@ -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."

View File

@ -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"

View File

@ -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 ""

View File

@ -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 ""

View File

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

View File

@ -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'}}
)

View File

@ -1,5 +0,0 @@
{
"entry": {
"main": "./index.js"
}
}

View File

@ -1,3 +0,0 @@
graft indico_importer_invenio/translations
global-exclude *.pyc __pycache__ .keep

View File

@ -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')

View File

@ -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)

View File

@ -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)]

View File

@ -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()])

View File

@ -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)

View File

@ -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,)

View File

@ -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"

View File

@ -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"

View File

@ -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 ""

View File

@ -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'}}
)

View File

@ -1,5 +0,0 @@
graft indico_search/static
graft indico_search/templates
graft indico_search/translations
global-exclude *.pyc __pycache__ .keep

View File

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

View File

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

View File

@ -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)

View File

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

View File

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

View File

@ -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())

View File

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

View File

@ -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)

View File

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

View File

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

View File

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

View File

@ -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..."

View File

@ -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..."

View File

@ -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 ""

View File

@ -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 ""

View File

@ -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)

View File

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

View File

@ -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'}}
)

View File

@ -1,5 +0,0 @@
{
"entry": {
"main": "./index.js"
}
}