mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-05-20 10:34:30 +00:00
sharing: Finish implementation
Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org> Reviewed-by: Joseph Nuthalapati <njoseph@thoughtworks.com>
This commit is contained in:
parent
a42aed78f1
commit
ebabb2f8aa
185
actions/sharing
Normal file → Executable file
185
actions/sharing
Normal file → Executable file
@ -1,7 +1,6 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/python3
|
||||||
# -*- mode: python -*-
|
|
||||||
#
|
#
|
||||||
# This file is part of Plinth.
|
# This file is part of FreedomBox.
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU Affero General Public License as
|
# it under the terms of the GNU Affero General Public License as
|
||||||
@ -16,77 +15,177 @@
|
|||||||
# You should have received a copy of the GNU Affero General Public License
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Configuration helper for the sharing module
|
Configuration helper for the sharing app.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
import json
|
||||||
import os
|
import os
|
||||||
|
import pathlib
|
||||||
|
import re
|
||||||
|
|
||||||
import augeas
|
import augeas
|
||||||
|
|
||||||
|
from plinth import action_utils
|
||||||
|
|
||||||
|
APACHE_CONFIGURATION = '/etc/apache2/conf-available/sharing-freedombox.conf'
|
||||||
|
|
||||||
|
|
||||||
def parse_arguments():
|
def parse_arguments():
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
subparsers = parser.add_subparsers(help='Sub command')
|
subparsers = parser.add_subparsers(dest='subcommand', help='Sub command')
|
||||||
|
|
||||||
subparsers.add_parser('add', help='Add a new share')
|
subparsers.add_parser('list', help='List all existing shares')
|
||||||
subparsers.add_parser('remove', help='Remove an existing share')
|
|
||||||
subparsers.add_parser('list', help='Remove an existing share')
|
add_parser = subparsers.add_parser('add', help='Add a new share')
|
||||||
|
add_parser.add_argument('--name', required=True, help='Name of the share')
|
||||||
|
add_parser.add_argument('--path', required=True, help='Disk path to share')
|
||||||
|
add_parser.add_argument('--groups', nargs='*',
|
||||||
|
help='List of groups that can access the share')
|
||||||
|
|
||||||
|
remove_parser = subparsers.add_parser('remove',
|
||||||
|
help='Remove an existing share')
|
||||||
|
remove_parser.add_argument('--name', required=True,
|
||||||
|
help='Name of the share to remove')
|
||||||
|
|
||||||
subparsers.required = True
|
subparsers.required = True
|
||||||
return parser.parse_args()
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
def load_augeas(conf_file):
|
def load_augeas():
|
||||||
aug = augeas.Augeas(flags=augeas.Augeas.NO_LOAD +
|
"""Initialize augeas for this app's configuration file."""
|
||||||
augeas.Augeas.NO_MODL_AUTOLOAD)
|
aug = augeas.Augeas(
|
||||||
|
flags=augeas.Augeas.NO_LOAD + augeas.Augeas.NO_MODL_AUTOLOAD)
|
||||||
aug.set('/augeas/load/Httpd/lens', 'Httpd.lns')
|
aug.set('/augeas/load/Httpd/lens', 'Httpd.lns')
|
||||||
aug.set('/augeas/load/Httpd/incl[last() + 1]', conf_file)
|
aug.set('/augeas/load/Httpd/incl[last() + 1]', APACHE_CONFIGURATION)
|
||||||
aug.load()
|
aug.load()
|
||||||
|
|
||||||
|
aug.defvar('conf', '/files' + APACHE_CONFIGURATION)
|
||||||
|
|
||||||
return aug
|
return aug
|
||||||
|
|
||||||
|
|
||||||
# TODO: Handle the error case scenarios
|
|
||||||
def subcommand_add(arguments):
|
def subcommand_add(arguments):
|
||||||
share_url = arguments.url
|
"""Add a share to Apache configuration."""
|
||||||
share_path = arguments.path
|
name = arguments.name
|
||||||
share_user = arguments.user
|
path = arguments.path
|
||||||
|
groups = arguments.groups
|
||||||
|
url = '/share/' + name
|
||||||
|
|
||||||
aug = load_augeas('/etc/apache2/sites-available/sharing.conf')
|
if not os.path.exists(APACHE_CONFIGURATION):
|
||||||
aug.defvar('conf', '/files/etc/apache2/sites-available/sharing.conf')
|
pathlib.Path(APACHE_CONFIGURATION).touch()
|
||||||
|
|
||||||
|
aug = load_augeas()
|
||||||
|
shares = _list(aug)
|
||||||
|
if any([share for share in shares if share['name'] == name]):
|
||||||
|
raise Exception('Share already present')
|
||||||
|
|
||||||
|
aug.set('$conf/directive[last() + 1]', 'Alias')
|
||||||
|
aug.set('$conf/directive[last()]/arg[1]', url)
|
||||||
|
aug.set('$conf/directive[last()]/arg[2]', path)
|
||||||
|
|
||||||
|
aug.set('$conf/Location[last() + 1]/arg', url)
|
||||||
|
|
||||||
|
aug.set('$conf/Location[last()]/directive[last() + 1]', 'Include')
|
||||||
|
aug.set('$conf/Location[last()]/directive[last()]/arg',
|
||||||
|
'includes/freedombox-sharing.conf')
|
||||||
|
aug.set('$conf/Location[last()]/directive[last() + 1]', 'Include')
|
||||||
|
aug.set('$conf/Location[last()]/directive[last()]/arg',
|
||||||
|
'includes/freedombox-single-sign-on.conf')
|
||||||
|
|
||||||
|
aug.set('$conf/Location[last()]/IfModule/arg', 'mod_auth_pubtkt.c')
|
||||||
|
aug.set('$conf/Location[last()]/IfModule/directive[1]', 'TKTAuthToken')
|
||||||
|
for group_name in groups:
|
||||||
|
aug.set('$conf/Location[last()]/IfModule/directive[1]/arg[last() + 1]',
|
||||||
|
group_name)
|
||||||
|
|
||||||
|
aug.save()
|
||||||
|
|
||||||
|
with action_utils.WebserverChange() as webserver_change:
|
||||||
|
webserver_change.enable('sharing-freedombox')
|
||||||
|
|
||||||
|
|
||||||
|
def subcommand_remove(arguments):
|
||||||
|
"""Remove a share from Apache configuration."""
|
||||||
|
url_to_remove = '/share/' + arguments.name
|
||||||
|
|
||||||
|
aug = load_augeas()
|
||||||
|
|
||||||
|
for directive in aug.match('$conf/directive'):
|
||||||
|
if aug.get(directive) != 'Alias':
|
||||||
|
continue
|
||||||
|
|
||||||
|
url = aug.get(directive + '/arg[1]')
|
||||||
|
if url == url_to_remove:
|
||||||
|
aug.remove(directive)
|
||||||
|
|
||||||
|
for location in aug.match('$conf/Location'):
|
||||||
|
url = aug.get(location + '/arg')
|
||||||
|
if url == url_to_remove:
|
||||||
|
aug.remove(location)
|
||||||
|
|
||||||
|
aug.save()
|
||||||
|
|
||||||
|
with action_utils.WebserverChange() as webserver_change:
|
||||||
|
webserver_change.enable('sharing-freedombox')
|
||||||
|
|
||||||
|
|
||||||
|
def _get_name_from_url(url):
|
||||||
|
"""Return the name of the share given the URL for it."""
|
||||||
|
matches = re.match(r'/share/([a-z0-9\-]*)', url)
|
||||||
|
if not matches:
|
||||||
|
raise ValueError
|
||||||
|
|
||||||
|
return matches[1]
|
||||||
|
|
||||||
|
|
||||||
|
def _list(aug=None):
|
||||||
|
"""List all Apache configuration shares."""
|
||||||
|
if not aug:
|
||||||
|
aug = load_augeas()
|
||||||
|
|
||||||
|
shares = []
|
||||||
|
|
||||||
|
for match in aug.match('$conf/directive'):
|
||||||
|
if aug.get(match) != 'Alias':
|
||||||
|
continue
|
||||||
|
|
||||||
|
url = aug.get(match + '/arg[1]')
|
||||||
|
path = aug.get(match + '/arg[2]')
|
||||||
|
|
||||||
if os.path.exists(share_path):
|
|
||||||
try:
|
try:
|
||||||
aug.set('$conf/directive[last() + 1]', 'Alias')
|
name = _get_name_from_url(url)
|
||||||
aug.set('$conf/directive[last()]/arg[last() + 1]', share_url)
|
shares.append({
|
||||||
aug.set('$conf/directive[last()]/arg[last() + 1]', share_path)
|
'name': name,
|
||||||
|
'path': path,
|
||||||
|
'url': '/share/' + name
|
||||||
|
})
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
|
||||||
aug.set('$conf/Directory[last() + 1]')
|
for location in aug.match('$conf/Location'):
|
||||||
aug.set('$conf/Directory[last()]/arg', share_path)
|
url = aug.get(location + '/arg')
|
||||||
aug.set('$conf/Directory[last()]/directive[last() + 1]', 'Include')
|
|
||||||
aug.set('$conf/Directory[last()]/directive[last()]/arg', 'includes/freedombox-sharing.conf')
|
try:
|
||||||
aug.set('$conf/Directory[last()]/directive[last() + 1]', 'Require')
|
name = _get_name_from_url(url)
|
||||||
aug.set('$conf/Directory[last()]/directive[last()]/arg', share_user)
|
except ValueError:
|
||||||
except Exception:
|
continue
|
||||||
pass
|
|
||||||
|
groups = []
|
||||||
|
for group in aug.match(location + '//directive["TKTAuthToken"]/arg'):
|
||||||
|
groups.append(aug.get(group))
|
||||||
|
|
||||||
|
for share in shares:
|
||||||
|
if share['name'] == name:
|
||||||
|
share['groups'] = groups
|
||||||
|
|
||||||
|
return shares
|
||||||
|
|
||||||
|
|
||||||
def subcommand_list():
|
def subcommand_list(_):
|
||||||
aug = load_augeas('/etc/apache2/sites-available/sharing.conf')
|
"""List all Apache configuration shares and print as JSON."""
|
||||||
aug.defvar('conf', '/files/etc/apache2/sites-available/sharing.conf')
|
print(json.dumps({'shares': _list()}))
|
||||||
|
|
||||||
path = '/files/etc/apache2/conf-available/sharing.conf/Directory'
|
|
||||||
list_of_shares = []
|
|
||||||
for match in aug.match(path):
|
|
||||||
path = aug.get(match + '/arg')
|
|
||||||
for directive_name in aug.match(match + '/directive'):
|
|
||||||
if directive_name == 'Require':
|
|
||||||
user = aug.get(directive_name + '/arg')
|
|
||||||
list_of_shares.append(dict(path=path, user_group=user))
|
|
||||||
return list_of_shares
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|||||||
@ -1 +1,2 @@
|
|||||||
Options -FollowSymLinks
|
Options +Indexes -FollowSymLinks -ExecCGI -Includes -IncludesNOEXEC
|
||||||
|
AllowOverride None
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
#
|
#
|
||||||
# This file is part of Plinth.
|
# This file is part of FreedomBox.
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU Affero General Public License as
|
# it under the terms of the GNU Affero General Public License as
|
||||||
@ -14,94 +14,46 @@
|
|||||||
# You should have received a copy of the GNU Affero General Public License
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Plinth module to configure sharing.
|
FreedomBox app to configure sharing.
|
||||||
"""
|
"""
|
||||||
import os
|
|
||||||
|
|
||||||
from django import forms
|
import json
|
||||||
from django.template.response import TemplateResponse
|
|
||||||
from django.urls import reverse_lazy
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.utils.translation import ugettext_lazy as _, ugettext_lazy
|
|
||||||
|
|
||||||
from plinth import actions
|
from plinth import actions
|
||||||
from plinth.menu import main_menu
|
from plinth.menu import main_menu
|
||||||
from plinth.modules.users import groups
|
|
||||||
|
|
||||||
version = 1
|
version = 1
|
||||||
|
|
||||||
name = _('Sharing')
|
name = _('Sharing')
|
||||||
|
|
||||||
short_description = _('File Sharing')
|
|
||||||
|
|
||||||
description = [
|
description = [
|
||||||
_('Sharing allows you to share your content over web with a group of '
|
_('Sharing allows you to share your content over web with chosen groups'
|
||||||
'users. Add the content you would like to share in the sharing app.'),
|
'of users. Add the content you would like to share in the sharing app.'),
|
||||||
|
|
||||||
_('Sharing app will be available from <a href="/plinth/apps/add_share">'
|
|
||||||
'/sharing</a> path on the web server.'),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
subsubmenu = [{'url': reverse_lazy('sharing:about'),
|
|
||||||
'text': ugettext_lazy('About')},
|
|
||||||
{'url': reverse_lazy('sharing:add_share'),
|
|
||||||
'text': ugettext_lazy('Add share')}]
|
|
||||||
|
|
||||||
|
|
||||||
def init():
|
def init():
|
||||||
"""Initialize the module."""
|
"""Initialize the module."""
|
||||||
menu = main_menu.get('apps')
|
menu = main_menu.get('apps')
|
||||||
menu.add_urlname(name, 'glyphicon-share', 'sharing:about')
|
menu.add_urlname(name, 'glyphicon-share', 'sharing:index')
|
||||||
|
|
||||||
|
|
||||||
def index(request):
|
def list_shares():
|
||||||
return TemplateResponse(request, 'about.html',
|
"""Return a list of shares."""
|
||||||
{'title': name,
|
output = actions.superuser_run('sharing', ['list'])
|
||||||
'description': description,
|
return json.loads(output)['shares']
|
||||||
'subsubmenu': subsubmenu})
|
|
||||||
|
|
||||||
|
|
||||||
# TODO: handle the error case
|
def add_share(name, path, groups):
|
||||||
def add_path_to_share(url, path, user_group):
|
"""Add a new share by called the action script."""
|
||||||
if os.path.exists(path):
|
actions.superuser_run(
|
||||||
actions.superuser_run('sharing', options=['add', url, path, user_group])
|
'sharing',
|
||||||
else:
|
['add', '--name', name, '--path', path, '--groups'] + groups)
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def share(request):
|
def remove_share(name):
|
||||||
if request.method == 'POST':
|
"""Remove a share by calling the action script."""
|
||||||
form = AddShareForm(request.POST)
|
actions.superuser_run('sharing', ['remove', '--name', name])
|
||||||
|
|
||||||
if form.is_valid():
|
|
||||||
path = form.cleaned_data['share_path']
|
|
||||||
user_group = form.cleaned_data['user_group']
|
|
||||||
share_url = 'share_' + path.split("/")[len(path.split("/")) - 1]
|
|
||||||
add_path_to_share(share_url, path, user_group)
|
|
||||||
|
|
||||||
form = AddShareForm()
|
|
||||||
|
|
||||||
else:
|
|
||||||
form = AddShareForm()
|
|
||||||
|
|
||||||
return TemplateResponse(request, 'share.html',
|
|
||||||
{'title': name,
|
|
||||||
'subsubmenu': subsubmenu,
|
|
||||||
'form': form})
|
|
||||||
|
|
||||||
|
|
||||||
class AddShareForm(forms.Form):
|
|
||||||
share_path = forms.CharField(
|
|
||||||
label=_('Add path'),
|
|
||||||
help_text=_('Add the path to the folder you want to share'))
|
|
||||||
|
|
||||||
user_group = forms.ChoiceField(
|
|
||||||
required=False,
|
|
||||||
choices=groups,
|
|
||||||
label=_('User-group'),
|
|
||||||
initial=None)
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(forms.Form, self).__init__(*args, **kwargs)
|
|
||||||
return
|
|
||||||
|
|||||||
64
plinth/modules/sharing/forms.py
Normal file
64
plinth/modules/sharing/forms.py
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
#
|
||||||
|
# This file is part of FreedomBox.
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
# published by the Free Software Foundation, either version 3 of the
|
||||||
|
# License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program 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 Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
"""
|
||||||
|
Django forms for sharing app.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django import forms
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from plinth.modules.users.forms import get_group_choices
|
||||||
|
from plinth.modules import sharing
|
||||||
|
|
||||||
|
|
||||||
|
class AddShareForm(forms.Form):
|
||||||
|
"""Form to add a new share."""
|
||||||
|
|
||||||
|
name = forms.RegexField(
|
||||||
|
label=_('Name of the share'), strip=True, regex=r'^[a-z0-9]+$',
|
||||||
|
help_text=_(
|
||||||
|
'A lowercase alpha-numeric string that uniquely identifies a '
|
||||||
|
'share. Example: <em>media</em>.'))
|
||||||
|
|
||||||
|
path = forms.CharField(
|
||||||
|
label=_('Path to share'), strip=True, help_text=_(
|
||||||
|
'Disk path to a folder on this server that you intend to share.'))
|
||||||
|
|
||||||
|
groups = forms.MultipleChoiceField(
|
||||||
|
widget=forms.CheckboxSelectMultiple,
|
||||||
|
label=_('User groups who can read the files in the share'),
|
||||||
|
help_text=_(
|
||||||
|
'Users who have these permissions will also be able to read the '
|
||||||
|
'files in the share.'))
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
"""Initialize the form with extra request argument."""
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.fields['groups'].choices = get_group_choices()
|
||||||
|
|
||||||
|
def clean_name(self):
|
||||||
|
"""Check if the name is valid."""
|
||||||
|
name = self.cleaned_data['name']
|
||||||
|
if 'name' in self.initial and name == self.initial['name']:
|
||||||
|
return name
|
||||||
|
|
||||||
|
if any((share for share in sharing.list_shares()
|
||||||
|
if name == share['name'])):
|
||||||
|
raise ValidationError(_('A share with this name already exists.'))
|
||||||
|
|
||||||
|
return name
|
||||||
@ -1,25 +0,0 @@
|
|||||||
{% extends "simple_service.html" %}
|
|
||||||
{% comment %}
|
|
||||||
#
|
|
||||||
# This file is part of Plinth.
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU Affero General Public License as
|
|
||||||
# published by the Free Software Foundation, either version 3 of the
|
|
||||||
# License, or (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program 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 Affero General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
{% endcomment %}
|
|
||||||
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block configuration %}
|
|
||||||
|
|
||||||
{% endblock %}
|
|
||||||
83
plinth/modules/sharing/templates/sharing.html
Normal file
83
plinth/modules/sharing/templates/sharing.html
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% comment %}
|
||||||
|
#
|
||||||
|
# This file is part of FreedomBox.
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
# published by the Free Software Foundation, either version 3 of the
|
||||||
|
# License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program 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 Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
{% endcomment %}
|
||||||
|
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<h2>{{ title }}</h2>
|
||||||
|
|
||||||
|
{% for paragraph in description %}
|
||||||
|
<p>{{ paragraph|safe }}</p>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<a title="{% trans 'Add new share' %}"
|
||||||
|
role="button" class="btn btn-primary"
|
||||||
|
href="{% url 'sharing:add' %}">
|
||||||
|
{% trans 'Add new share' %}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{% if not shares %}
|
||||||
|
<p>{% trans 'No shares currently configured.' %}</p>
|
||||||
|
{% else %}
|
||||||
|
<table class="table table-bordered table-condensed table-striped" id="shares-list">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{% trans "Share Name" %}</th>
|
||||||
|
<th>{% trans "Disk Path" %}</th>
|
||||||
|
<th>{% trans "Shared Over" %}</th>
|
||||||
|
<th>{% trans "To Groups" %}</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
{% for share in shares %}
|
||||||
|
<tr id="share-{{ share.name }}" class="share">
|
||||||
|
<td class="share-name">{{ share.name }}</td>
|
||||||
|
<td class="share-path">{{ share.path }}</td>
|
||||||
|
<td class="share-url">
|
||||||
|
<a href="{{ share.url }}"
|
||||||
|
title="{{ share.url}}">
|
||||||
|
{{ share.url }}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td class="share-groups">{{ share.groups|join:", " }}</td>
|
||||||
|
<td class="share-operations">
|
||||||
|
<form class="form form-inline" method="post"
|
||||||
|
action="{% url 'sharing:remove' share.name %}">
|
||||||
|
{% csrf_token %}
|
||||||
|
<button class="share-remove btn btn-sm btn-default glyphicon glyphicon-trash pull-right"
|
||||||
|
type="submit"></button>
|
||||||
|
</form>
|
||||||
|
<a class="share-edit btn btn-sm btn-default pull-right"
|
||||||
|
href="{% url 'sharing:edit' share.name %}">
|
||||||
|
<span class="glyphicon glyphicon-edit" aria-hidden="true"></span>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@ -1,7 +1,7 @@
|
|||||||
{% extends "simple_service.html" %}
|
{% extends "base.html" %}
|
||||||
{% comment %}
|
{% comment %}
|
||||||
#
|
#
|
||||||
# This file is part of Plinth.
|
# This file is part of FreedomBox.
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU Affero General Public License as
|
# it under the terms of the GNU Affero General Public License as
|
||||||
@ -21,16 +21,23 @@
|
|||||||
{% load bootstrap %}
|
{% load bootstrap %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
{% block configuration %}
|
{% block content %}
|
||||||
|
|
||||||
<form class="form" method="post">
|
<h3>{{ title }}</h3>
|
||||||
{% csrf_token %}
|
|
||||||
|
|
||||||
{{ form|bootstrap }}
|
<form class="form" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
|
||||||
<input type="submit" class="btn btn-primary"
|
{{ form|bootstrap }}
|
||||||
value="{% trans "Submit" %}"/>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
|
<input type="submit" class="btn btn-primary"
|
||||||
|
value="{% trans "Submit" %}"/>
|
||||||
|
</form>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block page_js %}
|
||||||
|
<script>
|
||||||
|
$('#id_sharing-name').focus();
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
@ -1,5 +1,5 @@
|
|||||||
#
|
#
|
||||||
# This file is part of Plinth.
|
# This file is part of FreedomBox.
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU Affero General Public License as
|
# it under the terms of the GNU Affero General Public License as
|
||||||
@ -14,16 +14,18 @@
|
|||||||
# You should have received a copy of the GNU Affero General Public License
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
|
|
||||||
"""
|
"""
|
||||||
URLs for the sharing module.
|
URLs for the sharing app.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.conf.urls import url
|
||||||
|
|
||||||
from . import index, share
|
from .views import AddShareView, EditShareView, IndexView, remove
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^apps/sharing/$', index, name='about'),
|
url(r'^apps/sharing/$', IndexView.as_view(), name='index'),
|
||||||
url(r'^apps/sharing/add_share$', share, name='add_share'),
|
url(r'^apps/sharing/add/$', AddShareView.as_view(), name='add'),
|
||||||
]
|
url(r'^apps/sharing/(?P<name>[a-z0-9]+)/edit/$', EditShareView.as_view(),
|
||||||
|
name='edit'),
|
||||||
|
url(r'^apps/sharing/(?P<name>[a-z0-9]+)/remove/$', remove, name='remove'),
|
||||||
|
]
|
||||||
|
|||||||
109
plinth/modules/sharing/views.py
Normal file
109
plinth/modules/sharing/views.py
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
#
|
||||||
|
# This file is part of FreedomBox.
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
# published by the Free Software Foundation, either version 3 of the
|
||||||
|
# License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program 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 Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
"""
|
||||||
|
Views for the sharing app.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django.contrib import messages
|
||||||
|
from django.contrib.messages.views import SuccessMessageMixin
|
||||||
|
from django.http import Http404
|
||||||
|
from django.shortcuts import redirect
|
||||||
|
from django.urls import reverse_lazy
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from django.views.decorators.http import require_POST
|
||||||
|
from django.views.generic import FormView, TemplateView
|
||||||
|
|
||||||
|
from plinth.modules import sharing
|
||||||
|
|
||||||
|
from .forms import AddShareForm
|
||||||
|
|
||||||
|
|
||||||
|
class IndexView(TemplateView):
|
||||||
|
"""View to show list of shares."""
|
||||||
|
template_name = 'sharing.html'
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
"""Return additional context for rendering the template."""
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
context['title'] = sharing.name
|
||||||
|
context['description'] = sharing.description
|
||||||
|
context['shares'] = sharing.list_shares()
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
class AddShareView(SuccessMessageMixin, FormView):
|
||||||
|
"""View to add a new share."""
|
||||||
|
form_class = AddShareForm
|
||||||
|
prefix = 'sharing'
|
||||||
|
template_name = 'sharing_add_edit.html'
|
||||||
|
success_url = reverse_lazy('sharing:index')
|
||||||
|
success_message = _('Share added.')
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
"""Return additional context for rendering the template."""
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
context['title'] = _('Add Share')
|
||||||
|
return context
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
"""Add the share on valid form submission."""
|
||||||
|
sharing.add_share(form.cleaned_data['name'], form.cleaned_data['path'],
|
||||||
|
form.cleaned_data['groups'])
|
||||||
|
return super().form_valid(form)
|
||||||
|
|
||||||
|
|
||||||
|
class EditShareView(SuccessMessageMixin, FormView):
|
||||||
|
"""View to edit an existing share."""
|
||||||
|
form_class = AddShareForm
|
||||||
|
prefix = 'sharing'
|
||||||
|
template_name = 'sharing_add_edit.html'
|
||||||
|
success_url = reverse_lazy('sharing:index')
|
||||||
|
success_message = _('Share edited.')
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
"""Return additional context for rendering the template."""
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
context['title'] = _('Edit Share')
|
||||||
|
return context
|
||||||
|
|
||||||
|
def get_initial(self):
|
||||||
|
"""Load information about share being edited."""
|
||||||
|
try:
|
||||||
|
return [
|
||||||
|
share for share in sharing.list_shares()
|
||||||
|
if share['name'] == self.kwargs['name']
|
||||||
|
][0]
|
||||||
|
except IndexError:
|
||||||
|
raise Http404
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
"""Add the share on valid form submission."""
|
||||||
|
if form.initial != form.cleaned_data:
|
||||||
|
sharing.remove_share(form.initial['name'])
|
||||||
|
sharing.add_share(form.cleaned_data['name'],
|
||||||
|
form.cleaned_data['path'],
|
||||||
|
form.cleaned_data['groups'])
|
||||||
|
|
||||||
|
return super().form_valid(form)
|
||||||
|
|
||||||
|
|
||||||
|
@require_POST
|
||||||
|
def remove(request, name):
|
||||||
|
"""View to remove a share."""
|
||||||
|
sharing.remove_share(name)
|
||||||
|
messages.success(request, _('Share deleted.'))
|
||||||
|
return redirect(reverse_lazy('sharing:index'))
|
||||||
Loading…
x
Reference in New Issue
Block a user