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'):
continue
apps.append((module_name, module))
apps.append({
'name': module_name,
'app': module,
'has_data': bool(module.backup)
})
return apps

View File

@ -23,12 +23,26 @@ 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 django.utils.translation import ugettext, ugettext_lazy as _
from . import api
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):
name = forms.CharField(
label=_('Archive name'), strip=True,
@ -37,17 +51,15 @@ class CreateArchiveForm(forms.Form):
])
selected_apps = forms.MultipleChoiceField(
label=_('Included apps'),
help_text=_('Apps to include in the backup'),
label=_('Included apps'), help_text=_('Apps to include in the backup'),
widget=forms.CheckboxSelectMultiple)
def __init__(self, *args, **kwargs):
"""Initialize the form with selectable apps."""
super().__init__(*args, **kwargs)
apps = api.get_all_apps_for_backup()
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]
self.fields['selected_apps'].choices = _get_app_choices(apps)
self.fields['selected_apps'].initial = [app['name'] for app in apps]
class ExportArchiveForm(forms.Form):
@ -72,9 +84,8 @@ class RestoreForm(forms.Form):
"""Initialize the form with selectable apps."""
apps = kwargs.pop('apps')
super().__init__(*args, **kwargs)
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]
self.fields['selected_apps'].choices = _get_app_choices(apps)
self.fields['selected_apps'].initial = [app['name'] for app in apps]
class UploadForm(forms.Form):

View File

@ -23,7 +23,7 @@ from django.core.files.uploadedfile import SimpleUploadedFile
import unittest
from unittest.mock import MagicMock, call, patch
from plinth import cfg, module_loader
from plinth.errors import PlinthError
from plinth.module_loader import load_modules
from .. import api, forms, get_export_locations, get_location_path
@ -52,6 +52,12 @@ def _get_test_manifest(name):
class TestBackupProcesses(unittest.TestCase):
"""Test cases for backup processes"""
@classmethod
def setUpClass(cls):
"""Setup all the test cases."""
super().setUpClass()
cfg.read()
@staticmethod
def test_packet_process_manifests():
"""Test that directories/files are collected from manifests."""
@ -82,13 +88,34 @@ class TestBackupProcesses(unittest.TestCase):
api.restore_apps(restore_handler)
restore_handler.assert_called_once()
@staticmethod
def test_get_all_apps_for_backups():
"""Test that apps supporting backup are included in returned list."""
load_modules()
apps = api.get_all_apps_for_backup()
assert isinstance(apps, list)
# apps may be empty, if no apps supporting backup are installed.
@patch('plinth.module_loader.loaded_modules.items')
def test_get_all_apps_for_backup(self, modules):
"""Test listing apps supporting backup and needing backup."""
apps = [
('a', MagicMock(backup=_get_test_manifest('a'))),
('b', MagicMock(backup=_get_test_manifest('b'))),
('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):
"""Check get_export_locations returns a list of tuples of length 2."""
@ -99,7 +126,7 @@ class TestBackupProcesses(unittest.TestCase):
@staticmethod
def test__get_apps_in_order():
"""Test that apps are listed in correct dependency order."""
load_modules()
module_loader.load_modules()
app_names = ['config', 'names']
apps = api._get_apps_in_order(app_names)
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['subsubmenu'] = subsubmenu
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
@ -199,7 +199,7 @@ class RestoreView(SuccessMessageMixin, FormView):
included_apps = self._get_included_apps()
installed_apps = api.get_all_apps_for_backup()
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