From bd68997a5d880314f04262db52839161e70caf6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Norman=20J=C3=A4ckel?= Date: Wed, 19 Apr 2017 22:58:48 +0200 Subject: [PATCH] Added notify system. --- CHANGELOG | 3 + openslides/core/static/js/core/base.js | 63 ++++++++++++++------ openslides/motions/static/js/motions/site.js | 11 +++- openslides/routing.py | 2 + openslides/utils/autoupdate.py | 63 +++++++++++++++++++- 5 files changed, 122 insertions(+), 20 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 71c6fba32..4fe774f58 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -21,6 +21,8 @@ Motions: - Added support for export motions in a zip archive [#3189]. - Bugfix: changing motion line length did not invalidate cache [#3202] - Bugfix: Added more distance in motion PDF for DEL-tags in new lines [#3211]. +- Added warning message if an edit dialog was already opened by another + client [#3212]. Users: - User without permission to see users can now see agenda item speakers, @@ -36,6 +38,7 @@ Core: application [#3172]. - Adding support for choosing image files as logos [#3184, #3207]. - Fixing error when clearing empty chat [#3199]. +- Added notify system [#3212]. General: - Several bugfixes and minor improvements. diff --git a/openslides/core/static/js/core/base.js b/openslides/core/static/js/core/base.js index 6307c6b02..7acdb78e6 100644 --- a/openslides/core/static/js/core/base.js +++ b/openslides/core/static/js/core/base.js @@ -99,6 +99,11 @@ angular.module('OpenSlidesApp.core', [ ErrorMessage.clearConnectionError(); }; }; + Autoupdate.send = function (message) { + if (socket) { + socket.send(JSON.stringify(message)); + } + }; Autoupdate.closeConnection = function () { if (socket) { socket.close(); @@ -262,38 +267,40 @@ angular.module('OpenSlidesApp.core', [ 'autoupdate', 'dsEject', function (DS, autoupdate, dsEject) { + // Handler for normal autoupdate messages. autoupdate.onMessage(function(json) { - // TODO: when MODEL.find() is called after this - // a new request is fired. This could be a bug in DS var dataList = []; try { - dataList = JSON.parse(json); + dataList = JSON.parse(json); } catch(err) { console.error(json); } var dataListByCollection = _.groupBy(dataList, 'collection'); - _.forEach(dataListByCollection, function(list, key) { + _.forEach(dataListByCollection, function (list, key) { var changedElements = []; var deletedElements = []; var collectionString = key; - _.forEach(list, function(data) { + _.forEach(list, function (data) { // Uncomment this line for debugging to log all autoupdates: // console.log("Received object: " + data.collection + ", " + data.id); - // remove (=eject) object from local DS store - var instance = DS.get(data.collection, data.id); - if (instance) { - dsEject(data.collection, instance); - } - // check if object changed or deleted - if (data.action === 'changed') { - changedElements.push(data.data); - } else if (data.action === 'deleted') { - deletedElements.push(data.id); - } else { - console.error('Error: Undefined action for received object' + - '(' + data.collection + ', ' + data.id + ')'); + // Now handle autoupdate message but do not handle notify messages. + if (data.collection !== 'notify') { + // remove (=eject) object from local DS store + var instance = DS.get(data.collection, data.id); + if (instance) { + dsEject(data.collection, instance); + } + // check if object changed or deleted + if (data.action === 'changed') { + changedElements.push(data.data); + } else if (data.action === 'deleted') { + deletedElements.push(data.id); + } else { + console.error('Error: Undefined action for received object' + + '(' + data.collection + ', ' + data.id + ')'); + } } }); // add (=inject) all given objects into local DS store @@ -307,6 +314,26 @@ angular.module('OpenSlidesApp.core', [ }); }); }); + + // Handler for notify messages. + autoupdate.onMessage(function(json) { + var dataList = []; + try { + dataList = JSON.parse(json); + } catch(err) { + console.error(json); + } + + var dataListByCollection = _.groupBy(dataList, 'collection'); + _.forEach(dataListByCollection, function (list, key) { + _.forEach(list, function (data) { + if (data.collection === 'notify') { + // TODO: Add more code here. + console.log(data); + } + }); + }); + }); } ]) diff --git a/openslides/motions/static/js/motions/site.js b/openslides/motions/static/js/motions/site.js index d06d080f8..e50576f96 100644 --- a/openslides/motions/static/js/motions/site.js +++ b/openslides/motions/static/js/motions/site.js @@ -276,6 +276,7 @@ angular.module('OpenSlidesApp.motions.site', [ .factory('MotionForm', [ 'gettextCatalog', 'operator', + 'autoupdate', 'Editor', 'MotionComment', 'Category', @@ -287,10 +288,18 @@ angular.module('OpenSlidesApp.motions.site', [ 'Workflow', 'Agenda', 'AgendaTree', - function (gettextCatalog, operator, Editor, MotionComment, Category, Config, Mediafile, MotionBlock, Tag, User, Workflow, Agenda, AgendaTree) { + function (gettextCatalog, operator, autoupdate, Editor, MotionComment, Category, Config, Mediafile, MotionBlock, Tag, User, Workflow, Agenda, AgendaTree) { return { // ngDialog for motion form getDialog: function (motion) { + //TODO: This is just a test implementation. Remove it later. + autoupdate.send([{ + collection: 'notify', + name: 'motion_edit_dialog_opened', + //users: [4, 5, ], + //replyChannels: ["daphne.response.StSjKMGYeq!PfqbSbiJNP", ], + }]); + //End of test implementation return { template: 'static/templates/motions/motion-form.html', controller: motion ? 'MotionUpdateCtrl' : 'MotionCreateCtrl', diff --git a/openslides/routing.py b/openslides/routing.py index 69fa74e45..d2f144f73 100644 --- a/openslides/routing.py +++ b/openslides/routing.py @@ -6,6 +6,7 @@ from openslides.utils.autoupdate import ( ws_add_site, ws_disconnect_projector, ws_disconnect_site, + ws_receive_site, ) projector_routing = [ @@ -16,6 +17,7 @@ projector_routing = [ site_routing = [ route("websocket.connect", ws_add_site), route("websocket.disconnect", ws_disconnect_site), + route("websocket.receive", ws_receive_site), ] channel_routing = [ diff --git a/openslides/utils/autoupdate.py b/openslides/utils/autoupdate.py index 4218fb2ce..8a0c40c86 100644 --- a/openslides/utils/autoupdate.py +++ b/openslides/utils/autoupdate.py @@ -1,7 +1,7 @@ import json import time import warnings -from collections import Iterable +from collections import Iterable, defaultdict from channels import Channel, Group from channels.asgi import get_channel_layer @@ -87,6 +87,67 @@ def ws_disconnect_site(message): websocket_user_cache.remove(message.user.id or 0, message.reply_channel.name) +@channel_session_user +def ws_receive_site(message): + """ + This function is called if a message from a client comes in. The message + should be a list. Every item is broadcasted to the given users (or all + users if no user list is given) if it is a notify element. + + The server adds the sender's user id (0 for anonymous) and reply + channel name so that a receiver client may reply to the sender or to all + sender's instances. + """ + try: + incomming = json.loads(message.content['text']) + except ValueError: + # Message content is invalid. Just do nothing. + pass + else: + if isinstance(incomming, list): + # Parse all items + receivers_users = defaultdict(list) + receivers_reply_channels = defaultdict(list) + items_for_all = [] + for item in incomming: + if item.get('collection') == 'notify': + use_receivers_dict = False + item['senderReplyChannelName'] = message.reply_channel.name + item['senderUserId'] = message.user.id or 0 + + users = item.get('users') + if isinstance(users, list): + # Send this item only to all reply channels of some site users. + for user_id in users: + receivers_users[user_id].append(item) + use_receivers_dict = True + + reply_channels = item.get('replyChannels') + if isinstance(reply_channels, list): + # Send this item only to some reply channels. + for reply_channel_name in reply_channels: + receivers_reply_channels[reply_channel_name].append(item) + use_receivers_dict = True + + if not use_receivers_dict: + # Send this item to all reply channels. + items_for_all.append(item) + + # Send all items + for user_id, channel_names in websocket_user_cache.get_all().items(): + output = receivers_users[user_id] + if len(output) > 0: + for channel_name in channel_names: + send_or_wait(Channel(channel_name).send, {'text': json.dumps(output)}) + + for channel_name, output in receivers_reply_channels.items(): + if len(output) > 0: + send_or_wait(Channel(channel_name).send, {'text': json.dumps(output)}) + + if len(items_for_all) > 0: + send_or_wait(Group('site').send, {'text': json.dumps(items_for_all)}) + + @channel_session_user_from_http def ws_add_projector(message, projector_id): """