Merge pull request #3837 from FinnStutzenstein/constants
Angular constants via WebSocket
This commit is contained in:
commit
1b02b7c692
15
client/src/app/core/services/constants.service.spec.ts
Normal file
15
client/src/app/core/services/constants.service.spec.ts
Normal file
@ -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();
|
||||||
|
}));
|
||||||
|
});
|
97
client/src/app/core/services/constants.service.ts
Normal file
97
client/src/app/core/services/constants.service.ts
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
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
|
||||||
|
* ```ts
|
||||||
|
* 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<any> } = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param websocketService
|
||||||
|
*/
|
||||||
|
public constructor(private websocketService: WebsocketService) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
// The hook for recieving constants.
|
||||||
|
websocketService.getOberservable<Constants>('constants').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('constants', {});
|
||||||
|
}
|
||||||
|
this.websocketOpen = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the constant named by key.
|
||||||
|
* @param key The constant to get.
|
||||||
|
*/
|
||||||
|
public get(key: string): Observable<any> {
|
||||||
|
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('constants', {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!this.pendingSubject[key]) {
|
||||||
|
this.pendingSubject[key] = new Subject<any>();
|
||||||
|
}
|
||||||
|
return this.pendingSubject[key].asObservable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -38,7 +38,7 @@ export class OpenSlidesService extends OpenSlidesComponent {
|
|||||||
|
|
||||||
// Handler that gets called, if the websocket connection reconnects after a disconnection.
|
// 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.
|
// There might have changed something on the server, so we check the operator, if he changed.
|
||||||
websocketService.getReconnectObservable().subscribe(() => {
|
websocketService.reconnectEvent.subscribe(() => {
|
||||||
this.checkOperator();
|
this.checkOperator();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Injectable, NgZone } from '@angular/core';
|
import { Injectable, NgZone, EventEmitter } from '@angular/core';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { Observable, Subject } from 'rxjs';
|
import { Observable, Subject } from 'rxjs';
|
||||||
import { MatSnackBar, MatSnackBarRef, SimpleSnackBar } from '@angular/material';
|
import { MatSnackBar, MatSnackBarRef, SimpleSnackBar } from '@angular/material';
|
||||||
@ -36,7 +36,26 @@ export class WebsocketService {
|
|||||||
/**
|
/**
|
||||||
* Subjects that will be called, if a reconnect was successful.
|
* Subjects that will be called, if a reconnect was successful.
|
||||||
*/
|
*/
|
||||||
private reconnectSubject: Subject<void>;
|
private _reconnectEvent: EventEmitter<void> = new EventEmitter<void>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getter for the reconnect event.
|
||||||
|
*/
|
||||||
|
public get reconnectEvent(): EventEmitter<void> {
|
||||||
|
return this._reconnectEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listeners will be nofitied, if the wesocket connection is establiched.
|
||||||
|
*/
|
||||||
|
private _connectEvent: EventEmitter<void> = new EventEmitter<void>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getter for the connect event.
|
||||||
|
*/
|
||||||
|
public get connectEvent(): EventEmitter<void> {
|
||||||
|
return this._connectEvent;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The websocket.
|
* The websocket.
|
||||||
@ -57,9 +76,7 @@ export class WebsocketService {
|
|||||||
private matSnackBar: MatSnackBar,
|
private matSnackBar: MatSnackBar,
|
||||||
private zone: NgZone,
|
private zone: NgZone,
|
||||||
public translate: TranslateService
|
public translate: TranslateService
|
||||||
) {
|
) {}
|
||||||
this.reconnectSubject = new Subject<void>();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new WebSocket connection and handles incomming events.
|
* Creates a new WebSocket connection and handles incomming events.
|
||||||
@ -91,8 +108,9 @@ export class WebsocketService {
|
|||||||
this.connectionErrorNotice.dismiss();
|
this.connectionErrorNotice.dismiss();
|
||||||
this.connectionErrorNotice = null;
|
this.connectionErrorNotice = null;
|
||||||
}
|
}
|
||||||
this.reconnectSubject.next();
|
this._reconnectEvent.emit();
|
||||||
}
|
}
|
||||||
|
this._connectEvent.emit();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -156,13 +174,6 @@ export class WebsocketService {
|
|||||||
return this.subjects[type].asObservable();
|
return this.subjects[type].asObservable();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* get the reconnect observable. It will be published, if a reconnect was sucessful.
|
|
||||||
*/
|
|
||||||
public getReconnectObservable(): Observable<void> {
|
|
||||||
return this.reconnectSubject.asObservable();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends a message to the server with the content and the given type.
|
* Sends a message to the server with the content and the given type.
|
||||||
*
|
*
|
||||||
|
@ -72,6 +72,13 @@ export class LoginComponent extends BaseComponent implements OnInit, OnDestroy {
|
|||||||
*/
|
*/
|
||||||
public inProcess = false;
|
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
|
* Constructor for the login component
|
||||||
*
|
*
|
||||||
@ -106,9 +113,12 @@ export class LoginComponent extends BaseComponent implements OnInit, OnDestroy {
|
|||||||
super.setTitle('Login');
|
super.setTitle('Login');
|
||||||
|
|
||||||
this.http.get<any>(environment.urlPrefix + '/users/login/', {}).subscribe(response => {
|
this.http.get<any>(environment.urlPrefix + '/users/login/', {}).subscribe(response => {
|
||||||
this.installationNotice = this.matSnackBar.open(response.info_text, this.translate.instant('OK'), {
|
if (response.info_text) {
|
||||||
duration: 5000
|
this.installationNotice = this.matSnackBar.open(response.info_text, this.translate.instant('OK'), {
|
||||||
});
|
duration: 5000
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.privacyPolicy = response.privacy_policy;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,14 +47,11 @@ class AssignmentsAppConfig(AppConfig):
|
|||||||
|
|
||||||
def get_angular_constants(self):
|
def get_angular_constants(self):
|
||||||
assignment = self.get_model('Assignment')
|
assignment = self.get_model('Assignment')
|
||||||
InnerItem = TypedDict('InnerItem', {'value': int, 'display_name': str})
|
Item = TypedDict('Item', {'value': int, 'display_name': str})
|
||||||
Item = TypedDict('Item', {'name': str, 'value': List[InnerItem]})
|
phases: List[Item] = []
|
||||||
data: Item = {
|
|
||||||
'name': 'AssignmentPhases',
|
|
||||||
'value': []}
|
|
||||||
for phase in assignment.PHASES:
|
for phase in assignment.PHASES:
|
||||||
data['value'].append({
|
phases.append({
|
||||||
'value': phase[0],
|
'value': phase[0],
|
||||||
'display_name': phase[1],
|
'display_name': phase[1],
|
||||||
})
|
})
|
||||||
return [data]
|
return {'AssignmentPhases': phases}
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from operator import attrgetter
|
from operator import attrgetter
|
||||||
from typing import Any, List
|
from typing import Any, Dict, List
|
||||||
|
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.db.models.signals import post_migrate
|
from django.db.models.signals import post_migrate
|
||||||
|
|
||||||
from ..utils.projector import register_projector_elements
|
from ..utils.projector import register_projector_elements
|
||||||
@ -37,6 +38,14 @@ class CoreAppConfig(AppConfig):
|
|||||||
ProjectorViewSet,
|
ProjectorViewSet,
|
||||||
TagViewSet,
|
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.
|
# Define config variables and projector elements.
|
||||||
config.update_config_variables(get_config_variables())
|
config.update_config_variables(get_config_variables())
|
||||||
@ -74,6 +83,8 @@ class CoreAppConfig(AppConfig):
|
|||||||
def get_angular_constants(self):
|
def get_angular_constants(self):
|
||||||
from .config import config
|
from .config import config
|
||||||
|
|
||||||
|
constants: Dict[str, Any] = {}
|
||||||
|
|
||||||
# Client settings
|
# Client settings
|
||||||
client_settings_keys = [
|
client_settings_keys = [
|
||||||
'MOTION_IDENTIFIER_MIN_DIGITS',
|
'MOTION_IDENTIFIER_MIN_DIGITS',
|
||||||
@ -88,9 +99,7 @@ class CoreAppConfig(AppConfig):
|
|||||||
# Settings key does not exist. Do nothing. The client will
|
# Settings key does not exist. Do nothing. The client will
|
||||||
# treat this as undefined.
|
# treat this as undefined.
|
||||||
pass
|
pass
|
||||||
client_settings = {
|
constants['OpenSlidesSettings'] = client_settings_dict
|
||||||
'name': 'OpenSlidesSettings',
|
|
||||||
'value': client_settings_dict}
|
|
||||||
|
|
||||||
# Config variables
|
# Config variables
|
||||||
config_groups: List[Any] = []
|
config_groups: List[Any] = []
|
||||||
@ -110,17 +119,9 @@ class CoreAppConfig(AppConfig):
|
|||||||
items=[]))
|
items=[]))
|
||||||
# Add the config variable to the current group and subgroup.
|
# Add the config variable to the current group and subgroup.
|
||||||
config_groups[-1]['subgroups'][-1]['items'].append(config_variable.data)
|
config_groups[-1]['subgroups'][-1]['items'].append(config_variable.data)
|
||||||
config_variables = {
|
constants['OpenSlidesConfigVariables'] = config_groups
|
||||||
'name': 'OpenSlidesConfigVariables',
|
|
||||||
'value': config_groups}
|
|
||||||
|
|
||||||
# Send the privacy policy to the client. A user should view them, even he is
|
return constants
|
||||||
# not logged in (so does not have the config values yet).
|
|
||||||
privacy_policy = {
|
|
||||||
'name': 'PrivacyPolicy',
|
|
||||||
'value': config['general_event_privacy_policy']}
|
|
||||||
|
|
||||||
return [client_settings, config_variables, privacy_policy]
|
|
||||||
|
|
||||||
|
|
||||||
def call_save_default_values(**kwargs):
|
def call_save_default_values(**kwargs):
|
||||||
|
@ -15,6 +15,7 @@ from .. import __license__ as license, __url__ as url, __version__ as version
|
|||||||
from ..utils import views as utils_views
|
from ..utils import views as utils_views
|
||||||
from ..utils.auth import anonymous_is_enabled, has_perm
|
from ..utils.auth import anonymous_is_enabled, has_perm
|
||||||
from ..utils.autoupdate import inform_changed_data, inform_deleted_data
|
from ..utils.autoupdate import inform_changed_data, inform_deleted_data
|
||||||
|
from ..utils.constants import get_constants
|
||||||
from ..utils.plugins import (
|
from ..utils.plugins import (
|
||||||
get_plugin_description,
|
get_plugin_description,
|
||||||
get_plugin_license,
|
get_plugin_license,
|
||||||
@ -133,18 +134,9 @@ class WebclientJavaScriptView(utils_views.View):
|
|||||||
|
|
||||||
# angular constants
|
# angular constants
|
||||||
angular_constants = ''
|
angular_constants = ''
|
||||||
for app in apps.get_app_configs():
|
for key, value in get_constants().items():
|
||||||
try:
|
value = json.dumps(value)
|
||||||
# Each app can deliver values to angular when implementing this method.
|
angular_constants += ".constant('{}', {})".format(key, value)
|
||||||
# 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 constant in get_angular_constants():
|
|
||||||
value = json.dumps(constant['value'])
|
|
||||||
name = constant['name']
|
|
||||||
angular_constants += ".constant('{}', {})".format(name, value)
|
|
||||||
|
|
||||||
# Use JavaScript loadScript function from
|
# Use JavaScript loadScript function from
|
||||||
# http://balpha.de/2011/10/jquery-script-insertion-and-its-consequences-for-debugging/
|
# http://balpha.de/2011/10/jquery-script-insertion-and-its-consequences-for-debugging/
|
||||||
|
@ -58,7 +58,4 @@ class UsersAppConfig(AppConfig):
|
|||||||
permissions.append({
|
permissions.append({
|
||||||
'display_name': permission.name,
|
'display_name': permission.name,
|
||||||
'value': '.'.join((permission.content_type.app_label, permission.codename,))})
|
'value': '.'.join((permission.content_type.app_label, permission.codename,))})
|
||||||
permission_settings = {
|
return {'permissions': permissions}
|
||||||
'name': 'permissions',
|
|
||||||
'value': permissions}
|
|
||||||
return [permission_settings]
|
|
||||||
|
@ -453,6 +453,8 @@ class UserLoginView(APIView):
|
|||||||
password='<strong>admin</strong>')
|
password='<strong>admin</strong>')
|
||||||
else:
|
else:
|
||||||
context['info_text'] = ''
|
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:
|
else:
|
||||||
# self.request.method == 'POST'
|
# self.request.method == 'POST'
|
||||||
context['user_id'] = self.user.pk
|
context['user_id'] = self.user.pk
|
||||||
|
40
openslides/utils/constants.py
Normal file
40
openslides/utils/constants.py
Normal file
@ -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
|
@ -15,6 +15,7 @@ from .collection import (
|
|||||||
format_for_autoupdate,
|
format_for_autoupdate,
|
||||||
from_channel_message,
|
from_channel_message,
|
||||||
)
|
)
|
||||||
|
from .constants import get_constants
|
||||||
|
|
||||||
|
|
||||||
class ProtocollAsyncJsonWebsocketConsumer(AsyncJsonWebsocketConsumer):
|
class ProtocollAsyncJsonWebsocketConsumer(AsyncJsonWebsocketConsumer):
|
||||||
@ -30,7 +31,7 @@ class ProtocollAsyncJsonWebsocketConsumer(AsyncJsonWebsocketConsumer):
|
|||||||
"type": {
|
"type": {
|
||||||
"description": "Defines what kind of packages is packed.",
|
"description": "Defines what kind of packages is packed.",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"pattern": "notify", # The server can sent other types
|
"pattern": "notify|constants", # The server can sent other types
|
||||||
},
|
},
|
||||||
"content": {
|
"content": {
|
||||||
"description": "The content of the package.",
|
"description": "The content of the package.",
|
||||||
@ -137,6 +138,10 @@ class SiteConsumer(ProtocollAsyncJsonWebsocketConsumer):
|
|||||||
else:
|
else:
|
||||||
await self.send_json(type='error', content='Invalid notify message', in_response=id)
|
await self.send_json(type='error', content='Invalid notify message', 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:
|
async def send_notify(self, event: Dict[str, Any]) -> None:
|
||||||
"""
|
"""
|
||||||
Send a notify message to the user.
|
Send a notify message to the user.
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
|
import pytest
|
||||||
from django.test import TestCase, TransactionTestCase
|
from django.test import TestCase, TransactionTestCase
|
||||||
|
from pytest_django.django_compat import is_django_unittest
|
||||||
from pytest_django.plugin import validate_django_db
|
from pytest_django.plugin import validate_django_db
|
||||||
|
|
||||||
|
|
||||||
@ -38,3 +40,20 @@ def pytest_collection_modifyitems(items):
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
items.sort(key=weight_test_case)
|
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'})
|
||||||
|
@ -94,17 +94,17 @@ class WebclientJavaScriptView(TestCase):
|
|||||||
response = self.client.get(reverse('core_webclient_javascript', args=['site']))
|
response = self.client.get(reverse('core_webclient_javascript', args=['site']))
|
||||||
content = response.content.decode()
|
content = response.content.decode()
|
||||||
constants = self.get_angular_constants_from_apps()
|
constants = self.get_angular_constants_from_apps()
|
||||||
for constant in constants:
|
for key, constant in constants.items():
|
||||||
self.assertTrue(json.dumps(constant['value']) in content)
|
self.assertTrue(json.dumps(constant) in content)
|
||||||
|
|
||||||
def get_angular_constants_from_apps(self):
|
def get_angular_constants_from_apps(self):
|
||||||
constants = []
|
constants = {}
|
||||||
for app in apps.get_app_configs():
|
for app in apps.get_app_configs():
|
||||||
try:
|
try:
|
||||||
get_angular_constants = app.get_angular_constants
|
get_angular_constants = app.get_angular_constants
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
continue
|
continue
|
||||||
constants.extend(get_angular_constants())
|
constants.update(get_angular_constants())
|
||||||
return constants
|
return constants
|
||||||
|
|
||||||
|
|
||||||
|
@ -241,3 +241,18 @@ async def test_send_unknown_type(communicator):
|
|||||||
response = await communicator.receive_json_from()
|
response = await communicator.receive_json_from()
|
||||||
assert response['type'] == 'error'
|
assert response['type'] == 'error'
|
||||||
assert response['in_response'] == 'test_id'
|
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'}
|
||||||
|
Loading…
Reference in New Issue
Block a user