From 6ce5d7e2233d2881fd9dbdb72f025390b486a247 Mon Sep 17 00:00:00 2001 From: Adrian Moennich Date: Mon, 17 Nov 2014 17:33:00 +0100 Subject: [PATCH] Add agent management webinterface fixes #3 --- livesync/MANIFEST.in | 2 + livesync/indico_livesync/__init__.py | 5 +- livesync/indico_livesync/base.py | 3 + livesync/indico_livesync/blueprint.py | 27 ++++++ livesync/indico_livesync/controllers.py | 88 +++++++++++++++++++ livesync/indico_livesync/forms.py | 29 ++++++ livesync/indico_livesync/models/agents.py | 4 + livesync/indico_livesync/plugin.py | 18 ++++ .../static/js/livesync_admin.js | 19 ++++ .../indico_livesync/templates/edit_agent.html | 23 +++++ .../templates/plugin_details_extra.html | 70 +++++++++++++++ livesync/indico_livesync/views.py | 24 +++++ 12 files changed, 310 insertions(+), 2 deletions(-) create mode 100644 livesync/indico_livesync/blueprint.py create mode 100644 livesync/indico_livesync/controllers.py create mode 100644 livesync/indico_livesync/forms.py create mode 100644 livesync/indico_livesync/static/js/livesync_admin.js create mode 100644 livesync/indico_livesync/templates/edit_agent.html create mode 100644 livesync/indico_livesync/templates/plugin_details_extra.html create mode 100644 livesync/indico_livesync/views.py diff --git a/livesync/MANIFEST.in b/livesync/MANIFEST.in index 2cdd223..b0cf6e7 100644 --- a/livesync/MANIFEST.in +++ b/livesync/MANIFEST.in @@ -1 +1,3 @@ graft indico_livesync/migrations +graft indico_livesync/templates +graft indico_livesync/static diff --git a/livesync/indico_livesync/__init__.py b/livesync/indico_livesync/__init__.py index 324a874..6257257 100644 --- a/livesync/indico_livesync/__init__.py +++ b/livesync/indico_livesync/__init__.py @@ -16,10 +16,11 @@ from __future__ import unicode_literals -__all__ = ('LiveSyncPluginBase', 'LiveSyncAgentBase', 'SimpleChange', 'process_records', 'MARCXMLGenerator', - 'Uploader', 'MARCXMLUploader') +__all__ = ('LiveSyncPluginBase', 'LiveSyncAgentBase', 'AgentForm', 'SimpleChange', 'process_records', + 'MARCXMLGenerator', 'Uploader', 'MARCXMLUploader') from .base import LiveSyncPluginBase, LiveSyncAgentBase +from .forms import AgentForm from .simplify import SimpleChange, process_records from .marcxml import MARCXMLGenerator from .uploader import Uploader, MARCXMLUploader diff --git a/livesync/indico_livesync/base.py b/livesync/indico_livesync/base.py index 8c5b2bb..a381042 100644 --- a/livesync/indico_livesync/base.py +++ b/livesync/indico_livesync/base.py @@ -22,6 +22,7 @@ from indico.core.plugins import IndicoPlugin from indico.util.decorators import classproperty from MaKaC.conference import CategoryManager +from indico_livesync.forms import AgentForm from indico_livesync.models.queue import LiveSyncQueueEntry from indico_livesync.plugin import LiveSyncPlugin @@ -48,6 +49,8 @@ class LiveSyncAgentBase(object): plugin = None # set automatically when the agent is registered #: the Uploader to use. only needed if run and run_initial_export are not overridden uploader = None + #: the form used when creating/editing the agent + form = AgentForm @classproperty @classmethod diff --git a/livesync/indico_livesync/blueprint.py b/livesync/indico_livesync/blueprint.py new file mode 100644 index 0000000..782a850 --- /dev/null +++ b/livesync/indico_livesync/blueprint.py @@ -0,0 +1,27 @@ +# This file is part of Indico. +# Copyright (C) 2002 - 2014 European Organization for Nuclear Research (CERN). +# +# Indico is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# Indico is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Indico; if not, see . + +from __future__ import unicode_literals + +from indico.core.plugins import IndicoPluginBlueprint + +from indico_livesync.controllers import RHAddAgent, RHEditAgent, RHDeleteAgent + +blueprint = IndicoPluginBlueprint('livesync', 'indico_livesync', url_prefix='/admin/plugins/livesync') + +blueprint.add_url_rule('/agents/create/', 'add_agent', RHAddAgent, methods=('GET', 'POST')) +blueprint.add_url_rule('/agents//', 'edit_agent', RHEditAgent, methods=('GET', 'POST')) +blueprint.add_url_rule('/agents//delete', 'delete_agent', RHDeleteAgent, methods=('POST',)) diff --git a/livesync/indico_livesync/controllers.py b/livesync/indico_livesync/controllers.py new file mode 100644 index 0000000..3c3bd90 --- /dev/null +++ b/livesync/indico_livesync/controllers.py @@ -0,0 +1,88 @@ +# This file is part of Indico. +# Copyright (C) 2002 - 2014 European Organization for Nuclear Research (CERN). +# +# Indico is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# Indico is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Indico; if not, see . + +from __future__ import unicode_literals + +from flask import request, redirect, flash +from flask_pluginengine import render_plugin_template, current_plugin + +from indico.core.db import db +from indico.util.i18n import _ +from indico.web.flask.util import url_for +from indico.web.forms.base import FormDefaults +from MaKaC.webinterface.rh.admins import RHAdminBase + +from indico_livesync.models.agents import LiveSyncAgent +from indico_livesync.views import WPLiveSync + + +def extend_plugin_details(): + agents = LiveSyncAgent.find().order_by(LiveSyncAgent.name, LiveSyncAgent.id).all() + return render_plugin_template('plugin_details_extra.html', agents=agents, backends=current_plugin.agent_classes) + + +class RHDeleteAgent(RHAdminBase): + """Deletes a LiveSync agent""" + + def _checkParams(self): + self.agent = LiveSyncAgent.find_one(id=request.view_args['agent_id']) + + def _process(self): + db.session.delete(self.agent) + flash(_('Agent deleted'), 'success') + return redirect(url_for('plugins.details', plugin='livesync')) + + +class RHAddAgent(RHAdminBase): + """Adds a LiveSync agent""" + + def _checkParams(self): + self.backend_name = request.view_args['backend'] + self.backend = current_plugin.agent_classes[self.backend_name] + + def _process(self): + form = self.backend.form(obj=FormDefaults(name=self.backend.title)) + if form.validate_on_submit(): + data = form.data + name = data.pop('name') + agent = LiveSyncAgent(name=name, backend_name=self.backend_name, settings=data) + db.session.add(agent) + flash(_('Agent added'), 'success') + flash(_("Don't forget to run the initial export!"), 'highlight') + return redirect(url_for('plugins.details', plugin='livesync')) + + return WPLiveSync.render_template('edit_agent.html', form=form, backend=self.backend) + + +class RHEditAgent(RHAdminBase): + """Edits a LiveSync agent""" + + def _checkParams(self): + self.agent = LiveSyncAgent.find_one(id=request.view_args['agent_id']) + if self.agent.backend is None: + flash(_('Cannot edit an agent that is not loaded'), 'error') + return redirect(url_for('plugins.details', plugin='livesync')) + + def _process(self): + form = self.agent.backend.form(obj=FormDefaults(self.agent, {'name'}, **self.agent.settings)) + if form.validate_on_submit(): + data = form.data + self.agent.name = data.pop('name') + self.agent.settings = data + flash(_('Agent updated'), 'success') + return redirect(url_for('plugins.details', plugin='livesync')) + + return WPLiveSync.render_template('edit_agent.html', form=form, backend=self.agent.backend, agent=self.agent) diff --git a/livesync/indico_livesync/forms.py b/livesync/indico_livesync/forms.py new file mode 100644 index 0000000..5f9435b --- /dev/null +++ b/livesync/indico_livesync/forms.py @@ -0,0 +1,29 @@ +# This file is part of Indico. +# Copyright (C) 2002 - 2014 European Organization for Nuclear Research (CERN). +# +# Indico is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# Indico is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Indico; if not, see . + +from __future__ import unicode_literals + +from wtforms.fields.core import StringField +from wtforms.validators import DataRequired + +from indico.util.i18n import _ +from indico.util.string import strip_whitespace +from indico.web.forms.base import IndicoForm + + +class AgentForm(IndicoForm): + name = StringField(_('Name'), [DataRequired()], filters=[strip_whitespace], + description=_("The name of the agent. Only used in the administration interface.")) diff --git a/livesync/indico_livesync/models/agents.py b/livesync/indico_livesync/models/agents.py index 0b93589..ed9ea60 100644 --- a/livesync/indico_livesync/models/agents.py +++ b/livesync/indico_livesync/models/agents.py @@ -58,6 +58,10 @@ class LiveSyncAgent(db.Model): default={} ) + @property + def locator(self): + return {'agent_id': self.id} + @property def backend(self): """Returns the backend class""" diff --git a/livesync/indico_livesync/plugin.py b/livesync/indico_livesync/plugin.py index 06d2629..4401463 100644 --- a/livesync/indico_livesync/plugin.py +++ b/livesync/indico_livesync/plugin.py @@ -16,12 +16,17 @@ from __future__ import unicode_literals +from flask import request + from indico.core.plugins import IndicoPlugin, wrap_cli_manager +from indico.core.plugins.views import WPPlugins from indico.util.i18n import _ from indico.web.forms.base import IndicoForm from indico.web.forms.fields import MultipleItemsField +from indico_livesync.blueprint import blueprint from indico_livesync.cli import cli_manager +from indico_livesync.controllers import extend_plugin_details from indico_livesync.handler import connect_signals @@ -45,6 +50,15 @@ class LiveSyncPlugin(IndicoPlugin): super(LiveSyncPlugin, self).init() self.agent_classes = {} connect_signals(self) + self.template_hook('plugin-details', self._extend_plugin_details) + self.inject_js('livesync_admin_js', WPPlugins, subclasses=False, + condition=lambda: request.view_args.get('plugin') == self.name) + + def get_blueprints(self): + return blueprint + + def register_assets(self): + self.register_js_bundle('livesync_admin_js', 'js/livesync_admin.js') def add_cli_command(self, manager): manager.add_command('livesync', wrap_cli_manager(cli_manager, self)) @@ -53,3 +67,7 @@ class LiveSyncPlugin(IndicoPlugin): if name in self.agent_classes: raise RuntimeError('Duplicate livesync agent: {}'.format(name)) self.agent_classes[name] = agent_class + + def _extend_plugin_details(self, plugin, **kwargs): + if plugin == self: + return extend_plugin_details() diff --git a/livesync/indico_livesync/static/js/livesync_admin.js b/livesync/indico_livesync/static/js/livesync_admin.js new file mode 100644 index 0000000..0c57db6 --- /dev/null +++ b/livesync/indico_livesync/static/js/livesync_admin.js @@ -0,0 +1,19 @@ +(function(global) { + global.liveSyncPluginPage = function liveSyncPluginPage() { + $('.js-delete-agent').on('click', function(e) { + e.preventDefault(); + var $this = $(this); + var msg = $T('Do you really want to delete this agent and all its queue entries?'); + new ConfirmPopup($T('Delete this agent?'), msg, function(confirmed) { + if (!confirmed) { + return; + } + + $('
', { + action: $this.data('href'), + method: 'post' + }).appendTo('body').submit(); + }).open(); + }); + }; +})(window); diff --git a/livesync/indico_livesync/templates/edit_agent.html b/livesync/indico_livesync/templates/edit_agent.html new file mode 100644 index 0000000..015cb2d --- /dev/null +++ b/livesync/indico_livesync/templates/edit_agent.html @@ -0,0 +1,23 @@ +{% from 'forms/form_widget.html' import form_header, form_row, form_footer %} + +
+

LiveSync

+

+ {%- if agent -%} + {% trans %}Edit Agent{% endtrans %} + {%- else -%} + {% trans %}Add{% endtrans %} {{ backend.title }} + {%- endif -%} +

+
+ +

{{ backend.description }}

+ +{{ form_header() }} +{% for field in form.visible_fields %} + {{ form_row(field) }} +{% endfor %} +{% call form_footer() %} + + {% trans %}Cancel{% endtrans %} +{% endcall %} diff --git a/livesync/indico_livesync/templates/plugin_details_extra.html b/livesync/indico_livesync/templates/plugin_details_extra.html new file mode 100644 index 0000000..77b6b73 --- /dev/null +++ b/livesync/indico_livesync/templates/plugin_details_extra.html @@ -0,0 +1,70 @@ +

{% trans %}LiveSync Agents{% endtrans %}

+ +
+ + + + + + + + + + + + + {% for agent in agents %} + + + + + + + + + {% endfor %} + +
{% trans %}ID{% endtrans %}{% trans %}Name{% endtrans %}{% trans %}Backend{% endtrans %}{% trans %}Initial Export{% endtrans %}{% trans %}Queue{% endtrans %}{% trans %}Actions{% endtrans %}
{{ agent.id }}{{ agent.name }} + {% if agent.backend %} + {{ agent.backend.title }} + {% else %} + {% trans name=agent.backend_name %}Not loaded: {{ name }}{% endtrans %} + {% endif %} + + {% if agent.initial_data_exported %} + {% trans %}Done{% endtrans %} + {% else %} + {% trans %}Pending{% endtrans %} + {% endif %} + {{ agent.queue.count() }} + + {%- if agent.backend -%} + + {%- endif -%} +
+ + {% for name, backend in backends.items() | sort(attribute='1.title') %} + {% trans %}Add{% endtrans %} {{ backend.title }} + {% endfor %} + + {% set missing_initial_export = agents|rejectattr('initial_data_exported')|list %} + {% if missing_initial_export %} +

+ {% trans -%} + You still need to run the initial export for some agents by executing the commands below in a shell.
+ Please note that this may take a very long time if there are many events in Indico! + {%- endtrans %} +

+

+{#- Don't "fix" the indentation of these lines! -#}
+{%- for agent in missing_initial_export -%}
+indico livesync initial_export {{ agent.id }}
+{% endfor -%}
+        
+ {% endif %} +
+ + diff --git a/livesync/indico_livesync/views.py b/livesync/indico_livesync/views.py new file mode 100644 index 0000000..0a9eb7f --- /dev/null +++ b/livesync/indico_livesync/views.py @@ -0,0 +1,24 @@ +# This file is part of Indico. +# Copyright (C) 2002 - 2014 European Organization for Nuclear Research (CERN). +# +# Indico is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# Indico is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Indico; if not, see . + +from __future__ import unicode_literals + +from indico.core.plugins import WPJinjaMixinPlugin +from indico.core.plugins.views import WPPlugins + + +class WPLiveSync(WPJinjaMixinPlugin, WPPlugins): + pass