diff --git a/plinth/modules/backups/api.py b/plinth/modules/backups/api.py index e8ff67257..b4b3016f1 100644 --- a/plinth/modules/backups/api.py +++ b/plinth/modules/backups/api.py @@ -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 diff --git a/plinth/modules/backups/forms.py b/plinth/modules/backups/forms.py index 9dcfea0b1..088ed2aba 100644 --- a/plinth/modules/backups/forms.py +++ b/plinth/modules/backups/forms.py @@ -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): diff --git a/plinth/modules/backups/tests/test_api.py b/plinth/modules/backups/tests/test_api.py index bd92a5834..c9b257482 100644 --- a/plinth/modules/backups/tests/test_api.py +++ b/plinth/modules/backups/tests/test_api.py @@ -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] diff --git a/plinth/modules/backups/views.py b/plinth/modules/backups/views.py index ea1ff11b1..560b4ca5d 100644 --- a/plinth/modules/backups/views.py +++ b/plinth/modules/backups/views.py @@ -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