Change system for autoupdate on the projector (#2394)
* Second websocket channel for the projector * Removed use of projector requirements for REST API requests. Refactored data serializing for projector websocket connection. * Refactor the way that the projector autoupdate get its data. * Fixed missing assignment slide title for hidden items. * Release all items for item list slide and list of speakers slide. Fixed error with motion workflow. * Created CollectionElement class which helps to handle autoupdate.
This commit is contained in:
parent
6ade5630ff
commit
6abb0976c2
@ -16,7 +16,6 @@ Assignments:
|
|||||||
- Remove unused assignment config to publish winner election results only.
|
- Remove unused assignment config to publish winner election results only.
|
||||||
|
|
||||||
Core:
|
Core:
|
||||||
- Used Django Channels instead of Tornado.
|
|
||||||
- Added support for big assemblies with lots of users.
|
- Added support for big assemblies with lots of users.
|
||||||
- Added HTML support for messages on the projector.
|
- Added HTML support for messages on the projector.
|
||||||
|
|
||||||
@ -38,6 +37,7 @@ Other:
|
|||||||
- Removed config cache to support multiple threads or processes.
|
- Removed config cache to support multiple threads or processes.
|
||||||
- Fixed bug, that the last change of a config value was not send via autoupdate.
|
- Fixed bug, that the last change of a config value was not send via autoupdate.
|
||||||
- Added template hooks for plugins (in item detail view and motion poll form).
|
- Added template hooks for plugins (in item detail view and motion poll form).
|
||||||
|
- Used Django Channels instead of Tornado. Refactoring of the autoupdate process.
|
||||||
|
|
||||||
|
|
||||||
Version 2.0 (2016-04-18)
|
Version 2.0 (2016-04-18)
|
||||||
|
@ -5,7 +5,7 @@ class ItemAccessPermissions(BaseAccessPermissions):
|
|||||||
"""
|
"""
|
||||||
Access permissions container for Item and ItemViewSet.
|
Access permissions container for Item and ItemViewSet.
|
||||||
"""
|
"""
|
||||||
def can_retrieve(self, user):
|
def check_permissions(self, user):
|
||||||
"""
|
"""
|
||||||
Returns True if the user has read access model instances.
|
Returns True if the user has read access model instances.
|
||||||
"""
|
"""
|
||||||
@ -19,15 +19,28 @@ class ItemAccessPermissions(BaseAccessPermissions):
|
|||||||
|
|
||||||
return ItemSerializer
|
return ItemSerializer
|
||||||
|
|
||||||
|
# TODO: In the following method we use full_data['is_hidden'] but this can be out of date.
|
||||||
|
|
||||||
def get_restricted_data(self, full_data, user):
|
def get_restricted_data(self, full_data, user):
|
||||||
"""
|
"""
|
||||||
Returns the restricted serialized data for the instance prepared
|
Returns the restricted serialized data for the instance prepared
|
||||||
for the user.
|
for the user.
|
||||||
"""
|
"""
|
||||||
if (self.can_retrieve(user) and
|
if (user.has_perm('agenda.can_see') and
|
||||||
(not full_data['is_hidden'] or
|
(not full_data['is_hidden'] or
|
||||||
user.has_perm('agenda.can_see_hidden_items'))):
|
user.has_perm('agenda.can_see_hidden_items'))):
|
||||||
data = full_data
|
data = full_data
|
||||||
else:
|
else:
|
||||||
data = None
|
data = None
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
def get_projector_data(self, full_data):
|
||||||
|
"""
|
||||||
|
Returns the restricted serialized data for the instance prepared
|
||||||
|
for the projector. Removes field 'comment'.
|
||||||
|
"""
|
||||||
|
data = {}
|
||||||
|
for key in full_data.keys():
|
||||||
|
if key != 'comment':
|
||||||
|
data[key] = full_data[key]
|
||||||
|
return data
|
||||||
|
@ -1,15 +1,14 @@
|
|||||||
from openslides.core.exceptions import ProjectorException
|
from ..core.config import config
|
||||||
from openslides.utils.projector import ProjectorElement, ProjectorRequirement
|
from ..core.exceptions import ProjectorException
|
||||||
|
from ..utils.projector import ProjectorElement
|
||||||
from .models import Item
|
from .models import Item
|
||||||
from .views import ItemViewSet
|
|
||||||
|
|
||||||
|
|
||||||
class ItemListSlide(ProjectorElement):
|
class ItemListSlide(ProjectorElement):
|
||||||
"""
|
"""
|
||||||
Slide definitions for Item model.
|
Slide definitions for Item model.
|
||||||
|
|
||||||
This is only for list slides.
|
This is only for item list slides.
|
||||||
|
|
||||||
Set 'id' to None to get a list slide of all root items. Set 'id' to an
|
Set 'id' to None to get a list slide of all root items. Set 'id' to an
|
||||||
integer to get a list slide of the children of the metioned item.
|
integer to get a list slide of the children of the metioned item.
|
||||||
@ -26,18 +25,7 @@ class ItemListSlide(ProjectorElement):
|
|||||||
raise ProjectorException('Item does not exist.')
|
raise ProjectorException('Item does not exist.')
|
||||||
|
|
||||||
def get_requirements(self, config_entry):
|
def get_requirements(self, config_entry):
|
||||||
pk = config_entry.get('id', 'tree')
|
yield from Item.objects.all()
|
||||||
if pk is None or config_entry.get('tree', False):
|
|
||||||
# Root list slide or slide with tree.
|
|
||||||
yield ProjectorRequirement(
|
|
||||||
view_class=ItemViewSet,
|
|
||||||
view_action='tree')
|
|
||||||
|
|
||||||
# Root list slide and children list slide.
|
|
||||||
# Related objects like users and tags are not unlocked.
|
|
||||||
yield ProjectorRequirement(
|
|
||||||
view_class=ItemViewSet,
|
|
||||||
view_action='list')
|
|
||||||
|
|
||||||
|
|
||||||
class ListOfSpeakersSlide(ProjectorElement):
|
class ListOfSpeakersSlide(ProjectorElement):
|
||||||
@ -49,10 +37,7 @@ class ListOfSpeakersSlide(ProjectorElement):
|
|||||||
name = 'agenda/list-of-speakers'
|
name = 'agenda/list-of-speakers'
|
||||||
|
|
||||||
def check_data(self):
|
def check_data(self):
|
||||||
pk = self.config_entry.get('id')
|
if not Item.objects.filter(pk=self.config_entry.get('id')).exists():
|
||||||
if pk is None:
|
|
||||||
raise ProjectorException('Id must not be None.')
|
|
||||||
if not Item.objects.filter(pk=pk).exists():
|
|
||||||
raise ProjectorException('Item does not exist.')
|
raise ProjectorException('Item does not exist.')
|
||||||
|
|
||||||
def get_requirements(self, config_entry):
|
def get_requirements(self, config_entry):
|
||||||
@ -65,12 +50,12 @@ class ListOfSpeakersSlide(ProjectorElement):
|
|||||||
# Item does not exist. Just do nothing.
|
# Item does not exist. Just do nothing.
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
yield ProjectorRequirement(
|
yield item
|
||||||
view_class=ItemViewSet,
|
for speaker in item.speakers.filter(end_time=None):
|
||||||
view_action='retrieve',
|
# Yield current speaker and next speakers
|
||||||
pk=str(item.pk))
|
yield speaker.user
|
||||||
for speaker in item.speakers.all():
|
query = (item.speakers.exclude(end_time=None)
|
||||||
yield ProjectorRequirement(
|
.order_by('-end_time')[:config['agenda_show_last_speakers']])
|
||||||
view_class=speaker.user.get_view_class(),
|
for speaker in query:
|
||||||
view_action='retrieve',
|
# Yield last speakers
|
||||||
pk=str(speaker.user_id))
|
yield speaker.user
|
||||||
|
@ -10,9 +10,14 @@ def listen_to_related_object_post_save(sender, instance, created, **kwargs):
|
|||||||
Receiver function to create agenda items. It is connected to the signal
|
Receiver function to create agenda items. It is connected to the signal
|
||||||
django.db.models.signals.post_save during app loading.
|
django.db.models.signals.post_save during app loading.
|
||||||
"""
|
"""
|
||||||
if created and hasattr(instance, 'get_agenda_title'):
|
if hasattr(instance, 'get_agenda_title'):
|
||||||
|
if created:
|
||||||
|
# If the object is created, the related_object has to be sent again.
|
||||||
Item.objects.create(content_object=instance)
|
Item.objects.create(content_object=instance)
|
||||||
inform_changed_data(instance)
|
inform_changed_data(instance)
|
||||||
|
else:
|
||||||
|
# If the object has changed, then also the agenda item has to be sent.
|
||||||
|
inform_changed_data(instance.agenda_item)
|
||||||
|
|
||||||
|
|
||||||
def listen_to_related_object_post_delete(sender, instance, **kwargs):
|
def listen_to_related_object_post_delete(sender, instance, **kwargs):
|
||||||
|
@ -25,8 +25,6 @@ angular.module('OpenSlidesApp.agenda.projector', ['OpenSlidesApp.agenda'])
|
|||||||
// Add it to the coresponding get_requirements method of the ProjectorElement
|
// Add it to the coresponding get_requirements method of the ProjectorElement
|
||||||
// class.
|
// class.
|
||||||
var id = $scope.element.id;
|
var id = $scope.element.id;
|
||||||
Agenda.find(id);
|
|
||||||
User.findAll();
|
|
||||||
Agenda.bindOne(id, $scope, 'item');
|
Agenda.bindOne(id, $scope, 'item');
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
@ -41,7 +39,7 @@ angular.module('OpenSlidesApp.agenda.projector', ['OpenSlidesApp.agenda'])
|
|||||||
// Attention! Each object that is used here has to be dealt on server side.
|
// Attention! Each object that is used here has to be dealt on server side.
|
||||||
// Add it to the coresponding get_requirements method of the ProjectorElement
|
// Add it to the coresponding get_requirements method of the ProjectorElement
|
||||||
// class.
|
// class.
|
||||||
Agenda.findAll();
|
|
||||||
// Bind agenda tree to the scope
|
// Bind agenda tree to the scope
|
||||||
$scope.$watch(function () {
|
$scope.$watch(function () {
|
||||||
return Agenda.lastModified();
|
return Agenda.lastModified();
|
||||||
|
@ -41,9 +41,9 @@ class ItemViewSet(ListModelMixin, RetrieveModelMixin, UpdateModelMixin, GenericV
|
|||||||
"""
|
"""
|
||||||
Returns True if the user has required permissions.
|
Returns True if the user has required permissions.
|
||||||
"""
|
"""
|
||||||
if self.action == 'retrieve':
|
if self.action in ('list', 'retrieve'):
|
||||||
result = self.get_access_permissions().can_retrieve(self.request.user)
|
result = self.get_access_permissions().check_permissions(self.request.user)
|
||||||
elif self.action in ('metadata', 'list', 'manage_speaker', 'tree'):
|
elif self.action in ('metadata', 'manage_speaker', 'tree'):
|
||||||
result = self.request.user.has_perm('agenda.can_see')
|
result = self.request.user.has_perm('agenda.can_see')
|
||||||
# For manage_speaker and tree requests the rest of the check is
|
# For manage_speaker and tree requests the rest of the check is
|
||||||
# done in the specific method. See below.
|
# done in the specific method. See below.
|
||||||
@ -63,6 +63,7 @@ class ItemViewSet(ListModelMixin, RetrieveModelMixin, UpdateModelMixin, GenericV
|
|||||||
Checks if the requesting user has permission to see also an
|
Checks if the requesting user has permission to see also an
|
||||||
organizational item if it is one.
|
organizational item if it is one.
|
||||||
"""
|
"""
|
||||||
|
# TODO: Move this logic to access_permissions.ItemAccessPermissions.
|
||||||
if obj.is_hidden() and not request.user.has_perm('agenda.can_see_hidden_items'):
|
if obj.is_hidden() and not request.user.has_perm('agenda.can_see_hidden_items'):
|
||||||
self.permission_denied(request)
|
self.permission_denied(request)
|
||||||
|
|
||||||
@ -70,6 +71,7 @@ class ItemViewSet(ListModelMixin, RetrieveModelMixin, UpdateModelMixin, GenericV
|
|||||||
"""
|
"""
|
||||||
Filters organizational items if the user has no permission to see them.
|
Filters organizational items if the user has no permission to see them.
|
||||||
"""
|
"""
|
||||||
|
# TODO: Move this logic to access_permissions.ItemAccessPermissions.
|
||||||
queryset = super().get_queryset()
|
queryset = super().get_queryset()
|
||||||
if not self.request.user.has_perm('agenda.can_see_hidden_items'):
|
if not self.request.user.has_perm('agenda.can_see_hidden_items'):
|
||||||
pk_list = [item.pk for item in Item.objects.get_only_agenda_items()]
|
pk_list = [item.pk for item in Item.objects.get_only_agenda_items()]
|
||||||
|
@ -5,7 +5,7 @@ class AssignmentAccessPermissions(BaseAccessPermissions):
|
|||||||
"""
|
"""
|
||||||
Access permissions container for Assignment and AssignmentViewSet.
|
Access permissions container for Assignment and AssignmentViewSet.
|
||||||
"""
|
"""
|
||||||
def can_retrieve(self, user):
|
def check_permissions(self, user):
|
||||||
"""
|
"""
|
||||||
Returns True if the user has read access model instances.
|
Returns True if the user has read access model instances.
|
||||||
"""
|
"""
|
||||||
@ -17,7 +17,7 @@ class AssignmentAccessPermissions(BaseAccessPermissions):
|
|||||||
"""
|
"""
|
||||||
from .serializers import AssignmentFullSerializer, AssignmentShortSerializer
|
from .serializers import AssignmentFullSerializer, AssignmentShortSerializer
|
||||||
|
|
||||||
if user is None or user.has_perm('assignments.can_manage'):
|
if user is None or (user.has_perm('assignments.can_see') and user.has_perm('assignments.can_manage')):
|
||||||
serializer_class = AssignmentFullSerializer
|
serializer_class = AssignmentFullSerializer
|
||||||
else:
|
else:
|
||||||
serializer_class = AssignmentShortSerializer
|
serializer_class = AssignmentShortSerializer
|
||||||
@ -29,9 +29,20 @@ class AssignmentAccessPermissions(BaseAccessPermissions):
|
|||||||
for the user. Removes unpublished polls for non admins so that they
|
for the user. Removes unpublished polls for non admins so that they
|
||||||
only get a result like the AssignmentShortSerializer would give them.
|
only get a result like the AssignmentShortSerializer would give them.
|
||||||
"""
|
"""
|
||||||
if user.has_perm('assignments.can_manage'):
|
if user.has_perm('assignments.can_see') and user.has_perm('assignments.can_manage'):
|
||||||
data = full_data
|
data = full_data
|
||||||
|
elif user.has_perm('assignments.can_see'):
|
||||||
|
data = full_data.copy()
|
||||||
|
data['polls'] = [poll for poll in data['polls'] if poll['published']]
|
||||||
else:
|
else:
|
||||||
|
data = None
|
||||||
|
return data
|
||||||
|
|
||||||
|
def get_projector_data(self, full_data):
|
||||||
|
"""
|
||||||
|
Returns the restricted serialized data for the instance prepared
|
||||||
|
for the projector. Removes several fields.
|
||||||
|
"""
|
||||||
data = full_data.copy()
|
data = full_data.copy()
|
||||||
data['polls'] = [poll for poll in data['polls'] if poll['published']]
|
data['polls'] = [poll for poll in data['polls'] if poll['published']]
|
||||||
return data
|
return data
|
||||||
|
@ -1,63 +1,29 @@
|
|||||||
from openslides.core.exceptions import ProjectorException
|
from ..core.exceptions import ProjectorException
|
||||||
from openslides.core.views import TagViewSet
|
from ..utils.projector import ProjectorElement
|
||||||
from openslides.utils.projector import ProjectorElement, ProjectorRequirement
|
from .models import Assignment
|
||||||
|
|
||||||
from .models import Assignment, AssignmentPoll
|
|
||||||
from .views import AssignmentViewSet
|
|
||||||
|
|
||||||
|
|
||||||
class AssignmentSlide(ProjectorElement):
|
class AssignmentSlide(ProjectorElement):
|
||||||
"""
|
"""
|
||||||
Slide definitions for Assignment model.
|
Slide definitions for Assignment model.
|
||||||
|
|
||||||
Set 'id' to get a detail slide. Omit it to get a list slide.
|
|
||||||
"""
|
"""
|
||||||
name = 'assignments/assignment'
|
name = 'assignments/assignment'
|
||||||
|
|
||||||
def check_data(self):
|
def check_data(self):
|
||||||
pk = self.config_entry.get('id')
|
if not Assignment.objects.filter(pk=self.config_entry.get('id')).exists():
|
||||||
if pk is not None:
|
|
||||||
# Detail slide.
|
|
||||||
if not Assignment.objects.filter(pk=pk).exists():
|
|
||||||
raise ProjectorException('Election does not exist.')
|
raise ProjectorException('Election does not exist.')
|
||||||
poll_id = self.config_entry.get('poll')
|
|
||||||
if poll_id is not None:
|
|
||||||
# Poll slide.
|
|
||||||
if not AssignmentPoll.objects.filter(pk=poll_id).exists():
|
|
||||||
raise ProjectorException('Poll does not exist.')
|
|
||||||
|
|
||||||
def get_requirements(self, config_entry):
|
def get_requirements(self, config_entry):
|
||||||
pk = config_entry.get('id')
|
|
||||||
if pk is None:
|
|
||||||
# List slide. Related objects like users and tags are not unlocked.
|
|
||||||
yield ProjectorRequirement(
|
|
||||||
view_class=AssignmentViewSet,
|
|
||||||
view_action='list')
|
|
||||||
else:
|
|
||||||
# Detail slide.
|
|
||||||
try:
|
try:
|
||||||
assignment = Assignment.objects.get(pk=pk)
|
assignment = Assignment.objects.get(pk=config_entry.get('id'))
|
||||||
except Assignment.DoesNotExist:
|
except Assignment.DoesNotExist:
|
||||||
# Assignment does not exist. Just do nothing.
|
# Assignment does not exist. Just do nothing.
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
yield ProjectorRequirement(
|
yield assignment
|
||||||
view_class=AssignmentViewSet,
|
yield assignment.agenda_item
|
||||||
view_action='retrieve',
|
|
||||||
pk=str(assignment.pk))
|
|
||||||
for user in assignment.related_users.all():
|
for user in assignment.related_users.all():
|
||||||
yield ProjectorRequirement(
|
yield user
|
||||||
view_class=user.get_view_class(),
|
|
||||||
view_action='retrieve',
|
|
||||||
pk=str(user.pk))
|
|
||||||
for poll in assignment.polls.all().prefetch_related('options'):
|
for poll in assignment.polls.all().prefetch_related('options'):
|
||||||
for option in poll.options.all():
|
for option in poll.options.all():
|
||||||
yield ProjectorRequirement(
|
yield option.candidate
|
||||||
view_class=option.candidate.get_view_class(),
|
|
||||||
view_action='retrieve',
|
|
||||||
pk=str(option.candidate_id))
|
|
||||||
for tag in assignment.tags.all():
|
|
||||||
yield ProjectorRequirement(
|
|
||||||
view_class=TagViewSet,
|
|
||||||
view_action='retrieve',
|
|
||||||
pk=str(tag.pk))
|
|
||||||
|
@ -24,16 +24,10 @@ angular.module('OpenSlidesApp.assignments.projector', ['OpenSlidesApp.assignment
|
|||||||
var id = $scope.element.id;
|
var id = $scope.element.id;
|
||||||
var poll = $scope.element.poll;
|
var poll = $scope.element.poll;
|
||||||
|
|
||||||
// load assignemt object and related agenda item
|
|
||||||
Assignment.find(id).then(function(assignment) {
|
|
||||||
Assignment.loadRelations(assignment, 'agenda_item');
|
|
||||||
});
|
|
||||||
Assignment.bindOne(id, $scope, 'assignment');
|
Assignment.bindOne(id, $scope, 'assignment');
|
||||||
Assignment.getPhases().then(function(phases) {
|
Assignment.getPhases().then(function(phases) {
|
||||||
$scope.phases = phases;
|
$scope.phases = phases;
|
||||||
});
|
});
|
||||||
// load all users
|
|
||||||
User.findAll();
|
|
||||||
User.bindAll({}, $scope, 'users');
|
User.bindAll({}, $scope, 'users');
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
<!-- Title -->
|
<!-- Title -->
|
||||||
<div id="title">
|
<div id="title">
|
||||||
<h1>{{ assignment.agenda_item.getTitle() }}</h1>
|
<h1>{{ assignment.agenda_item.getTitle() || assignment.title }}</h1>
|
||||||
<h2>
|
<h2>
|
||||||
<translate>Election</translate>
|
<translate>Election</translate>
|
||||||
</h2>
|
</h2>
|
||||||
|
@ -52,10 +52,11 @@ class AssignmentViewSet(ModelViewSet):
|
|||||||
"""
|
"""
|
||||||
Returns True if the user has required permissions.
|
Returns True if the user has required permissions.
|
||||||
"""
|
"""
|
||||||
if self.action == 'retrieve':
|
if self.action in ('list', 'retrieve'):
|
||||||
result = self.get_access_permissions().can_retrieve(self.request.user)
|
result = self.get_access_permissions().check_permissions(self.request.user)
|
||||||
elif self.action in ('metadata', 'list'):
|
elif self.action == 'metadata':
|
||||||
result = self.request.user.has_perm('assignments.can_see')
|
# Everybody is allowed to see the metadata.
|
||||||
|
result = True
|
||||||
elif self.action in ('create', 'partial_update', 'update', 'destroy',
|
elif self.action in ('create', 'partial_update', 'update', 'destroy',
|
||||||
'mark_elected', 'create_poll'):
|
'mark_elected', 'create_poll'):
|
||||||
result = (self.request.user.has_perm('assignments.can_see') and
|
result = (self.request.user.has_perm('assignments.can_see') and
|
||||||
|
@ -5,7 +5,7 @@ class ProjectorAccessPermissions(BaseAccessPermissions):
|
|||||||
"""
|
"""
|
||||||
Access permissions container for Projector and ProjectorViewSet.
|
Access permissions container for Projector and ProjectorViewSet.
|
||||||
"""
|
"""
|
||||||
def can_retrieve(self, user):
|
def check_permissions(self, user):
|
||||||
"""
|
"""
|
||||||
Returns True if the user has read access model instances.
|
Returns True if the user has read access model instances.
|
||||||
"""
|
"""
|
||||||
@ -24,7 +24,7 @@ class CustomSlideAccessPermissions(BaseAccessPermissions):
|
|||||||
"""
|
"""
|
||||||
Access permissions container for CustomSlide and CustomSlideViewSet.
|
Access permissions container for CustomSlide and CustomSlideViewSet.
|
||||||
"""
|
"""
|
||||||
def can_retrieve(self, user):
|
def check_permissions(self, user):
|
||||||
"""
|
"""
|
||||||
Returns True if the user has read access model instances.
|
Returns True if the user has read access model instances.
|
||||||
"""
|
"""
|
||||||
@ -43,7 +43,7 @@ class TagAccessPermissions(BaseAccessPermissions):
|
|||||||
"""
|
"""
|
||||||
Access permissions container for Tag and TagViewSet.
|
Access permissions container for Tag and TagViewSet.
|
||||||
"""
|
"""
|
||||||
def can_retrieve(self, user):
|
def check_permissions(self, user):
|
||||||
"""
|
"""
|
||||||
Returns True if the user has read access model instances.
|
Returns True if the user has read access model instances.
|
||||||
"""
|
"""
|
||||||
@ -66,7 +66,7 @@ class ChatMessageAccessPermissions(BaseAccessPermissions):
|
|||||||
"""
|
"""
|
||||||
Access permissions container for ChatMessage and ChatMessageViewSet.
|
Access permissions container for ChatMessage and ChatMessageViewSet.
|
||||||
"""
|
"""
|
||||||
def can_retrieve(self, user):
|
def check_permissions(self, user):
|
||||||
"""
|
"""
|
||||||
Returns True if the user has read access model instances.
|
Returns True if the user has read access model instances.
|
||||||
"""
|
"""
|
||||||
@ -88,7 +88,7 @@ class ConfigAccessPermissions(BaseAccessPermissions):
|
|||||||
Access permissions container for the config (ConfigStore and
|
Access permissions container for the config (ConfigStore and
|
||||||
ConfigViewSet).
|
ConfigViewSet).
|
||||||
"""
|
"""
|
||||||
def can_retrieve(self, user):
|
def check_permissions(self, user):
|
||||||
"""
|
"""
|
||||||
Returns True if the user has read access model instances.
|
Returns True if the user has read access model instances.
|
||||||
"""
|
"""
|
||||||
|
@ -141,6 +141,12 @@ class ConfigHandler:
|
|||||||
if config_variable.translatable:
|
if config_variable.translatable:
|
||||||
yield config_variable.name
|
yield config_variable.name
|
||||||
|
|
||||||
|
def get_collection_string(self):
|
||||||
|
"""
|
||||||
|
Returns the collection_string from the CollectionStore.
|
||||||
|
"""
|
||||||
|
return ConfigStore.get_collection_string()
|
||||||
|
|
||||||
config = ConfigHandler()
|
config = ConfigHandler()
|
||||||
"""
|
"""
|
||||||
Final entry point to get an set config variables. To get a config variable
|
Final entry point to get an set config variables. To get a config variable
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
from django.core.validators import MaxLengthValidator
|
from django.core.validators import MaxLengthValidator
|
||||||
|
|
||||||
from openslides.core.config import ConfigVariable
|
from openslides.core.config import ConfigVariable
|
||||||
|
|
||||||
|
|
||||||
|
@ -111,11 +111,9 @@ class Projector(RESTModelMixin, models.Model):
|
|||||||
result[key]['error'] = str(e)
|
result[key]['error'] = str(e)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@classmethod
|
def get_all_requirements(self):
|
||||||
def get_all_requirements(cls):
|
|
||||||
"""
|
"""
|
||||||
Generator which returns all ProjectorRequirement instances of all
|
Generator which returns all instances that are shown on this projector.
|
||||||
active projector elements.
|
|
||||||
"""
|
"""
|
||||||
# Get all elements from all apps.
|
# Get all elements from all apps.
|
||||||
elements = {}
|
elements = {}
|
||||||
@ -123,12 +121,38 @@ class Projector(RESTModelMixin, models.Model):
|
|||||||
elements[element.name] = element
|
elements[element.name] = element
|
||||||
|
|
||||||
# Generator
|
# Generator
|
||||||
for projector in cls.objects.all():
|
for key, value in self.config.items():
|
||||||
for key, value in projector.config.items():
|
|
||||||
element = elements.get(value['name'])
|
element = elements.get(value['name'])
|
||||||
if element is not None:
|
if element is not None:
|
||||||
for requirement in element.get_requirements(value):
|
yield from element.get_requirements(value)
|
||||||
yield requirement
|
|
||||||
|
def collection_element_is_shown(self, collection_element):
|
||||||
|
"""
|
||||||
|
Returns True if this collection element is shown on this projector.
|
||||||
|
"""
|
||||||
|
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
|
||||||
|
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(self, collection_element):
|
||||||
|
# TODO: Implement this for all ProjectorElements (also for config values!)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
class CustomSlide(RESTModelMixin, models.Model):
|
class CustomSlide(RESTModelMixin, models.Model):
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
|
|
||||||
from openslides.utils.projector import ProjectorElement, ProjectorRequirement
|
from ..utils.projector import ProjectorElement
|
||||||
|
|
||||||
from .config import config
|
from .config import config
|
||||||
from .exceptions import ProjectorException
|
from .exceptions import ProjectorException
|
||||||
from .models import CustomSlide, Projector
|
from .models import CustomSlide, Projector
|
||||||
from .views import CustomSlideViewSet
|
|
||||||
|
|
||||||
|
|
||||||
class CustomSlideSlide(ProjectorElement):
|
class CustomSlideSlide(ProjectorElement):
|
||||||
@ -19,12 +17,14 @@ class CustomSlideSlide(ProjectorElement):
|
|||||||
raise ProjectorException('Custom slide does not exist.')
|
raise ProjectorException('Custom slide does not exist.')
|
||||||
|
|
||||||
def get_requirements(self, config_entry):
|
def get_requirements(self, config_entry):
|
||||||
pk = config_entry.get('id')
|
try:
|
||||||
if pk is not None:
|
custom_slide = CustomSlide.objects.get(pk=config_entry.get('id'))
|
||||||
yield ProjectorRequirement(
|
except CustomSlide.DoesNotExist:
|
||||||
view_class=CustomSlideViewSet,
|
# Custom slide does not exist. Just do nothing.
|
||||||
view_action='retrieve',
|
pass
|
||||||
pk=str(pk))
|
else:
|
||||||
|
yield custom_slide
|
||||||
|
yield custom_slide.agenda_item
|
||||||
|
|
||||||
|
|
||||||
class Clock(ProjectorElement):
|
class Clock(ProjectorElement):
|
||||||
|
@ -38,11 +38,22 @@ angular.module('OpenSlidesApp.core', [
|
|||||||
.factory('autoupdate', [
|
.factory('autoupdate', [
|
||||||
'DS',
|
'DS',
|
||||||
'$rootScope',
|
'$rootScope',
|
||||||
function (DS, $rootScope) {
|
'REALM',
|
||||||
|
function (DS, $rootScope, REALM) {
|
||||||
var socket = null;
|
var socket = null;
|
||||||
var recInterval = null;
|
var recInterval = null;
|
||||||
$rootScope.connected = false;
|
$rootScope.connected = false;
|
||||||
|
|
||||||
|
var websocketPath;
|
||||||
|
if (REALM == 'site') {
|
||||||
|
websocketPath = '/ws/site/';
|
||||||
|
} else if (REALM == 'projector') {
|
||||||
|
// TODO: At the moment there is only one projector. Find out which one is requested
|
||||||
|
websocketPath = '/ws/projector/1/';
|
||||||
|
} else {
|
||||||
|
console.error('The constant REALM is not set properly.');
|
||||||
|
}
|
||||||
|
|
||||||
var Autoupdate = {
|
var Autoupdate = {
|
||||||
messageReceivers: [],
|
messageReceivers: [],
|
||||||
onMessage: function (receiver) {
|
onMessage: function (receiver) {
|
||||||
@ -55,7 +66,7 @@ angular.module('OpenSlidesApp.core', [
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
var newConnect = function () {
|
var newConnect = function () {
|
||||||
socket = new WebSocket('ws://' + location.host + '/ws/');
|
socket = new WebSocket('ws://' + location.host + websocketPath);
|
||||||
clearInterval(recInterval);
|
clearInterval(recInterval);
|
||||||
socket.onopen = function () {
|
socket.onopen = function () {
|
||||||
$rootScope.connected = true;
|
$rootScope.connected = true;
|
||||||
@ -175,8 +186,11 @@ angular.module('OpenSlidesApp.core', [
|
|||||||
autoupdate.onMessage(function(json) {
|
autoupdate.onMessage(function(json) {
|
||||||
// TODO: when MODEL.find() is called after this
|
// TODO: when MODEL.find() is called after this
|
||||||
// a new request is fired. This could be a bug in DS
|
// a new request is fired. This could be a bug in DS
|
||||||
|
// TODO: If you don't have the permission to see a projector, the
|
||||||
var data = JSON.parse(json);
|
// variable json is a string with an error message. Therefor
|
||||||
|
// the next line fails.
|
||||||
|
var dataList = JSON.parse(json);
|
||||||
|
_.forEach(dataList, function(data) {
|
||||||
console.log("Received object: " + data.collection + ", " + data.id);
|
console.log("Received object: " + data.collection + ", " + data.id);
|
||||||
var instance = DS.get(data.collection, data.id);
|
var instance = DS.get(data.collection, data.id);
|
||||||
if (data.action == 'changed') {
|
if (data.action == 'changed') {
|
||||||
@ -192,32 +206,47 @@ angular.module('OpenSlidesApp.core', [
|
|||||||
}
|
}
|
||||||
DS.eject(data.collection, data.id);
|
DS.eject(data.collection, data.id);
|
||||||
}
|
}
|
||||||
// If you want to handle more status codes, change server
|
});
|
||||||
// restrictions in utils/autoupdate.py.
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
.factory('loadGlobalData', [
|
// Save the server time to the rootscope.
|
||||||
'$rootScope',
|
.run([
|
||||||
'$http',
|
'$http',
|
||||||
'ChatMessage',
|
'$rootScope',
|
||||||
|
function ($http, $rootScope) {
|
||||||
|
// Loads server time and calculates server offset
|
||||||
|
$rootScope.serverOffset = Math.floor(Date.now() / 1000);
|
||||||
|
$http.get('/core/servertime/')
|
||||||
|
.then(function(data) {
|
||||||
|
$rootScope.serverOffset = Math.floor(Date.now() / 1000 - data.data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
.run([
|
||||||
'Config',
|
'Config',
|
||||||
'Projector',
|
'$rootScope',
|
||||||
function ($rootScope, $http, ChatMessage, Config, Projector) {
|
function (Config, $rootScope) {
|
||||||
return function () {
|
$rootScope.config = function (key) {
|
||||||
// Puts the config object into each scope.
|
|
||||||
Config.findAll().then(function() {
|
|
||||||
$rootScope.config = function(key) {
|
|
||||||
try {
|
try {
|
||||||
return Config.get(key).value;
|
return Config.get(key).value;
|
||||||
}
|
}
|
||||||
catch(err) {
|
catch(err) {
|
||||||
console.log("Unkown config key: " + key);
|
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
.factory('loadGlobalData', [
|
||||||
|
'ChatMessage',
|
||||||
|
'Config',
|
||||||
|
'Projector',
|
||||||
|
function (ChatMessage, Config, Projector) {
|
||||||
|
return function () {
|
||||||
|
Config.findAll();
|
||||||
|
|
||||||
// Loads all projector data
|
// Loads all projector data
|
||||||
Projector.findAll();
|
Projector.findAll();
|
||||||
@ -233,23 +262,10 @@ angular.module('OpenSlidesApp.core', [
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
//}
|
//}
|
||||||
|
|
||||||
// Loads server time and calculates server offset
|
|
||||||
$http.get('/core/servertime/').then(function(data) {
|
|
||||||
$rootScope.serverOffset = Math.floor( Date.now() / 1000 - data.data );
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
// Load the global data on startup
|
|
||||||
.run([
|
|
||||||
'loadGlobalData',
|
|
||||||
function(loadGlobalData) {
|
|
||||||
loadGlobalData();
|
|
||||||
}
|
|
||||||
])
|
|
||||||
|
|
||||||
|
|
||||||
// Template hooks
|
// Template hooks
|
||||||
|
|
||||||
|
@ -5,6 +5,9 @@
|
|||||||
// The core module for the OpenSlides projector
|
// The core module for the OpenSlides projector
|
||||||
angular.module('OpenSlidesApp.core.projector', ['OpenSlidesApp.core'])
|
angular.module('OpenSlidesApp.core.projector', ['OpenSlidesApp.core'])
|
||||||
|
|
||||||
|
// Can be used to find out if the projector or the side is used
|
||||||
|
.constant('REALM', 'projector')
|
||||||
|
|
||||||
// Provider to register slides in a .config() statement.
|
// Provider to register slides in a .config() statement.
|
||||||
.provider('slides', [
|
.provider('slides', [
|
||||||
function() {
|
function() {
|
||||||
@ -26,7 +29,7 @@ angular.module('OpenSlidesApp.core.projector', ['OpenSlidesApp.core'])
|
|||||||
element.template = slidesMap[element.name].template;
|
element.template = slidesMap[element.name].template;
|
||||||
elements.push(element);
|
elements.push(element);
|
||||||
} else {
|
} else {
|
||||||
console.log("Unknown slide: " + element.name);
|
console.error("Unknown slide: " + element.name);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return elements;
|
return elements;
|
||||||
@ -61,7 +64,9 @@ angular.module('OpenSlidesApp.core.projector', ['OpenSlidesApp.core'])
|
|||||||
.controller('ProjectorContainerCtrl', [
|
.controller('ProjectorContainerCtrl', [
|
||||||
'$scope',
|
'$scope',
|
||||||
'Config',
|
'Config',
|
||||||
function($scope, Config) {
|
'loadGlobalData',
|
||||||
|
function($scope, Config, loadGlobalData) {
|
||||||
|
loadGlobalData();
|
||||||
// watch for changes in Config
|
// watch for changes in Config
|
||||||
var last_conf;
|
var last_conf;
|
||||||
$scope.$watch(function () {
|
$scope.$watch(function () {
|
||||||
@ -123,21 +128,25 @@ angular.module('OpenSlidesApp.core.projector', ['OpenSlidesApp.core'])
|
|||||||
'Projector',
|
'Projector',
|
||||||
'slides',
|
'slides',
|
||||||
function($scope, Projector, slides) {
|
function($scope, Projector, slides) {
|
||||||
Projector.find(1).then(function() {
|
|
||||||
$scope.$watch(function () {
|
$scope.$watch(function () {
|
||||||
|
// TODO: Use the current projector. At the moment there is only one.
|
||||||
return Projector.lastModified(1);
|
return Projector.lastModified(1);
|
||||||
}, function () {
|
}, function () {
|
||||||
|
// TODO: Use the current projector. At the moment there is only one
|
||||||
|
var projector = Projector.get(1);
|
||||||
|
if (projector) {
|
||||||
$scope.elements = [];
|
$scope.elements = [];
|
||||||
_.forEach(slides.getElements(Projector.get(1)), function(element) {
|
_.forEach(slides.getElements(projector), function(element) {
|
||||||
if (!element.error) {
|
if (!element.error) {
|
||||||
$scope.elements.push(element);
|
$scope.elements.push(element);
|
||||||
} else {
|
} else {
|
||||||
console.error("Error for slide " + element.name + ": " + element.error);
|
console.error("Error for slide " + element.name + ": " + element.error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
// TODO: Use the current projector. At the moment there is only one
|
||||||
$scope.scroll = -5 * Projector.get(1).scroll;
|
$scope.scroll = -5 * Projector.get(1).scroll;
|
||||||
$scope.scale = 100 + 20 * Projector.get(1).scale;
|
$scope.scale = 100 + 20 * Projector.get(1).scale;
|
||||||
});
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
@ -150,9 +159,6 @@ angular.module('OpenSlidesApp.core.projector', ['OpenSlidesApp.core'])
|
|||||||
// Add it to the coresponding get_requirements method of the ProjectorElement
|
// Add it to the coresponding get_requirements method of the ProjectorElement
|
||||||
// class.
|
// class.
|
||||||
var id = $scope.element.id;
|
var id = $scope.element.id;
|
||||||
Customslide.find(id).then(function(customslide) {
|
|
||||||
Customslide.loadRelations(customslide, 'agenda_item');
|
|
||||||
});
|
|
||||||
Customslide.bindOne(id, $scope, 'customslide');
|
Customslide.bindOne(id, $scope, 'customslide');
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
@ -18,6 +18,10 @@ angular.module('OpenSlidesApp.core.site', [
|
|||||||
'ui.tinymce',
|
'ui.tinymce',
|
||||||
'luegg.directives',
|
'luegg.directives',
|
||||||
])
|
])
|
||||||
|
|
||||||
|
// Can be used to find out if the projector or the side is used
|
||||||
|
.constant('REALM', 'site')
|
||||||
|
|
||||||
.factory('PdfMakeDocumentProvider', [
|
.factory('PdfMakeDocumentProvider', [
|
||||||
'gettextCatalog',
|
'gettextCatalog',
|
||||||
'Config',
|
'Config',
|
||||||
@ -802,6 +806,14 @@ angular.module('OpenSlidesApp.core.site', [
|
|||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
|
// Load the global data on startup
|
||||||
|
.run([
|
||||||
|
'loadGlobalData',
|
||||||
|
function(loadGlobalData) {
|
||||||
|
loadGlobalData();
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
// Options for TinyMCE editor used in various create and edit views.
|
// Options for TinyMCE editor used in various create and edit views.
|
||||||
.factory('Editor', [
|
.factory('Editor', [
|
||||||
'gettextCatalog',
|
'gettextCatalog',
|
||||||
|
@ -177,9 +177,9 @@ class ProjectorViewSet(ReadOnlyModelViewSet):
|
|||||||
"""
|
"""
|
||||||
Returns True if the user has required permissions.
|
Returns True if the user has required permissions.
|
||||||
"""
|
"""
|
||||||
if self.action == 'retrieve':
|
if self.action in ('list', 'retrieve'):
|
||||||
result = self.get_access_permissions().can_retrieve(self.request.user)
|
result = self.get_access_permissions().check_permissions(self.request.user)
|
||||||
elif self.action in ('metadata', 'list'):
|
elif self.action == 'metadata':
|
||||||
result = self.request.user.has_perm('core.can_see_projector')
|
result = self.request.user.has_perm('core.can_see_projector')
|
||||||
elif self.action in ('activate_elements', 'prune_elements', 'update_elements',
|
elif self.action in ('activate_elements', 'prune_elements', 'update_elements',
|
||||||
'deactivate_elements', 'clear_elements', 'control_view', 'set_resolution'):
|
'deactivate_elements', 'clear_elements', 'control_view', 'set_resolution'):
|
||||||
@ -433,8 +433,8 @@ class CustomSlideViewSet(ModelViewSet):
|
|||||||
"""
|
"""
|
||||||
Returns True if the user has required permissions.
|
Returns True if the user has required permissions.
|
||||||
"""
|
"""
|
||||||
if self.action == 'retrieve':
|
if self.action in ('list', 'retrieve'):
|
||||||
result = self.get_access_permissions().can_retrieve(self.request.user)
|
result = self.get_access_permissions().check_permissions(self.request.user)
|
||||||
else:
|
else:
|
||||||
result = self.request.user.has_perm('core.can_manage_projector')
|
result = self.request.user.has_perm('core.can_manage_projector')
|
||||||
return result
|
return result
|
||||||
@ -454,9 +454,9 @@ class TagViewSet(ModelViewSet):
|
|||||||
"""
|
"""
|
||||||
Returns True if the user has required permissions.
|
Returns True if the user has required permissions.
|
||||||
"""
|
"""
|
||||||
if self.action == 'retrieve':
|
if self.action in ('list', 'retrieve'):
|
||||||
result = self.get_access_permissions().can_retrieve(self.request.user)
|
result = self.get_access_permissions().check_permissions(self.request.user)
|
||||||
elif self.action in ('metadata', 'list'):
|
elif self.action == 'metadata':
|
||||||
# Every authenticated user can see the metadata and list tags.
|
# Every authenticated user can see the metadata and list tags.
|
||||||
# Anonymous users can do so if they are enabled.
|
# Anonymous users can do so if they are enabled.
|
||||||
result = self.request.user.is_authenticated() or config['general_system_enable_anonymous']
|
result = self.request.user.is_authenticated() or config['general_system_enable_anonymous']
|
||||||
@ -510,9 +510,9 @@ class ConfigViewSet(ViewSet):
|
|||||||
"""
|
"""
|
||||||
Returns True if the user has required permissions.
|
Returns True if the user has required permissions.
|
||||||
"""
|
"""
|
||||||
if self.action == 'retrieve':
|
if self.action in ('list', 'retrieve'):
|
||||||
result = self.get_access_permissions().can_retrieve(self.request.user)
|
result = self.get_access_permissions().check_permissions(self.request.user)
|
||||||
elif self.action in ('metadata', 'list'):
|
elif self.action == 'metadata':
|
||||||
# Every authenticated user can see the metadata and list or
|
# Every authenticated user can see the metadata and list or
|
||||||
# retrieve the config. Anonymous users can do so if they are
|
# retrieve the config. Anonymous users can do so if they are
|
||||||
# enabled.
|
# enabled.
|
||||||
@ -579,13 +579,13 @@ class ChatMessageViewSet(ModelViewSet):
|
|||||||
"""
|
"""
|
||||||
Returns True if the user has required permissions.
|
Returns True if the user has required permissions.
|
||||||
"""
|
"""
|
||||||
if self.action == 'retrieve':
|
if self.action in ('list', 'retrieve'):
|
||||||
result = self.get_access_permissions().can_retrieve(self.request.user)
|
result = self.get_access_permissions().check_permissions(self.request.user)
|
||||||
else:
|
else:
|
||||||
# We do not want anonymous users to use the chat even the anonymous
|
# We do not want anonymous users to use the chat even the anonymous
|
||||||
# group has the permission core.can_use_chat.
|
# group has the permission core.can_use_chat.
|
||||||
result = (
|
result = (
|
||||||
self.action in ('metadata', 'list', 'create') and
|
self.action in ('metadata', 'create') and
|
||||||
self.request.user.is_authenticated() and
|
self.request.user.is_authenticated() and
|
||||||
self.request.user.has_perm('core.can_use_chat'))
|
self.request.user.has_perm('core.can_use_chat'))
|
||||||
return result
|
return result
|
||||||
|
@ -145,5 +145,8 @@ CHANNEL_LAYERS = {
|
|||||||
'default': {
|
'default': {
|
||||||
'BACKEND': 'asgiref.inmemory.ChannelLayer',
|
'BACKEND': 'asgiref.inmemory.ChannelLayer',
|
||||||
'ROUTING': 'openslides.routing.channel_routing',
|
'ROUTING': 'openslides.routing.channel_routing',
|
||||||
|
'CONFIG': {
|
||||||
|
'capacity': 1000,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ class MediafileAccessPermissions(BaseAccessPermissions):
|
|||||||
"""
|
"""
|
||||||
Access permissions container for Mediafile and MediafileViewSet.
|
Access permissions container for Mediafile and MediafileViewSet.
|
||||||
"""
|
"""
|
||||||
def can_retrieve(self, user):
|
def check_permissions(self, user):
|
||||||
"""
|
"""
|
||||||
Returns True if the user has read access model instances.
|
Returns True if the user has read access model instances.
|
||||||
"""
|
"""
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
from openslides.core.exceptions import ProjectorException
|
from ..core.exceptions import ProjectorException
|
||||||
from openslides.utils.projector import ProjectorElement, ProjectorRequirement
|
from ..utils.projector import ProjectorElement
|
||||||
|
|
||||||
from .models import Mediafile
|
from .models import Mediafile
|
||||||
from .views import MediafileViewSet
|
|
||||||
|
|
||||||
|
|
||||||
class MediafileSlide(ProjectorElement):
|
class MediafileSlide(ProjectorElement):
|
||||||
@ -12,15 +10,14 @@ class MediafileSlide(ProjectorElement):
|
|||||||
name = 'mediafiles/mediafile'
|
name = 'mediafiles/mediafile'
|
||||||
|
|
||||||
def check_data(self):
|
def check_data(self):
|
||||||
try:
|
if not Mediafile.objects.filter(pk=self.config_entry.get('id')).exists():
|
||||||
Mediafile.objects.get(pk=self.config_entry.get('id'))
|
|
||||||
except Mediafile.DoesNotExist:
|
|
||||||
raise ProjectorException('File does not exist.')
|
raise ProjectorException('File does not exist.')
|
||||||
|
|
||||||
def get_requirements(self, config_entry):
|
def get_requirements(self, config_entry):
|
||||||
pk = config_entry.get('id')
|
try:
|
||||||
if pk is not None:
|
mediafile = Mediafile.objects.get(pk=config_entry.get('id'))
|
||||||
yield ProjectorRequirement(
|
except Mediafile.DoesNotExist:
|
||||||
view_class=MediafileViewSet,
|
# Mediafile does not exist. Just do nothing.
|
||||||
view_action='retrieve',
|
pass
|
||||||
pk=str(pk))
|
else:
|
||||||
|
yield mediafile
|
||||||
|
@ -18,11 +18,9 @@ angular.module('OpenSlidesApp.mediafiles.projector', ['OpenSlidesApp.mediafiles'
|
|||||||
'Mediafile',
|
'Mediafile',
|
||||||
function($scope, Mediafile) {
|
function($scope, Mediafile) {
|
||||||
// load mediafile object
|
// load mediafile object
|
||||||
var mediafile = Mediafile.find($scope.element.id);
|
var mediafile = Mediafile.get($scope.element.id);
|
||||||
mediafile.then(function(mediafile) {
|
|
||||||
$scope.pdfName = mediafile.title;
|
$scope.pdfName = mediafile.title;
|
||||||
$scope.pdfUrl = mediafile.mediafileUrl;
|
$scope.pdfUrl = mediafile.mediafileUrl;
|
||||||
});
|
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@ -19,9 +19,9 @@ class MediafileViewSet(ModelViewSet):
|
|||||||
"""
|
"""
|
||||||
Returns True if the user has required permissions.
|
Returns True if the user has required permissions.
|
||||||
"""
|
"""
|
||||||
if self.action == 'retrieve':
|
if self.action in ('list', 'retrieve'):
|
||||||
result = self.get_access_permissions().can_retrieve(self.request.user)
|
result = self.get_access_permissions().check_permissions(self.request.user)
|
||||||
elif self.action in ('metadata', 'list'):
|
elif self.action == 'metadata':
|
||||||
result = self.request.user.has_perm('mediafiles.can_see')
|
result = self.request.user.has_perm('mediafiles.can_see')
|
||||||
elif self.action == 'create':
|
elif self.action == 'create':
|
||||||
result = (self.request.user.has_perm('mediafiles.can_see') and
|
result = (self.request.user.has_perm('mediafiles.can_see') and
|
||||||
|
@ -10,7 +10,7 @@ class MotionAccessPermissions(BaseAccessPermissions):
|
|||||||
"""
|
"""
|
||||||
Access permissions container for Motion and MotionViewSet.
|
Access permissions container for Motion and MotionViewSet.
|
||||||
"""
|
"""
|
||||||
def can_retrieve(self, user):
|
def check_permissions(self, user):
|
||||||
"""
|
"""
|
||||||
Returns True if the user has read access model instances.
|
Returns True if the user has read access model instances.
|
||||||
"""
|
"""
|
||||||
@ -43,6 +43,21 @@ class MotionAccessPermissions(BaseAccessPermissions):
|
|||||||
pass
|
pass
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
def get_projector_data(self, full_data):
|
||||||
|
"""
|
||||||
|
Returns the restricted serialized data for the instance prepared
|
||||||
|
for the projector. Removes several fields.
|
||||||
|
"""
|
||||||
|
data = full_data.copy()
|
||||||
|
for i, field in enumerate(self.get_comments_config_fields()):
|
||||||
|
if not field.get('public'):
|
||||||
|
try:
|
||||||
|
data['comments'][i] = None
|
||||||
|
except IndexError:
|
||||||
|
# No data in range. Just do nothing.
|
||||||
|
pass
|
||||||
|
return data
|
||||||
|
|
||||||
def get_comments_config_fields(self):
|
def get_comments_config_fields(self):
|
||||||
"""
|
"""
|
||||||
Take input from config field and parse it. It can be some
|
Take input from config field and parse it. It can be some
|
||||||
@ -110,7 +125,7 @@ class CategoryAccessPermissions(BaseAccessPermissions):
|
|||||||
"""
|
"""
|
||||||
Access permissions container for Category and CategoryViewSet.
|
Access permissions container for Category and CategoryViewSet.
|
||||||
"""
|
"""
|
||||||
def can_retrieve(self, user):
|
def check_permissions(self, user):
|
||||||
"""
|
"""
|
||||||
Returns True if the user has read access model instances.
|
Returns True if the user has read access model instances.
|
||||||
"""
|
"""
|
||||||
@ -129,7 +144,7 @@ class WorkflowAccessPermissions(BaseAccessPermissions):
|
|||||||
"""
|
"""
|
||||||
Access permissions container for Workflow and WorkflowViewSet.
|
Access permissions container for Workflow and WorkflowViewSet.
|
||||||
"""
|
"""
|
||||||
def can_retrieve(self, user):
|
def check_permissions(self, user):
|
||||||
"""
|
"""
|
||||||
Returns True if the user has read access model instances.
|
Returns True if the user has read access model instances.
|
||||||
"""
|
"""
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import jsonfield.fields
|
import jsonfield.fields
|
||||||
|
|
||||||
from django.db import migrations
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,66 +1,27 @@
|
|||||||
from openslides.core.exceptions import ProjectorException
|
from ..core.exceptions import ProjectorException
|
||||||
from openslides.core.views import TagViewSet
|
from ..utils.projector import ProjectorElement
|
||||||
from openslides.utils.projector import ProjectorElement, ProjectorRequirement
|
|
||||||
|
|
||||||
from .models import Motion
|
from .models import Motion
|
||||||
from .views import CategoryViewSet, MotionViewSet, WorkflowViewSet
|
|
||||||
|
|
||||||
|
|
||||||
class MotionSlide(ProjectorElement):
|
class MotionSlide(ProjectorElement):
|
||||||
"""
|
"""
|
||||||
Slide definitions for Motion model.
|
Slide definitions for Motion model.
|
||||||
|
|
||||||
Set 'id' to get a detail slide. Omit it to get a list slide.
|
|
||||||
"""
|
"""
|
||||||
name = 'motions/motion'
|
name = 'motions/motion'
|
||||||
|
|
||||||
def check_data(self):
|
def check_data(self):
|
||||||
pk = self.config_entry.get('id')
|
if not Motion.objects.filter(pk=self.config_entry.get('id')).exists():
|
||||||
if pk is not None:
|
|
||||||
# Detail slide.
|
|
||||||
if not Motion.objects.filter(pk=pk).exists():
|
|
||||||
raise ProjectorException('Motion does not exist.')
|
raise ProjectorException('Motion does not exist.')
|
||||||
|
|
||||||
def get_requirements(self, config_entry):
|
def get_requirements(self, config_entry):
|
||||||
pk = config_entry.get('id')
|
|
||||||
if pk is None:
|
|
||||||
# List slide. Related objects like users and tags are not unlocked.
|
|
||||||
yield ProjectorRequirement(
|
|
||||||
view_class=MotionViewSet,
|
|
||||||
view_action='list')
|
|
||||||
else:
|
|
||||||
# Detail slide.
|
|
||||||
try:
|
try:
|
||||||
motion = Motion.objects.get(pk=pk)
|
motion = Motion.objects.get(pk=config_entry.get('id'))
|
||||||
except Motion.DoesNotExist:
|
except Motion.DoesNotExist:
|
||||||
# Motion does not exist. Just do nothing.
|
# Motion does not exist. Just do nothing.
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
yield ProjectorRequirement(
|
yield motion
|
||||||
view_class=MotionViewSet,
|
yield motion.agenda_item
|
||||||
view_action='retrieve',
|
yield motion.state.workflow
|
||||||
pk=str(motion.pk))
|
yield from motion.submitters.all()
|
||||||
if motion.category:
|
yield from motion.supporters.all()
|
||||||
yield ProjectorRequirement(
|
|
||||||
view_class=CategoryViewSet,
|
|
||||||
view_action='retrieve',
|
|
||||||
pk=str(motion.category.pk))
|
|
||||||
yield ProjectorRequirement(
|
|
||||||
view_class=WorkflowViewSet,
|
|
||||||
view_action='retrieve',
|
|
||||||
pk=str(motion.workflow))
|
|
||||||
for submitter in motion.submitters.all():
|
|
||||||
yield ProjectorRequirement(
|
|
||||||
view_class=submitter.get_view_class(),
|
|
||||||
view_action='retrieve',
|
|
||||||
pk=str(submitter.pk))
|
|
||||||
for supporter in motion.supporters.all():
|
|
||||||
yield ProjectorRequirement(
|
|
||||||
view_class=supporter.get_view_class(),
|
|
||||||
view_action='retrieve',
|
|
||||||
pk=str(supporter.pk))
|
|
||||||
for tag in motion.tags.all():
|
|
||||||
yield ProjectorRequirement(
|
|
||||||
view_class=TagViewSet,
|
|
||||||
view_action='retrieve',
|
|
||||||
pk=str(tag.pk))
|
|
||||||
|
@ -60,14 +60,6 @@ angular.module('OpenSlidesApp.motions', [
|
|||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
// Load all MotionWorkflows at startup
|
|
||||||
.run([
|
|
||||||
'Workflow',
|
|
||||||
function (Workflow) {
|
|
||||||
Workflow.findAll();
|
|
||||||
}
|
|
||||||
])
|
|
||||||
|
|
||||||
.factory('MotionPoll', [
|
.factory('MotionPoll', [
|
||||||
'DS',
|
'DS',
|
||||||
'gettextCatalog',
|
'gettextCatalog',
|
||||||
@ -452,7 +444,8 @@ angular.module('OpenSlidesApp.motions', [
|
|||||||
.run([
|
.run([
|
||||||
'Motion',
|
'Motion',
|
||||||
'Category',
|
'Category',
|
||||||
function(Motion, Category) {}
|
'Workflow',
|
||||||
|
function(Motion, Category, Workflow) {}
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
||||||
|
@ -23,18 +23,8 @@ angular.module('OpenSlidesApp.motions.projector', ['OpenSlidesApp.motions'])
|
|||||||
// Add it to the coresponding get_requirements method of the ProjectorElement
|
// Add it to the coresponding get_requirements method of the ProjectorElement
|
||||||
// class.
|
// class.
|
||||||
var id = $scope.element.id;
|
var id = $scope.element.id;
|
||||||
|
|
||||||
// load motion object and related agenda item
|
|
||||||
Motion.find(id).then(function(motion) {
|
|
||||||
Motion.loadRelations(motion, 'agenda_item');
|
|
||||||
});
|
|
||||||
Motion.bindOne(id, $scope, 'motion');
|
Motion.bindOne(id, $scope, 'motion');
|
||||||
|
|
||||||
// load all users
|
|
||||||
User.findAll();
|
|
||||||
User.bindAll({}, $scope, 'users');
|
User.bindAll({}, $scope, 'users');
|
||||||
|
|
||||||
Config.bindOne('motions_default_line_numbering', $scope, 'line_numbering');
|
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@ -525,6 +525,14 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions', 'OpenSlid
|
|||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
|
// Load all MotionWorkflows at startup
|
||||||
|
.run([
|
||||||
|
'Workflow',
|
||||||
|
function (Workflow) {
|
||||||
|
Workflow.findAll();
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
// Service for generic motion form (create and update)
|
// Service for generic motion form (create and update)
|
||||||
.factory('MotionForm', [
|
.factory('MotionForm', [
|
||||||
'gettextCatalog',
|
'gettextCatalog',
|
||||||
|
@ -71,7 +71,7 @@
|
|||||||
|
|
||||||
<!-- Text -->
|
<!-- Text -->
|
||||||
<div ng-bind-html="motion.getTextWithLineBreaks() | trusted"
|
<div ng-bind-html="motion.getTextWithLineBreaks() | trusted"
|
||||||
class="motion-text line-numbers-{{ line_numbering.value }}"></div>
|
class="motion-text line-numbers-{{ config('motions_default_line_numbering') }}"></div>
|
||||||
|
|
||||||
<!-- Reason -->
|
<!-- Reason -->
|
||||||
<h3 ng-if="motion.getReason()" translate>Reason</h3>
|
<h3 ng-if="motion.getReason()" translate>Reason</h3>
|
||||||
|
@ -53,9 +53,9 @@ class MotionViewSet(ModelViewSet):
|
|||||||
"""
|
"""
|
||||||
Returns True if the user has required permissions.
|
Returns True if the user has required permissions.
|
||||||
"""
|
"""
|
||||||
if self.action == 'retrieve':
|
if self.action in ('list', 'retrieve'):
|
||||||
result = self.get_access_permissions().can_retrieve(self.request.user)
|
result = self.get_access_permissions().check_permissions(self.request.user)
|
||||||
elif self.action in ('metadata', 'list', 'partial_update', 'update'):
|
elif self.action in ('metadata', 'partial_update', 'update'):
|
||||||
result = self.request.user.has_perm('motions.can_see')
|
result = self.request.user.has_perm('motions.can_see')
|
||||||
# For partial_update and update requests the rest of the check is
|
# For partial_update and update requests the rest of the check is
|
||||||
# done in the update method. See below.
|
# done in the update method. See below.
|
||||||
@ -373,9 +373,9 @@ class CategoryViewSet(ModelViewSet):
|
|||||||
"""
|
"""
|
||||||
Returns True if the user has required permissions.
|
Returns True if the user has required permissions.
|
||||||
"""
|
"""
|
||||||
if self.action == 'retrieve':
|
if self.action in ('list', 'retrieve'):
|
||||||
result = self.get_access_permissions().can_retrieve(self.request.user)
|
result = self.get_access_permissions().check_permissions(self.request.user)
|
||||||
elif self.action in ('metadata', 'list'):
|
elif self.action == 'metadata':
|
||||||
result = self.request.user.has_perm('motions.can_see')
|
result = self.request.user.has_perm('motions.can_see')
|
||||||
elif self.action in ('create', 'partial_update', 'update', 'destroy', 'numbering'):
|
elif self.action in ('create', 'partial_update', 'update', 'destroy', 'numbering'):
|
||||||
result = (self.request.user.has_perm('motions.can_see') and
|
result = (self.request.user.has_perm('motions.can_see') and
|
||||||
@ -450,9 +450,9 @@ class WorkflowViewSet(ModelViewSet):
|
|||||||
"""
|
"""
|
||||||
Returns True if the user has required permissions.
|
Returns True if the user has required permissions.
|
||||||
"""
|
"""
|
||||||
if self.action == 'retrieve':
|
if self.action in ('list', 'retrieve'):
|
||||||
result = self.get_access_permissions().can_retrieve(self.request.user)
|
result = self.get_access_permissions().check_permissions(self.request.user)
|
||||||
elif self.action in ('metadata', 'list'):
|
elif self.action == 'metadata':
|
||||||
result = self.request.user.has_perm('motions.can_see')
|
result = self.request.user.has_perm('motions.can_see')
|
||||||
elif self.action in ('create', 'partial_update', 'update', 'destroy'):
|
elif self.action in ('create', 'partial_update', 'update', 'destroy'):
|
||||||
result = (self.request.user.has_perm('motions.can_see') and
|
result = (self.request.user.has_perm('motions.can_see') and
|
||||||
|
@ -1,9 +1,25 @@
|
|||||||
from channels.routing import route
|
from channels.routing import include, route
|
||||||
|
|
||||||
from openslides.utils.autoupdate import send_data, ws_add, ws_disconnect
|
from openslides.utils.autoupdate import (
|
||||||
|
send_data,
|
||||||
|
ws_add_projector,
|
||||||
|
ws_add_site,
|
||||||
|
ws_disconnect_projector,
|
||||||
|
ws_disconnect_site,
|
||||||
|
)
|
||||||
|
|
||||||
|
projector_routing = [
|
||||||
|
route("websocket.connect", ws_add_projector),
|
||||||
|
route("websocket.disconnect", ws_disconnect_projector),
|
||||||
|
]
|
||||||
|
|
||||||
|
site_routing = [
|
||||||
|
route("websocket.connect", ws_add_site),
|
||||||
|
route("websocket.disconnect", ws_disconnect_site),
|
||||||
|
]
|
||||||
|
|
||||||
channel_routing = [
|
channel_routing = [
|
||||||
route("websocket.connect", ws_add, path='/ws/'),
|
include(projector_routing, path=r'^/ws/projector/(?P<projector_id>\d+)/$'),
|
||||||
route("websocket.disconnect", ws_disconnect),
|
include(site_routing, path=r'^/ws/site/$'),
|
||||||
route("autoupdate.send_data", send_data),
|
route("autoupdate.send_data", send_data),
|
||||||
]
|
]
|
||||||
|
@ -5,7 +5,7 @@ class UserAccessPermissions(BaseAccessPermissions):
|
|||||||
"""
|
"""
|
||||||
Access permissions container for User and UserViewSet.
|
Access permissions container for User and UserViewSet.
|
||||||
"""
|
"""
|
||||||
def can_retrieve(self, user):
|
def check_permissions(self, user):
|
||||||
"""
|
"""
|
||||||
Returns True if the user has read access model instances.
|
Returns True if the user has read access model instances.
|
||||||
"""
|
"""
|
||||||
@ -33,12 +33,34 @@ class UserAccessPermissions(BaseAccessPermissions):
|
|||||||
"""
|
"""
|
||||||
from .serializers import USERCANSEESERIALIZER_FIELDS, USERCANSEEEXTRASERIALIZER_FIELDS
|
from .serializers import USERCANSEESERIALIZER_FIELDS, USERCANSEEEXTRASERIALIZER_FIELDS
|
||||||
|
|
||||||
if user.has_perm('users.can_manage'):
|
NO_DATA = 0
|
||||||
data = full_data
|
LITTLE_DATA = 1
|
||||||
else:
|
MANY_DATA = 2
|
||||||
|
FULL_DATA = 3
|
||||||
|
|
||||||
|
# Check user permissions.
|
||||||
|
if user.has_perm('users.can_see_name'):
|
||||||
if user.has_perm('users.can_see_extra_data'):
|
if user.has_perm('users.can_see_extra_data'):
|
||||||
|
if user.has_perm('users.can_manage'):
|
||||||
|
case = FULL_DATA
|
||||||
|
else:
|
||||||
|
case = MANY_DATA
|
||||||
|
else:
|
||||||
|
case = LITTLE_DATA
|
||||||
|
else:
|
||||||
|
case = NO_DATA
|
||||||
|
|
||||||
|
# Setup data.
|
||||||
|
if case == FULL_DATA:
|
||||||
|
data = full_data
|
||||||
|
elif case == NO_DATA:
|
||||||
|
data = None
|
||||||
|
else:
|
||||||
|
# case in (LITTLE_DATA, ḾANY_DATA)
|
||||||
|
if case == MANY_DATA:
|
||||||
fields = USERCANSEEEXTRASERIALIZER_FIELDS
|
fields = USERCANSEEEXTRASERIALIZER_FIELDS
|
||||||
else:
|
else:
|
||||||
|
# case == LITTLE_DATA
|
||||||
fields = USERCANSEESERIALIZER_FIELDS
|
fields = USERCANSEESERIALIZER_FIELDS
|
||||||
# Let only some fields pass this method.
|
# Let only some fields pass this method.
|
||||||
data = {}
|
data = {}
|
||||||
@ -46,3 +68,17 @@ class UserAccessPermissions(BaseAccessPermissions):
|
|||||||
if key in fields:
|
if key in fields:
|
||||||
data[key] = full_data[key]
|
data[key] = full_data[key]
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
def get_projector_data(self, full_data):
|
||||||
|
"""
|
||||||
|
Returns the restricted serialized data for the instance prepared
|
||||||
|
for the projector. Removes several fields.
|
||||||
|
"""
|
||||||
|
from .serializers import USERCANSEESERIALIZER_FIELDS
|
||||||
|
|
||||||
|
# Let only some fields pass this method.
|
||||||
|
data = {}
|
||||||
|
for key in full_data.keys():
|
||||||
|
if key in USERCANSEESERIALIZER_FIELDS:
|
||||||
|
data[key] = full_data[key]
|
||||||
|
return data
|
||||||
|
@ -207,14 +207,6 @@ class User(RESTModelMixin, PermissionsMixin, AbstractBaseUser):
|
|||||||
# Return result
|
# Return result
|
||||||
return name
|
return name
|
||||||
|
|
||||||
def get_view_class(self):
|
|
||||||
"""
|
|
||||||
Returns the main view class (viewset class) that should be unlocked
|
|
||||||
if the user (means its name) appears on a slide.
|
|
||||||
"""
|
|
||||||
from .views import UserViewSet
|
|
||||||
return UserViewSet
|
|
||||||
|
|
||||||
def get_search_index_string(self):
|
def get_search_index_string(self):
|
||||||
"""
|
"""
|
||||||
Returns a string that can be indexed for the search.
|
Returns a string that can be indexed for the search.
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
from ..core.exceptions import ProjectorException
|
from ..core.exceptions import ProjectorException
|
||||||
from ..utils.projector import ProjectorElement, ProjectorRequirement
|
from ..utils.projector import ProjectorElement
|
||||||
from .models import User
|
from .models import User
|
||||||
from .views import GroupViewSet, UserViewSet
|
|
||||||
|
|
||||||
|
|
||||||
class UserSlide(ProjectorElement):
|
class UserSlide(ProjectorElement):
|
||||||
"""
|
"""
|
||||||
Slide definitions for user model.
|
Slide definitions for User model.
|
||||||
"""
|
"""
|
||||||
name = 'users/user'
|
name = 'users/user'
|
||||||
|
|
||||||
@ -15,20 +14,10 @@ class UserSlide(ProjectorElement):
|
|||||||
raise ProjectorException('User does not exist.')
|
raise ProjectorException('User does not exist.')
|
||||||
|
|
||||||
def get_requirements(self, config_entry):
|
def get_requirements(self, config_entry):
|
||||||
pk = config_entry.get('id')
|
|
||||||
if pk is not None:
|
|
||||||
try:
|
try:
|
||||||
user = User.objects.get(pk=pk)
|
user = User.objects.get(pk=config_entry.get('id'))
|
||||||
except User.DoesNotExist:
|
except User.DoesNotExist:
|
||||||
# User does not exist. Just do nothing.
|
# User does not exist. Just do nothing.
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
yield ProjectorRequirement(
|
yield user
|
||||||
view_class=UserViewSet,
|
|
||||||
view_action='retrieve',
|
|
||||||
pk=str(user.pk))
|
|
||||||
for group in user.groups.all():
|
|
||||||
yield ProjectorRequirement(
|
|
||||||
view_class=GroupViewSet,
|
|
||||||
view_action='retrieve',
|
|
||||||
pk=str(group.pk))
|
|
||||||
|
@ -11,7 +11,6 @@ from ..utils.rest_api import (
|
|||||||
)
|
)
|
||||||
from .models import Group, User
|
from .models import Group, User
|
||||||
|
|
||||||
|
|
||||||
USERCANSEESERIALIZER_FIELDS = (
|
USERCANSEESERIALIZER_FIELDS = (
|
||||||
'id',
|
'id',
|
||||||
'username',
|
'username',
|
||||||
|
@ -21,7 +21,6 @@ angular.module('OpenSlidesApp.users.projector', ['OpenSlidesApp.users'])
|
|||||||
// Add it to the coresponding get_requirements method of the ProjectorElement
|
// Add it to the coresponding get_requirements method of the ProjectorElement
|
||||||
// class.
|
// class.
|
||||||
var id = $scope.element.id;
|
var id = $scope.element.id;
|
||||||
User.find(id);
|
|
||||||
User.bindOne(id, $scope, 'user');
|
User.bindOne(id, $scope, 'user');
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
@ -37,9 +37,9 @@ class UserViewSet(ModelViewSet):
|
|||||||
"""
|
"""
|
||||||
Returns True if the user has required permissions.
|
Returns True if the user has required permissions.
|
||||||
"""
|
"""
|
||||||
if self.action == 'retrieve':
|
if self.action in ('list', 'retrieve'):
|
||||||
result = self.get_access_permissions().can_retrieve(self.request.user)
|
result = self.get_access_permissions().check_permissions(self.request.user)
|
||||||
elif self.action in ('metadata', 'list', 'update', 'partial_update'):
|
elif self.action in ('metadata', 'update', 'partial_update'):
|
||||||
result = self.request.user.has_perm('users.can_see_name')
|
result = self.request.user.has_perm('users.can_see_name')
|
||||||
elif self.action in ('create', 'destroy', 'reset_password'):
|
elif self.action in ('create', 'destroy', 'reset_password'):
|
||||||
result = (self.request.user.has_perm('users.can_see_name') and
|
result = (self.request.user.has_perm('users.can_see_name') and
|
||||||
|
@ -33,7 +33,7 @@ class BaseAccessPermissions(object, metaclass=SignalConnectMetaClass):
|
|||||||
if not cls.__name__ == 'BaseAccessPermissions':
|
if not cls.__name__ == 'BaseAccessPermissions':
|
||||||
return cls.__name__
|
return cls.__name__
|
||||||
|
|
||||||
def can_retrieve(self, user):
|
def check_permissions(self, user):
|
||||||
"""
|
"""
|
||||||
Returns True if the user has read access to model instances.
|
Returns True if the user has read access to model instances.
|
||||||
"""
|
"""
|
||||||
@ -65,13 +65,21 @@ class BaseAccessPermissions(object, metaclass=SignalConnectMetaClass):
|
|||||||
if the user has limited access. Default: Returns full data if the
|
if the user has limited access. Default: Returns full data if the
|
||||||
user has read access to model instances.
|
user has read access to model instances.
|
||||||
|
|
||||||
Hint: You should override this method if your
|
Hint: You should override this method if your get_serializer_class()
|
||||||
get_serializer_class() method may return different serializer for
|
method returns different serializers for different users or if you
|
||||||
different users or if you have access restrictions in your view or
|
have access restrictions in your view or viewset in methods like
|
||||||
viewset in methods like retrieve() or check_object_permissions().
|
retrieve(), list() or check_object_permissions().
|
||||||
"""
|
"""
|
||||||
if self.can_retrieve(user):
|
if self.check_permissions(user):
|
||||||
data = full_data
|
data = full_data
|
||||||
else:
|
else:
|
||||||
data = None
|
data = None
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
def get_projector_data(self, full_data):
|
||||||
|
"""
|
||||||
|
Returns the serialized data for the projector. Returns None if has no
|
||||||
|
access to this specific data. Returns reduced data if the user has
|
||||||
|
limited access. Default: Returns full data.
|
||||||
|
"""
|
||||||
|
return full_data
|
||||||
|
@ -4,13 +4,14 @@ import json
|
|||||||
from asgiref.inmemory import ChannelLayer
|
from asgiref.inmemory import ChannelLayer
|
||||||
from channels import Channel, Group
|
from channels import Channel, Group
|
||||||
from channels.auth import channel_session_user, channel_session_user_from_http
|
from channels.auth import channel_session_user, channel_session_user_from_http
|
||||||
from django.apps import apps
|
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
|
from ..core.config import config
|
||||||
|
from ..core.models import Projector
|
||||||
from ..users.auth import AnonymousUser
|
from ..users.auth import AnonymousUser
|
||||||
from ..users.models import User
|
from ..users.models import User
|
||||||
from .access_permissions import BaseAccessPermissions
|
from .collection import CollectionElement
|
||||||
|
|
||||||
|
|
||||||
def get_logged_in_users():
|
def get_logged_in_users():
|
||||||
@ -22,37 +23,23 @@ 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_model_from_collection_string(collection_string):
|
def get_projector_element_data(projector):
|
||||||
"""
|
"""
|
||||||
Returns a model class which belongs to the argument collection_string.
|
Returns a list of dicts that are required for a specific projector.
|
||||||
"""
|
|
||||||
def model_generator():
|
|
||||||
"""
|
|
||||||
Yields all models of all apps.
|
|
||||||
"""
|
|
||||||
for app_config in apps.get_app_configs():
|
|
||||||
for model in app_config.get_models():
|
|
||||||
yield model
|
|
||||||
|
|
||||||
for model in model_generator():
|
The argument projector has to be a projector instance.
|
||||||
try:
|
"""
|
||||||
model_collection_string = model.get_collection_string()
|
output = []
|
||||||
except AttributeError:
|
for requirement in projector.get_all_requirements():
|
||||||
# Skip models which do not have the method get_collection_string.
|
required_collection_element = CollectionElement.from_instance(requirement)
|
||||||
pass
|
element_dict = required_collection_element.as_autoupdate_for_projector()
|
||||||
else:
|
if element_dict is not None:
|
||||||
if model_collection_string == collection_string:
|
output.append(element_dict)
|
||||||
# The model was found.
|
return output
|
||||||
break
|
|
||||||
else:
|
|
||||||
# No model was found in all apps.
|
|
||||||
raise ValueError('Invalid message. A valid collection_string is missing.')
|
|
||||||
return model
|
|
||||||
|
|
||||||
|
|
||||||
# Connected to websocket.connect
|
|
||||||
@channel_session_user_from_http
|
@channel_session_user_from_http
|
||||||
def ws_add(message):
|
def ws_add_site(message):
|
||||||
"""
|
"""
|
||||||
Adds the websocket connection to a group specific to the connecting user.
|
Adds the websocket connection to a group specific to the connecting user.
|
||||||
|
|
||||||
@ -61,45 +48,107 @@ def ws_add(message):
|
|||||||
Group('user-{}'.format(message.user.id)).add(message.reply_channel)
|
Group('user-{}'.format(message.user.id)).add(message.reply_channel)
|
||||||
|
|
||||||
|
|
||||||
# Connected to websocket.disconnect
|
|
||||||
@channel_session_user
|
@channel_session_user
|
||||||
def ws_disconnect(message):
|
def ws_disconnect_site(message):
|
||||||
|
"""
|
||||||
|
This function is called, when a client on the site disconnects.
|
||||||
|
"""
|
||||||
Group('user-{}'.format(message.user.id)).discard(message.reply_channel)
|
Group('user-{}'.format(message.user.id)).discard(message.reply_channel)
|
||||||
|
|
||||||
|
|
||||||
|
@channel_session_user_from_http
|
||||||
|
def ws_add_projector(message, projector_id):
|
||||||
|
"""
|
||||||
|
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 = message.user
|
||||||
|
# user is the django anonymous user. We have our own.
|
||||||
|
if user.is_anonymous:
|
||||||
|
user = AnonymousUser()
|
||||||
|
|
||||||
|
if not user.has_perm('core.can_see_projector'):
|
||||||
|
message.reply_channel.send({'text': 'No permissions to see this projector.'})
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
projector = Projector.objects.get(pk=projector_id)
|
||||||
|
except Projector.DoesNotExist:
|
||||||
|
message.reply_channel.send({'text': 'The projector {} does not exist.'.format(projector_id)})
|
||||||
|
else:
|
||||||
|
# At first, the client is added to the projector group, so it is
|
||||||
|
# informed if the data change.
|
||||||
|
Group('projector-{}'.format(projector_id)).add(message.reply_channel)
|
||||||
|
|
||||||
|
# Send all elements that are on the projector.
|
||||||
|
output = get_projector_element_data(projector)
|
||||||
|
|
||||||
|
# Send all config elements.
|
||||||
|
for key, value in config.items():
|
||||||
|
output.append({
|
||||||
|
'collection': config.get_collection_string(),
|
||||||
|
'id': key,
|
||||||
|
'action': 'changed',
|
||||||
|
'data': {'key': key, 'value': value}})
|
||||||
|
|
||||||
|
# Send the projector instance.
|
||||||
|
collection_element = CollectionElement.from_instance(projector)
|
||||||
|
output.append(collection_element.as_autoupdate_for_projector())
|
||||||
|
|
||||||
|
# Send all the data that was only collected before
|
||||||
|
message.reply_channel.send({'text': json.dumps(output)})
|
||||||
|
|
||||||
|
|
||||||
|
def ws_disconnect_projector(message, projector_id):
|
||||||
|
"""
|
||||||
|
This function is called, when a client on the projector disconnects.
|
||||||
|
"""
|
||||||
|
Group('projector-{}'.format(projector_id)).discard(message.reply_channel)
|
||||||
|
|
||||||
|
|
||||||
def send_data(message):
|
def send_data(message):
|
||||||
"""
|
"""
|
||||||
Informs all users about changed data.
|
Informs all users about changed data.
|
||||||
|
|
||||||
The argument message has to be a dict with the keywords collection_string
|
|
||||||
(string), pk (positive integer), id_deleted (boolean) and dispatch_uid
|
|
||||||
(string).
|
|
||||||
"""
|
"""
|
||||||
for access_permissions in BaseAccessPermissions.get_all():
|
collection_element = CollectionElement.from_values(**message)
|
||||||
if access_permissions.get_dispatch_uid() == message['dispatch_uid']:
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
raise ValueError('Invalid message. A valid dispatch_uid is missing.')
|
|
||||||
|
|
||||||
if not message['is_deleted']:
|
|
||||||
Model = get_model_from_collection_string(message['collection_string'])
|
|
||||||
instance = Model.objects.get(pk=message['pk'])
|
|
||||||
full_data = access_permissions.get_full_data(instance)
|
|
||||||
|
|
||||||
# Loop over all logged in users and the anonymous user.
|
# Loop over all logged in users and the anonymous user.
|
||||||
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 = {
|
output = collection_element.as_autoupdate_for_user(user)
|
||||||
'collection': message['collection_string'],
|
if output is None:
|
||||||
'id': message['pk'], # == instance.get_rest_pk()
|
|
||||||
'action': 'deleted' if message['is_deleted'] else 'changed'}
|
|
||||||
if not message['is_deleted']:
|
|
||||||
data = access_permissions.get_restricted_data(full_data, user)
|
|
||||||
if data is None:
|
|
||||||
# There are no data for the user so he can't see the object. Skip him.
|
# There are no data for the user so he can't see the object. Skip him.
|
||||||
continue
|
continue
|
||||||
output['data'] = data
|
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
|
||||||
|
# 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(message)
|
||||||
|
send_all = None # The decission is done later
|
||||||
|
|
||||||
|
for projector in projectors:
|
||||||
|
if send_all is None:
|
||||||
|
send_all = projector.need_full_update_for(message)
|
||||||
|
if send_all:
|
||||||
|
output = get_projector_element_data(projector)
|
||||||
|
else:
|
||||||
|
output = []
|
||||||
|
output.append(collection_element.as_autoupdate_for_projector())
|
||||||
|
if output:
|
||||||
|
Group('projector-{}'.format(projector.pk)).send(
|
||||||
|
{'text': json.dumps(output)})
|
||||||
|
|
||||||
|
|
||||||
def inform_changed_data(instance, is_deleted=False):
|
def inform_changed_data(instance, is_deleted=False):
|
||||||
@ -109,23 +158,20 @@ def inform_changed_data(instance, is_deleted=False):
|
|||||||
# Instance has no method get_root_rest_element. Just ignore it.
|
# Instance has no method get_root_rest_element. Just ignore it.
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
message_dict = {
|
collection_element = CollectionElement.from_instance(
|
||||||
'collection_string': root_instance.get_collection_string(),
|
root_instance,
|
||||||
'pk': root_instance.pk,
|
is_deleted=is_deleted and instance == root_instance)
|
||||||
'is_deleted': is_deleted and instance == root_instance,
|
|
||||||
'dispatch_uid': root_instance.get_access_permissions().get_dispatch_uid(),
|
|
||||||
}
|
|
||||||
|
|
||||||
# If currently there is an open database transaction, then the following
|
# If currently there is an open database transaction, then the following
|
||||||
# function is only called, when the transaction is commited. If there
|
# function is only called, when the transaction is commited. If there
|
||||||
# is currently no transaction, then the function is called immediately.
|
# is currently no transaction, then the function is called immediately.
|
||||||
def send_autoupdate(message):
|
def send_autoupdate():
|
||||||
try:
|
try:
|
||||||
Channel('autoupdate.send_data').send(message)
|
Channel('autoupdate.send_data').send(collection_element.as_channels_message())
|
||||||
except ChannelLayer.ChannelFull:
|
except ChannelLayer.ChannelFull:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
transaction.on_commit(lambda: send_autoupdate(message_dict))
|
transaction.on_commit(send_autoupdate)
|
||||||
|
|
||||||
|
|
||||||
def inform_changed_data_receiver(sender, instance, **kwargs):
|
def inform_changed_data_receiver(sender, instance, **kwargs):
|
||||||
|
154
openslides/utils/collection.py
Normal file
154
openslides/utils/collection.py
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
from django.apps import apps
|
||||||
|
|
||||||
|
|
||||||
|
class CollectionElement:
|
||||||
|
@classmethod
|
||||||
|
def from_instance(cls, instance, is_deleted=False):
|
||||||
|
"""
|
||||||
|
Returns a collection element from a database instance.
|
||||||
|
"""
|
||||||
|
return cls(instance=instance, is_deleted=is_deleted)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_values(cls, collection_string, id, is_deleted=False):
|
||||||
|
"""
|
||||||
|
Returns a collection element from a collection_string and an id.
|
||||||
|
"""
|
||||||
|
return cls(collection_string=collection_string, id=id, is_deleted=is_deleted)
|
||||||
|
|
||||||
|
def __init__(self, instance=None, is_deleted=False, collection_string=None, id=None):
|
||||||
|
"""
|
||||||
|
Do not use this. Use the methods from_instance() or from_values().
|
||||||
|
"""
|
||||||
|
if instance is not None:
|
||||||
|
self.collection_string = instance.get_collection_string()
|
||||||
|
self.id = instance.pk
|
||||||
|
elif collection_string is not None and id is not None:
|
||||||
|
self.collection_string = collection_string
|
||||||
|
self.id = id
|
||||||
|
else:
|
||||||
|
raise RuntimeError(
|
||||||
|
'Invalid state. Use CollectionElement.from_instance() or '
|
||||||
|
'CollectionElement.from_values() but not CollectionElement() '
|
||||||
|
'directly.')
|
||||||
|
self.instance = instance
|
||||||
|
self.deleted = is_deleted
|
||||||
|
|
||||||
|
def as_channels_message(self):
|
||||||
|
"""
|
||||||
|
Returns a dictonary that can be used to send the object through the
|
||||||
|
channels system.
|
||||||
|
"""
|
||||||
|
return {
|
||||||
|
'collection_string': self.collection_string,
|
||||||
|
'id': self.id,
|
||||||
|
'is_deleted': self.is_deleted()}
|
||||||
|
|
||||||
|
def as_autoupdate_for_user(self, user):
|
||||||
|
"""
|
||||||
|
Returns a dict that can be sent through the autoupdate system for a site
|
||||||
|
user.
|
||||||
|
|
||||||
|
Returns None if the user can not see the element.
|
||||||
|
"""
|
||||||
|
output = {
|
||||||
|
'collection': self.collection_string,
|
||||||
|
'id': self.id,
|
||||||
|
'action': 'deleted' if self.is_deleted() else 'changed',
|
||||||
|
}
|
||||||
|
if not self.is_deleted():
|
||||||
|
data = self.get_access_permissions().get_restricted_data(
|
||||||
|
self.get_full_data(), user)
|
||||||
|
if data is None:
|
||||||
|
# The user is not allowed to see this element. Reset output to None.
|
||||||
|
output = None
|
||||||
|
else:
|
||||||
|
output['data'] = data
|
||||||
|
return output
|
||||||
|
|
||||||
|
def as_autoupdate_for_projector(self):
|
||||||
|
"""
|
||||||
|
Returns a dict that can be sent through the autoupdate system for the
|
||||||
|
projector.
|
||||||
|
|
||||||
|
Returns None if the projector can not see the element.
|
||||||
|
"""
|
||||||
|
output = {
|
||||||
|
'collection': self.collection_string,
|
||||||
|
'id': self.id,
|
||||||
|
'action': 'deleted' if self.is_deleted() else 'changed',
|
||||||
|
}
|
||||||
|
if not self.is_deleted():
|
||||||
|
data = self.get_access_permissions().get_projector_data(
|
||||||
|
self.get_full_data())
|
||||||
|
if data is None:
|
||||||
|
# The user is not allowed to see this element. Reset output to None.
|
||||||
|
output = None
|
||||||
|
else:
|
||||||
|
output['data'] = data
|
||||||
|
return output
|
||||||
|
|
||||||
|
def get_model(self):
|
||||||
|
"""
|
||||||
|
Returns the django model that is used for this collection.
|
||||||
|
"""
|
||||||
|
return get_model_from_collection_string(self.collection_string)
|
||||||
|
|
||||||
|
def get_instance(self):
|
||||||
|
"""
|
||||||
|
Returns the instance as django object.
|
||||||
|
|
||||||
|
May raise a DoesNotExist exception.
|
||||||
|
"""
|
||||||
|
if self.is_deleted():
|
||||||
|
raise RuntimeError("The collection element is deleted.")
|
||||||
|
if self.instance is None:
|
||||||
|
self.instance = self.get_model().objects.get(pk=self.id)
|
||||||
|
return self.instance
|
||||||
|
|
||||||
|
def get_access_permissions(self):
|
||||||
|
"""
|
||||||
|
Returns the get_access_permissions object for the this collection element.
|
||||||
|
"""
|
||||||
|
return self.get_model().get_access_permissions()
|
||||||
|
|
||||||
|
def get_full_data(self):
|
||||||
|
"""
|
||||||
|
Returns the full_data of this collection_element from with all other
|
||||||
|
dics can be generated.
|
||||||
|
"""
|
||||||
|
return self.get_access_permissions().get_full_data(self.get_instance())
|
||||||
|
|
||||||
|
def is_deleted(self):
|
||||||
|
"""
|
||||||
|
Returns Ture if the item is marked as deleted.
|
||||||
|
"""
|
||||||
|
return self.deleted
|
||||||
|
|
||||||
|
|
||||||
|
def get_model_from_collection_string(collection_string):
|
||||||
|
"""
|
||||||
|
Returns a model class which belongs to the argument collection_string.
|
||||||
|
"""
|
||||||
|
def model_generator():
|
||||||
|
"""
|
||||||
|
Yields all models of all apps.
|
||||||
|
"""
|
||||||
|
for app_config in apps.get_app_configs():
|
||||||
|
for model in app_config.get_models():
|
||||||
|
yield model
|
||||||
|
|
||||||
|
for model in model_generator():
|
||||||
|
try:
|
||||||
|
model_collection_string = model.get_collection_string()
|
||||||
|
except AttributeError:
|
||||||
|
# Skip models which do not have the method get_collection_string.
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
if model_collection_string == collection_string:
|
||||||
|
# The model was found.
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
# No model was found in all apps.
|
||||||
|
raise ValueError('Invalid message. A valid collection_string is missing.')
|
||||||
|
return model
|
@ -31,12 +31,13 @@ class RESTModelMixin:
|
|||||||
"""
|
"""
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def get_access_permissions(self):
|
@classmethod
|
||||||
|
def get_access_permissions(cls):
|
||||||
"""
|
"""
|
||||||
Returns a container to handle access permissions for this model and
|
Returns a container to handle access permissions for this model and
|
||||||
its corresponding viewset.
|
its corresponding viewset.
|
||||||
"""
|
"""
|
||||||
return self.access_permissions
|
return cls.access_permissions
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_collection_string(cls):
|
def get_collection_string(cls):
|
||||||
|
@ -68,36 +68,7 @@ class ProjectorElement(object, metaclass=SignalConnectMetaClass):
|
|||||||
|
|
||||||
def get_requirements(self, config_entry):
|
def get_requirements(self, config_entry):
|
||||||
"""
|
"""
|
||||||
Returns an iterable of ProjectorRequirement instances to setup
|
Returns an iterable of instances that are required for this projector
|
||||||
which views should be accessable for projector clients if the
|
element. The config_entry has to be given.
|
||||||
projector element is active. The config_entry has to be given.
|
|
||||||
"""
|
"""
|
||||||
return ()
|
return ()
|
||||||
|
|
||||||
|
|
||||||
class ProjectorRequirement:
|
|
||||||
"""
|
|
||||||
Container for required views. Such a view is defined by its class, its
|
|
||||||
action and its kwargs which come from the URL path.
|
|
||||||
"""
|
|
||||||
def __init__(self, view_class, view_action, **kwargs):
|
|
||||||
self.view_class = view_class
|
|
||||||
self.view_action = view_action
|
|
||||||
self.kwargs = kwargs
|
|
||||||
|
|
||||||
def is_currently_required(self, view_instance):
|
|
||||||
"""
|
|
||||||
Returns True if the view_instance matches the initiated data of this
|
|
||||||
requirement.
|
|
||||||
"""
|
|
||||||
if not type(view_instance) == self.view_class:
|
|
||||||
result = False
|
|
||||||
elif not view_instance.action == self.view_action:
|
|
||||||
result = False
|
|
||||||
else:
|
|
||||||
result = True
|
|
||||||
for key in view_instance.kwargs:
|
|
||||||
if not self.kwargs[key] == view_instance.kwargs[key]:
|
|
||||||
result = False
|
|
||||||
break
|
|
||||||
return result
|
|
||||||
|
@ -95,9 +95,9 @@ class PermissionMixin:
|
|||||||
"""
|
"""
|
||||||
Mixin for subclasses of APIView like GenericViewSet and ModelViewSet.
|
Mixin for subclasses of APIView like GenericViewSet and ModelViewSet.
|
||||||
|
|
||||||
The methods check_view_permissions or check_projector_requirements are
|
The method check_view_permissions is evaluated. If it returns False
|
||||||
evaluated. If both return False self.permission_denied() is called.
|
self.permission_denied() is called. Django REST Framework's permission
|
||||||
Django REST Framework's permission system is disabled.
|
system is disabled.
|
||||||
|
|
||||||
Also connects container to handle access permissions for model and
|
Also connects container to handle access permissions for model and
|
||||||
viewset.
|
viewset.
|
||||||
@ -106,12 +106,12 @@ class PermissionMixin:
|
|||||||
|
|
||||||
def get_permissions(self):
|
def get_permissions(self):
|
||||||
"""
|
"""
|
||||||
Overridden method to check view and projector permissions. Returns an
|
Overridden method to check view permissions. Returns an empty
|
||||||
empty iterable so Django REST framework won't do any other
|
iterable so Django REST framework won't do any other permission
|
||||||
permission checks by evaluating Django REST framework style permission
|
checks by evaluating Django REST framework style permission classes
|
||||||
classes and the request passes.
|
and the request passes.
|
||||||
"""
|
"""
|
||||||
if not self.check_view_permissions() and not self.check_projector_requirements():
|
if not self.check_view_permissions():
|
||||||
self.permission_denied(self.request)
|
self.permission_denied(self.request)
|
||||||
return ()
|
return ()
|
||||||
|
|
||||||
@ -120,25 +120,11 @@ class PermissionMixin:
|
|||||||
Override this and return True if the requesting user should be able to
|
Override this and return True if the requesting user should be able to
|
||||||
get access to your view.
|
get access to your view.
|
||||||
|
|
||||||
Use access permissions container for retrieve requests.
|
Don't forget to use access permissions container for list and retrieve
|
||||||
|
requests.
|
||||||
"""
|
"""
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def check_projector_requirements(self):
|
|
||||||
"""
|
|
||||||
Helper method which returns True if the current request (on this
|
|
||||||
view instance) is required for at least one active projector element.
|
|
||||||
"""
|
|
||||||
from openslides.core.models import Projector
|
|
||||||
|
|
||||||
result = False
|
|
||||||
if self.request.user.has_perm('core.can_see_projector'):
|
|
||||||
for requirement in Projector.get_all_requirements():
|
|
||||||
if requirement.is_currently_required(view_instance=self):
|
|
||||||
result = True
|
|
||||||
break
|
|
||||||
return result
|
|
||||||
|
|
||||||
def get_access_permissions(self):
|
def get_access_permissions(self):
|
||||||
"""
|
"""
|
||||||
Returns a container to handle access permissions for this viewset and
|
Returns a container to handle access permissions for this viewset and
|
||||||
|
@ -6,7 +6,6 @@ import os
|
|||||||
|
|
||||||
from openslides.global_settings import * # noqa
|
from openslides.global_settings import * # noqa
|
||||||
|
|
||||||
|
|
||||||
# Path to the directory for user specific data files
|
# Path to the directory for user specific data files
|
||||||
|
|
||||||
OPENSLIDES_USER_DATA_PATH = os.path.realpath(os.path.dirname(os.path.abspath(__file__)))
|
OPENSLIDES_USER_DATA_PATH = os.path.realpath(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
@ -6,7 +6,6 @@ import os
|
|||||||
|
|
||||||
from openslides.global_settings import * # noqa
|
from openslides.global_settings import * # noqa
|
||||||
|
|
||||||
|
|
||||||
# Path to the directory for user specific data files
|
# Path to the directory for user specific data files
|
||||||
|
|
||||||
OPENSLIDES_USER_DATA_PATH = os.path.realpath(os.path.dirname(os.path.abspath(__file__)))
|
OPENSLIDES_USER_DATA_PATH = os.path.realpath(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
Loading…
Reference in New Issue
Block a user