From 8adaa6118a2bb0332680b5ada0a8a92bea99e0a5 Mon Sep 17 00:00:00 2001 From: FinnStutzenstein Date: Wed, 29 Aug 2018 15:49:44 +0200 Subject: [PATCH 1/3] Angular constants via WebSocket - new format for constants on the server - adaptions for the old client --- .../core/services/constants.service.spec.ts | 15 +++ .../app/core/services/constants.service.ts | 95 +++++++++++++++++++ .../app/core/services/openslides.service.ts | 2 +- .../app/core/services/websocket.service.ts | 37 +++++--- openslides/assignments/apps.py | 11 +-- openslides/core/apps.py | 18 ++-- openslides/core/views.py | 7 +- openslides/users/apps.py | 5 +- openslides/utils/consumers.py | 16 +++- tests/integration/core/test_views.py | 8 +- 10 files changed, 169 insertions(+), 45 deletions(-) create mode 100644 client/src/app/core/services/constants.service.spec.ts create mode 100644 client/src/app/core/services/constants.service.ts diff --git a/client/src/app/core/services/constants.service.spec.ts b/client/src/app/core/services/constants.service.spec.ts new file mode 100644 index 000000000..60b8ae0a2 --- /dev/null +++ b/client/src/app/core/services/constants.service.spec.ts @@ -0,0 +1,15 @@ +import { TestBed, inject } from '@angular/core/testing'; + +import { ConstantsService } from './constants.service'; + +describe('ConstantsService', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [ConstantsService] + }); + }); + + it('should be created', inject([ConstantsService], (service: ConstantsService) => { + expect(service).toBeTruthy(); + })); +}); diff --git a/client/src/app/core/services/constants.service.ts b/client/src/app/core/services/constants.service.ts new file mode 100644 index 000000000..3c67ba77a --- /dev/null +++ b/client/src/app/core/services/constants.service.ts @@ -0,0 +1,95 @@ +import { Injectable } from '@angular/core'; + +import { OpenSlidesComponent } from 'app/openslides.component'; +import { WebsocketService } from './websocket.service'; +import { Observable, of, Subject } from 'rxjs'; + +/** + * constants have a key associated with the data. + */ +interface Constants { + [key: string]: any; +} + +/** + * Get constants from the server. + * + * @example + * this.constantsService.get('OpenSlidesSettings').subscribe(constant => { + * console.log(constant); + * }); + */ +@Injectable({ + providedIn: 'root' +}) +export class ConstantsService extends OpenSlidesComponent { + /** + * The constants + */ + private constants: Constants; + + /** + * Flag, if the websocket connection is open. + */ + private websocketOpen = false; + + /** + * Flag, if constants are requested, but the server hasn't send them yet. + */ + private pending = false; + + /** + * Pending requests will be notified by these subjects, one per key. + */ + private pendingSubject: { [key: string]: Subject } = {}; + + /** + * @param websocketService + */ + public constructor(private websocketService: WebsocketService) { + super(); + + // The hook for recieving constants. + websocketService.getOberservable('constantsResponse').subscribe(constants => { + this.constants = constants; + if (this.pending) { + // send constants to subscribers that await constants. + this.pending = false; + Object.keys(this.pendingSubject).forEach(key => { + this.pendingSubject[key].next(this.constants[key]); + }); + } + }); + + // We can request constants, if the websocket connection opens. + websocketService.connectEvent.subscribe(() => { + if (!this.websocketOpen && this.pending) { + this.websocketService.send('constantsRequest', {}); + } + this.websocketOpen = true; + }); + } + + /** + * Get the constant named by key. + * @param key The constant to get. + */ + public get(key: string): Observable { + if (this.constants) { + return of(this.constants[key]); + } else { + // we have to request constants. + if (!this.pending) { + this.pending = true; + // if the connection is open, we directly can send the request. + if (this.websocketOpen) { + this.websocketService.send('constantsRequest', {}); + } + } + if (!this.pendingSubject[key]) { + this.pendingSubject[key] = new Subject(); + } + return this.pendingSubject[key].asObservable(); + } + } +} diff --git a/client/src/app/core/services/openslides.service.ts b/client/src/app/core/services/openslides.service.ts index 9a061d7a2..769dbee87 100644 --- a/client/src/app/core/services/openslides.service.ts +++ b/client/src/app/core/services/openslides.service.ts @@ -38,7 +38,7 @@ export class OpenSlidesService extends OpenSlidesComponent { // Handler that gets called, if the websocket connection reconnects after a disconnection. // There might have changed something on the server, so we check the operator, if he changed. - websocketService.getReconnectObservable().subscribe(() => { + websocketService.reconnectEvent.subscribe(() => { this.checkOperator(); }); } diff --git a/client/src/app/core/services/websocket.service.ts b/client/src/app/core/services/websocket.service.ts index 472a86bc0..83ba5ac7f 100644 --- a/client/src/app/core/services/websocket.service.ts +++ b/client/src/app/core/services/websocket.service.ts @@ -1,4 +1,4 @@ -import { Injectable, NgZone } from '@angular/core'; +import { Injectable, NgZone, EventEmitter } from '@angular/core'; import { Router } from '@angular/router'; import { Observable, Subject } from 'rxjs'; import { MatSnackBar, MatSnackBarRef, SimpleSnackBar } from '@angular/material'; @@ -36,7 +36,26 @@ export class WebsocketService { /** * Subjects that will be called, if a reconnect was successful. */ - private reconnectSubject: Subject; + private _reconnectEvent: EventEmitter = new EventEmitter(); + + /** + * Getter for the reconnect event. + */ + public get reconnectEvent(): EventEmitter { + return this._reconnectEvent; + } + + /** + * Listeners will be nofitied, if the wesocket connection is establiched. + */ + private _connectEvent: EventEmitter = new EventEmitter(); + + /** + * Getter for the connect event. + */ + public get connectEvent(): EventEmitter { + return this._connectEvent; + } /** * The websocket. @@ -57,9 +76,7 @@ export class WebsocketService { private matSnackBar: MatSnackBar, private zone: NgZone, public translate: TranslateService - ) { - this.reconnectSubject = new Subject(); - } + ) {} /** * Creates a new WebSocket connection and handles incomming events. @@ -91,8 +108,9 @@ export class WebsocketService { this.connectionErrorNotice.dismiss(); this.connectionErrorNotice = null; } - this.reconnectSubject.next(); + this._reconnectEvent.emit(); } + this._connectEvent.emit(); }); }; @@ -156,13 +174,6 @@ export class WebsocketService { return this.subjects[type].asObservable(); } - /** - * get the reconnect observable. It will be published, if a reconnect was sucessful. - */ - public getReconnectObservable(): Observable { - return this.reconnectSubject.asObservable(); - } - /** * Sends a message to the server with the content and the given type. * diff --git a/openslides/assignments/apps.py b/openslides/assignments/apps.py index 984776b40..b3c49a251 100644 --- a/openslides/assignments/apps.py +++ b/openslides/assignments/apps.py @@ -47,14 +47,11 @@ class AssignmentsAppConfig(AppConfig): def get_angular_constants(self): assignment = self.get_model('Assignment') - InnerItem = TypedDict('InnerItem', {'value': int, 'display_name': str}) - Item = TypedDict('Item', {'name': str, 'value': List[InnerItem]}) - data: Item = { - 'name': 'AssignmentPhases', - 'value': []} + Item = TypedDict('Item', {'value': int, 'display_name': str}) + phases: List[Item] = [] for phase in assignment.PHASES: - data['value'].append({ + phases.append({ 'value': phase[0], 'display_name': phase[1], }) - return [data] + return {'AssignmentPhases': phases} diff --git a/openslides/core/apps.py b/openslides/core/apps.py index 0c342ea4a..305778978 100644 --- a/openslides/core/apps.py +++ b/openslides/core/apps.py @@ -1,6 +1,6 @@ from collections import OrderedDict from operator import attrgetter -from typing import Any, List +from typing import Any, Dict, List from django.apps import AppConfig from django.conf import settings @@ -74,6 +74,8 @@ class CoreAppConfig(AppConfig): def get_angular_constants(self): from .config import config + constants: Dict[str, Any] = {} + # Client settings client_settings_keys = [ 'MOTION_IDENTIFIER_MIN_DIGITS', @@ -88,9 +90,7 @@ class CoreAppConfig(AppConfig): # Settings key does not exist. Do nothing. The client will # treat this as undefined. pass - client_settings = { - 'name': 'OpenSlidesSettings', - 'value': client_settings_dict} + constants['OpenSlidesSettings'] = client_settings_dict # Config variables config_groups: List[Any] = [] @@ -110,17 +110,13 @@ class CoreAppConfig(AppConfig): items=[])) # Add the config variable to the current group and subgroup. config_groups[-1]['subgroups'][-1]['items'].append(config_variable.data) - config_variables = { - 'name': 'OpenSlidesConfigVariables', - 'value': config_groups} + constants['OpenSlidesConfigVariables'] = config_groups # Send the privacy policy to the client. A user should view them, even he is # not logged in (so does not have the config values yet). - privacy_policy = { - 'name': 'PrivacyPolicy', - 'value': config['general_event_privacy_policy']} + constants['PrivacyPolicy'] = config['general_event_privacy_policy'] - return [client_settings, config_variables, privacy_policy] + return constants def call_save_default_values(**kwargs): diff --git a/openslides/core/views.py b/openslides/core/views.py index 23264b37b..7e53e4530 100644 --- a/openslides/core/views.py +++ b/openslides/core/views.py @@ -141,10 +141,9 @@ class WebclientJavaScriptView(utils_views.View): except AttributeError: # The app doesn't have this method. Continue to next app. continue - for constant in get_angular_constants(): - value = json.dumps(constant['value']) - name = constant['name'] - angular_constants += ".constant('{}', {})".format(name, value) + for key, value in get_angular_constants().items(): + value = json.dumps(value) + angular_constants += ".constant('{}', {})".format(key, value) # Use JavaScript loadScript function from # http://balpha.de/2011/10/jquery-script-insertion-and-its-consequences-for-debugging/ diff --git a/openslides/users/apps.py b/openslides/users/apps.py index 5dfb93305..85d3820eb 100644 --- a/openslides/users/apps.py +++ b/openslides/users/apps.py @@ -58,7 +58,4 @@ class UsersAppConfig(AppConfig): permissions.append({ 'display_name': permission.name, 'value': '.'.join((permission.content_type.app_label, permission.codename,))}) - permission_settings = { - 'name': 'permissions', - 'value': permissions} - return [permission_settings] + return {'permissions': permissions} diff --git a/openslides/utils/consumers.py b/openslides/utils/consumers.py index 8609ccbb6..1c91981ad 100644 --- a/openslides/utils/consumers.py +++ b/openslides/utils/consumers.py @@ -4,6 +4,7 @@ import jsonschema from asgiref.sync import sync_to_async from channels.db import database_sync_to_async from channels.generic.websocket import AsyncJsonWebsocketConsumer +from django.apps import apps from ..core.config import config from ..core.models import Projector @@ -30,7 +31,7 @@ class ProtocollAsyncJsonWebsocketConsumer(AsyncJsonWebsocketConsumer): "type": { "description": "Defines what kind of packages is packed.", "type": "string", - "pattern": "notify", # The server can sent other types + "pattern": "notify|constantsRequest", # The server can sent other types }, "content": { "description": "The content of the package.", @@ -136,6 +137,19 @@ class SiteConsumer(ProtocollAsyncJsonWebsocketConsumer): ) else: await self.send_json(type='error', content='Invalid notify message', in_response=id) + elif type == 'constantsRequest': + angular_constants: Dict[str, Any] = {} + for app in apps.get_app_configs(): + try: + # Each app can deliver values to angular when implementing this method. + # It should return a list with dicts containing the 'name' and 'value'. + get_angular_constants = app.get_angular_constants + except AttributeError: + # The app doesn't have this method. Continue to next app. + continue + constants = await database_sync_to_async(get_angular_constants)() + angular_constants.update(constants) + await self.send_json(type='constantsResponse', content=angular_constants, in_response=id) async def send_notify(self, event: Dict[str, Any]) -> None: """ diff --git a/tests/integration/core/test_views.py b/tests/integration/core/test_views.py index e88098175..cb4eda5e8 100644 --- a/tests/integration/core/test_views.py +++ b/tests/integration/core/test_views.py @@ -94,17 +94,17 @@ class WebclientJavaScriptView(TestCase): response = self.client.get(reverse('core_webclient_javascript', args=['site'])) content = response.content.decode() constants = self.get_angular_constants_from_apps() - for constant in constants: - self.assertTrue(json.dumps(constant['value']) in content) + for key, constant in constants.items(): + self.assertTrue(json.dumps(constant) in content) def get_angular_constants_from_apps(self): - constants = [] + constants = {} for app in apps.get_app_configs(): try: get_angular_constants = app.get_angular_constants except AttributeError: continue - constants.extend(get_angular_constants()) + constants.update(get_angular_constants()) return constants From 732de97ec285fc1cf5e10ab082c1f5b374b4ca47 Mon Sep 17 00:00:00 2001 From: Oskar Hahn Date: Thu, 30 Aug 2018 09:07:06 +0200 Subject: [PATCH 2/3] Make Consumer real async again --- openslides/core/apps.py | 9 +++++ openslides/core/views.py | 15 +++------ openslides/utils/constants.py | 40 +++++++++++++++++++++++ openslides/utils/consumers.py | 21 ++++-------- tests/conftest.py | 19 +++++++++++ tests/integration/utils/test_consumers.py | 15 +++++++++ 6 files changed, 93 insertions(+), 26 deletions(-) create mode 100644 openslides/utils/constants.py diff --git a/openslides/core/apps.py b/openslides/core/apps.py index 305778978..56fa8af78 100644 --- a/openslides/core/apps.py +++ b/openslides/core/apps.py @@ -4,6 +4,7 @@ from typing import Any, Dict, List from django.apps import AppConfig from django.conf import settings +from django.core.exceptions import ImproperlyConfigured from django.db.models.signals import post_migrate from ..utils.projector import register_projector_elements @@ -37,6 +38,14 @@ class CoreAppConfig(AppConfig): ProjectorViewSet, TagViewSet, ) + from ..utils.constants import set_constants, get_constants_from_apps + + # Set constants + try: + set_constants(get_constants_from_apps()) + except ImproperlyConfigured: + # Database is not loaded. This happens in tests. + pass # Define config variables and projector elements. config.update_config_variables(get_config_variables()) diff --git a/openslides/core/views.py b/openslides/core/views.py index 7e53e4530..7d987fed9 100644 --- a/openslides/core/views.py +++ b/openslides/core/views.py @@ -48,6 +48,7 @@ from .models import ( ProjectorMessage, Tag, ) +from ..utils.constants import get_constants # Special Django views @@ -133,17 +134,9 @@ class WebclientJavaScriptView(utils_views.View): # angular constants angular_constants = '' - for app in apps.get_app_configs(): - try: - # Each app can deliver values to angular when implementing this method. - # It should return a list with dicts containing the 'name' and 'value'. - get_angular_constants = app.get_angular_constants - except AttributeError: - # The app doesn't have this method. Continue to next app. - continue - for key, value in get_angular_constants().items(): - value = json.dumps(value) - angular_constants += ".constant('{}', {})".format(key, value) + for key, value in get_constants().items(): + value = json.dumps(value) + angular_constants += ".constant('{}', {})".format(key, value) # Use JavaScript loadScript function from # http://balpha.de/2011/10/jquery-script-insertion-and-its-consequences-for-debugging/ diff --git a/openslides/utils/constants.py b/openslides/utils/constants.py new file mode 100644 index 000000000..d0717aef4 --- /dev/null +++ b/openslides/utils/constants.py @@ -0,0 +1,40 @@ +from typing import Any, Dict + +from django.apps import apps + + +def get_constants_from_apps() -> Dict[str, Any]: + out: Dict[str, Any] = {} + for app in apps.get_app_configs(): + try: + # Each app can deliver values to angular when implementing this method. + # It should return a list with dicts containing the 'name' and 'value'. + get_angular_constants = app.get_angular_constants + except AttributeError: + # The app doesn't have this method. Continue to next app. + continue + out.update(get_angular_constants()) + return out + + +constants = None + + +def get_constants() -> Dict[str, Any]: + """ + Returns the constants. + + This method only returns a static dict, so it is fast and can be used in a + async context. + """ + if constants is None: + raise RuntimeError("Constants are not set.") + return constants + + +def set_constants(value: Dict[str, Any]) -> None: + """ + Sets the constants variable. + """ + global constants + constants = value diff --git a/openslides/utils/consumers.py b/openslides/utils/consumers.py index 1c91981ad..1fbe5edc7 100644 --- a/openslides/utils/consumers.py +++ b/openslides/utils/consumers.py @@ -4,7 +4,6 @@ import jsonschema from asgiref.sync import sync_to_async from channels.db import database_sync_to_async from channels.generic.websocket import AsyncJsonWebsocketConsumer -from django.apps import apps from ..core.config import config from ..core.models import Projector @@ -16,6 +15,7 @@ from .collection import ( format_for_autoupdate, from_channel_message, ) +from .constants import get_constants class ProtocollAsyncJsonWebsocketConsumer(AsyncJsonWebsocketConsumer): @@ -31,7 +31,7 @@ class ProtocollAsyncJsonWebsocketConsumer(AsyncJsonWebsocketConsumer): "type": { "description": "Defines what kind of packages is packed.", "type": "string", - "pattern": "notify|constantsRequest", # The server can sent other types + "pattern": "notify|constants", # The server can sent other types }, "content": { "description": "The content of the package.", @@ -137,19 +137,10 @@ class SiteConsumer(ProtocollAsyncJsonWebsocketConsumer): ) else: await self.send_json(type='error', content='Invalid notify message', in_response=id) - elif type == 'constantsRequest': - angular_constants: Dict[str, Any] = {} - for app in apps.get_app_configs(): - try: - # Each app can deliver values to angular when implementing this method. - # It should return a list with dicts containing the 'name' and 'value'. - get_angular_constants = app.get_angular_constants - except AttributeError: - # The app doesn't have this method. Continue to next app. - continue - constants = await database_sync_to_async(get_angular_constants)() - angular_constants.update(constants) - await self.send_json(type='constantsResponse', content=angular_constants, in_response=id) + + elif type == 'constants': + # Return all constants to the client. + await self.send_json(type='constants', content=get_constants(), in_response=id) async def send_notify(self, event: Dict[str, Any]) -> None: """ diff --git a/tests/conftest.py b/tests/conftest.py index d288c1458..d029d2942 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,6 @@ +import pytest from django.test import TestCase, TransactionTestCase +from pytest_django.django_compat import is_django_unittest from pytest_django.plugin import validate_django_db @@ -38,3 +40,20 @@ def pytest_collection_modifyitems(items): return 0 items.sort(key=weight_test_case) + + +@pytest.fixture(autouse=True) +def constants(request): + """ + Resets the constants on every test. + + Uses fake constants, if the db is not in use. + """ + from openslides.utils.constants import set_constants, get_constants_from_apps + + if 'django_db' in request.node.keywords or is_django_unittest(request): + # When the db is created, use the original constants + set_constants(get_constants_from_apps()) + else: + # Else: Use fake constants + set_constants({'constant1': 'value1', 'constant2': 'value2'}) diff --git a/tests/integration/utils/test_consumers.py b/tests/integration/utils/test_consumers.py index 1752d1102..40a16e017 100644 --- a/tests/integration/utils/test_consumers.py +++ b/tests/integration/utils/test_consumers.py @@ -241,3 +241,18 @@ async def test_send_unknown_type(communicator): response = await communicator.receive_json_from() assert response['type'] == 'error' assert response['in_response'] == 'test_id' + + +@pytest.mark.asyncio +async def test_request_constants(communicator, settings): + await set_config('general_system_enable_anonymous', True) + await communicator.connect() + # Await the startup data + await communicator.receive_json_from() + + await communicator.send_json_to({'type': 'constants', 'content': '', 'id': 'test_id'}) + + response = await communicator.receive_json_from() + assert response['type'] == 'constants' + # See conftest.py for the content of 'content' + assert response['content'] == {'constant1': 'value1', 'constant2': 'value2'} From ea71d0a94237e5d3f583787f3cb464271fa1917c Mon Sep 17 00:00:00 2001 From: FinnStutzenstein Date: Fri, 31 Aug 2018 12:37:47 +0200 Subject: [PATCH 3/3] moved privacy policy to users, adapt client --- .../src/app/core/services/constants.service.ts | 14 ++++++++------ client/src/app/site/login/login.component.ts | 16 +++++++++++++--- openslides/core/apps.py | 4 ---- openslides/core/views.py | 2 +- openslides/users/views.py | 2 ++ 5 files changed, 24 insertions(+), 14 deletions(-) diff --git a/client/src/app/core/services/constants.service.ts b/client/src/app/core/services/constants.service.ts index 3c67ba77a..6b08ba1c9 100644 --- a/client/src/app/core/services/constants.service.ts +++ b/client/src/app/core/services/constants.service.ts @@ -15,9 +15,11 @@ interface Constants { * Get constants from the server. * * @example - * this.constantsService.get('OpenSlidesSettings').subscribe(constant => { - * console.log(constant); - * }); + * ```ts + * this.constantsService.get('OpenSlidesSettings').subscribe(constant => { + * console.log(constant); + * }); + * ``` */ @Injectable({ providedIn: 'root' @@ -50,7 +52,7 @@ export class ConstantsService extends OpenSlidesComponent { super(); // The hook for recieving constants. - websocketService.getOberservable('constantsResponse').subscribe(constants => { + websocketService.getOberservable('constants').subscribe(constants => { this.constants = constants; if (this.pending) { // send constants to subscribers that await constants. @@ -64,7 +66,7 @@ export class ConstantsService extends OpenSlidesComponent { // We can request constants, if the websocket connection opens. websocketService.connectEvent.subscribe(() => { if (!this.websocketOpen && this.pending) { - this.websocketService.send('constantsRequest', {}); + this.websocketService.send('constants', {}); } this.websocketOpen = true; }); @@ -83,7 +85,7 @@ export class ConstantsService extends OpenSlidesComponent { this.pending = true; // if the connection is open, we directly can send the request. if (this.websocketOpen) { - this.websocketService.send('constantsRequest', {}); + this.websocketService.send('constants', {}); } } if (!this.pendingSubject[key]) { diff --git a/client/src/app/site/login/login.component.ts b/client/src/app/site/login/login.component.ts index 48937f332..dc870a76f 100644 --- a/client/src/app/site/login/login.component.ts +++ b/client/src/app/site/login/login.component.ts @@ -72,6 +72,13 @@ export class LoginComponent extends BaseComponent implements OnInit, OnDestroy { */ public inProcess = false; + /** + * The provacy policy send by the server. + * + * TODO: give an option to show it during login. + */ + public privacyPolicy: string; + /** * Constructor for the login component * @@ -106,9 +113,12 @@ export class LoginComponent extends BaseComponent implements OnInit, OnDestroy { super.setTitle('Login'); this.http.get(environment.urlPrefix + '/users/login/', {}).subscribe(response => { - this.installationNotice = this.matSnackBar.open(response.info_text, this.translate.instant('OK'), { - duration: 5000 - }); + if (response.info_text) { + this.installationNotice = this.matSnackBar.open(response.info_text, this.translate.instant('OK'), { + duration: 5000 + }); + } + this.privacyPolicy = response.privacy_policy; }); } diff --git a/openslides/core/apps.py b/openslides/core/apps.py index 56fa8af78..413ac7a67 100644 --- a/openslides/core/apps.py +++ b/openslides/core/apps.py @@ -121,10 +121,6 @@ class CoreAppConfig(AppConfig): config_groups[-1]['subgroups'][-1]['items'].append(config_variable.data) constants['OpenSlidesConfigVariables'] = config_groups - # Send the privacy policy to the client. A user should view them, even he is - # not logged in (so does not have the config values yet). - constants['PrivacyPolicy'] = config['general_event_privacy_policy'] - return constants diff --git a/openslides/core/views.py b/openslides/core/views.py index 7d987fed9..081d2dbc7 100644 --- a/openslides/core/views.py +++ b/openslides/core/views.py @@ -15,6 +15,7 @@ from .. import __license__ as license, __url__ as url, __version__ as version from ..utils import views as utils_views from ..utils.auth import anonymous_is_enabled, has_perm from ..utils.autoupdate import inform_changed_data, inform_deleted_data +from ..utils.constants import get_constants from ..utils.plugins import ( get_plugin_description, get_plugin_license, @@ -48,7 +49,6 @@ from .models import ( ProjectorMessage, Tag, ) -from ..utils.constants import get_constants # Special Django views diff --git a/openslides/users/views.py b/openslides/users/views.py index e0aed75bf..2622db1b9 100644 --- a/openslides/users/views.py +++ b/openslides/users/views.py @@ -453,6 +453,8 @@ class UserLoginView(APIView): password='admin') else: context['info_text'] = '' + # Add the privacy policy, so the client can display it even, it is not logged in. + context['privacy_policy'] = config['general_event_privacy_policy'] else: # self.request.method == 'POST' context['user_id'] = self.user.pk