middleware, views: Reduce use of setup_helper

Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org>
Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
This commit is contained in:
Sunil Mohan Adapa 2021-11-21 10:47:49 -08:00 committed by James Valleroy
parent a3d4d99b33
commit 528fd08245
No known key found for this signature in database
GPG Key ID: 77C0C75E7B650808
3 changed files with 69 additions and 48 deletions

View File

@ -4,6 +4,7 @@ Common Django middleware.
""" """
import logging import logging
import sys
from django import urls from django import urls
from django.conf import settings from django.conf import settings
@ -15,7 +16,6 @@ from django.utils.deprecation import MiddlewareMixin
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from stronghold.utils import is_view_func_public from stronghold.utils import is_view_func_public
import plinth
from plinth import app as app_module from plinth import app as app_module
from plinth import setup from plinth import setup
from plinth.package import PackageException from plinth.package import PackageException
@ -26,14 +26,15 @@ from . import views
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def _collect_setup_result(request, module): def _collect_setup_result(request, app):
"""Show success/fail message from previous install operation.""" """Show success/fail message from previous install operation."""
if not module.setup_helper.is_finished: setup_helper = sys.modules[app.__module__].setup_helper
if not setup_helper.is_finished:
return return
exception = module.setup_helper.collect_result() exception = setup_helper.collect_result()
if not exception: if not exception:
if not module.setup_helper.app.info.is_essential: if not app.info.is_essential:
messages.success(request, _('Application installed.')) messages.success(request, _('Application installed.'))
else: else:
if isinstance(exception, PackageException): if isinstance(exception, PackageException):
@ -72,16 +73,16 @@ class SetupMiddleware(MiddlewareMixin):
# Requested URL does not belong to any application # Requested URL does not belong to any application
return return
module_name = resolver_match.namespaces[0] app_id = resolver_match.namespaces[0]
module = plinth.module_loader.loaded_modules[module_name] app = app_module.App.get(app_id)
is_admin = is_user_admin(request) is_admin = is_user_admin(request)
# Collect and show setup operation result to admins # Collect and show setup operation result to admins
if is_admin: if is_admin:
_collect_setup_result(request, module) _collect_setup_result(request, app)
# Check if application is up-to-date # Check if application is up-to-date
if module.app.get_setup_state() == \ if app.get_setup_state() == \
app_module.App.SetupState.UP_TO_DATE: app_module.App.SetupState.UP_TO_DATE:
return return
@ -90,7 +91,7 @@ class SetupMiddleware(MiddlewareMixin):
# Only allow logged-in users to access any setup page # Only allow logged-in users to access any setup page
view = login_required(views.SetupView.as_view()) view = login_required(views.SetupView.as_view())
return view(request, setup_helper=module.setup_helper) return view(request, app_id=app_id)
class AdminRequiredMiddleware(MiddlewareMixin): class AdminRequiredMiddleware(MiddlewareMixin):

View File

@ -15,6 +15,8 @@ from stronghold.decorators import public
from plinth import app as app_module from plinth import app as app_module
from plinth.middleware import AdminRequiredMiddleware, SetupMiddleware from plinth.middleware import AdminRequiredMiddleware, SetupMiddleware
setup_helper = None
@pytest.fixture(name='kwargs') @pytest.fixture(name='kwargs')
def fixture_kwargs(): def fixture_kwargs():
@ -26,6 +28,26 @@ def fixture_kwargs():
} }
@pytest.fixture(name='app')
def fixture_app():
"""Fixture for returning a test app."""
class AppTest(app_module.App):
"""Test app."""
app_id = 'mockapp'
def __init__(self):
"""Add info component."""
super().__init__()
self.add(app_module.Info('mockapp', version=1))
def get_setup_state(self):
return app_module.App.SetupState.NEEDS_SETUP
app_module.App._all_apps = {}
return AppTest()
class TestSetupMiddleware: class TestSetupMiddleware:
"""Test cases for setup middleware.""" """Test cases for setup middleware."""
@ -52,18 +74,15 @@ class TestSetupMiddleware:
assert response is None assert response is None
@staticmethod @staticmethod
@patch('plinth.module_loader.loaded_modules') @patch('plinth.tests.test_middleware.setup_helper')
@patch('django.urls.resolve') @patch('django.urls.resolve')
@patch('django.urls.reverse', return_value='users:login') @patch('django.urls.reverse', return_value='users:login')
def test_module_is_up_to_date(reverse, resolve, loaded_modules, middleware, def test_module_is_up_to_date(_reverse, resolve, setup_helper_, app,
kwargs): middleware, kwargs):
"""Test that none is returned when module is up-to-date.""" """Test that none is returned when module is up-to-date."""
resolve.return_value.namespaces = ['mockapp'] resolve.return_value.namespaces = ['mockapp']
module = Mock() setup_helper_.is_finished = None
module.setup_helper.is_finished = None app.get_setup_state = lambda: app_module.App.SetupState.UP_TO_DATE
module.app.get_setup_state.return_value = \
app_module.App.SetupState.UP_TO_DATE
loaded_modules.__getitem__.return_value = module
request = RequestFactory().get('/plinth/mockapp') request = RequestFactory().get('/plinth/mockapp')
request.user = AnonymousUser() request.user = AnonymousUser()
@ -71,22 +90,20 @@ class TestSetupMiddleware:
assert response is None assert response is None
@staticmethod @staticmethod
@patch('plinth.tests.test_middleware.setup_helper')
@patch('plinth.views.SetupView') @patch('plinth.views.SetupView')
@patch('plinth.module_loader.loaded_modules')
@patch('django.urls.resolve') @patch('django.urls.resolve')
@patch('django.urls.reverse', return_value='users:login') @patch('django.urls.reverse', return_value='users:login')
@pytest.mark.django_db @pytest.mark.django_db
def test_module_view(reverse, resolve, loaded_modules, setup_view, def test_module_view(_reverse, resolve, setup_view, setup_helper, app,
middleware, kwargs): middleware, kwargs):
"""Test that only registered users can access the setup view.""" """Test that only registered users can access the setup view."""
resolve.return_value.namespaces = ['mockapp'] resolve.return_value.namespaces = ['mockapp']
module = Mock()
module.setup_helper.is_finished = None
loaded_modules.__getitem__.return_value = module
view = Mock() view = Mock()
setup_view.as_view.return_value = view setup_view.as_view.return_value = view
request = RequestFactory().get('/plinth/mockapp') request = RequestFactory().get('/plinth/mockapp')
request.session = MagicMock() request.session = MagicMock()
setup_helper.is_finished = None
# Verify that anonymous users cannot access the setup page # Verify that anonymous users cannot access the setup page
request.user = AnonymousUser() request.user = AnonymousUser()
@ -116,24 +133,21 @@ class TestSetupMiddleware:
request.user = user request.user = user
middleware.process_view(request, **kwargs) middleware.process_view(request, **kwargs)
setup_view.as_view.assert_called_once_with() setup_view.as_view.assert_called_once_with()
view.assert_called_once_with(request, setup_helper=module.setup_helper) view.assert_called_once_with(request, app_id='mockapp')
@staticmethod @staticmethod
@patch('plinth.tests.test_middleware.setup_helper')
@patch('django.contrib.messages.success') @patch('django.contrib.messages.success')
@patch('plinth.module_loader.loaded_modules')
@patch('django.urls.resolve') @patch('django.urls.resolve')
@patch('django.urls.reverse', return_value='users:login') @patch('django.urls.reverse', return_value='users:login')
@pytest.mark.django_db @pytest.mark.django_db
def test_install_result_collection(reverse, resolve, loaded_modules, def test_install_result_collection(reverse, resolve, messages_success,
messages_success, middleware, kwargs): setup_helper_, app, middleware, kwargs):
"""Test that module installation result is collected properly.""" """Test that module installation result is collected properly."""
resolve.return_value.namespaces = ['mockapp'] resolve.return_value.namespaces = ['mockapp']
module = Mock() setup_helper_.is_finished = True
module.setup_helper.is_finished = True setup_helper_.collect_result.return_value = None
module.setup_helper.collect_result.return_value = None app.get_setup_state = lambda: app_module.App.SetupState.UP_TO_DATE
module.app.get_setup_state.return_value = \
app_module.App.SetupState.UP_TO_DATE
loaded_modules.__getitem__.return_value = module
# Admin user can't collect result # Admin user can't collect result
request = RequestFactory().get('/plinth/mockapp') request = RequestFactory().get('/plinth/mockapp')
@ -149,11 +163,11 @@ class TestSetupMiddleware:
assert response is None assert response is None
assert messages_success.called assert messages_success.called
module.setup_helper.collect_result.assert_called_once_with() setup_helper_.collect_result.assert_called_once_with()
# Non-admin user can't collect result # Non-admin user can't collect result
messages_success.reset_mock() messages_success.reset_mock()
module.setup_helper.collect_result.reset_mock() setup_helper_.collect_result.reset_mock()
request = RequestFactory().get('/plinth/mockapp') request = RequestFactory().get('/plinth/mockapp')
user = User(username='johndoe') user = User(username='johndoe')
user.save() user.save()
@ -163,7 +177,7 @@ class TestSetupMiddleware:
assert response is None assert response is None
assert not messages_success.called assert not messages_success.called
module.setup_helper.collect_result.assert_not_called() setup_helper_.collect_result.assert_not_called()
class TestAdminMiddleware: class TestAdminMiddleware:

View File

@ -3,6 +3,7 @@
Main FreedomBox views. Main FreedomBox views.
""" """
import sys
import time import time
import urllib.parse import urllib.parse
@ -16,7 +17,8 @@ from django.views.generic import TemplateView
from django.views.generic.edit import FormView from django.views.generic.edit import FormView
from stronghold.decorators import public from stronghold.decorators import public
from plinth import app, package from plinth import app as app_module
from plinth import package
from plinth.daemon import app_is_running from plinth.daemon import app_is_running
from plinth.modules.config import get_advanced_mode from plinth.modules.config import get_advanced_mode
from plinth.modules.firewall.components import get_port_forwarding_info from plinth.modules.firewall.components import get_port_forwarding_info
@ -182,7 +184,7 @@ class AppView(FormView):
if not self.app_id: if not self.app_id:
raise ImproperlyConfigured('Missing attribute: app_id') raise ImproperlyConfigured('Missing attribute: app_id')
return app.App.get(self.app_id) return app_module.App.get(self.app_id)
def get_form(self, *args, **kwargs): def get_form(self, *args, **kwargs):
"""Return an instance of this view's form. """Return an instance of this view's form.
@ -269,19 +271,21 @@ class SetupView(TemplateView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
"""Return the context data rendering the template.""" """Return the context data rendering the template."""
context = super(SetupView, self).get_context_data(**kwargs) context = super().get_context_data(**kwargs)
setup_helper = self.kwargs['setup_helper'] app_id = self.kwargs['app_id']
context['setup_helper'] = setup_helper app = app_module.App.get(app_id)
context['app_info'] = setup_helper.module.app.info
context['app_info'] = app.info
# Report any installed conflicting packages that will be removed. # Report any installed conflicting packages that will be removed.
package_conflicts, package_conflicts_action = \ package_conflicts, package_conflicts_action = \
self._get_app_package_conflicts(setup_helper.module.app) self._get_app_package_conflicts(app)
context['package_conflicts'] = package_conflicts context['package_conflicts'] = package_conflicts
context['package_conflicts_action'] = package_conflicts_action context['package_conflicts_action'] = package_conflicts_action
# Reuse the value of setup_state throughout the view for consistency. # Reuse the value of setup_state throughout the view for consistency.
context['setup_state'] = setup_helper.module.app.get_setup_state() context['setup_state'] = app.get_setup_state()
setup_helper = sys.modules[app.__module__].setup_helper
context['setup_current_operation'] = setup_helper.current_operation context['setup_current_operation'] = setup_helper.current_operation
# Perform expensive operation only if needed. # Perform expensive operation only if needed.
@ -290,10 +294,10 @@ class SetupView(TemplateView):
'package_manager_is_busy'] = package.is_package_manager_busy() 'package_manager_is_busy'] = package.is_package_manager_busy()
context[ context[
'has_unavailable_packages'] = self._has_unavailable_packages( 'has_unavailable_packages'] = self._has_unavailable_packages(
setup_helper.module.app) app)
context['refresh_page_sec'] = None context['refresh_page_sec'] = None
if context['setup_state'] == app.App.SetupState.UP_TO_DATE: if context['setup_state'] == app_module.App.SetupState.UP_TO_DATE:
context['refresh_page_sec'] = 0 context['refresh_page_sec'] = 0
elif context['setup_current_operation']: elif context['setup_current_operation']:
context['refresh_page_sec'] = 3 context['refresh_page_sec'] = 3
@ -306,7 +310,9 @@ class SetupView(TemplateView):
# Handle installing/upgrading applications. # Handle installing/upgrading applications.
# Start the application setup, and refresh the page every few # Start the application setup, and refresh the page every few
# seconds to keep displaying the status. # seconds to keep displaying the status.
self.kwargs['setup_helper'].run_in_thread() app = app_module.App.get(self.kwargs['app_id'])
setup_helper = sys.modules[app.__module__].setup_helper
setup_helper.run_in_thread()
# Give a moment for the setup process to start and show # Give a moment for the setup process to start and show
# meaningful status. # meaningful status.
@ -321,7 +327,7 @@ class SetupView(TemplateView):
package.refresh_package_lists() package.refresh_package_lists()
return self.render_to_response(self.get_context_data()) return self.render_to_response(self.get_context_data())
return super(SetupView, self).dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)
@staticmethod @staticmethod
def _get_app_package_conflicts(app_): def _get_app_package_conflicts(app_):