OwnCloud: Add plugin (#173)

This commit is contained in:
Javier Ferrer 2022-04-20 11:23:53 +02:00 committed by GitHub
parent 0eb4d54e6c
commit f0c90988bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 468 additions and 0 deletions

6
owncloud/MANIFEST.in Normal file
View File

@ -0,0 +1,6 @@
graft indico_owncloud/client
graft indico_owncloud/static
graft indico_owncloud/templates
graft indico_owncloud/translations
global-exclude *.pyc __pycache__ .keep

13
owncloud/README.md Normal file
View File

@ -0,0 +1,13 @@
# OwnCloud integration plugin
This plugin enables integration with an ownCloud server, initially for the
purpose of attaching materials to an event or category. A new "from the cloud"
button shows up in the materials section which enables a manager to log into his
ownCloud account and pick files from his personal storage. Those files will be
copied as attachments.
## Changelog
### 3.2
- Initial release for Indico 3.2

View File

@ -0,0 +1,11 @@
# This file is part of the Indico plugins.
# Copyright (C) 2002 - 2022 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.i18n import make_bound_gettext
_ = make_bound_gettext('owncloud')

View File

@ -0,0 +1,17 @@
# This file is part of the Indico plugins.
# Copyright (C) 2002 - 2022 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.core.plugins import IndicoPluginBlueprint
from indico_owncloud.controllers import RHAddCategoryAttachmentOwncloud, RHAddEventAttachmentOwncloud
blueprint = IndicoPluginBlueprint('owncloud', 'indico_owncloud')
blueprint.add_url_rule('/category/<int:category_id>/manage/attachments/add/owncloud', 'owncloud_category',
RHAddCategoryAttachmentOwncloud, methods=('GET', 'POST'), defaults={'object_type': 'category'})
blueprint.add_url_rule('/event/<int:event_id>/manage/attachments/add/owncloud', 'owncloud_event',
RHAddEventAttachmentOwncloud, methods=('GET', 'POST'), defaults={'object_type': 'event'})

View File

@ -0,0 +1,21 @@
// This file is part of the Indico plugins.
// Copyright (C) 2002 - 2022 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.
// eslint-disable-next-line import/unambiguous
window.setupOwncloudFilePickerWidget = ({filepickerUrl, fieldId}) => {
window.addEventListener('message', message => {
const iframe = document.querySelector('#owncloud_filepicker-file-picker');
if (
message.origin === filepickerUrl &&
message.source === iframe.contentWindow &&
message.data &&
message.data.files
) {
document.getElementById(`${fieldId}-files`).value = message.data.files.join('\n');
}
});
};

View File

@ -0,0 +1,23 @@
// This file is part of the Indico plugins.
// Copyright (C) 2002 - 2022 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 'base/palette';
form#attachment-owncloudfilepicker-form.full-width .owncloudfilepicker {
width: 770px;
height: 280px;
border: 1px solid $light-gray;
}
.owncloudfilepicker {
border: 0px none;
width: 100%;
}
.button-label {
margin-left: 0.4rem;
}

View File

@ -0,0 +1,64 @@
# This file is part of the Indico plugins.
# Copyright (C) 2002 - 2022 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 mimetypes
import requests
from flask import flash, session
from flask_pluginengine import render_plugin_template
from indico.core.db import db
from indico.modules.attachments.controllers.management.base import (_get_folders_protection_info,
_render_attachment_list, _render_protection_message)
from indico.modules.attachments.controllers.management.category import RHCategoryAttachmentManagementBase
from indico.modules.attachments.controllers.management.event import RHEventAttachmentManagementBase
from indico.modules.attachments.models.attachments import Attachment, AttachmentFile, AttachmentType
from indico.modules.attachments.models.folders import AttachmentFolder
from indico.util.fs import secure_client_filename
from indico.web.util import jsonify_data, jsonify_template
from indico_owncloud import _
from indico_owncloud.forms import AddAttachmentOwncloudForm
class AddAttachmentOwncloudMixin:
"""Add cloud storage attachment."""
def _process(self):
form = AddAttachmentOwncloudForm(linked_object=self.object)
if form.validate_on_submit():
files = form.data['owncloud_filepicker']['files']
folder = form.data['folder'] or AttachmentFolder.get_or_create_default(linked_object=self.object)
for f in files:
filename = f.split('/')[-1].split('?')[0]
local_filename = secure_client_filename(filename)
attachment = Attachment(folder=folder, user=session.user, title=local_filename,
type=AttachmentType.file, protection_mode=form.data['protection_mode'])
if attachment.is_self_protected:
attachment.acl = form.data['acl']
content_type = mimetypes.guess_type(local_filename)[0] or 'application/octet-stream'
attachment.file = AttachmentFile(user=session.user, filename=local_filename, content_type=content_type)
file_response = requests.get(f)
file_response.raise_for_status()
attachment.file.save(file_response.content)
db.session.add(attachment)
db.session.flush()
flash(_('Attachment added'))
return jsonify_data(attachment_list=_render_attachment_list(self.object))
return jsonify_template('add_owncloud_files.html', _render_func=render_plugin_template, form=form,
protection_message=_render_protection_message(self.object),
folders_protection_info=_get_folders_protection_info(self.object))
class RHAddCategoryAttachmentOwncloud(AddAttachmentOwncloudMixin, RHCategoryAttachmentManagementBase):
pass
class RHAddEventAttachmentOwncloud(AddAttachmentOwncloudMixin, RHEventAttachmentManagementBase):
pass

View File

@ -0,0 +1,45 @@
# This file is part of the Indico plugins.
# Copyright (C) 2002 - 2022 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 wtforms import Field
from wtforms.validators import DataRequired, ValidationError
from indico.core.config import config
from indico.modules.attachments.forms import AttachmentFormBase
from indico.util.i18n import _
from indico.web.forms.widgets import JinjaWidget
from indico_owncloud.util import get_filepicker_url
class IndicoOwncloudField(Field):
widget = JinjaWidget('owncloud_filepicker_widget.html', plugin='owncloud', single_line=True, single_kwargs=True)
def __init__(self, label=None, validators=None, **kwargs):
super().__init__(label, validators, **kwargs)
self.filepicker_url = get_filepicker_url()
self.origin = config.BASE_URL
def process_formdata(self, valuelist):
if valuelist:
self.data = {'files': [f.rstrip() for f in valuelist[0].split('\n')]}
class AttachmentOwncloudFormMixin:
owncloud_filepicker = IndicoOwncloudField(_('Files'), [DataRequired()])
def validate_owncloud_filepicker(self, field):
if self.owncloud_filepicker.data and self.owncloud_filepicker.data['files'] == ['']:
raise ValidationError("Select files to add and click 'Select resources'")
class AddAttachmentOwncloudForm(AttachmentOwncloudFormMixin, AttachmentFormBase):
pass
class EditAttachmentOwncloudForm(AttachmentOwncloudFormMixin, AttachmentFormBase):
pass

View File

@ -0,0 +1,66 @@
# This file is part of the Indico plugins.
# Copyright (C) 2002 - 2022 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.plugin import render_plugin_template
from wtforms.fields import StringField, URLField
from wtforms.validators import DataRequired
from indico.core.plugins import IndicoPlugin
from indico.modules.attachments.views import WPEventAttachments
from indico.modules.categories.views import WPCategoryManagement
from indico.modules.events.views import WPConferenceDisplay, WPSimpleEventDisplay
from indico.web.forms.base import IndicoForm
from indico_owncloud import _
from indico_owncloud.blueprint import blueprint
from indico_owncloud.util import is_configured
class PluginSettingsForm(IndicoForm):
filepicker_url = URLField(_('File-picker URL'), [DataRequired()])
service_name = StringField(_('Service name'), [],
description=_('A customized name for the cloud service. If empty, the default '
"'The cloud' will be used."))
button_icon_url = URLField(_('Custom icon URL'), [],
description=_('URL for a customized icon to show in the add attachment button. If '
'empty, the default cloud icon will be used'))
class OwncloudPlugin(IndicoPlugin):
"""OwnCloud integration
Provides an integration with OwnCloud storage servers, enabling managers
to attach files to categories/events from their cloud storage.
"""
configurable = True
settings_form = PluginSettingsForm
default_settings = {
'filepicker_url': '',
'service_name': '',
'button_icon_url': '',
}
def init(self):
super().init()
self.template_hook('attachment-sources', self._inject_owncloud_button)
self.inject_bundle('owncloud.js', WPEventAttachments)
self.inject_bundle('owncloud.js', WPSimpleEventDisplay)
self.inject_bundle('owncloud.js', WPConferenceDisplay)
self.inject_bundle('owncloud.js', WPCategoryManagement)
self.inject_bundle('main.css', WPEventAttachments)
self.inject_bundle('main.css', WPSimpleEventDisplay)
self.inject_bundle('main.css', WPConferenceDisplay)
self.inject_bundle('main.css', WPCategoryManagement)
def get_blueprints(self):
return blueprint
def _inject_owncloud_button(self, linked_object=None, **kwargs):
if is_configured():
return render_plugin_template('owncloud_button.html', id=linked_object.id, linked_object=linked_object,
service_name=self.settings.get('service_name'),
button_icon_url=self.settings.get('button_icon_url'))

View File

@ -0,0 +1,34 @@
{% from 'forms/_form.html' import form_header, form_footer, form_rows, form_row %}
{%- block content %}
{{ form_header(form, id='attachment-owncloudfilepicker-form', classes=('full-width')) }}
{{ form_row(form.owncloud_filepicker, skip_label=true) }}
{{ form_rows(form, fields=('folder', 'protected', 'acl')) }}
{{ protection_message | safe }}
{% call form_footer(attach_form) %}
<input class="i-button big highlight" type="submit" value="{% trans %}Add{% endtrans %}">
<button class="i-button big" data-button-back>{% trans %}Cancel{% endtrans %}</button>
{% endcall %}
<script>
(function() {
'use strict';
$('#{{ form.folder.id }}').nullableselector();
const form = $('#attachment-owncloudfilepicker-form');
aclIfProtected(
$('#{{ form.protected.id }}'),
$('#{{ form.acl.id }}'),
form.find('.protected-protection-message'),
form.find('.inheriting-protection-message'),
$('#{{ form.folder.id }}')
);
messageIfFolderProtected(
$('#{{ form.protected.id }}'), $('#{{ form.folder.id }}'),
{{ folders_protection_info | tojson }},
$('.protected-protection-message'),
$('.inheriting-protection-message'),
$('.folder-protection-message')
);
})();
</script>
{%- endblock %}

View File

@ -0,0 +1,10 @@
{% set service_name = service_name or _('the cloud') %}
<a href="#" class="i-button js-dialog-action"
data-href="{{ url_for_plugin('owncloud.owncloud_event', event_id=id) }}"
data-title="{%- trans -%}Add files from {{ service_name }}{%- endtrans -%}">
<span class="{{ 'icon-cloud2' if not button_icon_url }}"></span>
{%- if button_icon_url -%}
<img height="24" src="{{ button_icon_url }}">
{%- endif -%}
<span class="button-label">{%- trans -%}From {{ service_name }}{%- endtrans -%}</span>
</a>

View File

@ -0,0 +1,20 @@
{% extends 'forms/base_widget.html' %}
{% block html %}
{% set src = field.filepicker_url + '?origin=' + field.origin %}
<div class="i-form-field-fixed-width">
<input type="hidden" name="{{ field.name }}" id="{{ field.id }}-files" {{ input_args | html_params }}>
<span id="{{ field.id }}-container">
<iframe id="{{ field.id }}-file-picker" class="owncloudfilepicker" src="{{ src }}"></iframe>
</span>
</div>
{% endblock %}
{% block javascript %}
<script>
setupOwncloudFilePickerWidget({
filepickerUrl: {{ field.filepicker_url | tojson }},
fieldId: {{ field.id | tojson }},
});
</script>
{% endblock %}

View File

@ -0,0 +1,73 @@
# Translations template for PROJECT.
# Copyright (C) 2022 ORGANIZATION
# This file is distributed under the same license as the PROJECT project.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2022.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2022-03-16 15:30+0100\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.9.1\n"
#: indico_owncloud/controllers.py:51
msgid "Attachment added"
msgstr ""
#: indico_owncloud/forms.py:34
msgid "Files"
msgstr ""
#: indico_owncloud/plugin.py:24
msgid "File-picker URL"
msgstr ""
#: indico_owncloud/plugin.py:25
msgid "Service name"
msgstr ""
#: indico_owncloud/plugin.py:26
msgid ""
"A customized name for the cloud service. If empty, the default 'The "
"cloud' will be used."
msgstr ""
#: indico_owncloud/plugin.py:28
msgid "Custom icon URL"
msgstr ""
#: indico_owncloud/plugin.py:29
msgid ""
"URL for a customized icon to show in the add attachment button. If empty,"
" the default cloud icon will be used"
msgstr ""
#: indico_owncloud/templates/add_owncloud_files.html:9
msgid "Add"
msgstr ""
#: indico_owncloud/templates/add_owncloud_files.html:10
msgid "Cancel"
msgstr ""
#: indico_owncloud/templates/owncloud_button.html:1
msgid "the cloud"
msgstr ""
#: indico_owncloud/templates/owncloud_button.html:4
#, python-format
msgid "Add files from %(service_name)s"
msgstr ""
#: indico_owncloud/templates/owncloud_button.html:9
#, python-format
msgid "From %(service_name)s"
msgstr ""

View File

@ -0,0 +1,18 @@
# This file is part of the Indico plugins.
# Copyright (C) 2002 - 2022 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.
def is_configured():
"""Check whether the plugin is properly configured."""
from indico_owncloud.plugin import OwncloudPlugin
return bool(OwncloudPlugin.settings.get('filepicker_url'))
def get_filepicker_url():
from indico_owncloud.plugin import OwncloudPlugin
return OwncloudPlugin.settings.get('filepicker_url').rstrip('/')

30
owncloud/setup.cfg Normal file
View File

@ -0,0 +1,30 @@
[metadata]
name = indico-plugin-owncloud
version = 3.2-dev
description = Integrates ownCloud storage as a source for materials
long_description = file: README.md
long_description_content_type = text/markdown; charset=UTF-8; variant=GFM
url = https://github.com/indico/indico-plugins
license = MIT
author = Indico Team
author_email = indico-team@cern.ch
classifiers =
Environment :: Plugins
Environment :: Web Environment
License :: OSI Approved :: MIT License
Programming Language :: Python :: 3.9
[options]
packages = find:
zip_safe = false
include_package_data = true
python_requires = ~=3.9.0
install_requires =
indico>=3.2.dev0
[options.entry_points]
indico.plugins =
owncloud = indico_owncloud.plugin:OwncloudPlugin
[pydocstyle]
ignore = D100,D101,D102,D103,D104,D105,D107,D203,D213

11
owncloud/setup.py Normal file
View File

@ -0,0 +1,11 @@
# This file is part of the Indico plugins.
# Copyright (C) 2002 - 2022 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 setuptools import setup
setup()

View File

@ -0,0 +1,6 @@
{
"entry": {
"owncloud": "./index.js",
"main": "./style.scss"
}
}