Backup module: Implemented uploading files

Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
This commit is contained in:
Michael Pimmer 2018-09-16 15:15:56 +00:00 committed by James Valleroy
parent 35446f2ca4
commit 7da361bbca
No known key found for this signature in database
GPG Key ID: 77C0C75E7B650808
6 changed files with 138 additions and 16 deletions

View File

@ -45,6 +45,8 @@ service = None
MANIFESTS_FOLDER = '/var/lib/plinth/backups-manifests/'
BACKUP_FOLDER_NAME = 'FreedomBox-backups'
def init():
"""Intialize the module."""
@ -106,13 +108,10 @@ def delete_archive(name):
def export_archive(name, location):
if location[-1] != '/':
location += '/'
filename = location + 'FreedomBox-backups/' + get_valid_filename(
name) + '.tar.gz'
filepath = get_archive_path(location, get_valid_filename(name) + '.tar.gz')
# TODO: that's a full path, not a filename; rename argument
actions.superuser_run('backups',
['export', '--name', name, '--filename', filename])
['export', '--name', name, '--filename', filepath])
def get_export_locations():
@ -140,13 +139,17 @@ def get_export_files():
return export_files
def get_archive_path(location, archive_name):
return os.path.join(location, BACKUP_FOLDER_NAME, archive_name)
def find_exported_archive(disk_label, archive_name):
"""Return the full path for the exported archive file."""
locations = get_export_locations()
for location in locations:
if location[1] == disk_label:
return os.path.join(location[0], 'FreedomBox-backups',
archive_name)
for (location_path, location_name) in locations:
if location_name == disk_label:
return get_archive_path(location_path, archive_name)
raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT),
archive_name)

View File

@ -18,12 +18,15 @@
Forms for backups module.
"""
import os
from django import forms
from django.core import validators
from django.core.validators import FileExtensionValidator
from django.utils.translation import ugettext_lazy as _
from . import backups as backups_api
from . import get_export_locations
from . import get_export_locations, get_archive_path
class CreateArchiveForm(forms.Form):
@ -72,3 +75,35 @@ class RestoreForm(forms.Form):
self.fields['selected_apps'].choices = [
(app[0], app[1].name) for app in apps]
self.fields['selected_apps'].initial = [app[0] for app in apps]
class UploadForm(forms.Form):
location = forms.ChoiceField(
choices=(),
label=_('Location'),
initial='',
widget=forms.Select(),
required=True,
help_text=_('Location to upload the archive to'))
file = forms.FileField(label=_('Upload File'), required=True,
validators=[FileExtensionValidator(['gz'],
'Backup files have to be in .tar.gz format')],
help_text=_('Select the backup file you want to upload'))
def __init__(self, *args, **kwargs):
"""Initialize the form with location choices."""
super().__init__(*args, **kwargs)
# TODO: write a test that assures the format of get_export_locations
self.fields['location'].choices = get_export_locations()
def clean(self):
"""Check that the uploaded file does not exist"""
cleaned_data = super().clean()
file = cleaned_data.get('file')
location = cleaned_data.get('location')
if (file and file.name):
filepath = get_archive_path(location, file.name)
if os.path.exists(filepath):
raise forms.ValidationError("File %s already exists" % file.name)
else:
self.cleaned_data.update({'filepath': filepath})

View File

@ -39,6 +39,8 @@
<p>{{ paragraph|safe }}</p>
{% endfor %}
<h3>{% trans 'Backup archives' %}</h3>
{% if available_apps %}
<p>
<a title="{% trans 'New backup' %}"
@ -63,7 +65,6 @@
</p>
{% endif %}
<h3>{% trans 'Backup archives' %}</h3>
{% if not archives %}
<p>{% trans 'No archives currently exist.' %}</p>
{% else %}
@ -97,9 +98,9 @@
</table>
{% endif %}
<h3>{% trans 'Exported backup archives' %}</h3>
<h3>{% trans 'Existing backup files' %}</h3>
{% if not exports %}
<p>{% trans 'No exported backup archives were found.' %}</p>
<p>{% trans 'No existing backup files were found.' %}</p>
{% else %}
<table class="table table-bordered table-condensed table-striped"
id="exports-list">
@ -134,4 +135,12 @@
</table>
{% endif %}
<p>
<a title="{% trans 'Upload a backup file' %}"
role="button" class="btn btn-primary"
href="{% url 'backups:upload' %}">
{% trans 'Upload backup file' %}
</a>
</p>
{% endblock %}

View File

@ -0,0 +1,40 @@
{% 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 bootstrap %}
{% load i18n %}
{% block page_head %}
{% endblock %}
{% block content %}
<h3>{{ title }}</h3>
<form class="form" enctype="multipart/form-data" method="post">
{% csrf_token %}
{{ form|bootstrap }}
<input type="submit" class="btn btn-primary"
value="{% trans "Upload file" %}"/>
</form>
{% endblock %}

View File

@ -21,7 +21,7 @@ URLs for the backups module.
from django.conf.urls import url
from .views import IndexView, CreateArchiveView, DownloadArchiveView, \
ExportArchiveView, DeleteArchiveView, RestoreView
DeleteArchiveView, ExportArchiveView, RestoreView, UploadArchiveView
urlpatterns = [
url(r'^sys/backups/$', IndexView.as_view(), name='index'),
@ -32,6 +32,7 @@ urlpatterns = [
DownloadArchiveView.as_view(), name='download'),
url(r'^sys/backups/delete/(?P<name>[^/]+)/$',
DeleteArchiveView.as_view(), name='delete'),
url(r'^sys/backups/upload/$', UploadArchiveView.as_view(), name='upload'),
url(r'^sys/backups/restore/(?P<label>[^/]+)/(?P<name>[^/]+)/$',
RestoreView.as_view(), name='restore'),
]

View File

@ -29,12 +29,22 @@ from django.http import Http404, HttpResponse
from django.shortcuts import redirect
from django.urls import reverse_lazy
from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy
from django.views.generic import View, FormView, TemplateView
from plinth.modules import backups
from . import backups as backups_api, find_exported_archive
from .forms import CreateArchiveForm, ExportArchiveForm, RestoreForm
from .forms import CreateArchiveForm, ExportArchiveForm, RestoreForm, UploadForm
subsubmenu = [{
'url': reverse_lazy('backups:index'),
'text': ugettext_lazy('Backups')
}, {
'url': reverse_lazy('backups:upload'),
'text': ugettext_lazy('Upload backup')
}]
class IndexView(TemplateView):
@ -49,6 +59,7 @@ class IndexView(TemplateView):
context['info'] = backups.get_info()
context['archives'] = backups.list_archives()
context['exports'] = backups.get_export_files()
context['subsubmenu'] = subsubmenu
apps = backups_api.get_all_apps_for_backup()
context['available_apps'] = [app[0] for app in apps]
return context
@ -66,6 +77,7 @@ class CreateArchiveView(SuccessMessageMixin, FormView):
"""Return additional context for rendering the template."""
context = super().get_context_data(**kwargs)
context['title'] = _('New Backup')
context['subsubmenu'] = subsubmenu
return context
def get_initial(self):
@ -121,6 +133,28 @@ class DownloadArchiveView(View):
return response
class UploadArchiveView(SuccessMessageMixin, FormView):
form_class = UploadForm
prefix = 'backups'
template_name = 'backups_upload.html'
success_url = reverse_lazy('backups:index')
success_message = _('Archive uploaded.')
def get_context_data(self, **kwargs):
"""Return additional context for rendering the template."""
context = super().get_context_data(**kwargs)
context['title'] = _('Upload Backup File')
context['subsubmenu'] = subsubmenu
return context
def form_valid(self, form):
"""store uploaded file."""
with open(form.cleaned_data['filepath'], 'wb+') as destination:
for chunk in self.request.FILES['backups-file'].chunks():
destination.write(chunk)
return super().form_valid(form)
class ExportArchiveView(SuccessMessageMixin, FormView):
"""View to export an archive."""
form_class = ExportArchiveForm