diff --git a/openslides/agenda/projector.py b/openslides/agenda/projector.py index c93e70124..b318ba5d6 100644 --- a/openslides/agenda/projector.py +++ b/openslides/agenda/projector.py @@ -1,5 +1,6 @@ from ..core.config import config from ..core.exceptions import ProjectorException +from ..utils.collection import CollectionElement from ..utils.projector import ProjectorElement from .models import Item @@ -60,10 +61,13 @@ class ListOfSpeakersSlide(ProjectorElement): # Yield last speakers yield speaker.user - def need_full_update_for_this(self, collection_element): - # Full update if item changes because then we may have new speakers - # and therefor need new users. - return collection_element.collection_string == Item.get_collection_string() + def get_collection_elements_required_for_this(self, collection_element, config_entry): + output = super().get_collections_required_for_this(collection_element, config_entry) + # Full update if item changes because then we may have new + # candidates and therefor need new users. + if collection_element == CollectionElement.from_values(Item.get_collection_string(), config_entry.get('id')): + output.extend(self.get_requirements_as_collection_elements(config_entry)) + return output class CurrentListOfSpeakersSlide(ProjectorElement): diff --git a/openslides/assignments/projector.py b/openslides/assignments/projector.py index 6e61fb513..fac925372 100644 --- a/openslides/assignments/projector.py +++ b/openslides/assignments/projector.py @@ -1,4 +1,5 @@ from ..core.exceptions import ProjectorException +from ..utils.collection import CollectionElement from ..utils.projector import ProjectorElement from .models import Assignment, AssignmentPoll @@ -46,7 +47,10 @@ class AssignmentSlide(ProjectorElement): for option in poll.options.all(): yield option.candidate - def need_full_update_for_this(self, collection_element): + def get_collection_elements_required_for_this(self, collection_element, config_entry): + output = super().get_collection_elements_required_for_this(collection_element, config_entry) # Full update if assignment changes because then we may have new # candidates and therefor need new users. - return collection_element.collection_string == Assignment.get_collection_string() + if collection_element == CollectionElement.from_values(Assignment.get_collection_string(), config_entry.get('id')): + output.extend(self.get_requirements_as_collection_elements(config_entry)) + return output diff --git a/openslides/core/models.py b/openslides/core/models.py index 4088c26e9..926bc3073 100644 --- a/openslides/core/models.py +++ b/openslides/core/models.py @@ -116,7 +116,7 @@ class Projector(RESTModelMixin, models.Model): result[key]['error'] = str(e) return result - def get_all_requirements(self, on_slide=None): # TODO autoupdate: Refactor or rename this. + def get_all_requirements(self): """ Generator which returns all instances that are shown on this projector. """ @@ -131,50 +131,35 @@ class Projector(RESTModelMixin, models.Model): if element is not None: yield from element.get_requirements(value) - def collection_element_is_shown(self, collection_element): + def get_collection_elements_required_for_this(self, collection_element): """ - Returns True if this collection element is shown on this projector. + Returns an iterable of CollectionElements that have to be sent to this + projector according to the given collection_element and information. """ - for requirement in self.get_all_requirements(): - if (requirement.get_collection_string() == collection_element.collection_string and - requirement.pk == collection_element.id): - result = True - break + from .config import config + + output = [] + changed_fields = collection_element.information.get('changed_fields', []) + if (collection_element.collection_string == self.get_collection_string() and + changed_fields and + 'config' not in changed_fields): + # Projector model changed without changeing the projector config. So we just send this data. + output.append(collection_element) else: - result = False - return result - - @classmethod - def get_projectors_that_show_this(cls, collection_element): - """ - Returns a list of the projectors that show this collection element. - """ - result = [] - for projector in cls.objects.all(): - if projector.collection_element_is_shown(collection_element): - result.append(projector) - return result - - def need_full_update_for_this(self, collection_element): - """ - Returns True if this projector needs to be updated with all - instances as defined in get_all_requirements() because one active - projector element requires this. - """ - # Get all elements from all apps. - elements = {} - for element in ProjectorElement.get_all(): - elements[element.name] = element - - for key, value in self.config.items(): - element = elements.get(value['name']) - if element is not None and element.need_full_update_for_this(collection_element): - result = True - break - else: - result = False - - return result + # It is necessary to parse all active projector elements to check whether they require some data. + this_projector = collection_element.collection_string == self.get_collection_string() and collection_element.id == self.pk + collection_element.information['this_projector'] = this_projector + elements = {} + for element in ProjectorElement.get_all(): + elements[element.name] = element + for key, value in self.config.items(): + element = elements.get(value['name']) + if element is not None: + output.extend(element.get_collection_elements_required_for_this(collection_element, value)) + # If config changed, send also this to the projector. + if collection_element.collection_string == config.get_collection_string(): + output.append(collection_element) + return output class ProjectionDefault(RESTModelMixin, models.Model): diff --git a/openslides/motions/projector.py b/openslides/motions/projector.py index efee87177..07e006bfa 100644 --- a/openslides/motions/projector.py +++ b/openslides/motions/projector.py @@ -1,4 +1,5 @@ from ..core.exceptions import ProjectorException +from ..utils.collection import CollectionElement from ..utils.projector import ProjectorElement from .models import Motion @@ -26,10 +27,13 @@ class MotionSlide(ProjectorElement): yield from motion.submitters.all() yield from motion.supporters.all() - def need_full_update_for_this(self, collection_element): + def get_collection_elements_required_for_this(self, collection_element, config_entry): + output = super().get_collection_elements_required_for_this(collection_element, config_entry) # Full update if motion changes because then we may have new # submitters or supporters and therefor need new users. # # Add some logic here if we support live changing of workflows later. # - return collection_element.collection_string == Motion.get_collection_string() + if collection_element == CollectionElement.from_values(Motion.get_collection_string(), config_entry.get('id')): + output.extend(self.get_requirements_as_collection_elements(config_entry)) + return output diff --git a/openslides/utils/autoupdate.py b/openslides/utils/autoupdate.py index a1209d7bb..18ec21e9b 100644 --- a/openslides/utils/autoupdate.py +++ b/openslides/utils/autoupdate.py @@ -23,22 +23,6 @@ def get_logged_in_users(): return User.objects.exclude(session=None).filter(session__expire_date__gte=timezone.now()).distinct() -def get_projector_element_data(projector, on_slide=None): # TODO autoupdate - """ - Returns a list of dicts that are required for a specific projector. - - The argument projector has to be a projector instance. - - If on_slide is a string that matches an slide on the projector, then only - elements on this slide are returned. - """ - output = [] - for requirement in projector.get_all_requirements(on_slide): - required_collection_element = CollectionElement.from_instance(requirement) - output.append(required_collection_element.as_autoupdate_for_projector()) - return output - - @channel_session_user_from_http def ws_add_site(message): """ @@ -81,7 +65,10 @@ def ws_add_projector(message, projector_id): Group('projector-{}'.format(projector_id)).add(message.reply_channel) # Send all elements that are on the projector. - output = get_projector_element_data(projector) # TODO autoupdate + output = [] + for requirement in projector.get_all_requirements(): + required_collection_element = CollectionElement.from_instance(requirement) + output.append(required_collection_element.as_autoupdate_for_projector()) # Send all config elements. collection = Collection(config.get_collection_string()) @@ -102,59 +89,25 @@ def ws_disconnect_projector(message, projector_id): Group('projector-{}'.format(projector_id)).discard(message.reply_channel) -def send_data(message): # TODO autoupdate +def send_data(message): """ - Informs all users about changed data. + Informs all site users and projector clients about changed data. """ collection_element = CollectionElement.from_values(**message) - # Loop over all logged in users and the anonymous user. + # Loop over all logged in site users and the anonymous user and send changed data. for user in itertools.chain(get_logged_in_users(), [AnonymousUser()]): channel = Group('user-{}'.format(user.id)) - output = collection_element.as_autoupdate_for_user(user) - channel.send({'text': json.dumps([output])}) + output = [collection_element.as_autoupdate_for_user(user)] + channel.send({'text': json.dumps(output)}) - # Get the projector elements where data have to be sent and if whole projector - # has to be updated. - if collection_element.collection_string == config.get_collection_string(): - # Config elements are always send to each projector - projectors = Projector.objects.all() - send_all = None # The decission is done later - elif collection_element.collection_string == Projector.get_collection_string(): - # Update a projector, when the projector element is updated. - projectors = [collection_element.get_instance()] - send_all = True - elif collection_element.is_deleted(): - projectors = Projector.objects.all() - send_all = False - else: - # Other elements are only send to the projector they are currently shown - projectors = Projector.get_projectors_that_show_this(collection_element) - send_all = None # The decission is done later - - broadcast_id = config['projector_broadcast'] - if broadcast_id > 0: - projectors = Projector.objects.all() # Also the broadcasted projector should get his data - send_all = True - broadcast_projector_data = get_projector_element_data(Projector.objects.get(pk=broadcast_id)) - broadcast_projector_data.append(CollectionElement.from_values( - collection_string=Projector.get_collection_string(), id=broadcast_id).as_autoupdate_for_projector()) - else: - broadcast_projector_data = None - - for projector in projectors: - if send_all is None: - send_all = projector.need_full_update_for_this(collection_element) - if send_all: - if broadcast_projector_data is None: - output = get_projector_element_data(projector) - else: - output = broadcast_projector_data + # Loop over all projectors and send data that they need. + for projector in Projector.objects.all(): + if collection_element.is_deleted(): + output = [collection_element.as_autoupdate_for_projector()] else: - # The list will be filled in the next lines. - output = [] - - output.append(collection_element.as_autoupdate_for_projector()) + collection_elements = projector.get_collection_elements_required_for_this(collection_element) + output = [collection_element.as_autoupdate_for_projector() for collection_element in collection_elements] if output: Group('projector-{}'.format(projector.pk)).send( {'text': json.dumps(output)}) diff --git a/openslides/utils/projector.py b/openslides/utils/projector.py index 89ae0a6a4..5f3686211 100644 --- a/openslides/utils/projector.py +++ b/openslides/utils/projector.py @@ -1,5 +1,6 @@ from django.dispatch import Signal +from .collection import CollectionElement from .dispatch import SignalConnectMetaClass @@ -73,11 +74,31 @@ class ProjectorElement(object, metaclass=SignalConnectMetaClass): """ return () - def need_full_update_for_this(self, collection_element): + def get_requirements_as_collection_elements(self, config_entry): """ - Returns True if this projector element needs to be updated with all - instances as defined in get_requirements(). The given - collection_element contains information about the changed instance. - Default is False. + Returns an iterable of collection elements that are required for this + projector element. The config_entry has to be given. """ - return False + return (CollectionElement.from_instance(instance) for instance in self.get_requirements(config_entry)) + + def get_collection_elements_required_for_this(self, collection_element, config_entry): + """ + Returns a list of CollectionElements that have to be sent to every + projector that shows this projector element according to the given + collection_element and information. + + Default: Returns only the collection_element if it belongs to the + requirements but return all requirements if the projector changes. + """ + requirements_as_collection_elements = list(self.get_requirements_as_collection_elements(config_entry)) + for requirement in requirements_as_collection_elements: + if collection_element == requirement: + output = [collection_element] + break + else: + if collection_element.information.get('this_projector'): + output = [collection_element] + output.extend(requirements_as_collection_elements) + else: + output = [] + return output