Merge pull request #3926 from ostcar/new_autoupdate_format

New autoupdate format
This commit is contained in:
Finn Stutzenstein 2018-10-19 07:40:42 +02:00 committed by GitHub
commit 236dc21d62
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 96 additions and 210 deletions

View File

@ -18,7 +18,7 @@ matrix:
- flake8 openslides tests - flake8 openslides tests
- isort --check-only --diff --recursive openslides tests - isort --check-only --diff --recursive openslides tests
- python -m mypy openslides/ - python -m mypy openslides/
- pytest tests/old/ tests/integration/ tests/unit/ --cov --cov-fail-under=75 - python -W ignore -m pytest --cov --cov-fail-under=75
- language: python - language: python
cache: cache:
@ -35,7 +35,7 @@ matrix:
- flake8 openslides tests - flake8 openslides tests
- isort --check-only --diff --recursive openslides tests - isort --check-only --diff --recursive openslides tests
- python -m mypy openslides/ - python -m mypy openslides/
- pytest tests/old/ tests/integration/ tests/unit/ --cov --cov-fail-under=75 - python -W ignore -m pytest --cov --cov-fail-under=75
- language: node_js - language: node_js
node_js: node_js:

View File

@ -16,6 +16,7 @@ Core:
- Changed URL schema [#3798]. - Changed URL schema [#3798].
- Enabled docs for using OpenSlides with Gunicorn and Uvicorn in big - Enabled docs for using OpenSlides with Gunicorn and Uvicorn in big
mode [#3799, #3817]. mode [#3799, #3817].
- Changed format for elements send via autoupdate [#3926].
Motions: Motions:
- Option to customly sort motions [#3894]. - Option to customly sort motions [#3894].

View File

@ -6,6 +6,27 @@ import { WebsocketService } from './websocket.service';
import { CollectionStringModelMapperService } from './collectionStringModelMapper.service'; import { CollectionStringModelMapperService } from './collectionStringModelMapper.service';
import { DataStoreService } from './data-store.service'; import { DataStoreService } from './data-store.service';
interface AutoupdateFormat {
/**
* All changed (and created) items as their full/restricted data grouped by their collection.
*/
changed: {
[collectionString: string]: object[];
};
/**
* All deleted items (by id) grouped by their collection.
*/
deleted: {
[collectionString: string]: number[];
};
/**
* The current change id for this autoupdate
*/
change_id: number;
}
/** /**
* Handles the initial update and automatic updates using the {@link WebsocketService} * Handles the initial update and automatic updates using the {@link WebsocketService}
* Incoming objects, usually BaseModels, will be saved in the dataStore (`this.DS`) * Incoming objects, usually BaseModels, will be saved in the dataStore (`this.DS`)
@ -27,7 +48,7 @@ export class AutoupdateService extends OpenSlidesComponent {
private modelMapper: CollectionStringModelMapperService private modelMapper: CollectionStringModelMapperService
) { ) {
super(); super();
websocketService.getOberservable<any>('autoupdate').subscribe(response => { websocketService.getOberservable<AutoupdateFormat>('autoupdate').subscribe(response => {
this.storeResponse(response); this.storeResponse(response);
}); });
} }
@ -42,36 +63,19 @@ export class AutoupdateService extends OpenSlidesComponent {
* *
* Saves models in DataStore. * Saves models in DataStore.
*/ */
public storeResponse(socketResponse: any): void { public storeResponse(autoupdate: AutoupdateFormat): void {
// Reorganize the autoupdate: groupy by action, then by collection. The final
// entries are the single autoupdate objects.
const autoupdate = {
changed: {},
deleted: {}
};
// Reorganize them.
socketResponse.forEach(obj => {
if (!autoupdate[obj.action][obj.collection]) {
autoupdate[obj.action][obj.collection] = [];
}
autoupdate[obj.action][obj.collection].push(obj);
});
// Delete the removed objects from the DataStore // Delete the removed objects from the DataStore
Object.keys(autoupdate.deleted).forEach(collection => { Object.keys(autoupdate.deleted).forEach(collection => {
this.DS.remove(collection, ...autoupdate.deleted[collection].map(_obj => _obj.id)); this.DS.remove(collection, autoupdate.deleted[collection], autoupdate.change_id);
}); });
// Add the objects to the DataStore. // Add the objects to the DataStore.
Object.keys(autoupdate.changed).forEach(collection => { Object.keys(autoupdate.changed).forEach(collection => {
const targetClass = this.modelMapper.getModelConstructor(collection); const targetClass = this.modelMapper.getModelConstructor(collection);
if (!targetClass) { if (!targetClass) {
// TODO: throw an error later.. throw new Error(`Unregistered resource ${collection}`);
/*throw new Error*/ console.log(`Unregistered resource ${collection}`);
return;
} }
this.DS.add(...autoupdate.changed[collection].map(_obj => new targetClass(_obj.data))); this.DS.add(autoupdate.changed[collection].map(model => new targetClass(model)), autoupdate.change_id);
}); });
} }

View File

@ -261,20 +261,15 @@ export class DataStoreService {
/** /**
* Add one or multiple models to dataStore. * Add one or multiple models to dataStore.
* *
* @param ...models The model(s) that shall be add use spread operator ("...") * @param models BaseModels to add to the store
* @example this.DS.add(new User(1)) * @param changeId The changeId of this update
* @example this.DS.add((new User(2), new User(3))) * @example this.DS.add([new User(1)], changeId)
* @example this.DS.add(...arrayWithUsers) * @example this.DS.add([new User(2), new User(3)], changeId)
* @example this.DS.add(arrayWithUsers, changeId)
*/ */
public add(...models: BaseModel[]): void { public add(models: BaseModel[], changeId: number): void {
const maxChangeId = 0;
models.forEach(model => { models.forEach(model => {
const collectionString = model.collectionString; const collectionString = model.collectionString;
if (!model.id) {
throw new Error('The model must have an id!');
} else if (collectionString === 'invalid-collection-string') {
throw new Error('Cannot save a BaseModel');
}
if (this.modelStore[collectionString] === undefined) { if (this.modelStore[collectionString] === undefined) {
this.modelStore[collectionString] = {}; this.modelStore[collectionString] = {};
} }
@ -284,25 +279,22 @@ export class DataStoreService {
this.JsonStore[collectionString] = {}; this.JsonStore[collectionString] = {};
} }
this.JsonStore[collectionString][model.id] = JSON.stringify(model); this.JsonStore[collectionString][model.id] = JSON.stringify(model);
// if (model.changeId > maxChangeId) {maxChangeId = model.maxChangeId;}
this.changedSubject.next(model); this.changedSubject.next(model);
}); });
this.storeToCache(maxChangeId); this.storeToCache(changeId);
} }
/** /**
* removes one or multiple models from dataStore. * removes one or multiple models from dataStore.
* *
* @param Type The desired BaseModel type to be read from the dataStore * @param Type The desired BaseModel type to be read from the datastore
* @param ...ids An or multiple IDs or a list of IDs of BaseModels. use spread operator ("...") for arrays * @param ids A list of IDs of BaseModels to remove from the datastore
* @example this.DS.remove('users/user', myUser.id, 3, 4) * @param changeId The changeId of this update
* @example this.DS.remove('users/user', [myUser.id, 3, 4], 38213)
*/ */
public remove(collectionString: string, ...ids: number[]): void { public remove(collectionString: string, ids: number[], changeId: number): void {
const maxChangeId = 0;
ids.forEach(id => { ids.forEach(id => {
if (this.modelStore[collectionString]) { if (this.modelStore[collectionString]) {
// get changeId from store
// if (model.changeId > maxChangeId) {maxChangeId = model.maxChangeId;}
delete this.modelStore[collectionString][id]; delete this.modelStore[collectionString][id];
} }
if (this.JsonStore[collectionString]) { if (this.JsonStore[collectionString]) {
@ -313,18 +305,18 @@ export class DataStoreService {
id: id id: id
}); });
}); });
this.storeToCache(maxChangeId); this.storeToCache(changeId);
} }
/** /**
* Updates the cache by inserting the serialized DataStore. Also changes the chageId, if it's larger * Updates the cache by inserting the serialized DataStore. Also changes the chageId, if it's larger
* @param maxChangeId * @param changeId The changeId from the update. If it's the highest change id seen, it will be set into the cache.
*/ */
private storeToCache(maxChangeId: number): void { private storeToCache(changeId: number): void {
this.cacheService.set(DataStoreService.cachePrefix + 'DS', this.JsonStore); this.cacheService.set(DataStoreService.cachePrefix + 'DS', this.JsonStore);
if (maxChangeId > this._maxChangeId) { if (changeId > this._maxChangeId) {
this._maxChangeId = maxChangeId; this._maxChangeId = changeId;
this.cacheService.set(DataStoreService.cachePrefix + 'maxChangeId', maxChangeId); this.cacheService.set(DataStoreService.cachePrefix + 'maxChangeId', changeId);
} }
} }
@ -334,5 +326,6 @@ export class DataStoreService {
*/ */
public printWhole(): void { public printWhole(): void {
console.log('Everything in DataStore: ', this.modelStore); console.log('Everything in DataStore: ', this.modelStore);
console.log('changeId', this.maxChangeId);
} }
} }

View File

@ -10,8 +10,5 @@
<button mat-button (click)="TranslateTest()">Translate in console</button> <button mat-button (click)="TranslateTest()">Translate in console</button>
<br /> <br />
<button mat-button (click)="giveDataStore()">print the dataStore</button> <button mat-button (click)="giveDataStore()">print the dataStore</button>
<br />
<input matInput #motionNumber placeholder="Number of Motions to add" value="100">
<button mat-button (click)="createMotions(motionNumber.value)">Add Random Motions</button>
</div> </div>
</mat-card> </mat-card>

View File

@ -6,8 +6,6 @@ import { TranslateService } from '@ngx-translate/core'; // showcase
// for testing the DS and BaseModel // for testing the DS and BaseModel
import { Config } from '../../../../shared/models/core/config'; import { Config } from '../../../../shared/models/core/config';
import { Motion } from '../../../../shared/models/motions/motion';
import { MotionSubmitter } from '../../../../shared/models/motions/motion-submitter';
import { DataStoreService } from '../../../../core/services/data-store.service'; import { DataStoreService } from '../../../../core/services/data-store.service';
@Component({ @Component({
@ -87,60 +85,4 @@ export class StartComponent extends BaseComponent implements OnInit {
console.log('lets translate the word "motion" in the current in the current lang'); console.log('lets translate the word "motion" in the current in the current lang');
console.log('Motions in ' + this.translate.currentLang + ' is ' + this.translate.instant('Motions')); console.log('Motions in ' + this.translate.currentLang + ' is ' + this.translate.instant('Motions'));
} }
/**
* Adds random generated motions
*/
public createMotions(requiredMotions: number): void {
console.log('adding ' + requiredMotions + ' Motions.');
const newMotionsArray = [];
const longMotionText = `
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.
Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi.
Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat.
Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis.
At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat.
Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus.
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.
Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi.
Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo
`;
for (let i = 1; i <= requiredMotions; ++i) {
// submitter
const newMotionSubmitter = new MotionSubmitter({
id: 1,
user_id: 1,
motion_id: 200 + i,
weight: 0
});
// motion
const newMotion = new Motion({
id: 200 + i,
identifier: 'GenMo ' + i,
title: 'title',
text: longMotionText,
reason: longMotionText,
origin: 'Generated',
submitters: [newMotionSubmitter],
state_id: 1
});
newMotionsArray.push(newMotion);
}
this.DS.add(...newMotionsArray);
console.log('Done adding motions');
}
} }

View File

@ -25,6 +25,16 @@ if TYPE_CHECKING:
AutoupdateFormat = TypedDict( AutoupdateFormat = TypedDict(
'AutoupdateFormat', 'AutoupdateFormat',
{
'changed': Dict[str, List[Dict[str, Any]]],
'deleted': Dict[str, List[int]],
'change_id': int,
},
)
AutoupdateFormatOld = TypedDict(
'AutoupdateFormatOld',
{ {
'collection': str, 'collection': str,
'id': int, 'id': int,
@ -116,24 +126,7 @@ class CollectionElement:
return (self.collection_string == collection_element.collection_string and return (self.collection_string == collection_element.collection_string and
self.id == collection_element.id) self.id == collection_element.id)
def as_autoupdate_for_user(self, user: Optional['CollectionElement']) -> AutoupdateFormat: def as_autoupdate_for_projector(self) -> AutoupdateFormatOld:
"""
Returns a dict that can be sent through the autoupdate system for a site
user.
"""
if not self.is_deleted():
restricted_data = self.get_access_permissions().get_restricted_data([self.get_full_data()], user)
data = restricted_data[0] if restricted_data else None
else:
data = None
return format_for_autoupdate(
collection_string=self.collection_string,
id=self.id,
action='deleted' if self.is_deleted() else 'changed',
data=data)
def as_autoupdate_for_projector(self) -> AutoupdateFormat:
""" """
Returns a dict that can be sent through the autoupdate system for the Returns a dict that can be sent through the autoupdate system for the
projector. projector.
@ -144,7 +137,7 @@ class CollectionElement:
else: else:
data = None data = None
return format_for_autoupdate( return format_for_autoupdate_old(
collection_string=self.collection_string, collection_string=self.collection_string,
id=self.id, id=self.id,
action='deleted' if self.is_deleted() else 'changed', action='deleted' if self.is_deleted() else 'changed',
@ -337,10 +330,12 @@ def get_model_from_collection_string(collection_string: str) -> Type[Model]:
return model return model
def format_for_autoupdate( def format_for_autoupdate_old(
collection_string: str, id: int, action: str, data: Dict[str, Any] = None) -> AutoupdateFormat: collection_string: str, id: int, action: str, data: Dict[str, Any] = None) -> AutoupdateFormatOld:
""" """
Returns a dict that can be used for autoupdate. Returns a dict that can be used for autoupdate.
This is depricated. Use format_for_autoupdate.
""" """
if data is None: if data is None:
# If the data is None then the action has to be deleted, # If the data is None then the action has to be deleted,
@ -348,7 +343,7 @@ def format_for_autoupdate(
# deleted, but the user has no permission to see it. # deleted, but the user has no permission to see it.
action = 'deleted' action = 'deleted'
output = AutoupdateFormat( output = AutoupdateFormatOld(
collection=collection_string, collection=collection_string,
id=id, id=id,
action=action, action=action,

View File

@ -1,3 +1,4 @@
from collections import defaultdict
from typing import Any, Dict, List, Optional from typing import Any, Dict, List, Optional
import jsonschema import jsonschema
@ -10,9 +11,10 @@ from ..core.models import Projector
from .auth import async_anonymous_is_enabled, has_perm from .auth import async_anonymous_is_enabled, has_perm
from .cache import element_cache, split_element_id from .cache import element_cache, split_element_id
from .collection import ( from .collection import (
AutoupdateFormat,
Collection, Collection,
CollectionElement, CollectionElement,
format_for_autoupdate, format_for_autoupdate_old,
from_channel_message, from_channel_message,
) )
from .constants import get_constants from .constants import get_constants
@ -169,22 +171,13 @@ class SiteConsumer(ProtocollAsyncJsonWebsocketConsumer):
Send changed or deleted elements to the user. Send changed or deleted elements to the user.
""" """
change_id = event['change_id'] change_id = event['change_id']
output = [] changed_elements, deleted_elements_ids = await element_cache.get_restricted_data(self.scope['user'], change_id, max_change_id=change_id)
changed_elements, deleted_elements = await element_cache.get_restricted_data(self.scope['user'], change_id, max_change_id=change_id)
for collection_string, elements in changed_elements.items(): deleted_elements: Dict[str, List[int]] = defaultdict(list)
for element in elements: for element_id in deleted_elements_ids:
output.append(format_for_autoupdate(
collection_string=collection_string,
id=element['id'],
action='changed',
data=element))
for element_id in deleted_elements:
collection_string, id = split_element_id(element_id) collection_string, id = split_element_id(element_id)
output.append(format_for_autoupdate( deleted_elements[collection_string].append(id)
collection_string=collection_string, await self.send_json(type='autoupdate', content=AutoupdateFormat(changed=changed_elements, deleted=deleted_elements, change_id=change_id))
id=id,
action='deleted'))
await self.send_json(type='autoupdate', content=output)
class ProjectorConsumer(ProtocollAsyncJsonWebsocketConsumer): class ProjectorConsumer(ProtocollAsyncJsonWebsocketConsumer):
@ -274,23 +267,21 @@ class ProjectorConsumer(ProtocollAsyncJsonWebsocketConsumer):
await self.send_json(type='autoupdate', content=output) await self.send_json(type='autoupdate', content=output)
async def startup_data(user: Optional[CollectionElement], change_id: int = 0) -> List[Any]: async def startup_data(user: Optional[CollectionElement], change_id: int = 0) -> AutoupdateFormat:
""" """
Returns all data for startup. Returns all data for startup.
""" """
# TODO: use the change_id argument # TODO: use the change_id argument
output = [] # TODO: This two calls have to be atomic
restricted_data = await element_cache.get_all_restricted_data(user) changed_elements, deleted_element_ids = await element_cache.get_restricted_data(user)
for collection_string, elements in restricted_data.items(): current_change_id = await element_cache.get_current_change_id()
for element in elements:
formatted_data = format_for_autoupdate(
collection_string=collection_string,
id=element['id'],
action='changed',
data=element)
output.append(formatted_data) deleted_elements: Dict[str, List[int]] = defaultdict(list)
return output for element_id in deleted_element_ids:
collection_string, id = split_element_id(element_id)
deleted_elements[collection_string].append(id)
return AutoupdateFormat(changed=changed_elements, deleted=deleted_elements, change_id=current_change_id)
def projector_startup_data(projector_id: int) -> Any: def projector_startup_data(projector_id: int) -> Any:
@ -318,7 +309,7 @@ def projector_startup_data(projector_id: int) -> Any:
projector_data = (config_collection.get_access_permissions() projector_data = (config_collection.get_access_permissions()
.get_projector_data(config_collection.get_full_data())) .get_projector_data(config_collection.get_full_data()))
for data in projector_data: for data in projector_data:
output.append(format_for_autoupdate( output.append(format_for_autoupdate_old(
config_collection.collection_string, config_collection.collection_string,
data['id'], data['id'],
'changed', 'changed',

View File

@ -59,8 +59,13 @@ async def test_normal_connection(communicator):
type = response.get('type') type = response.get('type')
content = response.get('content') content = response.get('content')
assert type == 'autoupdate' assert type == 'autoupdate'
# Test, that both example objects are returned assert 'changed' in content
assert len(content) > 10 assert 'deleted' in content
assert 'change_id' in content
assert Collection1().get_collection_string() in content['changed']
assert Collection2().get_collection_string() in content['changed']
assert TConfig().get_collection_string() in content['changed']
assert TUser().get_collection_string() in content['changed']
@pytest.mark.asyncio @pytest.mark.asyncio
@ -77,11 +82,8 @@ async def test_receive_changed_data(communicator):
type = response.get('type') type = response.get('type')
content = response.get('content') content = response.get('content')
assert type == 'autoupdate' assert type == 'autoupdate'
assert content == [ assert content['changed'] == {
{'action': 'changed', 'core/config': [{'id': id, 'key': 'general_event_name', 'value': 'Test Event'}]}
'collection': 'core/config',
'data': {'id': id, 'key': 'general_event_name', 'value': 'Test Event'},
'id': id}]
@pytest.mark.asyncio @pytest.mark.asyncio
@ -124,7 +126,7 @@ async def test_receive_deleted_data(communicator):
type = response.get('type') type = response.get('type')
content = response.get('content') content = response.get('content')
assert type == 'autoupdate' assert type == 'autoupdate'
assert content == [{'action': 'deleted', 'collection': Collection1().get_collection_string(), 'id': 1}] assert content['deleted'] == {Collection1().get_collection_string(): [1]}
@pytest.mark.asyncio @pytest.mark.asyncio

View File

@ -1,5 +1,5 @@
from unittest import TestCase from unittest import TestCase
from unittest.mock import MagicMock, patch from unittest.mock import patch
from openslides.core.models import Projector from openslides.core.models import Projector
from openslides.utils import collection from openslides.utils import collection
@ -43,45 +43,6 @@ class TestCollectionElement(TestCase):
self.assertEqual(created_collection_element.full_data, {'data': 'value'}) self.assertEqual(created_collection_element.full_data, {'data': 'value'})
self.assertEqual(created_collection_element.information, {'some': 'information'}) self.assertEqual(created_collection_element.information, {'some': 'information'})
def test_as_autoupdate_for_user(self):
with patch.object(collection.CollectionElement, 'get_full_data'):
collection_element = collection.CollectionElement.from_values('testmodule/model', 42)
fake_user = MagicMock()
collection_element.get_access_permissions = MagicMock()
collection_element.get_access_permissions().get_restricted_data.return_value = ['restricted_data']
collection_element.get_full_data = MagicMock()
self.assertEqual(
collection_element.as_autoupdate_for_user(fake_user),
{'collection': 'testmodule/model',
'id': 42,
'action': 'changed',
'data': 'restricted_data'})
def test_as_autoupdate_for_user_no_permission(self):
with patch.object(collection.CollectionElement, 'get_full_data'):
collection_element = collection.CollectionElement.from_values('testmodule/model', 42)
fake_user = MagicMock()
collection_element.get_access_permissions = MagicMock()
collection_element.get_access_permissions().get_restricted_data.return_value = None
collection_element.get_full_data = MagicMock()
self.assertEqual(
collection_element.as_autoupdate_for_user(fake_user),
{'collection': 'testmodule/model',
'id': 42,
'action': 'deleted'})
def test_as_autoupdate_for_user_deleted(self):
collection_element = collection.CollectionElement.from_values('testmodule/model', 42, deleted=True)
fake_user = MagicMock()
self.assertEqual(
collection_element.as_autoupdate_for_user(fake_user),
{'collection': 'testmodule/model',
'id': 42,
'action': 'deleted'})
@patch.object(collection.CollectionElement, 'get_full_data') @patch.object(collection.CollectionElement, 'get_full_data')
def test_equal(self, mock_get_full_data): def test_equal(self, mock_get_full_data):
self.assertEqual( self.assertEqual(