backups: List apps that don't require backup too

- This serves two purposes. First is to assure user in the interface that backup
  of the module is not required. Second is to make sure that if an application
  is installed during backup it is also reinstalled during restore process (this
  need to be implemented).

- Allow backup test to run independently. Initialize the cfg module so that
  load_modules() works.

Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
This commit is contained in:
Joseph Nuthalapati 2018-09-28 19:45:35 -07:00 committed by James Valleroy
parent 26764b7370
commit 8d7ede728e
No known key found for this signature in database
GPG Key ID: 77C0C75E7B650808
4 changed files with 63 additions and 21 deletions

View File

@ -202,7 +202,11 @@ def get_all_apps_for_backup():
if not hasattr(module, 'backup'): if not hasattr(module, 'backup'):
continue continue
apps.append((module_name, module)) apps.append({
'name': module_name,
'app': module,
'has_data': bool(module.backup)
})
return apps return apps

View File

@ -23,12 +23,26 @@ 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.core.validators import FileExtensionValidator
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext, ugettext_lazy as _
from . import api from . import api
from . import get_export_locations, get_archive_path, get_location_path from . import get_export_locations, get_archive_path, get_location_path
def _get_app_choices(apps):
"""Return a list of check box multiple choices from list of apps."""
choices = []
for app in apps:
name = app['app'].name
if not app['has_data']:
name = ugettext('{app} (No data to backup)').format(
app=app['app'].name)
choices.append((app['name'], name))
return choices
class CreateArchiveForm(forms.Form): class CreateArchiveForm(forms.Form):
name = forms.CharField( name = forms.CharField(
label=_('Archive name'), strip=True, label=_('Archive name'), strip=True,
@ -37,17 +51,15 @@ class CreateArchiveForm(forms.Form):
]) ])
selected_apps = forms.MultipleChoiceField( selected_apps = forms.MultipleChoiceField(
label=_('Included apps'), label=_('Included apps'), help_text=_('Apps to include in the backup'),
help_text=_('Apps to include in the backup'),
widget=forms.CheckboxSelectMultiple) widget=forms.CheckboxSelectMultiple)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
"""Initialize the form with selectable apps.""" """Initialize the form with selectable apps."""
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
apps = api.get_all_apps_for_backup() apps = api.get_all_apps_for_backup()
self.fields['selected_apps'].choices = [ self.fields['selected_apps'].choices = _get_app_choices(apps)
(app[0], app[1].name) for app in apps] self.fields['selected_apps'].initial = [app['name'] for app in apps]
self.fields['selected_apps'].initial = [app[0] for app in apps]
class ExportArchiveForm(forms.Form): class ExportArchiveForm(forms.Form):
@ -72,9 +84,8 @@ class RestoreForm(forms.Form):
"""Initialize the form with selectable apps.""" """Initialize the form with selectable apps."""
apps = kwargs.pop('apps') apps = kwargs.pop('apps')
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.fields['selected_apps'].choices = [ self.fields['selected_apps'].choices = _get_app_choices(apps)
(app[0], app[1].name) for app in apps] self.fields['selected_apps'].initial = [app['name'] for app in apps]
self.fields['selected_apps'].initial = [app[0] for app in apps]
class UploadForm(forms.Form): class UploadForm(forms.Form):

View File

@ -23,7 +23,7 @@ from django.core.files.uploadedfile import SimpleUploadedFile
import unittest import unittest
from unittest.mock import MagicMock, call, patch from unittest.mock import MagicMock, call, patch
from plinth import cfg, module_loader
from plinth.errors import PlinthError from plinth.errors import PlinthError
from plinth.module_loader import load_modules from plinth.module_loader import load_modules
from .. import api, forms, get_export_locations, get_location_path from .. import api, forms, get_export_locations, get_location_path
@ -52,6 +52,12 @@ def _get_test_manifest(name):
class TestBackupProcesses(unittest.TestCase): class TestBackupProcesses(unittest.TestCase):
"""Test cases for backup processes""" """Test cases for backup processes"""
@classmethod
def setUpClass(cls):
"""Setup all the test cases."""
super().setUpClass()
cfg.read()
@staticmethod @staticmethod
def test_packet_process_manifests(): def test_packet_process_manifests():
"""Test that directories/files are collected from manifests.""" """Test that directories/files are collected from manifests."""
@ -82,13 +88,34 @@ class TestBackupProcesses(unittest.TestCase):
api.restore_apps(restore_handler) api.restore_apps(restore_handler)
restore_handler.assert_called_once() restore_handler.assert_called_once()
@staticmethod @patch('plinth.module_loader.loaded_modules.items')
def test_get_all_apps_for_backups(): def test_get_all_apps_for_backup(self, modules):
"""Test that apps supporting backup are included in returned list.""" """Test listing apps supporting backup and needing backup."""
load_modules() apps = [
apps = api.get_all_apps_for_backup() ('a', MagicMock(backup=_get_test_manifest('a'))),
assert isinstance(apps, list) ('b', MagicMock(backup=_get_test_manifest('b'))),
# apps may be empty, if no apps supporting backup are installed. ('c', MagicMock(backup=None)),
('d', MagicMock()),
]
del apps[3][1].backup
modules.return_value = apps
module_loader.load_modules()
returned_apps = api.get_all_apps_for_backup()
expected_apps = [{
'name': 'a',
'app': apps[0][1],
'has_data': True
}, {
'name': 'b',
'app': apps[1][1],
'has_data': True
}, {
'name': 'c',
'app': apps[2][1],
'has_data': False
}]
self.assertEqual(returned_apps, expected_apps)
def test_export_locations(self): def test_export_locations(self):
"""Check get_export_locations returns a list of tuples of length 2.""" """Check get_export_locations returns a list of tuples of length 2."""
@ -99,7 +126,7 @@ class TestBackupProcesses(unittest.TestCase):
@staticmethod @staticmethod
def test__get_apps_in_order(): def test__get_apps_in_order():
"""Test that apps are listed in correct dependency order.""" """Test that apps are listed in correct dependency order."""
load_modules() module_loader.load_modules()
app_names = ['config', 'names'] app_names = ['config', 'names']
apps = api._get_apps_in_order(app_names) apps = api._get_apps_in_order(app_names)
ordered_app_names = [app[0] for app in apps] ordered_app_names = [app[0] for app in apps]

View File

@ -60,7 +60,7 @@ class IndexView(TemplateView):
context['exports'] = backups.get_export_files() context['exports'] = backups.get_export_files()
context['subsubmenu'] = subsubmenu context['subsubmenu'] = subsubmenu
apps = api.get_all_apps_for_backup() apps = api.get_all_apps_for_backup()
context['available_apps'] = [app[0] for app in apps] context['available_apps'] = [app['name'] for app in apps]
return context return context
@ -199,7 +199,7 @@ class RestoreView(SuccessMessageMixin, FormView):
included_apps = self._get_included_apps() included_apps = self._get_included_apps()
installed_apps = api.get_all_apps_for_backup() installed_apps = api.get_all_apps_for_backup()
kwargs['apps'] = [ kwargs['apps'] = [
app for app in installed_apps if app[0] in included_apps app for app in installed_apps if app['name'] in included_apps
] ]
return kwargs return kwargs