From 5e089bfcdce802c716caba801e6ea9b2a055127f Mon Sep 17 00:00:00 2001 From: FinnStutzenstein Date: Tue, 16 Jan 2018 16:02:23 +0100 Subject: [PATCH] Preparations for the SAML plugin; Fixed caching of main views. --- CHANGELOG | 1 + openslides/__init__.py | 2 + openslides/__main__.py | 47 ++++++---- openslides/core/static/js/core/base.js | 2 +- .../static/templates/core/login-form.html | 1 + openslides/core/urls.py | 2 +- openslides/core/views.py | 10 ++- openslides/users/static/js/users/site.js | 23 +++-- openslides/utils/main.py | 89 ++++++++++--------- openslides/utils/plugins.py | 18 ++-- openslides/utils/settings.py.tpl | 12 +-- openslides/utils/views.py | 11 ++- tests/old/utils/test_main.py | 32 +++---- 13 files changed, 143 insertions(+), 107 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 4470f0ddc..c2ad735d6 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -105,6 +105,7 @@ Core: - Use native twisted mode for daphne [#3487]. - Save language selection to session storage [#3543] - Set default of projector resolution to 1220x915 [#2549]. +- Preparations for the SAML plugin; Fixed caching of main views [#3535]. Mediafiles: - Fixed reloading of PDF on page change [#3274]. diff --git a/openslides/__init__.py b/openslides/__init__.py index 6dc8e6638..6339bc670 100644 --- a/openslides/__init__.py +++ b/openslides/__init__.py @@ -3,3 +3,5 @@ __description__ = 'Presentation and assembly system' __version__ = '2.2b2-dev' __license__ = 'MIT' __url__ = 'https://openslides.org' + +args = None diff --git a/openslides/__main__.py b/openslides/__main__.py index 9be6fdefa..a36d67052 100644 --- a/openslides/__main__.py +++ b/openslides/__main__.py @@ -8,13 +8,13 @@ from typing import Dict # noqa import django from django.core.management import call_command, execute_from_command_line -from openslides import __version__ as openslides_version +import openslides from openslides.utils.main import ( ExceptionArgumentParser, UnknownCommand, - get_default_settings_path, + get_default_settings_dir, get_geiss_path, - get_local_settings_path, + get_local_settings_dir, is_local_installation, open_browser, setup_django_settings_module, @@ -43,6 +43,10 @@ def main(): # Check for unknown_args. if unknown_args: parser.error('Unknown arguments {}'.format(' '.join(unknown_args))) + + # Save arguments, if one wants to access them later. + openslides.args = known_args + # Run a command that is defined here # These are commands that can not rely on an existing settings known_args.callback(known_args) @@ -81,7 +85,7 @@ def get_parser(): parser.add_argument( '--version', action='version', - version=openslides_version, + version=openslides.__version__, help='Show version number and exit.') # Init subparsers @@ -122,10 +126,15 @@ def get_parser(): default='8000', help='Port to listen on. Default is 8000.') subcommand_start.add_argument( - '--settings_path', + '--settings_dir', action='store', default=None, - help='The used settings file. The file is created, if it does not exist.') + help='The settings directory.') + subcommand_start.add_argument( + '--settings_filename', + action='store', + default='settings.py', + help='The used settings file name. The file is created, if it does not exist.') subcommand_start.add_argument( '--local-installation', action='store_true', @@ -143,10 +152,10 @@ def get_parser(): help=createsettings_help) subcommand_createsettings.set_defaults(callback=createsettings) subcommand_createsettings.add_argument( - '--settings_path', + '--settings_dir', action='store', default=None, - help='The used settings file. The file is created, even if it exists.') + help='The used settings file directory. All settings files are created, even if they exist.') subcommand_createsettings.add_argument( '--local-installation', action='store_true', @@ -173,16 +182,18 @@ def start(args): """ Starts OpenSlides: Runs migrations and runs runserver. """ - settings_path = args.settings_path + settings_dir = args.settings_dir + settings_filename = args.settings_filename local_installation = is_local_installation() - if settings_path is None: + if settings_dir is None: if local_installation: - settings_path = get_local_settings_path() + settings_dir = get_local_settings_dir() else: - settings_path = get_default_settings_path() + settings_dir = get_default_settings_dir() - # Write settings if it does not exists. + # Write django settings if it does not exists. + settings_path = os.path.join(settings_dir, settings_filename) if not os.path.isfile(settings_path): createsettings(args) @@ -254,18 +265,18 @@ def createsettings(args): """ Creates settings for OpenSlides. """ - settings_path = args.settings_path + settings_dir = args.settings_dir local_installation = is_local_installation() context = {} # type: Dict[str, str] if local_installation: - if settings_path is None: - settings_path = get_local_settings_path() + if settings_dir is None: + settings_dir = get_local_settings_dir() context = { - 'openslides_user_data_path': repr(os.path.join(os.getcwd(), 'personal_data', 'var')), + 'openslides_user_data_dir': repr(os.path.join(os.getcwd(), 'personal_data', 'var')), 'debug': 'True'} - settings_path = write_settings(settings_path, **context) + settings_path = write_settings(settings_dir, args.settings_filename, **context) print('Settings created at %s' % settings_path) diff --git a/openslides/core/static/js/core/base.js b/openslides/core/static/js/core/base.js index 57c85d052..e0067c93a 100644 --- a/openslides/core/static/js/core/base.js +++ b/openslides/core/static/js/core/base.js @@ -595,7 +595,7 @@ angular.module('OpenSlidesApp.core', [ // to be given. If a scope is provided, the schope of this templateHook // is populated with the given functions/values. if (hook.template) { - return '
' + hook.template + '
'; + return hook.template; } else { return $templateCache.get(hook.templateUrl); } diff --git a/openslides/core/static/templates/core/login-form.html b/openslides/core/static/templates/core/login-form.html index b516c8ead..5cb168d9f 100644 --- a/openslides/core/static/templates/core/login-form.html +++ b/openslides/core/static/templates/core/login-form.html @@ -23,6 +23,7 @@ + diff --git a/openslides/core/urls.py b/openslides/core/urls.py index 7d5fcbe7c..351c239d7 100644 --- a/openslides/core/urls.py +++ b/openslides/core/urls.py @@ -23,6 +23,6 @@ urlpatterns = [ # Main entry point for all angular pages. # Has to be the last entry in the urls.py - url(r'^.*$', views.IndexView.as_view()), + url(r'^.*$', views.IndexView.as_view(), name="index"), ] diff --git a/openslides/core/views.py b/openslides/core/views.py index 018523e5f..7b5cd5a3c 100644 --- a/openslides/core/views.py +++ b/openslides/core/views.py @@ -96,11 +96,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. """ + cache = {} # type: Dict[str, str] + 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') + + if 'site' not in self.cache: + self.init_cache('site') + if 'projector' not in self.cache: + self.init_cache('projector') def init_cache(self, realm: str) -> None: angular_modules = [] # type: List[str] diff --git a/openslides/users/static/js/users/site.js b/openslides/users/static/js/users/site.js index 51ed5f779..30d502469 100644 --- a/openslides/users/static/js/users/site.js +++ b/openslides/users/static/js/users/site.js @@ -1643,20 +1643,29 @@ angular.module('OpenSlidesApp.users.site', [ } ]) -.controller('userMenu', [ - '$scope', +.factory('Logout', [ '$http', 'OpenSlides', - 'ngDialog', - 'UserProfileForm', - 'UserPasswordForm', - function($scope, $http, OpenSlides, ngDialog, UserProfileForm, UserPasswordForm) { - $scope.logout = function () { + function ($http, OpenSlides) { + return function () { $http.post('/users/logout/').then(function (response) { // Success: User logged out, so reboot OpenSlides. OpenSlides.reboot(); }); }; + } +]) + +.controller('userMenu', [ + '$scope', + '$http', + 'ngDialog', + 'UserProfileForm', + 'UserPasswordForm', + 'Logout', + function($scope, $http, ngDialog, UserProfileForm, UserPasswordForm, Logout) { + $scope.logout = Logout; + $scope.editProfile = function () { ngDialog.open(UserProfileForm.getDialog()); }; diff --git a/openslides/utils/main.py b/openslides/utils/main.py index 4f01fb9be..f41cf2ff0 100644 --- a/openslides/utils/main.py +++ b/openslides/utils/main.py @@ -60,7 +60,7 @@ def detect_openslides_type() -> str: return openslides_type -def get_default_settings_path(openslides_type: str=None) -> str: +def get_default_settings_dir(openslides_type: str=None) -> str: """ Returns the default settings path according to the OpenSlides type. @@ -74,21 +74,21 @@ def get_default_settings_path(openslides_type: str=None) -> str: parent_directory = os.environ.get( 'XDG_CONFIG_HOME', os.path.expanduser('~/.config')) elif openslides_type == WINDOWS_VERSION: - parent_directory = get_win32_app_data_path() + parent_directory = get_win32_app_data_dir() elif openslides_type == WINDOWS_PORTABLE_VERSION: - parent_directory = get_win32_portable_path() + parent_directory = get_win32_portable_dir() else: raise TypeError('%s is not a valid OpenSlides type.' % openslides_type) - return os.path.join(parent_directory, 'openslides', 'settings.py') + return os.path.join(parent_directory, 'openslides') -def get_local_settings_path() -> str: +def get_local_settings_dir() -> str: """ Returns the path to a local settings. - On Unix systems: 'personal_data/var/settings.py' + On Unix systems: 'personal_data/var/' """ - return os.path.join('personal_data', 'var', 'settings.py') + return os.path.join('personal_data', 'var') def setup_django_settings_module(settings_path: str =None, local_installation: bool=False) -> None: @@ -107,9 +107,10 @@ def setup_django_settings_module(settings_path: str =None, local_installation: b if settings_path is None: if local_installation: - settings_path = get_local_settings_path() + settings_dir = get_local_settings_dir() else: - settings_path = get_default_settings_path() + settings_dir = get_default_settings_dir() + settings_path = os.path.join(settings_dir, 'settings.py') settings_file = os.path.basename(settings_path) settings_module_name = ".".join(settings_file.split('.')[:-1]) @@ -130,7 +131,7 @@ def setup_django_settings_module(settings_path: str =None, local_installation: b os.environ[ENVIRONMENT_VARIABLE] = settings_module_name -def get_default_settings_context(user_data_path: str=None) -> Dict[str, str]: +def get_default_settings_context(user_data_dir: str=None) -> Dict[str, str]: """ Returns the default context values for the settings template: 'openslides_user_data_path', 'import_function' and 'debug'. @@ -140,45 +141,45 @@ def get_default_settings_context(user_data_path: str=None) -> Dict[str, str]: # Setup path for user specific data (SQLite3 database, media, ...): # Take it either from command line or get default path default_context = {} - if user_data_path: - default_context['openslides_user_data_path'] = repr(user_data_path) + if user_data_dir: + default_context['openslides_user_data_dir'] = repr(user_data_dir) default_context['import_function'] = '' else: openslides_type = detect_openslides_type() if openslides_type == WINDOWS_PORTABLE_VERSION: - default_context['openslides_user_data_path'] = 'get_win32_portable_user_data_path()' - default_context['import_function'] = 'from openslides.utils.main import get_win32_portable_user_data_path' + default_context['openslides_user_data_dir'] = 'get_win32_portable_user_data_dir()' + default_context['import_function'] = 'from openslides.utils.main import get_win32_portable_user_data_dir' else: - path = get_default_user_data_path(openslides_type) - default_context['openslides_user_data_path'] = repr(os.path.join(path, 'openslides')) + data_dir = get_default_user_data_dir(openslides_type) + default_context['openslides_user_data_dir'] = repr(os.path.join(data_dir, 'openslides')) default_context['import_function'] = '' default_context['debug'] = 'False' return default_context -def get_default_user_data_path(openslides_type: str) -> str: +def get_default_user_data_dir(openslides_type: str) -> str: """ - Returns the default path for user specific data according to the OpenSlides + Returns the default directory for user specific data according to the OpenSlides type. The argument 'openslides_type' has to be one of the three types mentioned in openslides.utils.main. """ if openslides_type == UNIX_VERSION: - default_user_data_path = os.environ.get( + default_user_data_dir = os.environ.get( 'XDG_DATA_HOME', os.path.expanduser('~/.local/share')) elif openslides_type == WINDOWS_VERSION: - default_user_data_path = get_win32_app_data_path() + default_user_data_dir = get_win32_app_data_dir() elif openslides_type == WINDOWS_PORTABLE_VERSION: - default_user_data_path = get_win32_portable_path() + default_user_data_dir = get_win32_portable_dir() else: raise TypeError('%s is not a valid OpenSlides type.' % openslides_type) - return default_user_data_path + return default_user_data_dir -def get_win32_app_data_path() -> str: +def get_win32_app_data_dir() -> str: """ - Returns the path to Windows' AppData directory. + Returns the directory of Windows' AppData directory. """ shell32 = ctypes.WinDLL("shell32.dll") SHGetFolderPath = shell32.SHGetFolderPathW @@ -199,16 +200,16 @@ def get_win32_app_data_path() -> str: return buf.value -def get_win32_portable_path() -> str: +def get_win32_portable_dir() -> str: """ - Returns the path to the Windows portable version. + Returns the directory of the Windows portable version. """ # NOTE: sys.executable will be the path to openslides.exe # since it is essentially a small wrapper that embeds the # python interpreter - portable_path = os.path.dirname(os.path.abspath(sys.executable)) + portable_dir = os.path.dirname(os.path.abspath(sys.executable)) try: - fd, test_file = tempfile.mkstemp(dir=portable_path) + fd, test_file = tempfile.mkstemp(dir=portable_dir) except OSError: raise PortableDirNotWritable( 'Portable directory is not writeable. ' @@ -216,25 +217,26 @@ def get_win32_portable_path() -> str: else: os.close(fd) os.unlink(test_file) - return portable_path + return portable_dir -def get_win32_portable_user_data_path() -> str: +def get_win32_portable_user_data_dir() -> str: """ - Returns the user data path to the Windows portable version. + Returns the user data directory to the Windows portable version. """ - return os.path.join(get_win32_portable_path(), 'openslides') + return os.path.join(get_win32_portable_dir(), 'openslides') -def write_settings(settings_path: str=None, template: str=None, **context: str) -> str: +def write_settings(settings_dir: str=None, settings_filename: str='settings.py', template: str=None, **context: str) -> str: """ - Creates the settings file at the given path using the given values for the + Creates the settings file at the given dir using the given values for the file template. Retuns the path to the created settings. """ - if settings_path is None: - settings_path = get_default_settings_path() + if settings_dir is None: + settings_dir = get_default_settings_dir() + settings_path = os.path.join(settings_dir, settings_filename) if template is None: with open(os.path.join(os.path.dirname(__file__), 'settings.py.tpl')) as template_file: @@ -248,16 +250,17 @@ def write_settings(settings_path: str=None, template: str=None, **context: str) context.setdefault(key, value) content = template % context - settings_module = os.path.realpath(os.path.dirname(settings_path)) + settings_module = os.path.realpath(settings_dir) if not os.path.exists(settings_module): os.makedirs(settings_module) with open(settings_path, 'w') as settings_file: settings_file.write(content) - if context['openslides_user_data_path'] == 'get_win32_portable_user_data_path()': - openslides_user_data_path = get_win32_portable_user_data_path() + + if context['openslides_user_data_dir'] == 'get_win32_portable_user_data_dir()': + openslides_user_data_dir = get_win32_portable_user_data_dir() else: - openslides_user_data_path = context['openslides_user_data_path'].strip("'") - os.makedirs(os.path.join(openslides_user_data_path, 'static'), exist_ok=True) + openslides_user_data_dir = context['openslides_user_data_dir'].strip("'") + os.makedirs(os.path.join(openslides_user_data_dir, 'static'), exist_ok=True) return os.path.realpath(settings_path) @@ -329,9 +332,9 @@ def get_geiss_path() -> str: Returns the path and file to the Geiss binary. """ from django.conf import settings - download_path = getattr(settings, 'OPENSLIDES_USER_DATA_PATH', '') + download_dir = getattr(settings, 'OPENSLIDES_USER_DATA_PATH', '') bin_name = 'geiss.exe' if is_windows() else 'geiss' - return os.path.join(download_path, bin_name) + return os.path.join(download_dir, bin_name) def is_windows() -> bool: diff --git a/openslides/utils/plugins.py b/openslides/utils/plugins.py index ddc773f97..987d00b6f 100644 --- a/openslides/utils/plugins.py +++ b/openslides/utils/plugins.py @@ -10,7 +10,7 @@ from pkg_resources import iter_entry_points from openslides.utils.main import ( WINDOWS_PORTABLE_VERSION, detect_openslides_type, - get_win32_portable_user_data_path, + get_win32_portable_user_data_dir, ) @@ -25,12 +25,12 @@ def collect_plugins_from_entry_points() -> Tuple[str, ...]: return tuple(entry_point.module_name for entry_point in iter_entry_points('openslides_plugins')) -def collect_plugins_from_path(path: str) -> Tuple[str, ...]: +def collect_plugins_from_dir(plugin_dir: str) -> Tuple[str, ...]: """ - Collects all modules/packages in the given `path` and returns a tuple + Collects all modules/packages in the given `plugin_dir` and returns a tuple of their names. """ - return tuple(x[1] for x in pkgutil.iter_modules([path])) + return tuple(x[1] for x in pkgutil.iter_modules([plugin_dir])) def collect_plugins() -> Tuple[str, ...]: @@ -42,11 +42,11 @@ def collect_plugins() -> Tuple[str, ...]: # Collect plugins in plugins/ directory of portable. if detect_openslides_type() == WINDOWS_PORTABLE_VERSION: - plugins_path = os.path.join( - get_win32_portable_user_data_path(), 'plugins') - if plugins_path not in sys.path: - sys.path.append(plugins_path) - collected_plugins += collect_plugins_from_path(plugins_path) + plugins_dir = os.path.join( + get_win32_portable_user_data_dir(), 'plugins') + if plugins_dir not in sys.path: + sys.path.append(plugins_dir) + collected_plugins += collect_plugins_from_dir(plugins_dir) return collected_plugins diff --git a/openslides/utils/settings.py.tpl b/openslides/utils/settings.py.tpl index e30afbd78..9f0d3b77e 100644 --- a/openslides/utils/settings.py.tpl +++ b/openslides/utils/settings.py.tpl @@ -12,9 +12,9 @@ import os from openslides.global_settings import * %(import_function)s -# Path to the directory for user specific data files +# The directory for user specific data files -OPENSLIDES_USER_DATA_PATH = %(openslides_user_data_path)s +OPENSLIDES_USER_DATA_DIR = %(openslides_user_data_dir)s # OpenSlides plugins @@ -70,7 +70,7 @@ EMAIL_HOST_PASSWORD = '' DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': os.path.join(OPENSLIDES_USER_DATA_PATH, 'db.sqlite3'), + 'NAME': os.path.join(OPENSLIDES_USER_DATA_DIR, 'db.sqlite3'), } } @@ -133,15 +133,15 @@ TIME_ZONE = 'Europe/Berlin' # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/1.10/howto/static-files/ -STATICFILES_DIRS = [os.path.join(OPENSLIDES_USER_DATA_PATH, 'static')] + STATICFILES_DIRS +STATICFILES_DIRS = [os.path.join(OPENSLIDES_USER_DATA_DIR, 'static')] + STATICFILES_DIRS -STATIC_ROOT = os.path.join(OPENSLIDES_USER_DATA_PATH, 'collected-static') +STATIC_ROOT = os.path.join(OPENSLIDES_USER_DATA_DIR, 'collected-static') # Files # https://docs.djangoproject.com/en/1.10/topics/files/ -MEDIA_ROOT = os.path.join(OPENSLIDES_USER_DATA_PATH, 'media', '') +MEDIA_ROOT = os.path.join(OPENSLIDES_USER_DATA_DIR, 'media', '') # Password validation diff --git a/openslides/utils/views.py b/openslides/utils/views.py index c92f40d2b..b657aab68 100644 --- a/openslides/utils/views.py +++ b/openslides/utils/views.py @@ -53,8 +53,12 @@ class APIView(_APIView): class TemplateView(View): """ A view to serve a single cached template file. Subclasses have to provide 'template_name'. + The state dict is used to cache the template. The state variable is static, but the object ID + is not allowed to change. So the State has to be saved in this dict. Search for 'Borg design + pattern' for more information. """ template_name = None # type: str + state = {} # type: Dict[str, str] def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) @@ -62,8 +66,9 @@ class TemplateView(View): 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() + if 'template' not in self.state: + with open(finders.find(self.template_name)) as template: + self.state['template'] = template.read() def get(self, *args: Any, **kwargs: Any) -> HttpResponse: - return HttpResponse(self.template) + return HttpResponse(self.state['template']) diff --git a/tests/old/utils/test_main.py b/tests/old/utils/test_main.py index 0397177f5..3988b01c7 100644 --- a/tests/old/utils/test_main.py +++ b/tests/old/utils/test_main.py @@ -37,25 +37,25 @@ class TestFunctions(TestCase): @patch('openslides.utils.main.detect_openslides_type') @patch('openslides.utils.main.os.path.expanduser') - def test_get_default_settings_path_unix(self, mock_expanduser, mock_detect): + def test_get_default_settings_dir_unix(self, mock_expanduser, mock_detect): mock_expanduser.return_value = '/home/test/.config' - self.assertEqual(main.get_default_settings_path(main.UNIX_VERSION), - '/home/test/.config/openslides/settings.py') + self.assertEqual(main.get_default_settings_dir(main.UNIX_VERSION), + '/home/test/.config/openslides') - @patch('openslides.utils.main.get_win32_app_data_path') - def test_get_default_settings_path_win(self, mock_win): + @patch('openslides.utils.main.get_win32_app_data_dir') + def test_get_default_settings_dir_win(self, mock_win): mock_win.return_value = 'win32' - self.assertEqual(main.get_default_settings_path(main.WINDOWS_VERSION), - 'win32/openslides/settings.py') + self.assertEqual(main.get_default_settings_dir(main.WINDOWS_VERSION), + 'win32/openslides') - @patch('openslides.utils.main.get_win32_portable_path') - def test_get_default_settings_path_portable(self, mock_portable): + @patch('openslides.utils.main.get_win32_portable_dir') + def test_get_default_settings_dir_portable(self, mock_portable): mock_portable.return_value = 'portable' - self.assertEqual(main.get_default_settings_path(main.WINDOWS_PORTABLE_VERSION), - 'portable/openslides/settings.py') + self.assertEqual(main.get_default_settings_dir(main.WINDOWS_PORTABLE_VERSION), + 'portable/openslides') - def test_get_local_settings_path(self): - self.assertEqual(main.get_local_settings_path(), os.sep.join(('personal_data', 'var', 'settings.py'))) + def test_get_local_settings_dir(self): + self.assertEqual(main.get_local_settings_dir(), os.sep.join(('personal_data', 'var'))) def test_setup_django_settings_module(self): main.setup_django_settings_module('test_dir_dhvnghfjdh456fzheg2f/test_path_bngjdhc756dzwncshdfnx.py') @@ -67,10 +67,10 @@ class TestFunctions(TestCase): def test_get_default_settings_context_portable(self, detect_mock): detect_mock.return_value = main.WINDOWS_PORTABLE_VERSION context = main.get_default_settings_context() - self.assertEqual(context['openslides_user_data_path'], 'get_win32_portable_user_data_path()') + self.assertEqual(context['openslides_user_data_dir'], 'get_win32_portable_user_data_dir()') - def test_get_default_user_data_path(self): - self.assertIn(os.path.join('.local', 'share'), main.get_default_user_data_path(main.UNIX_VERSION)) + def test_get_default_user_data_dir(self): + self.assertIn(os.path.join('.local', 'share'), main.get_default_user_data_dir(main.UNIX_VERSION)) @patch('openslides.utils.main.threading.Thread') @patch('openslides.utils.main.time')