From 178948b269dcbc6f5f728485872b99699870c8c1 Mon Sep 17 00:00:00 2001 From: FinnStutzenstein Date: Tue, 26 Sep 2017 14:19:48 +0200 Subject: [PATCH] Index and Webclient View caching --- CHANGELOG | 2 ++ openslides/core/views.py | 43 ++++++++++++++++------------------- openslides/utils/views.py | 22 ++++++++++++++++++ tests/unit/core/test_views.py | 9 ++++---- 4 files changed, 48 insertions(+), 28 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 65889089a..e29d1f21a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -85,6 +85,8 @@ Core: - Added custom translations in config [#3383]. - Added dynamic webpage title [#3404]. - Added 'go to top'-link [#3404]. +- Added caching for the index views. When using a Webserver for serving + static files see the example configuration in the PR [#3419]. Mediafiles: - Fixed reloading of PDF on page change [#3274]. diff --git a/openslides/core/views.py b/openslides/core/views.py index 42436f935..dc8ee47f7 100644 --- a/openslides/core/views.py +++ b/openslides/core/views.py @@ -3,11 +3,10 @@ import uuid from collections import OrderedDict from operator import attrgetter from textwrap import dedent -from typing import Any, Dict, List # noqa +from typing import Any, Dict, List, cast # noqa from django.apps import apps from django.conf import settings -from django.contrib.staticfiles import finders from django.db.models import F from django.http import Http404, HttpResponse from django.utils.timezone import now @@ -54,7 +53,7 @@ from .models import ( # Special Django views -class IndexView(utils_views.CSRFMixin, utils_views.View): +class IndexView(utils_views.CSRFMixin, utils_views.IndexView): """ The primary view for OpenSlides using AngularJS. @@ -62,28 +61,20 @@ class IndexView(utils_views.CSRFMixin, utils_views.View): You can override it by simply adding a custom 'templates/index.html' file to the custom staticfiles directory. See STATICFILES_DIRS in settings.py. """ - - def get(self, *args, **kwargs): - with open(finders.find('templates/index.html')) as f: - content = f.read() - return HttpResponse(content) + template_name = 'templates/index.html' -class ProjectorView(utils_views.View): +class ProjectorView(utils_views.IndexView): """ The primary view for OpenSlides projector using AngularJS. The projector container template is 'openslides/core/static/templates/projector-container.html'. This container is for controlling the projector resolution. """ - - def get(self, *args, **kwargs): - with open(finders.find('templates/projector-container.html')) as f: - content = f.read() - return HttpResponse(content) + template_name = 'templates/projector-container.html' -class RealProjectorView(utils_views.View): +class RealProjectorView(utils_views.IndexView): """ The original view without resolutioncontrol for OpenSlides projector using AngularJS. @@ -92,11 +83,7 @@ class RealProjectorView(utils_views.View): file to the custom staticfiles directory. See STATICFILES_DIRS in settings.py. """ - - def get(self, *args, **kwargs): - with open(finders.find('templates/projector.html')) as f: - content = f.read() - return HttpResponse(content) + template_name = 'templates/projector.html' class WebclientJavaScriptView(utils_views.View): @@ -105,10 +92,15 @@ class WebclientJavaScriptView(utils_views.View): AngularJS app for the requested realm (site or projector). Also code for plugins is appended. The result is not uglified. """ - def get(self, *args, **kwargs): - angular_modules = [] + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + self.cache = {} # type: Dict[str, str] + self.init_cache('site') + self.init_cache('projector') + + def init_cache(self, realm: str) -> None: + angular_modules = [] # type: List[str] js_files = [] # type: List[str] - realm = kwargs.get('realm') # Result is 'site' or 'projector' for app_config in apps.get_app_configs(): # Add the angular app if the module has one. if getattr(app_config, 'angular_{}_module'.format(realm), False): @@ -187,8 +179,11 @@ class WebclientJavaScriptView(utils_views.View): """ }()); """) + self.cache[realm] = content - return HttpResponse(content, content_type='application/javascript') + def get(self, *args: Any, **kwargs: Any) -> HttpResponse: + realm = cast(str, kwargs.get('realm')) # Result is 'site' or 'projector' + return HttpResponse(self.cache[realm], content_type='application/javascript') # Viewsets for the REST API diff --git a/openslides/utils/views.py b/openslides/utils/views.py index 7e12932e8..a479d8c75 100644 --- a/openslides/utils/views.py +++ b/openslides/utils/views.py @@ -1,5 +1,8 @@ from typing import Any, Dict, List # noqa +from django.contrib.staticfiles import finders +from django.core.exceptions import ImproperlyConfigured +from django.http import HttpResponse from django.views.decorators.csrf import ensure_csrf_cookie from django.views.generic.base import View from rest_framework.response import Response @@ -45,3 +48,22 @@ class APIView(_APIView): # Add the http-methods and delete the method "method_call" get = post = put = patch = delete = head = options = trace = method_call del method_call + + +class IndexView(View): + """ + A view to serve a single cached template file. Subclasses has to provide 'template_name'. + """ + template_name = None # type: str + + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + + if self.template_name is None: + raise ImproperlyConfigured("'template_name' is not provided") + + with open(finders.find(self.template_name)) as template: + self.template = template.read() + + def get(self, *args: Any, **kwargs: Any) -> HttpResponse: + return HttpResponse(self.template) diff --git a/tests/unit/core/test_views.py b/tests/unit/core/test_views.py index 01fe96280..492318c28 100644 --- a/tests/unit/core/test_views.py +++ b/tests/unit/core/test_views.py @@ -169,10 +169,11 @@ class ProjectorAPI(TestCase): class WebclientJavaScriptView(TestCase): def setUp(self): self.request = MagicMock() - self.view_instance = views.WebclientJavaScriptView() - self.view_instance.request = self.request @patch('django.contrib.auth.models.Permission.objects.all') def test_permissions_as_constant(self, mock_all): - self.view_instance.get() - self.assertEqual(mock_all.call_count, 1) + self.view_instance = views.WebclientJavaScriptView() + self.view_instance.request = self.request + response = self.view_instance.get(realm='site') + self.assertEqual(response.status_code, 200) + self.assertEqual(mock_all.call_count, 2)