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.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):

View File

@ -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

View File

@ -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.
# 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 and element.need_full_update_for_this(collection_element):
result = True
break
else:
result = False
return result
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):

View File

@ -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

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()
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
# 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:
# 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:
# 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)})

View File

@ -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