mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-05-20 10:34:30 +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/'
|
MANIFESTS_FOLDER = '/var/lib/plinth/backups-manifests/'
|
||||||
|
|
||||||
|
BACKUP_FOLDER_NAME = 'FreedomBox-backups'
|
||||||
|
|
||||||
|
|
||||||
def init():
|
def init():
|
||||||
"""Intialize the module."""
|
"""Intialize the module."""
|
||||||
@ -106,13 +108,10 @@ def delete_archive(name):
|
|||||||
|
|
||||||
|
|
||||||
def export_archive(name, location):
|
def export_archive(name, location):
|
||||||
if location[-1] != '/':
|
filepath = get_archive_path(location, get_valid_filename(name) + '.tar.gz')
|
||||||
location += '/'
|
# TODO: that's a full path, not a filename; rename argument
|
||||||
|
|
||||||
filename = location + 'FreedomBox-backups/' + get_valid_filename(
|
|
||||||
name) + '.tar.gz'
|
|
||||||
actions.superuser_run('backups',
|
actions.superuser_run('backups',
|
||||||
['export', '--name', name, '--filename', filename])
|
['export', '--name', name, '--filename', filepath])
|
||||||
|
|
||||||
|
|
||||||
def get_export_locations():
|
def get_export_locations():
|
||||||
@ -140,13 +139,17 @@ def get_export_files():
|
|||||||
return 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):
|
def find_exported_archive(disk_label, archive_name):
|
||||||
"""Return the full path for the exported archive file."""
|
"""Return the full path for the exported archive file."""
|
||||||
locations = get_export_locations()
|
locations = get_export_locations()
|
||||||
for location in locations:
|
for (location_path, location_name) in locations:
|
||||||
if location[1] == disk_label:
|
if location_name == disk_label:
|
||||||
return os.path.join(location[0], 'FreedomBox-backups',
|
return get_archive_path(location_path, archive_name)
|
||||||
archive_name)
|
|
||||||
|
|
||||||
raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT),
|
raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT),
|
||||||
archive_name)
|
archive_name)
|
||||||
|
|||||||
@ -18,12 +18,15 @@
|
|||||||
Forms for backups module.
|
Forms for backups module.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.core import validators
|
from django.core import validators
|
||||||
|
from django.core.validators import FileExtensionValidator
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from . import backups as backups_api
|
from . import backups as backups_api
|
||||||
from . import get_export_locations
|
from . import get_export_locations, get_archive_path
|
||||||
|
|
||||||
|
|
||||||
class CreateArchiveForm(forms.Form):
|
class CreateArchiveForm(forms.Form):
|
||||||
@ -72,3 +75,35 @@ class RestoreForm(forms.Form):
|
|||||||
self.fields['selected_apps'].choices = [
|
self.fields['selected_apps'].choices = [
|
||||||
(app[0], app[1].name) for app in apps]
|
(app[0], app[1].name) for app in apps]
|
||||||
self.fields['selected_apps'].initial = [app[0] 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>
|
<p>{{ paragraph|safe }}</p>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
|
<h3>{% trans 'Backup archives' %}</h3>
|
||||||
|
|
||||||
{% if available_apps %}
|
{% if available_apps %}
|
||||||
<p>
|
<p>
|
||||||
<a title="{% trans 'New backup' %}"
|
<a title="{% trans 'New backup' %}"
|
||||||
@ -63,7 +65,6 @@
|
|||||||
</p>
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<h3>{% trans 'Backup archives' %}</h3>
|
|
||||||
{% if not archives %}
|
{% if not archives %}
|
||||||
<p>{% trans 'No archives currently exist.' %}</p>
|
<p>{% trans 'No archives currently exist.' %}</p>
|
||||||
{% else %}
|
{% else %}
|
||||||
@ -97,9 +98,9 @@
|
|||||||
</table>
|
</table>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<h3>{% trans 'Exported backup archives' %}</h3>
|
<h3>{% trans 'Existing backup files' %}</h3>
|
||||||
{% if not exports %}
|
{% if not exports %}
|
||||||
<p>{% trans 'No exported backup archives were found.' %}</p>
|
<p>{% trans 'No existing backup files were found.' %}</p>
|
||||||
{% else %}
|
{% else %}
|
||||||
<table class="table table-bordered table-condensed table-striped"
|
<table class="table table-bordered table-condensed table-striped"
|
||||||
id="exports-list">
|
id="exports-list">
|
||||||
@ -134,4 +135,12 @@
|
|||||||
</table>
|
</table>
|
||||||
{% endif %}
|
{% 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 %}
|
{% 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 django.conf.urls import url
|
||||||
|
|
||||||
from .views import IndexView, CreateArchiveView, DownloadArchiveView, \
|
from .views import IndexView, CreateArchiveView, DownloadArchiveView, \
|
||||||
ExportArchiveView, DeleteArchiveView, RestoreView
|
DeleteArchiveView, ExportArchiveView, RestoreView, UploadArchiveView
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^sys/backups/$', IndexView.as_view(), name='index'),
|
url(r'^sys/backups/$', IndexView.as_view(), name='index'),
|
||||||
@ -32,6 +32,7 @@ urlpatterns = [
|
|||||||
DownloadArchiveView.as_view(), name='download'),
|
DownloadArchiveView.as_view(), name='download'),
|
||||||
url(r'^sys/backups/delete/(?P<name>[^/]+)/$',
|
url(r'^sys/backups/delete/(?P<name>[^/]+)/$',
|
||||||
DeleteArchiveView.as_view(), name='delete'),
|
DeleteArchiveView.as_view(), name='delete'),
|
||||||
|
url(r'^sys/backups/upload/$', UploadArchiveView.as_view(), name='upload'),
|
||||||
url(r'^sys/backups/restore/(?P<label>[^/]+)/(?P<name>[^/]+)/$',
|
url(r'^sys/backups/restore/(?P<label>[^/]+)/(?P<name>[^/]+)/$',
|
||||||
RestoreView.as_view(), name='restore'),
|
RestoreView.as_view(), name='restore'),
|
||||||
]
|
]
|
||||||
|
|||||||
@ -29,12 +29,22 @@ from django.http import Http404, HttpResponse
|
|||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
|
from django.utils.translation import ugettext_lazy
|
||||||
from django.views.generic import View, FormView, TemplateView
|
from django.views.generic import View, FormView, TemplateView
|
||||||
|
|
||||||
from plinth.modules import backups
|
from plinth.modules import backups
|
||||||
|
|
||||||
from . import backups as backups_api, find_exported_archive
|
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):
|
class IndexView(TemplateView):
|
||||||
@ -49,6 +59,7 @@ class IndexView(TemplateView):
|
|||||||
context['info'] = backups.get_info()
|
context['info'] = backups.get_info()
|
||||||
context['archives'] = backups.list_archives()
|
context['archives'] = backups.list_archives()
|
||||||
context['exports'] = backups.get_export_files()
|
context['exports'] = backups.get_export_files()
|
||||||
|
context['subsubmenu'] = subsubmenu
|
||||||
apps = backups_api.get_all_apps_for_backup()
|
apps = backups_api.get_all_apps_for_backup()
|
||||||
context['available_apps'] = [app[0] for app in apps]
|
context['available_apps'] = [app[0] for app in apps]
|
||||||
return context
|
return context
|
||||||
@ -66,6 +77,7 @@ class CreateArchiveView(SuccessMessageMixin, FormView):
|
|||||||
"""Return additional context for rendering the template."""
|
"""Return additional context for rendering the template."""
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
context['title'] = _('New Backup')
|
context['title'] = _('New Backup')
|
||||||
|
context['subsubmenu'] = subsubmenu
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def get_initial(self):
|
def get_initial(self):
|
||||||
@ -121,6 +133,28 @@ class DownloadArchiveView(View):
|
|||||||
return response
|
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):
|
class ExportArchiveView(SuccessMessageMixin, FormView):
|
||||||
"""View to export an archive."""
|
"""View to export an archive."""
|
||||||
form_class = ExportArchiveForm
|
form_class = ExportArchiveForm
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user