Refactored projector requirements system for autoupdate.

This commit is contained in:
Norman Jäckel 2016-09-30 23:39:42 +02:00 committed by Oskar Hahn
parent ac9c9f4ec3
commit 039795beb7
6 changed files with 89 additions and 118 deletions

View File

@ -1,5 +1,6 @@
from ..core.config import config from ..core.config import config
from ..core.exceptions import ProjectorException from ..core.exceptions import ProjectorException
from ..utils.collection import CollectionElement
from ..utils.projector import ProjectorElement from ..utils.projector import ProjectorElement
from .models import Item from .models import Item
@ -60,10 +61,13 @@ class ListOfSpeakersSlide(ProjectorElement):
# Yield last speakers # Yield last speakers
yield speaker.user yield speaker.user
def need_full_update_for_this(self, collection_element): def get_collection_elements_required_for_this(self, collection_element, config_entry):
# Full update if item changes because then we may have new speakers output = super().get_collections_required_for_this(collection_element, config_entry)
# and therefor need new users. # Full update if item changes because then we may have new
return collection_element.collection_string == Item.get_collection_string() # 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): class CurrentListOfSpeakersSlide(ProjectorElement):

View File

@ -1,4 +1,5 @@
from ..core.exceptions import ProjectorException from ..core.exceptions import ProjectorException
from ..utils.collection import CollectionElement
from ..utils.projector import ProjectorElement from ..utils.projector import ProjectorElement
from .models import Assignment, AssignmentPoll from .models import Assignment, AssignmentPoll
@ -46,7 +47,10 @@ class AssignmentSlide(ProjectorElement):
for option in poll.options.all(): for option in poll.options.all():
yield option.candidate 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 # Full update if assignment changes because then we may have new
# candidates and therefor need new users. # 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

View File

@ -116,7 +116,7 @@ class Projector(RESTModelMixin, models.Model):
result[key]['error'] = str(e) result[key]['error'] = str(e)
return result 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. 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: if element is not None:
yield from element.get_requirements(value) 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(): from .config import config
if (requirement.get_collection_string() == collection_element.collection_string and
requirement.pk == collection_element.id): output = []
result = True changed_fields = collection_element.information.get('changed_fields', [])
break 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: else:
result = False # It is necessary to parse all active projector elements to check whether they require some data.
return result this_projector = collection_element.collection_string == self.get_collection_string() and collection_element.id == self.pk
collection_element.information['this_projector'] = this_projector
@classmethod elements = {}
def get_projectors_that_show_this(cls, collection_element): for element in ProjectorElement.get_all():
""" elements[element.name] = element
Returns a list of the projectors that show this collection element. for key, value in self.config.items():
""" element = elements.get(value['name'])
result = [] if element is not None:
for projector in cls.objects.all(): output.extend(element.get_collection_elements_required_for_this(collection_element, value))
if projector.collection_element_is_shown(collection_element): # If config changed, send also this to the projector.
result.append(projector) if collection_element.collection_string == config.get_collection_string():
return result output.append(collection_element)
return output
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
class ProjectionDefault(RESTModelMixin, models.Model): class ProjectionDefault(RESTModelMixin, models.Model):

View File

@ -1,4 +1,5 @@
from ..core.exceptions import ProjectorException from ..core.exceptions import ProjectorException
from ..utils.collection import CollectionElement
from ..utils.projector import ProjectorElement from ..utils.projector import ProjectorElement
from .models import Motion from .models import Motion
@ -26,10 +27,13 @@ class MotionSlide(ProjectorElement):
yield from motion.submitters.all() yield from motion.submitters.all()
yield from motion.supporters.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 # Full update if motion changes because then we may have new
# submitters or supporters and therefor need new users. # submitters or supporters and therefor need new users.
# #
# Add some logic here if we support live changing of workflows later. # 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

View File

@ -23,22 +23,6 @@ def get_logged_in_users():
return User.objects.exclude(session=None).filter(session__expire_date__gte=timezone.now()).distinct() 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 @channel_session_user_from_http
def ws_add_site(message): 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) Group('projector-{}'.format(projector_id)).add(message.reply_channel)
# Send all elements that are on the projector. # 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. # Send all config elements.
collection = Collection(config.get_collection_string()) 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) 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) 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()]): for user in itertools.chain(get_logged_in_users(), [AnonymousUser()]):
channel = Group('user-{}'.format(user.id)) channel = Group('user-{}'.format(user.id))
output = collection_element.as_autoupdate_for_user(user) output = [collection_element.as_autoupdate_for_user(user)]
channel.send({'text': json.dumps([output])}) channel.send({'text': json.dumps(output)})
# Get the projector elements where data have to be sent and if whole projector # Loop over all projectors and send data that they need.
# has to be updated. for projector in Projector.objects.all():
if collection_element.collection_string == config.get_collection_string(): if collection_element.is_deleted():
# Config elements are always send to each projector output = [collection_element.as_autoupdate_for_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
else: else:
# The list will be filled in the next lines. collection_elements = projector.get_collection_elements_required_for_this(collection_element)
output = [] output = [collection_element.as_autoupdate_for_projector() for collection_element in collection_elements]
output.append(collection_element.as_autoupdate_for_projector())
if output: if output:
Group('projector-{}'.format(projector.pk)).send( Group('projector-{}'.format(projector.pk)).send(
{'text': json.dumps(output)}) {'text': json.dumps(output)})

View File

@ -1,5 +1,6 @@
from django.dispatch import Signal from django.dispatch import Signal
from .collection import CollectionElement
from .dispatch import SignalConnectMetaClass from .dispatch import SignalConnectMetaClass
@ -73,11 +74,31 @@ class ProjectorElement(object, metaclass=SignalConnectMetaClass):
""" """
return () 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 Returns an iterable of collection elements that are required for this
instances as defined in get_requirements(). The given projector element. The config_entry has to be given.
collection_element contains information about the changed instance.
Default is False.
""" """
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