mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-01-21 07:55:00 +00:00
Backup module: Implemented uploading files
Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
This commit is contained in:
parent
35446f2ca4
commit
7da361bbca
@ -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)
|
||||
|
||||
@ -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})
|
||||
|
||||
@ -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 %}
|
||||
|
||||
40
plinth/modules/backups/templates/backups_upload.html
Normal file
40
plinth/modules/backups/templates/backups_upload.html
Normal 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 %}
|
||||
@ -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'),
|
||||
]
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user