Merge pull request #3941 from ostcar/remove_projektor_code
Remove old projector code
This commit is contained in:
commit
4fb51d8e49
@ -92,21 +92,3 @@ class ItemAccessPermissions(BaseAccessPermissions):
|
||||
data = []
|
||||
|
||||
return data
|
||||
|
||||
def get_projector_data(self, full_data: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Returns the restricted serialized data for the instance prepared
|
||||
for the projector. Removes field 'comment'.
|
||||
"""
|
||||
def filtered_data(full_data, blocked_keys):
|
||||
"""
|
||||
Returns a new dict like full_data but with all blocked_keys removed.
|
||||
"""
|
||||
whitelist = full_data.keys() - blocked_keys
|
||||
return {key: full_data[key] for key in whitelist}
|
||||
|
||||
# Parse data.
|
||||
blocked_keys = ('comment',)
|
||||
data = [filtered_data(full, blocked_keys) for full in full_data]
|
||||
|
||||
return data
|
||||
|
@ -1,9 +1,6 @@
|
||||
from typing import Generator, Type
|
||||
|
||||
from ..core.config import config
|
||||
from ..core.exceptions import ProjectorException
|
||||
from ..core.models import Projector
|
||||
from ..utils.collection import CollectionElement
|
||||
from ..utils.projector import ProjectorElement
|
||||
from .models import Item
|
||||
|
||||
@ -28,9 +25,6 @@ class ItemListSlide(ProjectorElement):
|
||||
if not Item.objects.filter(pk=pk).exists():
|
||||
raise ProjectorException('Item does not exist.')
|
||||
|
||||
def get_requirements(self, config_entry):
|
||||
yield from Item.objects.all()
|
||||
|
||||
|
||||
class ListOfSpeakersSlide(ProjectorElement):
|
||||
"""
|
||||
@ -43,35 +37,6 @@ class ListOfSpeakersSlide(ProjectorElement):
|
||||
if not Item.objects.filter(pk=self.config_entry.get('id')).exists():
|
||||
raise ProjectorException('Item does not exist.')
|
||||
|
||||
def get_requirements(self, config_entry):
|
||||
pk = config_entry.get('id')
|
||||
if pk is not None:
|
||||
# List of speakers slide.
|
||||
try:
|
||||
item = Item.objects.get(pk=pk)
|
||||
except Item.DoesNotExist:
|
||||
# Item does not exist. Just do nothing.
|
||||
pass
|
||||
else:
|
||||
yield item
|
||||
yield item.content_object
|
||||
for speaker in item.speakers.filter(end_time=None):
|
||||
# Yield current speaker and next speakers
|
||||
yield speaker.user
|
||||
query = (item.speakers.exclude(end_time=None)
|
||||
.order_by('-end_time')[:config['agenda_show_last_speakers']])
|
||||
for speaker in query:
|
||||
# Yield last speakers
|
||||
yield speaker.user
|
||||
|
||||
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 item changes because then we may have new
|
||||
# candidates and therefor need new users.
|
||||
if collection_element.collection_string == Item.get_collection_string() and collection_element.id == config_entry.get('id'):
|
||||
output.extend(self.get_requirements_as_collection_elements(config_entry))
|
||||
return output
|
||||
|
||||
def update_data(self):
|
||||
return {'agenda_item_id': self.config_entry.get('id')}
|
||||
|
||||
@ -82,65 +47,6 @@ class CurrentListOfSpeakersSlide(ProjectorElement):
|
||||
"""
|
||||
name = 'agenda/current-list-of-speakers'
|
||||
|
||||
def get_requirements(self, config_entry):
|
||||
# The query mechanism on client needs the referenced projector.
|
||||
try:
|
||||
reference_projector = Projector.objects.get(
|
||||
pk=config['projector_currentListOfSpeakers_reference'])
|
||||
except Projector.DoesNotExist:
|
||||
# Reference projector was deleted so this projector element is empty.
|
||||
# Skip yielding more requirements (items and speakers).
|
||||
pass
|
||||
else:
|
||||
yield reference_projector
|
||||
|
||||
items = self.get_agenda_items(reference_projector)
|
||||
for item in items:
|
||||
yield item
|
||||
yield item.content_object
|
||||
for speaker in item.speakers.filter(end_time=None):
|
||||
yield speaker.user
|
||||
query = (item.speakers.exclude(end_time=None)
|
||||
.order_by('-end_time')[:config['agenda_show_last_speakers']])
|
||||
for speaker in query:
|
||||
# Yield last speakers
|
||||
yield speaker.user
|
||||
|
||||
def get_agenda_items(self, projector):
|
||||
for element in projector.elements.values():
|
||||
agenda_item_id = element.get('agenda_item_id')
|
||||
if agenda_item_id is not None:
|
||||
yield Item.objects.get(pk=agenda_item_id)
|
||||
|
||||
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 agenda_item or referenced projector changes because
|
||||
# then we may have new candidates and therefor need new users.
|
||||
try:
|
||||
reference_projector = Projector.objects.get(
|
||||
pk=config['projector_currentListOfSpeakers_reference'])
|
||||
except Projector.DoesNotExist:
|
||||
# Reference projector was deleted so this projector element is empty.
|
||||
# Skip appending more stuff to output.
|
||||
pass
|
||||
else:
|
||||
is_reference_projector = collection_element == CollectionElement.from_values(
|
||||
reference_projector.get_collection_string(),
|
||||
reference_projector.pk)
|
||||
is_config = (
|
||||
collection_element.collection_string == 'core/config' and
|
||||
collection_element.information.get('changed_config') == 'projector_currentListOfSpeakers_reference')
|
||||
|
||||
if is_reference_projector or is_config:
|
||||
output.extend(self.get_requirements_as_collection_elements(config_entry))
|
||||
else:
|
||||
items = self.get_agenda_items(reference_projector)
|
||||
for item in items:
|
||||
if collection_element == CollectionElement.from_values(item.get_collection_string(), item.pk):
|
||||
output.extend(self.get_requirements_as_collection_elements(config_entry))
|
||||
break
|
||||
return output
|
||||
|
||||
|
||||
def get_projector_elements() -> Generator[Type[ProjectorElement], None, None]:
|
||||
yield ItemListSlide
|
||||
|
@ -55,17 +55,3 @@ class AssignmentAccessPermissions(BaseAccessPermissions):
|
||||
data = []
|
||||
|
||||
return data
|
||||
|
||||
def get_projector_data(self, full_data: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Returns the restricted serialized data for the instance prepared
|
||||
for the projector. Removes unpublished polls.
|
||||
"""
|
||||
# Parse data. Exclude unpublished polls.
|
||||
data = []
|
||||
for full in full_data:
|
||||
full_copy = full.copy()
|
||||
full_copy['polls'] = [poll for poll in full['polls'] if poll['published']]
|
||||
data.append(full_copy)
|
||||
|
||||
return data
|
||||
|
@ -26,36 +26,6 @@ class AssignmentSlide(ProjectorElement):
|
||||
if poll.assignment_id != self.config_entry.get('id'):
|
||||
raise ProjectorException('Assignment id and poll do not belong together.')
|
||||
|
||||
def get_requirements(self, config_entry):
|
||||
try:
|
||||
assignment = Assignment.objects.get(pk=config_entry.get('id'))
|
||||
except Assignment.DoesNotExist:
|
||||
# Assignment does not exist. Just do nothing.
|
||||
pass
|
||||
else:
|
||||
yield assignment
|
||||
yield assignment.agenda_item
|
||||
if not config_entry.get('poll'):
|
||||
# Assignment detail slide. Yield user instances of current
|
||||
# candidates (i. e. future poll participants) and elected
|
||||
# persons (i. e. former poll participants).
|
||||
for user in assignment.related_users.all():
|
||||
yield user
|
||||
else:
|
||||
# Assignment poll slide. Yield user instances of the
|
||||
# participants of all polls.
|
||||
for poll in assignment.polls.all().prefetch_related('options'):
|
||||
for option in poll.options.all():
|
||||
yield option.candidate
|
||||
|
||||
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.
|
||||
if collection_element.collection_string == Assignment.get_collection_string() and collection_element.id == config_entry.get('id'):
|
||||
output.extend(self.get_requirements_as_collection_elements(config_entry))
|
||||
return output
|
||||
|
||||
def update_data(self):
|
||||
data = None
|
||||
try:
|
||||
|
@ -165,7 +165,7 @@ class ConfigHandler:
|
||||
# Save the new value to the database.
|
||||
db_value = ConfigStore.objects.get(key=key)
|
||||
db_value.value = value
|
||||
db_value.save(information={'changed_config': key})
|
||||
db_value.save()
|
||||
|
||||
# Call on_change callback.
|
||||
if config_variable.on_change:
|
||||
|
@ -3,7 +3,6 @@ from django.db import models
|
||||
from django.utils.timezone import now
|
||||
from jsonfield import JSONField
|
||||
|
||||
from ..utils.collection import CollectionElement
|
||||
from ..utils.models import RESTModelMixin
|
||||
from ..utils.projector import get_all_projector_elements
|
||||
from .access_permissions import (
|
||||
@ -130,61 +129,6 @@ class Projector(RESTModelMixin, models.Model):
|
||||
result[key]['error'] = str(e)
|
||||
return result
|
||||
|
||||
def get_all_requirements(self):
|
||||
"""
|
||||
Generator which returns all instances that are shown on this projector.
|
||||
"""
|
||||
# Get all elements from all apps.
|
||||
elements = get_all_projector_elements()
|
||||
|
||||
# Generator
|
||||
for key, value in self.config.items():
|
||||
element = elements.get(value['name'])
|
||||
if element is not None:
|
||||
yield from element.get_requirements(value)
|
||||
|
||||
def get_collection_elements_required_for_this(self, collection_element):
|
||||
"""
|
||||
Returns an iterable of CollectionElements that have to be sent to this
|
||||
projector according to the given collection_element.
|
||||
"""
|
||||
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:
|
||||
# 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 = get_all_projector_elements()
|
||||
|
||||
# Iterate over all active projector elements.
|
||||
for key, value in self.config.items():
|
||||
element = elements.get(value['name'])
|
||||
if element is not None:
|
||||
if collection_element.information.get('changed_config') == 'projector_broadcast':
|
||||
# In case of broadcast we need full update.
|
||||
output.extend(element.get_requirements_as_collection_elements(value))
|
||||
else:
|
||||
# In normal case we need all collections required by the element.
|
||||
output.extend(element.get_collection_elements_required_for_this(collection_element, value))
|
||||
|
||||
# If config changed, send also this config to the projector.
|
||||
if collection_element.collection_string == config.get_collection_string():
|
||||
output.append(collection_element)
|
||||
if collection_element.information.get('changed_config') == 'projector_broadcast':
|
||||
# In case of broadcast we also need the projector himself.
|
||||
output.append(CollectionElement.from_instance(self))
|
||||
|
||||
return output
|
||||
|
||||
@classmethod
|
||||
def remove_any(cls, skip_autoupdate=False, **kwargs):
|
||||
"""
|
||||
|
@ -22,15 +22,6 @@ class CountdownElement(ProjectorElement):
|
||||
if not Countdown.objects.filter(pk=self.config_entry.get('id')).exists():
|
||||
raise ProjectorException('Countdown does not exists.')
|
||||
|
||||
def get_requirements(self, config_entry):
|
||||
try:
|
||||
countdown = Countdown.objects.get(pk=config_entry.get('id'))
|
||||
except Countdown.DoesNotExist:
|
||||
# Just do nothing if message does not exist
|
||||
pass
|
||||
else:
|
||||
yield countdown
|
||||
|
||||
|
||||
class ProjectorMessageElement(ProjectorElement):
|
||||
"""
|
||||
@ -42,15 +33,6 @@ class ProjectorMessageElement(ProjectorElement):
|
||||
if not ProjectorMessage.objects.filter(pk=self.config_entry.get('id')).exists():
|
||||
raise ProjectorException('Message does not exists.')
|
||||
|
||||
def get_requirements(self, config_entry):
|
||||
try:
|
||||
message = ProjectorMessage.objects.get(pk=config_entry.get('id'))
|
||||
except ProjectorMessage.DoesNotExist:
|
||||
# Just do nothing if message does not exist
|
||||
pass
|
||||
else:
|
||||
yield message
|
||||
|
||||
|
||||
def get_projector_elements() -> Generator[Type[ProjectorElement], None, None]:
|
||||
yield Clock
|
||||
|
@ -15,15 +15,6 @@ class MediafileSlide(ProjectorElement):
|
||||
if not Mediafile.objects.filter(pk=self.config_entry.get('id')).exists():
|
||||
raise ProjectorException('File does not exist.')
|
||||
|
||||
def get_requirements(self, config_entry):
|
||||
try:
|
||||
mediafile = Mediafile.objects.get(pk=config_entry.get('id'))
|
||||
except Mediafile.DoesNotExist:
|
||||
# Mediafile does not exist. Just do nothing.
|
||||
pass
|
||||
else:
|
||||
yield mediafile
|
||||
|
||||
|
||||
def get_projector_elements() -> Generator[Type[ProjectorElement], None, None]:
|
||||
yield MediafileSlide
|
||||
|
@ -69,18 +69,6 @@ class MotionAccessPermissions(BaseAccessPermissions):
|
||||
|
||||
return data
|
||||
|
||||
def get_projector_data(self, full_data: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Returns the restricted serialized data for the instance prepared
|
||||
for the projector. Removes all comments.
|
||||
"""
|
||||
data = []
|
||||
for full in full_data:
|
||||
full_copy = deepcopy(full)
|
||||
full_copy['comments'] = []
|
||||
data.append(full_copy)
|
||||
return data
|
||||
|
||||
|
||||
class MotionChangeRecommendationAccessPermissions(BaseAccessPermissions):
|
||||
"""
|
||||
|
@ -1,10 +1,8 @@
|
||||
import re
|
||||
from typing import Generator, Type
|
||||
|
||||
from ..core.config import config
|
||||
from ..core.exceptions import ProjectorException
|
||||
from ..utils.projector import ProjectorElement
|
||||
from .models import Motion, MotionBlock, MotionChangeRecommendation, Workflow
|
||||
from .models import Motion, MotionBlock
|
||||
|
||||
|
||||
class MotionSlide(ProjectorElement):
|
||||
@ -17,68 +15,6 @@ class MotionSlide(ProjectorElement):
|
||||
if not Motion.objects.filter(pk=self.config_entry.get('id')).exists():
|
||||
raise ProjectorException('Motion does not exist.')
|
||||
|
||||
def get_requirements(self, config_entry):
|
||||
try:
|
||||
motion = Motion.objects.get(pk=config_entry.get('id'))
|
||||
except Motion.DoesNotExist:
|
||||
# Motion does not exist. Just do nothing.
|
||||
pass
|
||||
else:
|
||||
yield motion
|
||||
yield motion.agenda_item
|
||||
yield motion.state.workflow
|
||||
yield from self.required_motions_for_state_and_recommendation(motion)
|
||||
yield from motion.get_paragraph_based_amendments()
|
||||
for submitter in motion.submitters.all():
|
||||
yield submitter.user
|
||||
yield from motion.supporters.all()
|
||||
yield from MotionChangeRecommendation.objects.filter(motion_version=motion.get_active_version().id)
|
||||
if motion.parent:
|
||||
yield motion.parent
|
||||
|
||||
def required_motions_for_state_and_recommendation(self, motion):
|
||||
"""
|
||||
Returns a list of motions needed for the projector, because they are mentioned
|
||||
in additional fieds for the state and recommendation.
|
||||
Keep the motion_syntax syncronized with the MotionStateAndRecommendationParser on the client.
|
||||
"""
|
||||
# get the comments field for state and recommendation
|
||||
motion_syntax = re.compile(r'\[motion:(\d+)\]')
|
||||
fields = config['motions_comments']
|
||||
state_field_id = None
|
||||
recommendation_field_id = None
|
||||
|
||||
for id, field in fields.items():
|
||||
if isinstance(field, dict):
|
||||
if field.get('forState', False):
|
||||
state_field_id = id
|
||||
if field.get('forRecommendation', False):
|
||||
recommendation_field_id = id
|
||||
|
||||
# extract all mentioned motions from the state and recommendation
|
||||
motion_ids = set()
|
||||
if state_field_id is not None:
|
||||
state_text = motion.comments.get(state_field_id)
|
||||
motion_ids.update([int(id) for id in motion_syntax.findall(state_text)])
|
||||
|
||||
if recommendation_field_id is not None:
|
||||
recommendation_text = motion.comments.get(recommendation_field_id)
|
||||
motion_ids.update([int(id) for id in motion_syntax.findall(recommendation_text)])
|
||||
|
||||
# return all motions
|
||||
return Motion.objects.filter(pk__in=motion_ids)
|
||||
|
||||
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.
|
||||
#
|
||||
if collection_element.collection_string == Motion.get_collection_string() and collection_element.id == config_entry.get('id'):
|
||||
output.extend(self.get_requirements_as_collection_elements(config_entry))
|
||||
return output
|
||||
|
||||
def update_data(self):
|
||||
data = None
|
||||
try:
|
||||
@ -101,26 +37,6 @@ class MotionBlockSlide(ProjectorElement):
|
||||
if not MotionBlock.objects.filter(pk=self.config_entry.get('id')).exists():
|
||||
raise ProjectorException('MotionBlock does not exist.')
|
||||
|
||||
def get_requirements(self, config_entry):
|
||||
try:
|
||||
motion_block = MotionBlock.objects.get(pk=config_entry.get('id'))
|
||||
except MotionBlock.DoesNotExist:
|
||||
# MotionBlock does not exist. Just do nothing.
|
||||
pass
|
||||
else:
|
||||
yield motion_block
|
||||
yield motion_block.agenda_item
|
||||
yield from motion_block.motion_set.all()
|
||||
yield from Workflow.objects.all()
|
||||
|
||||
def get_collection_elements_required_for_this(self, collection_element, config_entry):
|
||||
output = super().get_collection_elements_required_for_this(collection_element, config_entry)
|
||||
# Send all changed motions to the projector, because it may be appended
|
||||
# or removed from the block.
|
||||
if collection_element.collection_string == Motion.get_collection_string():
|
||||
output.append(collection_element)
|
||||
return output
|
||||
|
||||
def update_data(self):
|
||||
data = None
|
||||
try:
|
||||
|
@ -1,7 +1,7 @@
|
||||
from channels.routing import ProtocolTypeRouter, URLRouter
|
||||
from django.conf.urls import url
|
||||
|
||||
from openslides.utils.consumers import ProjectorConsumer, SiteConsumer
|
||||
from openslides.utils.consumers import SiteConsumer
|
||||
from openslides.utils.middleware import AuthMiddlewareStack
|
||||
|
||||
|
||||
@ -10,7 +10,6 @@ application = ProtocolTypeRouter({
|
||||
"websocket": AuthMiddlewareStack(
|
||||
URLRouter([
|
||||
url(r"^ws/site/$", SiteConsumer),
|
||||
url(r"^ws/projector/(?P<projector_id>\d+)/$", ProjectorConsumer),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
@ -15,16 +15,6 @@ class TopicSlide(ProjectorElement):
|
||||
if not Topic.objects.filter(pk=self.config_entry.get('id')).exists():
|
||||
raise ProjectorException('Topic does not exist.')
|
||||
|
||||
def get_requirements(self, config_entry):
|
||||
try:
|
||||
topic = Topic.objects.get(pk=config_entry.get('id'))
|
||||
except Topic.DoesNotExist:
|
||||
# Topic does not exist. Just do nothing.
|
||||
pass
|
||||
else:
|
||||
yield topic
|
||||
yield topic.agenda_item
|
||||
|
||||
def update_data(self):
|
||||
data = None
|
||||
try:
|
||||
|
@ -99,27 +99,6 @@ class UserAccessPermissions(BaseAccessPermissions):
|
||||
|
||||
return data
|
||||
|
||||
def get_projector_data(self, full_data: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Returns the restricted serialized data for the instance prepared
|
||||
for the projector. Removes several fields.
|
||||
"""
|
||||
from .serializers import USERCANSEESERIALIZER_FIELDS
|
||||
|
||||
def filtered_data(full_data, whitelist):
|
||||
"""
|
||||
Returns a new dict like full_data but only with whitelisted keys.
|
||||
"""
|
||||
return {key: full_data[key] for key in whitelist}
|
||||
|
||||
# Parse data.
|
||||
litte_data_fields = set(USERCANSEESERIALIZER_FIELDS)
|
||||
litte_data_fields.add('groups_id')
|
||||
litte_data_fields.discard('groups')
|
||||
data = [filtered_data(full, litte_data_fields) for full in full_data]
|
||||
|
||||
return data
|
||||
|
||||
|
||||
class GroupAccessPermissions(BaseAccessPermissions):
|
||||
"""
|
||||
|
@ -15,15 +15,6 @@ class UserSlide(ProjectorElement):
|
||||
if not User.objects.filter(pk=self.config_entry.get('id')).exists():
|
||||
raise ProjectorException('User does not exist.')
|
||||
|
||||
def get_requirements(self, config_entry):
|
||||
try:
|
||||
user = User.objects.get(pk=config_entry.get('id'))
|
||||
except User.DoesNotExist:
|
||||
# User does not exist. Just do nothing.
|
||||
pass
|
||||
else:
|
||||
yield user
|
||||
|
||||
|
||||
def get_projector_elements() -> Generator[Type[ProjectorElement], None, None]:
|
||||
yield UserSlide
|
||||
|
@ -56,11 +56,3 @@ class BaseAccessPermissions:
|
||||
retrieve() or list().
|
||||
"""
|
||||
return full_data if self.check_permissions(user) else []
|
||||
|
||||
def get_projector_data(self, full_data: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Returns the serialized data for the projector. Returns an empty list if
|
||||
the user has no access to this specific data. Returns reduced data if
|
||||
the user has limited access. Default: Returns full data.
|
||||
"""
|
||||
return full_data
|
||||
|
@ -1,5 +1,4 @@
|
||||
import threading
|
||||
from collections import OrderedDict
|
||||
from typing import Any, Dict, Iterable, List, Optional, Tuple, Union
|
||||
|
||||
from asgiref.sync import async_to_sync
|
||||
@ -7,21 +6,10 @@ from channels.layers import get_channel_layer
|
||||
from django.db.models import Model
|
||||
|
||||
from .cache import element_cache, get_element_id
|
||||
from .collection import CollectionElement, to_channel_message
|
||||
from .collection import CollectionElement
|
||||
|
||||
|
||||
def to_ordered_dict(d: Optional[Dict]) -> Optional[OrderedDict]:
|
||||
"""
|
||||
Little helper to hash information dict in inform_*_data.
|
||||
"""
|
||||
if isinstance(d, dict):
|
||||
result: Optional[OrderedDict] = OrderedDict([(key, to_ordered_dict(d[key])) for key in sorted(d.keys())])
|
||||
else:
|
||||
result = d
|
||||
return result
|
||||
|
||||
|
||||
def inform_changed_data(instances: Union[Iterable[Model], Model], information: Dict[str, Any] = None) -> None:
|
||||
def inform_changed_data(instances: Union[Iterable[Model], Model]) -> None:
|
||||
"""
|
||||
Informs the autoupdate system and the caching system about the creation or
|
||||
update of an element.
|
||||
@ -41,10 +29,8 @@ def inform_changed_data(instances: Union[Iterable[Model], Model], information: D
|
||||
|
||||
collection_elements = {}
|
||||
for root_instance in root_instances:
|
||||
collection_element = CollectionElement.from_instance(
|
||||
root_instance,
|
||||
information=information)
|
||||
key = root_instance.get_collection_string() + str(root_instance.get_rest_pk()) + str(to_ordered_dict(information))
|
||||
collection_element = CollectionElement.from_instance(root_instance)
|
||||
key = root_instance.get_collection_string() + str(root_instance.get_rest_pk())
|
||||
collection_elements[key] = collection_element
|
||||
|
||||
bundle = autoupdate_bundle.get(threading.get_ident())
|
||||
@ -56,21 +42,18 @@ def inform_changed_data(instances: Union[Iterable[Model], Model], information: D
|
||||
async_to_sync(send_autoupdate)(collection_elements.values())
|
||||
|
||||
|
||||
def inform_deleted_data(elements: Iterable[Tuple[str, int]], information: Dict[str, Any] = None) -> None:
|
||||
def inform_deleted_data(elements: Iterable[Tuple[str, int]]) -> None:
|
||||
"""
|
||||
Informs the autoupdate system and the caching system about the deletion of
|
||||
elements.
|
||||
|
||||
The argument information is added to each collection element.
|
||||
"""
|
||||
collection_elements: Dict[str, Any] = {}
|
||||
for element in elements:
|
||||
collection_element = CollectionElement.from_values(
|
||||
collection_string=element[0],
|
||||
id=element[1],
|
||||
deleted=True,
|
||||
information=information)
|
||||
key = element[0] + str(element[1]) + str(to_ordered_dict(information))
|
||||
deleted=True)
|
||||
key = element[0] + str(element[1])
|
||||
collection_elements[key] = collection_element
|
||||
|
||||
bundle = autoupdate_bundle.get(threading.get_ident())
|
||||
@ -82,15 +65,14 @@ def inform_deleted_data(elements: Iterable[Tuple[str, int]], information: Dict[s
|
||||
async_to_sync(send_autoupdate)(collection_elements.values())
|
||||
|
||||
|
||||
def inform_data_collection_element_list(collection_elements: List[CollectionElement],
|
||||
information: Dict[str, Any] = None) -> None:
|
||||
def inform_data_collection_element_list(collection_elements: List[CollectionElement]) -> None:
|
||||
"""
|
||||
Informs the autoupdate system about some collection elements. This is
|
||||
used just to send some data to all users.
|
||||
"""
|
||||
elements = {}
|
||||
for collection_element in collection_elements:
|
||||
key = collection_element.collection_string + str(collection_element.id) + str(to_ordered_dict(information))
|
||||
key = collection_element.collection_string + str(collection_element.id)
|
||||
elements[key] = collection_element
|
||||
|
||||
bundle = autoupdate_bundle.get(threading.get_ident())
|
||||
@ -148,14 +130,6 @@ async def send_autoupdate(collection_elements: Iterable[CollectionElement]) -> N
|
||||
change_id = await element_cache.change_elements(cache_elements)
|
||||
|
||||
channel_layer = get_channel_layer()
|
||||
# TODO: don't await. They can be send in parallel
|
||||
await channel_layer.group_send(
|
||||
"projector",
|
||||
{
|
||||
"type": "send_data",
|
||||
"message": to_channel_message(collection_elements),
|
||||
},
|
||||
)
|
||||
await channel_layer.group_send(
|
||||
"autoupdate",
|
||||
{
|
||||
|
@ -1,14 +1,4 @@
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Dict,
|
||||
Generator,
|
||||
Iterable,
|
||||
List,
|
||||
Optional,
|
||||
Type,
|
||||
cast,
|
||||
)
|
||||
from typing import TYPE_CHECKING, Any, Dict, Generator, List, Optional, Type
|
||||
|
||||
from asgiref.sync import async_to_sync
|
||||
from django.apps import apps
|
||||
@ -34,46 +24,15 @@ AutoupdateFormat = TypedDict(
|
||||
)
|
||||
|
||||
|
||||
AutoupdateFormatOld = TypedDict(
|
||||
'AutoupdateFormatOld',
|
||||
{
|
||||
'collection': str,
|
||||
'id': int,
|
||||
'action': 'str',
|
||||
'data': Dict[str, Any],
|
||||
},
|
||||
total=False,
|
||||
)
|
||||
|
||||
InnerChannelMessageFormat = TypedDict(
|
||||
'InnerChannelMessageFormat',
|
||||
{
|
||||
'collection_string': str,
|
||||
'id': int,
|
||||
'deleted': bool,
|
||||
'information': Dict[str, Any],
|
||||
'full_data': Optional[Dict[str, Any]],
|
||||
}
|
||||
)
|
||||
|
||||
ChannelMessageFormat = TypedDict(
|
||||
'ChannelMessageFormat',
|
||||
{
|
||||
'elements': List[InnerChannelMessageFormat],
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class CollectionElement:
|
||||
def __init__(self, instance: Model = None, deleted: bool = False, collection_string: str = None,
|
||||
id: int = None, full_data: Dict[str, Any] = None, information: Dict[str, Any] = None) -> None:
|
||||
id: int = None, full_data: Dict[str, Any] = None) -> None:
|
||||
"""
|
||||
Do not use this. Use the methods from_instance() or from_values().
|
||||
"""
|
||||
self.instance = instance
|
||||
self.deleted = deleted
|
||||
self.full_data = full_data
|
||||
self.information = information or {}
|
||||
if instance is not None:
|
||||
# Collection element is created via instance
|
||||
self.collection_string = instance.get_collection_string()
|
||||
@ -93,7 +52,7 @@ class CollectionElement:
|
||||
|
||||
@classmethod
|
||||
def from_instance(
|
||||
cls, instance: Model, deleted: bool = False, information: Dict[str, Any] = None) -> 'CollectionElement':
|
||||
cls, instance: Model, deleted: bool = False) -> 'CollectionElement':
|
||||
"""
|
||||
Returns a collection element from a database instance.
|
||||
|
||||
@ -101,11 +60,11 @@ class CollectionElement:
|
||||
|
||||
If deleted is set to True, the element is deleted from the cache.
|
||||
"""
|
||||
return cls(instance=instance, deleted=deleted, information=information)
|
||||
return cls(instance=instance, deleted=deleted)
|
||||
|
||||
@classmethod
|
||||
def from_values(cls, collection_string: str, id: int, deleted: bool = False,
|
||||
full_data: Dict[str, Any] = None, information: Dict[str, Any] = None) -> 'CollectionElement':
|
||||
full_data: Dict[str, Any] = None) -> 'CollectionElement':
|
||||
"""
|
||||
Returns a collection element from a collection_string and an id.
|
||||
|
||||
@ -114,8 +73,7 @@ class CollectionElement:
|
||||
With the argument full_data, the content of the CollectionElement can be set.
|
||||
It has to be a dict in the format that is used be access_permission.get_full_data().
|
||||
"""
|
||||
return cls(collection_string=collection_string, id=id, deleted=deleted,
|
||||
full_data=full_data, information=information)
|
||||
return cls(collection_string=collection_string, id=id, deleted=deleted, full_data=full_data)
|
||||
|
||||
def __eq__(self, collection_element: 'CollectionElement') -> bool: # type: ignore
|
||||
"""
|
||||
@ -127,23 +85,6 @@ class CollectionElement:
|
||||
return (self.collection_string == collection_element.collection_string and
|
||||
self.id == collection_element.id)
|
||||
|
||||
def as_autoupdate_for_projector(self) -> AutoupdateFormatOld:
|
||||
"""
|
||||
Returns a dict that can be sent through the autoupdate system for the
|
||||
projector.
|
||||
"""
|
||||
if not self.is_deleted():
|
||||
restricted_data = self.get_access_permissions().get_projector_data([self.get_full_data()])
|
||||
data = restricted_data[0] if restricted_data else None
|
||||
else:
|
||||
data = None
|
||||
|
||||
return format_for_autoupdate_old(
|
||||
collection_string=self.collection_string,
|
||||
id=self.id,
|
||||
action='deleted' if self.is_deleted() else 'changed',
|
||||
data=data)
|
||||
|
||||
def as_dict_for_user(self, user: Optional['CollectionElement']) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
Returns a dict with the data for a user. Can be used for the rest api.
|
||||
@ -329,57 +270,3 @@ def get_model_from_collection_string(collection_string: str) -> Type[Model]:
|
||||
except KeyError:
|
||||
raise ValueError('Invalid message. A valid collection_string is missing.')
|
||||
return model
|
||||
|
||||
|
||||
def format_for_autoupdate_old(
|
||||
collection_string: str, id: int, action: str, data: Dict[str, Any] = None) -> AutoupdateFormatOld:
|
||||
"""
|
||||
Returns a dict that can be used for autoupdate.
|
||||
|
||||
This is depricated. Use format_for_autoupdate.
|
||||
"""
|
||||
if data is None:
|
||||
# If the data is None then the action has to be deleted,
|
||||
# even when it says diffrently. This can happen when the object is not
|
||||
# deleted, but the user has no permission to see it.
|
||||
action = 'deleted'
|
||||
|
||||
output = AutoupdateFormatOld(
|
||||
collection=collection_string,
|
||||
id=id,
|
||||
action=action,
|
||||
)
|
||||
|
||||
if action != 'deleted':
|
||||
data = cast(Dict[str, Any], data) # In this case data can not be None
|
||||
output['data'] = data
|
||||
|
||||
return output
|
||||
|
||||
|
||||
def to_channel_message(elements: Iterable[CollectionElement]) -> ChannelMessageFormat:
|
||||
"""
|
||||
Converts a list of collection elements to a dict, that can be send to the
|
||||
channels system.
|
||||
"""
|
||||
output = []
|
||||
for element in elements:
|
||||
output.append(InnerChannelMessageFormat(
|
||||
collection_string=element.collection_string,
|
||||
id=element.id,
|
||||
deleted=element.is_deleted(),
|
||||
information=element.information,
|
||||
full_data=element.full_data,
|
||||
))
|
||||
return ChannelMessageFormat(elements=output)
|
||||
|
||||
|
||||
def from_channel_message(message: ChannelMessageFormat) -> List[CollectionElement]:
|
||||
"""
|
||||
Converts a list of collection elements back from a dict, that was created
|
||||
via to_channel_message.
|
||||
"""
|
||||
elements = []
|
||||
for value in message['elements']:
|
||||
elements.append(CollectionElement.from_values(**value))
|
||||
return elements
|
||||
|
@ -2,20 +2,9 @@ from collections import defaultdict
|
||||
from typing import Any, Dict, List
|
||||
from urllib.parse import parse_qs
|
||||
|
||||
from asgiref.sync import sync_to_async
|
||||
from channels.db import database_sync_to_async
|
||||
|
||||
from ..core.config import config
|
||||
from ..core.models import Projector
|
||||
from .auth import async_anonymous_is_enabled, has_perm
|
||||
from .auth import async_anonymous_is_enabled
|
||||
from .cache import element_cache, split_element_id
|
||||
from .collection import (
|
||||
AutoupdateFormat,
|
||||
Collection,
|
||||
CollectionElement,
|
||||
format_for_autoupdate_old,
|
||||
from_channel_message,
|
||||
)
|
||||
from .collection import AutoupdateFormat
|
||||
from .websocket import ProtocollAsyncJsonWebsocketConsumer, get_element_data
|
||||
|
||||
|
||||
@ -72,13 +61,11 @@ class SiteConsumer(ProtocollAsyncJsonWebsocketConsumer):
|
||||
for item in event['incomming']:
|
||||
users = item.get('users')
|
||||
reply_channels = item.get('replyChannels')
|
||||
projectors = item.get('projectors')
|
||||
if ((isinstance(users, list) and user_id in users)
|
||||
or (isinstance(reply_channels, list) and self.channel_name in reply_channels)
|
||||
or (users is None and reply_channels is None and projectors is None)):
|
||||
or users is None and reply_channels is None):
|
||||
item['senderReplyChannelName'] = event.get('senderReplyChannelName')
|
||||
item['senderUserId'] = event.get('senderUserId')
|
||||
item['senderProjectorId'] = event.get('senderProjectorId')
|
||||
out.append(item)
|
||||
|
||||
if out:
|
||||
@ -101,153 +88,3 @@ class SiteConsumer(ProtocollAsyncJsonWebsocketConsumer):
|
||||
from_change_id=change_id,
|
||||
to_change_id=change_id,
|
||||
all_data=False))
|
||||
|
||||
|
||||
class ProjectorConsumer(ProtocollAsyncJsonWebsocketConsumer):
|
||||
"""
|
||||
Websocket Consumer for the projector.
|
||||
"""
|
||||
|
||||
groups = ['projector']
|
||||
|
||||
async def connect(self) -> None:
|
||||
"""
|
||||
Adds the websocket connection to a group specific to the projector with the given id.
|
||||
Also sends all data that are shown on the projector.
|
||||
"""
|
||||
user = self.scope['user']
|
||||
projector_id = self.scope["url_route"]["kwargs"]["projector_id"]
|
||||
await self.accept()
|
||||
|
||||
if not await database_sync_to_async(has_perm)(user, 'core.can_see_projector'):
|
||||
await self.send_json(type='error', content='No permissions to see this projector.')
|
||||
# TODO: Shouldend we just close the websocket connection with an error message?
|
||||
# self.close(code=4403)
|
||||
else:
|
||||
out = await sync_to_async(projector_startup_data)(projector_id)
|
||||
await self.send_json(type='autoupdate', content=out)
|
||||
|
||||
async def receive_content(self, type: str, content: Any, id: str) -> None:
|
||||
"""
|
||||
If we recieve something from the client we currently just interpret this
|
||||
as a notify message.
|
||||
|
||||
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.
|
||||
"""
|
||||
projector_id = self.scope["url_route"]["kwargs"]["projector_id"]
|
||||
await self.channel_layer.group_send(
|
||||
"projector",
|
||||
{
|
||||
"type": "send_notify",
|
||||
"incomming": content,
|
||||
"senderReplyChannelName": self.channel_name,
|
||||
"senderProjectorId": projector_id,
|
||||
},
|
||||
)
|
||||
await self.channel_layer.group_send(
|
||||
"site",
|
||||
{
|
||||
"type": "send_notify",
|
||||
"incomming": content,
|
||||
"senderReplyChannelName": self.channel_name,
|
||||
"senderProjectorId": projector_id,
|
||||
},
|
||||
)
|
||||
|
||||
async def send_notify(self, event: Dict[str, Any]) -> None:
|
||||
"""
|
||||
Send a notify message to the projector.
|
||||
"""
|
||||
projector_id = self.scope["url_route"]["kwargs"]["projector_id"]
|
||||
|
||||
out = []
|
||||
for item in event['incomming']:
|
||||
users = item.get('users')
|
||||
reply_channels = item.get('replyChannels')
|
||||
projectors = item.get('projectors')
|
||||
if ((isinstance(projectors, list) and projector_id in projectors)
|
||||
or (isinstance(reply_channels, list) and self.channel_name in reply_channels)
|
||||
or (users is None and reply_channels is None and projectors is None)):
|
||||
item['senderReplyChannelName'] = event.get('senderReplyChannelName')
|
||||
item['senderUserId'] = event.get('senderUserId')
|
||||
item['senderProjectorId'] = event.get('senderProjectorId')
|
||||
out.append(item)
|
||||
|
||||
if out:
|
||||
await self.send_json(type='notify', content=out)
|
||||
|
||||
async def send_data(self, event: Dict[str, Any]) -> None:
|
||||
"""
|
||||
Informs all projector clients about changed data.
|
||||
"""
|
||||
projector_id = self.scope["url_route"]["kwargs"]["projector_id"]
|
||||
collection_elements = from_channel_message(event['message'])
|
||||
|
||||
output = await projector_sync_send_data(projector_id, collection_elements)
|
||||
if output:
|
||||
await self.send_json(type='autoupdate', content=output)
|
||||
|
||||
|
||||
def projector_startup_data(projector_id: int) -> Any:
|
||||
"""
|
||||
Generate the startup data for a projector.
|
||||
"""
|
||||
try:
|
||||
projector = Projector.objects.get(pk=projector_id)
|
||||
except Projector.DoesNotExist:
|
||||
return {'text': 'The projector {} does not exist.'.format(projector_id)}
|
||||
else:
|
||||
# Now check whether broadcast is active at the moment. If yes,
|
||||
# change the local projector variable.
|
||||
if config['projector_broadcast'] > 0:
|
||||
projector = Projector.objects.get(pk=config['projector_broadcast'])
|
||||
|
||||
# Collect all elements that are on the projector.
|
||||
output = []
|
||||
for requirement in projector.get_all_requirements():
|
||||
required_collection_element = CollectionElement.from_instance(requirement)
|
||||
output.append(required_collection_element.as_autoupdate_for_projector())
|
||||
|
||||
# Collect all config elements.
|
||||
config_collection = Collection(config.get_collection_string())
|
||||
projector_data = (config_collection.get_access_permissions()
|
||||
.get_projector_data(config_collection.get_full_data()))
|
||||
for data in projector_data:
|
||||
output.append(format_for_autoupdate_old(
|
||||
config_collection.collection_string,
|
||||
data['id'],
|
||||
'changed',
|
||||
data))
|
||||
|
||||
# Collect the projector instance.
|
||||
collection_element = CollectionElement.from_instance(projector)
|
||||
output.append(collection_element.as_autoupdate_for_projector())
|
||||
|
||||
# Send all the data that were only collected before.
|
||||
return output
|
||||
|
||||
|
||||
@sync_to_async
|
||||
def projector_sync_send_data(projector_id: int, collection_elements: List[CollectionElement]) -> List[Any]:
|
||||
"""
|
||||
sync function that generates the elements for an projector.
|
||||
"""
|
||||
# Load the projector object. If broadcast is on, use the broadcast projector
|
||||
# instead.
|
||||
if config['projector_broadcast'] > 0:
|
||||
projector_id = config['projector_broadcast']
|
||||
|
||||
projector = Projector.objects.get(pk=projector_id)
|
||||
|
||||
# TODO: This runs once for every open projector tab. Either use
|
||||
# caching or something else, so this is only called once
|
||||
output = []
|
||||
for collection_element in collection_elements:
|
||||
if collection_element.is_deleted():
|
||||
output.append(collection_element.as_autoupdate_for_projector())
|
||||
else:
|
||||
for element in projector.get_collection_elements_required_for_this(collection_element):
|
||||
output.append(element.as_autoupdate_for_projector())
|
||||
return output
|
||||
|
@ -72,7 +72,7 @@ class RESTModelMixin:
|
||||
"""
|
||||
return self.pk # type: ignore
|
||||
|
||||
def save(self, skip_autoupdate: bool = False, information: Dict[str, str] = None, *args: Any, **kwargs: Any) -> Any:
|
||||
def save(self, skip_autoupdate: bool = False, *args: Any, **kwargs: Any) -> Any:
|
||||
"""
|
||||
Calls Django's save() method and afterwards hits the autoupdate system.
|
||||
|
||||
@ -82,18 +82,15 @@ class RESTModelMixin:
|
||||
element from the instance:
|
||||
|
||||
CollectionElement.from_instance(instance)
|
||||
|
||||
The optional argument information can be a dictionary that is given to
|
||||
the autoupdate system.
|
||||
"""
|
||||
# We don't know how to fix this circular import
|
||||
from .autoupdate import inform_changed_data
|
||||
return_value = super().save(*args, **kwargs) # type: ignore
|
||||
if not skip_autoupdate:
|
||||
inform_changed_data(self.get_root_rest_element(), information=information)
|
||||
inform_changed_data(self.get_root_rest_element())
|
||||
return return_value
|
||||
|
||||
def delete(self, skip_autoupdate: bool = False, information: Dict[str, str] = None, *args: Any, **kwargs: Any) -> Any:
|
||||
def delete(self, skip_autoupdate: bool = False, *args: Any, **kwargs: Any) -> Any:
|
||||
"""
|
||||
Calls Django's delete() method and afterwards hits the autoupdate system.
|
||||
|
||||
@ -107,9 +104,6 @@ class RESTModelMixin:
|
||||
or
|
||||
|
||||
CollectionElement.from_values(collection_string, id, deleted=True)
|
||||
|
||||
The optional argument information can be a dictionary that is given to
|
||||
the autoupdate system.
|
||||
"""
|
||||
# We don't know how to fix this circular import
|
||||
from .autoupdate import inform_changed_data, inform_deleted_data
|
||||
@ -118,9 +112,9 @@ class RESTModelMixin:
|
||||
if not skip_autoupdate:
|
||||
if self != self.get_root_rest_element():
|
||||
# The deletion of a included element is a change of the root element.
|
||||
inform_changed_data(self.get_root_rest_element(), information=information)
|
||||
inform_changed_data(self.get_root_rest_element())
|
||||
else:
|
||||
inform_deleted_data([(self.get_collection_string(), instance_pk)], information=information)
|
||||
inform_deleted_data([(self.get_collection_string(), instance_pk)])
|
||||
return return_value
|
||||
|
||||
@classmethod
|
||||
|
@ -1,6 +1,4 @@
|
||||
from typing import Any, Dict, Generator, Iterable, List, Optional, Type
|
||||
|
||||
from .collection import CollectionElement
|
||||
from typing import Any, Dict, Generator, Optional, Type
|
||||
|
||||
|
||||
class ProjectorElement:
|
||||
@ -45,44 +43,6 @@ class ProjectorElement:
|
||||
"""
|
||||
pass
|
||||
|
||||
def get_requirements(self, config_entry: Any) -> Iterable[Any]:
|
||||
"""
|
||||
Returns an iterable of instances that are required for this projector
|
||||
element. The config_entry has to be given.
|
||||
"""
|
||||
return ()
|
||||
|
||||
def get_requirements_as_collection_elements(self, config_entry: Any) -> Iterable[CollectionElement]:
|
||||
"""
|
||||
Returns an iterable of collection elements that are required for this
|
||||
projector element. The config_entry has to be given.
|
||||
"""
|
||||
return (CollectionElement.from_instance(instance) for instance in self.get_requirements(config_entry))
|
||||
|
||||
def get_collection_elements_required_for_this(
|
||||
self, collection_element: CollectionElement,
|
||||
config_entry: Any) -> List[CollectionElement]:
|
||||
"""
|
||||
Returns a list of CollectionElements that have to be sent to every
|
||||
projector that shows this projector element according to the given
|
||||
collection_element.
|
||||
|
||||
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
|
||||
|
||||
|
||||
projector_elements: Dict[str, ProjectorElement] = {}
|
||||
|
||||
|
@ -1,48 +1,9 @@
|
||||
from unittest import TestCase
|
||||
|
||||
from openslides.users.access_permissions import (
|
||||
PersonalNoteAccessPermissions,
|
||||
UserAccessPermissions,
|
||||
)
|
||||
from openslides.users.access_permissions import PersonalNoteAccessPermissions
|
||||
from openslides.utils.collection import CollectionElement
|
||||
|
||||
|
||||
class UserGetProjectorDataTest(TestCase):
|
||||
def test_get_projector_data_with_collection(self):
|
||||
"""
|
||||
This test ensures that comment field is removed.
|
||||
"""
|
||||
full_data = {
|
||||
'id': 42,
|
||||
'username': 'username_ai3Oofu7eit0eeyu1sie',
|
||||
'title': '',
|
||||
'first_name': 'first_name_iu8toShae0oolie8aevo',
|
||||
'last_name': 'last_name_OhZ4beezohY0doNoh2th',
|
||||
'structure_level': '',
|
||||
'number': '',
|
||||
'about_me': '',
|
||||
'groups_id': [],
|
||||
'is_present': False,
|
||||
'is_committee': False,
|
||||
'comment': 'comment_gah7aipeJohv9xethoku',
|
||||
}
|
||||
|
||||
data = UserAccessPermissions().get_projector_data([full_data])
|
||||
self.assertEqual(data[0], {
|
||||
'id': 42,
|
||||
'username': 'username_ai3Oofu7eit0eeyu1sie',
|
||||
'title': '',
|
||||
'first_name': 'first_name_iu8toShae0oolie8aevo',
|
||||
'last_name': 'last_name_OhZ4beezohY0doNoh2th',
|
||||
'structure_level': '',
|
||||
'number': '',
|
||||
'about_me': '',
|
||||
'groups_id': [],
|
||||
'is_present': False,
|
||||
'is_committee': False,
|
||||
})
|
||||
|
||||
|
||||
class TestPersonalNoteAccessPermissions(TestCase):
|
||||
def test_get_restricted_data(self):
|
||||
ap = PersonalNoteAccessPermissions()
|
||||
@ -54,9 +15,6 @@ class TestPersonalNoteAccessPermissions(TestCase):
|
||||
def test_get_restricted_data_for_anonymous(self):
|
||||
ap = PersonalNoteAccessPermissions()
|
||||
rd = ap.get_restricted_data(
|
||||
[CollectionElement.from_values(
|
||||
'users/personal_note',
|
||||
1,
|
||||
full_data={'user_id': 1}).get_full_data()],
|
||||
[{'user_id': 1}],
|
||||
None)
|
||||
self.assertEqual(rd, [])
|
||||
|
@ -24,25 +24,6 @@ class TestCollectionElement(TestCase):
|
||||
self.assertEqual(collection_element.collection_string, 'testmodule/model')
|
||||
self.assertEqual(collection_element.id, 42)
|
||||
|
||||
def test_channel_message(self):
|
||||
"""
|
||||
Test that to_channel_message works together with from_channel_message.
|
||||
"""
|
||||
collection_element = collection.CollectionElement.from_values(
|
||||
'testmodule/model',
|
||||
42,
|
||||
full_data={'data': 'value'},
|
||||
information={'some': 'information'})
|
||||
|
||||
created_collection_element = collection.from_channel_message(
|
||||
collection.to_channel_message([collection_element]))[0]
|
||||
|
||||
self.assertEqual(
|
||||
collection_element,
|
||||
created_collection_element)
|
||||
self.assertEqual(created_collection_element.full_data, {'data': 'value'})
|
||||
self.assertEqual(created_collection_element.information, {'some': 'information'})
|
||||
|
||||
@patch.object(collection.CollectionElement, 'get_full_data')
|
||||
def test_equal(self, mock_get_full_data):
|
||||
self.assertEqual(
|
||||
|
Loading…
Reference in New Issue
Block a user