From b60e2389dcc8fda6285e0a3ef8d4c845e28078d0 Mon Sep 17 00:00:00 2001 From: Florian Vessaz Date: Mon, 11 Apr 2016 13:41:15 +0200 Subject: [PATCH] Importer: Adapt contribution/subcontribution creation --- importer/indico_importer/controllers.py | 75 ++++++++ importer/indico_importer/plugin.py | 11 +- .../indico_importer/static/js/importer.js | 165 ++++++++++++++---- 3 files changed, 211 insertions(+), 40 deletions(-) diff --git a/importer/indico_importer/controllers.py b/importer/indico_importer/controllers.py index c118165..2447e2a 100644 --- a/importer/indico_importer/controllers.py +++ b/importer/indico_importer/controllers.py @@ -16,9 +16,17 @@ from __future__ import unicode_literals +from datetime import datetime + +from dateutil.relativedelta import relativedelta from flask import jsonify, request from flask_pluginengine import current_plugin +from pytz import timezone, utc +from werkzeug.exceptions import NotFound +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 MaKaC.webinterface.rh.base import RHProtected @@ -36,3 +44,70 @@ class RHImportData(RHProtected): 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_new + }, + '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_new.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 _checkParams(self, params): + RHEndTimeBase._checkParams(self, params) + self.date = self.event_new.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_new.tzinfo), db.Date) + entries = self.event_new.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_new.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 _checkParams(self, params): + RHEndTimeBase._checkParams(self, params) + self.date = timezone(self.event_new.timezone).localize(datetime.strptime(request.args['selectedDay'], + '%Y/%m/%d')) + self.timetable_entry = self.event_new.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) diff --git a/importer/indico_importer/plugin.py b/importer/indico_importer/plugin.py index 0657567..1b93fe9 100644 --- a/importer/indico_importer/plugin.py +++ b/importer/indico_importer/plugin.py @@ -18,9 +18,10 @@ from __future__ import unicode_literals from indico.core import signals from indico.core.plugins import IndicoPlugin, IndicoPluginBlueprint, plugin_url_rule_to_js, PluginCategory +from indico.modules.events.timetable.views import WPManageTimetable from indico_importer import _ -from indico_importer.controllers import RHGetImporters, RHImportData +from indico_importer.controllers import RHGetImporters, RHImportData, RHDayEndTime, RHBlockEndTime class ImporterPlugin(IndicoPlugin): @@ -33,8 +34,8 @@ class ImporterPlugin(IndicoPlugin): def init(self): super(ImporterPlugin, self).init() - # self.inject_js('importer_js', WPConfModifScheduleGraphic) - # self.inject_css('importer_css', WPConfModifScheduleGraphic) + self.inject_js('importer_js', WPManageTimetable) + self.inject_css('importer_css', WPManageTimetable) self.connect(signals.event.timetable_buttons, self.get_timetable_buttons) self.importer_engines = {} @@ -59,3 +60,7 @@ class ImporterPlugin(IndicoPlugin): blueprint = IndicoPluginBlueprint('importer', __name__) blueprint.add_url_rule('/importers//search', 'import_data', RHImportData, methods=('POST',)) blueprint.add_url_rule('/importers/', 'importers', RHGetImporters) + +blueprint.add_url_rule('/importers//event//day-end-date', 'day_end_date', RHDayEndTime) +blueprint.add_url_rule('/importers//event//entry//block-end-date', 'block_end_date', + RHBlockEndTime) diff --git a/importer/indico_importer/static/js/importer.js b/importer/indico_importer/static/js/importer.js index cb04531..efcf22d 100644 --- a/importer/indico_importer/static/js/importer.js +++ b/importer/indico_importer/static/js/importer.js @@ -83,7 +83,7 @@ * Checks if a dictionary contains empty person data. */ isPersonEmpty: function(person) { - return person && (person.firstName || person.familyName); + return !person || (!person.firstName && !person.familyName); }, /** @@ -96,6 +96,8 @@ * @param finalCallback Function executed after finishing all requests. */ multipleIndicoRequest: function(reqList, successCallback, errorCallback, finalCallback) { + return ImporterUtils._sendMultipleRequests(reqList, successCallback, errorCallback, finalCallback); + /* if (reqList.length > 0) { indicoRequest( reqList[0].method, reqList[0].args, function(result, error) { if (result && successCallback) { @@ -110,7 +112,42 @@ } else if (finalCallback) { finalCallback(); } - } + */ + }, + + _sendMultipleRequests: function(reqList, onSuccess, onError, onComplete) { + $.each(reqList, function(index, req) { + ImporterUtils._sendRequest(req.url, req.args, onSuccess, onError); + }); + if (onComplete) { + onComplete(); + } + }, + + _sendRequest: function(url, args, onSuccess, onError, onComplete) { + $.ajax({ + url: url, + method: 'POST', + data: args, + success: function(data) { + if (data.success) { + onSuccess(data.entries[0]); + } else { + // FIXME where is the error message? + window.console.log(data); + onError("An error occured while adding the contribution."); + } + }, + error: function(jqxhr, textStatus, errorThrown) { + onError(textStatus + ': ' + errorThrown); + }, + complete: onComplete + }); + if (onComplete) { + onComplete(); + } + }, + }; @@ -418,25 +455,28 @@ self.info.set("startTime", this.destination.startDate.time.substr(0, 5)); hook.set(true); } else { - var method; + var url; if (this.destination.entryType == 'Day') { - method = 'schedule.event.getDayEndDate'; + // FIXME build URL in a better way + url = '/importers/indico_importer/event/' + this.confId + '/day-end-date' } if (this.destination.entryType == 'Session') { - method = 'schedule.slot.getDayEndDate'; + // FIXME build URL in a better way + url = '/importers/indico_importer/event/' + this.confId + '/entry/' + + this.destination.scheduleEntryId + '/block-end-date'; } - indicoRequest(method, { - 'confId': this.confId, - 'sessionId': this.destination.sessionId, - 'slotId': this.destination.sessionSlotId, - 'selectedDay': this.destination.startDate.date.replace(/-/g, '/')}, - function(result, error) { - if (!error) { - self.info.set("startTime", result.substr(11, 5)); - } + $.ajax({ + url: url, + data: { + sessionId: this.destination.sessionId, + slotId: this.destination.sessionSlotId, + selectedDay: this.destination.startDate.date.replace(/-/g, '/') + }, + success: function(data) { + self.info.set('startTime', data); hook.set(true); } - ); + }); } } ], @@ -492,6 +532,47 @@ } }, + _getUrl: function(legacy_method, eventId, day, destination) { + var params = "?" + $.param({'day': day}); + if (legacy_method == 'schedule.event.addContribution') { + // FIXME build URL in a better way + return '/event/' + eventId + '/manage/timetable/add-contribution' + params; + } + if (legacy_method == 'schedule.slot.addContribution') { + params += '&' + $.param({'session_block_id': destination.sessionSlotId}); + // FIXME build URL in a better way + return '/event/' + eventId + '/manage/timetable/add-contribution' + params; + } + if (legacy_method == 'contribution.addSubContribution') { + // FIXME build URL in a better way + return '/event/' + eventId + '/manage/contributions/' + destination.contributionId + + '/subcontributions/create'; + } + // FIXME + alert('Unsupported method: ' + legacy_method); + }, + + _personLinkData: function(entry) { + var linkDataEntry = function(author, primary, speaker) { + return $.extend({ + 'authorType': primary ? 1 : 2, + 'isSpeaker': speaker, + 'isSubmitter': !speaker + }, author); + }; + var linkData = []; + if (!ImporterUtils.isPersonEmpty(entry.primaryAuthor)) { + linkData.push(linkDataEntry(entry.primaryAuthor, true, false)); + } + if (!ImporterUtils.isPersonEmpty(entry.secondaryAuthor)) { + linkData.push(linkDataEntry(entry.secondaryAuthor, false, false)); + } + if (!ImporterUtils.isPersonEmpty(entry.speaker)) { + linkData.push(linkDataEntry(entry.speaker, false, true)); + } + return linkData; + }, + _getButtons: function() { var self = this; return [ @@ -512,30 +593,40 @@ each(self.entries.getValues(), function(entry) { entry = entry.getAll(); var timeStr = ImporterUtils.minutesToTime(time); - args.push({'method' : method, - 'args' : {'conference' : self.confId, - 'duration' : duration, - 'title' : entry.title?entry.title:"Untitled", - 'sessionId' : self.destination.sessionId, - 'slotId' : self.destination.sessionSlotId, - 'contribId' : self.destination.contributionId, - 'startDate' : date + " " + timeStr, - 'keywords' : [], - 'authors':ImporterUtils.isPersonEmpty(entry.primaryAuthor)?[entry.primaryAuthor]:[], - 'coauthors' : ImporterUtils.isPersonEmpty(entry.secondaryAuthor)?[entry.secondaryAuthor]:[], - 'presenters' : ImporterUtils.isPersonEmpty(entry.speaker)?[entry.speaker]:[], - 'roomInfo' : {}, - 'field_content': entry.summary, - 'reportNumbers': entry.reportNumbers? - [{'system': ImporterUtils.reportNumberSystems[self.importer], 'number': entry.reportNumbers}]:[], - 'materials': entry.materials} + var params = { + 'csrf_token': $('#csrf-token').attr('content'), + 'title': entry.title ? entry.title : "Untitled", + 'description': entry.summary, + 'duration': [duration, 'minutes'], + 'references': '[]' + } + if (self.destination.entryType == "Contribution") { + $.extend(params, { + 'speakers': Json.write(self._personLinkData(entry)) + }); + } else { + $.extend(params, { + 'time': timeStr, + //'keywords' : [], + 'person_link_data': Json.write(self._personLinkData(entry)), + //'roomInfo' : {}, + 'location_data': '{"address": "", "inheriting": true}', + 'type': '__None', + //'reportNumbers': entry.reportNumbers? + // [{'system': ImporterUtils.reportNumberSystems[self.importer], 'number': entry.reportNumbers}]:[], + 'materials': Json.write(entry.materials) + }); + } + args.push({ + 'url': self._getUrl(method, self.confId, date, self.destination), + 'args': params }); time += duration; }); var successCallback = function(result) { if (exists(result.slotEntry) && self.timetable.contextInfo.id == result.slotEntry.id) { self.timetable._updateEntry(result, result.id); - } else{ + } else { var timetable = self.timetable.parentTimetable?self.timetable.parentTimetable:self.timetable; timetable._updateEntry(result, result.id); } @@ -832,13 +923,13 @@ recordDiv.append(Html.div({}, Html.em({}, $t.gettext("Meeting")), ": ", record.get("meetingName"))); } // Speaker, primary and secondary authors are stored in dictionaries. Their property have to be checked. - if (ImporterUtils.isPersonEmpty(record.get("primaryAuthor"))) { + if (!ImporterUtils.isPersonEmpty(record.get("primaryAuthor"))) { recordDiv.append(Html.div({}, Html.em({}, $t.gettext("Primary author")), ": ", this._getPersonString(record.get("primaryAuthor")))); } - if (ImporterUtils.isPersonEmpty(record.get("secondaryAuthor"))) { + if (!ImporterUtils.isPersonEmpty(record.get("secondaryAuthor"))) { recordDiv.append(Html.div({}, Html.em({}, $t.gettext("Secondary author")), ": ", this._getPersonString(record.get("secondaryAuthor")))); } - if (ImporterUtils.isPersonEmpty(record.get("speaker"))) { + if (!ImporterUtils.isPersonEmpty(record.get("speaker"))) { recordDiv.append(Html.div({}, Html.em({}, $t.gettext("Speaker")), ": ", this._getPersonString(record.get("speaker")))); } if (record.get("summary")) { @@ -1356,7 +1447,7 @@ $(function() { - $('#timetableDiv').on('click', '.js-create-importer-dialog', function() { + $('#timetable').on('click', '.js-create-importer-dialog', function() { var timetable = $(this).data('timetable'); new ImportDialog(timetable); });