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.
|
||||
|
||||
Core:
|
||||
- Used Django Channels instead of Tornado.
|
||||
- Added support for big assemblies with lots of users.
|
||||
- Added HTML support for messages on the projector.
|
||||
|
||||
@ -38,6 +37,7 @@ Other:
|
||||
- Removed config cache to support multiple threads or processes.
|
||||
- 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).
|
||||
- Used Django Channels instead of Tornado. Refactoring of the autoupdate process.
|
||||
|
||||
|
||||
Version 2.0 (2016-04-18)
|
||||
|
@ -5,7 +5,7 @@ class ItemAccessPermissions(BaseAccessPermissions):
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
@ -19,15 +19,28 @@ class ItemAccessPermissions(BaseAccessPermissions):
|
||||
|
||||
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):
|
||||
"""
|
||||
Returns the restricted serialized data for the instance prepared
|
||||
for the user.
|
||||
"""
|
||||
if (self.can_retrieve(user) and
|
||||
if (user.has_perm('agenda.can_see') and
|
||||
(not full_data['is_hidden'] or
|
||||
user.has_perm('agenda.can_see_hidden_items'))):
|
||||
data = full_data
|
||||
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 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 openslides.utils.projector import ProjectorElement, ProjectorRequirement
|
||||
|
||||
from ..core.config import config
|
||||
from ..core.exceptions import ProjectorException
|
||||
from ..utils.projector import ProjectorElement
|
||||
from .models import Item
|
||||
from .views import ItemViewSet
|
||||
|
||||
|
||||
class ItemListSlide(ProjectorElement):
|
||||
"""
|
||||
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
|
||||
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.')
|
||||
|
||||
def get_requirements(self, config_entry):
|
||||
pk = config_entry.get('id', 'tree')
|
||||
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')
|
||||
yield from Item.objects.all()
|
||||
|
||||
|
||||
class ListOfSpeakersSlide(ProjectorElement):
|
||||
@ -49,10 +37,7 @@ class ListOfSpeakersSlide(ProjectorElement):
|
||||
name = 'agenda/list-of-speakers'
|
||||
|
||||
def check_data(self):
|
||||
pk = self.config_entry.get('id')
|
||||
if pk is None:
|
||||
raise ProjectorException('Id must not be None.')
|
||||
if not Item.objects.filter(pk=pk).exists():
|
||||
if not Item.objects.filter(pk=self.config_entry.get('id')).exists():
|
||||
raise ProjectorException('Item does not exist.')
|
||||
|
||||
def get_requirements(self, config_entry):
|
||||
@ -65,12 +50,12 @@ class ListOfSpeakersSlide(ProjectorElement):
|
||||
# Item does not exist. Just do nothing.
|
||||
pass
|
||||
else:
|
||||
yield ProjectorRequirement(
|
||||
view_class=ItemViewSet,
|
||||
view_action='retrieve',
|
||||
pk=str(item.pk))
|
||||
for speaker in item.speakers.all():
|
||||
yield ProjectorRequirement(
|
||||
view_class=speaker.user.get_view_class(),
|
||||
view_action='retrieve',
|
||||
pk=str(speaker.user_id))
|
||||
yield item
|
||||
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
|
||||
|
@ -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
|
||||
django.db.models.signals.post_save during app loading.
|
||||
"""
|
||||
if created and hasattr(instance, 'get_agenda_title'):
|
||||
Item.objects.create(content_object=instance)
|
||||
inform_changed_data(instance)
|
||||
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)
|
||||
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):
|
||||
|
@ -25,8 +25,6 @@ angular.module('OpenSlidesApp.agenda.projector', ['OpenSlidesApp.agenda'])
|
||||
// Add it to the coresponding get_requirements method of the ProjectorElement
|
||||
// class.
|
||||
var id = $scope.element.id;
|
||||
Agenda.find(id);
|
||||
User.findAll();
|
||||
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.
|
||||
// Add it to the coresponding get_requirements method of the ProjectorElement
|
||||
// class.
|
||||
Agenda.findAll();
|
||||
|
||||
// Bind agenda tree to the scope
|
||||
$scope.$watch(function () {
|
||||
return Agenda.lastModified();
|
||||
|
@ -41,9 +41,9 @@ class ItemViewSet(ListModelMixin, RetrieveModelMixin, UpdateModelMixin, GenericV
|
||||
"""
|
||||
Returns True if the user has required permissions.
|
||||
"""
|
||||
if self.action == 'retrieve':
|
||||
result = self.get_access_permissions().can_retrieve(self.request.user)
|
||||
elif self.action in ('metadata', 'list', 'manage_speaker', 'tree'):
|
||||
if self.action in ('list', 'retrieve'):
|
||||
result = self.get_access_permissions().check_permissions(self.request.user)
|
||||
elif self.action in ('metadata', 'manage_speaker', 'tree'):
|
||||
result = self.request.user.has_perm('agenda.can_see')
|
||||
# For manage_speaker and tree requests the rest of the check is
|
||||
# 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
|
||||
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'):
|
||||
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.
|
||||
"""
|
||||
# TODO: Move this logic to access_permissions.ItemAccessPermissions.
|
||||
queryset = super().get_queryset()
|
||||
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()]
|
||||
|
@ -5,7 +5,7 @@ class AssignmentAccessPermissions(BaseAccessPermissions):
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
@ -17,7 +17,7 @@ class AssignmentAccessPermissions(BaseAccessPermissions):
|
||||
"""
|
||||
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
|
||||
else:
|
||||
serializer_class = AssignmentShortSerializer
|
||||
@ -29,9 +29,20 @@ class AssignmentAccessPermissions(BaseAccessPermissions):
|
||||
for the user. Removes unpublished polls for non admins so that they
|
||||
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
|
||||
else:
|
||||
elif user.has_perm('assignments.can_see'):
|
||||
data = full_data.copy()
|
||||
data['polls'] = [poll for poll in data['polls'] if poll['published']]
|
||||
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['polls'] = [poll for poll in data['polls'] if poll['published']]
|
||||
return data
|
||||
|
@ -1,63 +1,29 @@
|
||||
from openslides.core.exceptions import ProjectorException
|
||||
from openslides.core.views import TagViewSet
|
||||
from openslides.utils.projector import ProjectorElement, ProjectorRequirement
|
||||
|
||||
from .models import Assignment, AssignmentPoll
|
||||
from .views import AssignmentViewSet
|
||||
from ..core.exceptions import ProjectorException
|
||||
from ..utils.projector import ProjectorElement
|
||||
from .models import Assignment
|
||||
|
||||
|
||||
class AssignmentSlide(ProjectorElement):
|
||||
"""
|
||||
Slide definitions for Assignment model.
|
||||
|
||||
Set 'id' to get a detail slide. Omit it to get a list slide.
|
||||
"""
|
||||
name = 'assignments/assignment'
|
||||
|
||||
def check_data(self):
|
||||
pk = self.config_entry.get('id')
|
||||
if pk is not None:
|
||||
# Detail slide.
|
||||
if not Assignment.objects.filter(pk=pk).exists():
|
||||
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.')
|
||||
if not Assignment.objects.filter(pk=self.config_entry.get('id')).exists():
|
||||
raise ProjectorException('Election does not exist.')
|
||||
|
||||
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')
|
||||
try:
|
||||
assignment = Assignment.objects.get(pk=config_entry.get('id'))
|
||||
except Assignment.DoesNotExist:
|
||||
# Assignment does not exist. Just do nothing.
|
||||
pass
|
||||
else:
|
||||
# Detail slide.
|
||||
try:
|
||||
assignment = Assignment.objects.get(pk=pk)
|
||||
except Assignment.DoesNotExist:
|
||||
# Assignment does not exist. Just do nothing.
|
||||
pass
|
||||
else:
|
||||
yield ProjectorRequirement(
|
||||
view_class=AssignmentViewSet,
|
||||
view_action='retrieve',
|
||||
pk=str(assignment.pk))
|
||||
for user in assignment.related_users.all():
|
||||
yield ProjectorRequirement(
|
||||
view_class=user.get_view_class(),
|
||||
view_action='retrieve',
|
||||
pk=str(user.pk))
|
||||
for poll in assignment.polls.all().prefetch_related('options'):
|
||||
for option in poll.options.all():
|
||||
yield ProjectorRequirement(
|
||||
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))
|
||||
yield assignment
|
||||
yield assignment.agenda_item
|
||||
for user in assignment.related_users.all():
|
||||
yield user
|
||||
for poll in assignment.polls.all().prefetch_related('options'):
|
||||
for option in poll.options.all():
|
||||
yield option.candidate
|
||||
|
@ -24,16 +24,10 @@ angular.module('OpenSlidesApp.assignments.projector', ['OpenSlidesApp.assignment
|
||||
var id = $scope.element.id;
|
||||
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.getPhases().then(function(phases) {
|
||||
$scope.phases = phases;
|
||||
});
|
||||
// load all users
|
||||
User.findAll();
|
||||
User.bindAll({}, $scope, 'users');
|
||||
}
|
||||
]);
|
||||
|
@ -12,7 +12,7 @@
|
||||
|
||||
<!-- Title -->
|
||||
<div id="title">
|
||||
<h1>{{ assignment.agenda_item.getTitle() }}</h1>
|
||||
<h1>{{ assignment.agenda_item.getTitle() || assignment.title }}</h1>
|
||||
<h2>
|
||||
<translate>Election</translate>
|
||||
</h2>
|
||||
|
@ -52,10 +52,11 @@ class AssignmentViewSet(ModelViewSet):
|
||||
"""
|
||||
Returns True if the user has required permissions.
|
||||
"""
|
||||
if self.action == 'retrieve':
|
||||
result = self.get_access_permissions().can_retrieve(self.request.user)
|
||||
elif self.action in ('metadata', 'list'):
|
||||
result = self.request.user.has_perm('assignments.can_see')
|
||||
if self.action in ('list', 'retrieve'):
|
||||
result = self.get_access_permissions().check_permissions(self.request.user)
|
||||
elif self.action == 'metadata':
|
||||
# Everybody is allowed to see the metadata.
|
||||
result = True
|
||||
elif self.action in ('create', 'partial_update', 'update', 'destroy',
|
||||
'mark_elected', 'create_poll'):
|
||||
result = (self.request.user.has_perm('assignments.can_see') and
|
||||
|
@ -5,7 +5,7 @@ class ProjectorAccessPermissions(BaseAccessPermissions):
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
@ -24,7 +24,7 @@ class CustomSlideAccessPermissions(BaseAccessPermissions):
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
@ -43,7 +43,7 @@ class TagAccessPermissions(BaseAccessPermissions):
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
@ -66,7 +66,7 @@ class ChatMessageAccessPermissions(BaseAccessPermissions):
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
@ -88,7 +88,7 @@ class ConfigAccessPermissions(BaseAccessPermissions):
|
||||
Access permissions container for the config (ConfigStore and
|
||||
ConfigViewSet).
|
||||
"""
|
||||
def can_retrieve(self, user):
|
||||
def check_permissions(self, user):
|
||||
"""
|
||||
Returns True if the user has read access model instances.
|
||||
"""
|
||||
|
@ -141,6 +141,12 @@ class ConfigHandler:
|
||||
if config_variable.translatable:
|
||||
yield config_variable.name
|
||||
|
||||
def get_collection_string(self):
|
||||
"""
|
||||
Returns the collection_string from the CollectionStore.
|
||||
"""
|
||||
return ConfigStore.get_collection_string()
|
||||
|
||||
config = ConfigHandler()
|
||||
"""
|
||||
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 openslides.core.config import ConfigVariable
|
||||
|
||||
|
||||
|
@ -111,11 +111,9 @@ class Projector(RESTModelMixin, models.Model):
|
||||
result[key]['error'] = str(e)
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def get_all_requirements(cls):
|
||||
def get_all_requirements(self):
|
||||
"""
|
||||
Generator which returns all ProjectorRequirement instances of all
|
||||
active projector elements.
|
||||
Generator which returns all instances that are shown on this projector.
|
||||
"""
|
||||
# Get all elements from all apps.
|
||||
elements = {}
|
||||
@ -123,12 +121,38 @@ class Projector(RESTModelMixin, models.Model):
|
||||
elements[element.name] = element
|
||||
|
||||
# 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 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():
|
||||
for key, value in projector.config.items():
|
||||
element = elements.get(value['name'])
|
||||
if element is not None:
|
||||
for requirement in element.get_requirements(value):
|
||||
yield requirement
|
||||
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):
|
||||
|
@ -1,11 +1,9 @@
|
||||
from django.utils.timezone import now
|
||||
|
||||
from openslides.utils.projector import ProjectorElement, ProjectorRequirement
|
||||
|
||||
from ..utils.projector import ProjectorElement
|
||||
from .config import config
|
||||
from .exceptions import ProjectorException
|
||||
from .models import CustomSlide, Projector
|
||||
from .views import CustomSlideViewSet
|
||||
|
||||
|
||||
class CustomSlideSlide(ProjectorElement):
|
||||
@ -19,12 +17,14 @@ class CustomSlideSlide(ProjectorElement):
|
||||
raise ProjectorException('Custom slide does not exist.')
|
||||
|
||||
def get_requirements(self, config_entry):
|
||||
pk = config_entry.get('id')
|
||||
if pk is not None:
|
||||
yield ProjectorRequirement(
|
||||
view_class=CustomSlideViewSet,
|
||||
view_action='retrieve',
|
||||
pk=str(pk))
|
||||
try:
|
||||
custom_slide = CustomSlide.objects.get(pk=config_entry.get('id'))
|
||||
except CustomSlide.DoesNotExist:
|
||||
# Custom slide does not exist. Just do nothing.
|
||||
pass
|
||||
else:
|
||||
yield custom_slide
|
||||
yield custom_slide.agenda_item
|
||||
|
||||
|
||||
class Clock(ProjectorElement):
|
||||
|
@ -38,11 +38,22 @@ angular.module('OpenSlidesApp.core', [
|
||||
.factory('autoupdate', [
|
||||
'DS',
|
||||
'$rootScope',
|
||||
function (DS, $rootScope) {
|
||||
'REALM',
|
||||
function (DS, $rootScope, REALM) {
|
||||
var socket = null;
|
||||
var recInterval = null;
|
||||
$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 = {
|
||||
messageReceivers: [],
|
||||
onMessage: function (receiver) {
|
||||
@ -55,7 +66,7 @@ angular.module('OpenSlidesApp.core', [
|
||||
}
|
||||
};
|
||||
var newConnect = function () {
|
||||
socket = new WebSocket('ws://' + location.host + '/ws/');
|
||||
socket = new WebSocket('ws://' + location.host + websocketPath);
|
||||
clearInterval(recInterval);
|
||||
socket.onopen = function () {
|
||||
$rootScope.connected = true;
|
||||
@ -175,49 +186,67 @@ angular.module('OpenSlidesApp.core', [
|
||||
autoupdate.onMessage(function(json) {
|
||||
// TODO: when MODEL.find() is called after this
|
||||
// a new request is fired. This could be a bug in DS
|
||||
|
||||
var data = JSON.parse(json);
|
||||
console.log("Received object: " + data.collection + ", " + data.id);
|
||||
var instance = DS.get(data.collection, data.id);
|
||||
if (data.action == 'changed') {
|
||||
if (instance) {
|
||||
// The instance is in the local db
|
||||
dsEject(data.collection, instance);
|
||||
// TODO: If you don't have the permission to see a projector, the
|
||||
// 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);
|
||||
var instance = DS.get(data.collection, data.id);
|
||||
if (data.action == 'changed') {
|
||||
if (instance) {
|
||||
// The instance is in the local db
|
||||
dsEject(data.collection, instance);
|
||||
}
|
||||
DS.inject(data.collection, data.data);
|
||||
} else if (data.action == 'deleted') {
|
||||
if (instance) {
|
||||
// The instance is in the local db
|
||||
dsEject(data.collection, instance);
|
||||
}
|
||||
DS.eject(data.collection, data.id);
|
||||
}
|
||||
DS.inject(data.collection, data.data);
|
||||
} else if (data.action == 'deleted') {
|
||||
if (instance) {
|
||||
// The instance is in the local db
|
||||
dsEject(data.collection, instance);
|
||||
}
|
||||
DS.eject(data.collection, data.id);
|
||||
}
|
||||
// If you want to handle more status codes, change server
|
||||
// restrictions in utils/autoupdate.py.
|
||||
});
|
||||
});
|
||||
}
|
||||
])
|
||||
|
||||
.factory('loadGlobalData', [
|
||||
'$rootScope',
|
||||
// Save the server time to the rootscope.
|
||||
.run([
|
||||
'$http',
|
||||
'$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',
|
||||
'$rootScope',
|
||||
function (Config, $rootScope) {
|
||||
$rootScope.config = function (key) {
|
||||
try {
|
||||
return Config.get(key).value;
|
||||
}
|
||||
catch(err) {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
}
|
||||
])
|
||||
|
||||
.factory('loadGlobalData', [
|
||||
'ChatMessage',
|
||||
'Config',
|
||||
'Projector',
|
||||
function ($rootScope, $http, ChatMessage, Config, Projector) {
|
||||
function (ChatMessage, Config, Projector) {
|
||||
return function () {
|
||||
// Puts the config object into each scope.
|
||||
Config.findAll().then(function() {
|
||||
$rootScope.config = function(key) {
|
||||
try {
|
||||
return Config.get(key).value;
|
||||
}
|
||||
catch(err) {
|
||||
console.log("Unkown config key: " + key);
|
||||
return '';
|
||||
}
|
||||
};
|
||||
});
|
||||
Config.findAll();
|
||||
|
||||
// Loads all projector data
|
||||
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
|
||||
|
||||
|
@ -5,6 +5,9 @@
|
||||
// The core module for the OpenSlides projector
|
||||
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('slides', [
|
||||
function() {
|
||||
@ -26,7 +29,7 @@ angular.module('OpenSlidesApp.core.projector', ['OpenSlidesApp.core'])
|
||||
element.template = slidesMap[element.name].template;
|
||||
elements.push(element);
|
||||
} else {
|
||||
console.log("Unknown slide: " + element.name);
|
||||
console.error("Unknown slide: " + element.name);
|
||||
}
|
||||
});
|
||||
return elements;
|
||||
@ -61,7 +64,9 @@ angular.module('OpenSlidesApp.core.projector', ['OpenSlidesApp.core'])
|
||||
.controller('ProjectorContainerCtrl', [
|
||||
'$scope',
|
||||
'Config',
|
||||
function($scope, Config) {
|
||||
'loadGlobalData',
|
||||
function($scope, Config, loadGlobalData) {
|
||||
loadGlobalData();
|
||||
// watch for changes in Config
|
||||
var last_conf;
|
||||
$scope.$watch(function () {
|
||||
@ -123,21 +128,25 @@ angular.module('OpenSlidesApp.core.projector', ['OpenSlidesApp.core'])
|
||||
'Projector',
|
||||
'slides',
|
||||
function($scope, Projector, slides) {
|
||||
Projector.find(1).then(function() {
|
||||
$scope.$watch(function () {
|
||||
return Projector.lastModified(1);
|
||||
}, function () {
|
||||
$scope.$watch(function () {
|
||||
// TODO: Use the current projector. At the moment there is only one.
|
||||
return Projector.lastModified(1);
|
||||
}, function () {
|
||||
// TODO: Use the current projector. At the moment there is only one
|
||||
var projector = Projector.get(1);
|
||||
if (projector) {
|
||||
$scope.elements = [];
|
||||
_.forEach(slides.getElements(Projector.get(1)), function(element) {
|
||||
_.forEach(slides.getElements(projector), function(element) {
|
||||
if (!element.error) {
|
||||
$scope.elements.push(element);
|
||||
} else {
|
||||
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.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
|
||||
// class.
|
||||
var id = $scope.element.id;
|
||||
Customslide.find(id).then(function(customslide) {
|
||||
Customslide.loadRelations(customslide, 'agenda_item');
|
||||
});
|
||||
Customslide.bindOne(id, $scope, 'customslide');
|
||||
}
|
||||
])
|
||||
|
@ -18,6 +18,10 @@ angular.module('OpenSlidesApp.core.site', [
|
||||
'ui.tinymce',
|
||||
'luegg.directives',
|
||||
])
|
||||
|
||||
// Can be used to find out if the projector or the side is used
|
||||
.constant('REALM', 'site')
|
||||
|
||||
.factory('PdfMakeDocumentProvider', [
|
||||
'gettextCatalog',
|
||||
'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.
|
||||
.factory('Editor', [
|
||||
'gettextCatalog',
|
||||
|
@ -177,9 +177,9 @@ class ProjectorViewSet(ReadOnlyModelViewSet):
|
||||
"""
|
||||
Returns True if the user has required permissions.
|
||||
"""
|
||||
if self.action == 'retrieve':
|
||||
result = self.get_access_permissions().can_retrieve(self.request.user)
|
||||
elif self.action in ('metadata', 'list'):
|
||||
if self.action in ('list', 'retrieve'):
|
||||
result = self.get_access_permissions().check_permissions(self.request.user)
|
||||
elif self.action == 'metadata':
|
||||
result = self.request.user.has_perm('core.can_see_projector')
|
||||
elif self.action in ('activate_elements', 'prune_elements', 'update_elements',
|
||||
'deactivate_elements', 'clear_elements', 'control_view', 'set_resolution'):
|
||||
@ -433,8 +433,8 @@ class CustomSlideViewSet(ModelViewSet):
|
||||
"""
|
||||
Returns True if the user has required permissions.
|
||||
"""
|
||||
if self.action == 'retrieve':
|
||||
result = self.get_access_permissions().can_retrieve(self.request.user)
|
||||
if self.action in ('list', 'retrieve'):
|
||||
result = self.get_access_permissions().check_permissions(self.request.user)
|
||||
else:
|
||||
result = self.request.user.has_perm('core.can_manage_projector')
|
||||
return result
|
||||
@ -454,9 +454,9 @@ class TagViewSet(ModelViewSet):
|
||||
"""
|
||||
Returns True if the user has required permissions.
|
||||
"""
|
||||
if self.action == 'retrieve':
|
||||
result = self.get_access_permissions().can_retrieve(self.request.user)
|
||||
elif self.action in ('metadata', 'list'):
|
||||
if self.action in ('list', 'retrieve'):
|
||||
result = self.get_access_permissions().check_permissions(self.request.user)
|
||||
elif self.action == 'metadata':
|
||||
# Every authenticated user can see the metadata and list tags.
|
||||
# Anonymous users can do so if they are enabled.
|
||||
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.
|
||||
"""
|
||||
if self.action == 'retrieve':
|
||||
result = self.get_access_permissions().can_retrieve(self.request.user)
|
||||
elif self.action in ('metadata', 'list'):
|
||||
if self.action in ('list', 'retrieve'):
|
||||
result = self.get_access_permissions().check_permissions(self.request.user)
|
||||
elif self.action == 'metadata':
|
||||
# Every authenticated user can see the metadata and list or
|
||||
# retrieve the config. Anonymous users can do so if they are
|
||||
# enabled.
|
||||
@ -579,13 +579,13 @@ class ChatMessageViewSet(ModelViewSet):
|
||||
"""
|
||||
Returns True if the user has required permissions.
|
||||
"""
|
||||
if self.action == 'retrieve':
|
||||
result = self.get_access_permissions().can_retrieve(self.request.user)
|
||||
if self.action in ('list', 'retrieve'):
|
||||
result = self.get_access_permissions().check_permissions(self.request.user)
|
||||
else:
|
||||
# We do not want anonymous users to use the chat even the anonymous
|
||||
# group has the permission core.can_use_chat.
|
||||
result = (
|
||||
self.action in ('metadata', 'list', 'create') and
|
||||
self.action in ('metadata', 'create') and
|
||||
self.request.user.is_authenticated() and
|
||||
self.request.user.has_perm('core.can_use_chat'))
|
||||
return result
|
||||
|
@ -145,5 +145,8 @@ CHANNEL_LAYERS = {
|
||||
'default': {
|
||||
'BACKEND': 'asgiref.inmemory.ChannelLayer',
|
||||
'ROUTING': 'openslides.routing.channel_routing',
|
||||
'CONFIG': {
|
||||
'capacity': 1000,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ class MediafileAccessPermissions(BaseAccessPermissions):
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
|
@ -1,8 +1,6 @@
|
||||
from openslides.core.exceptions import ProjectorException
|
||||
from openslides.utils.projector import ProjectorElement, ProjectorRequirement
|
||||
|
||||
from ..core.exceptions import ProjectorException
|
||||
from ..utils.projector import ProjectorElement
|
||||
from .models import Mediafile
|
||||
from .views import MediafileViewSet
|
||||
|
||||
|
||||
class MediafileSlide(ProjectorElement):
|
||||
@ -12,15 +10,14 @@ class MediafileSlide(ProjectorElement):
|
||||
name = 'mediafiles/mediafile'
|
||||
|
||||
def check_data(self):
|
||||
try:
|
||||
Mediafile.objects.get(pk=self.config_entry.get('id'))
|
||||
except Mediafile.DoesNotExist:
|
||||
if not Mediafile.objects.filter(pk=self.config_entry.get('id')).exists():
|
||||
raise ProjectorException('File does not exist.')
|
||||
|
||||
def get_requirements(self, config_entry):
|
||||
pk = config_entry.get('id')
|
||||
if pk is not None:
|
||||
yield ProjectorRequirement(
|
||||
view_class=MediafileViewSet,
|
||||
view_action='retrieve',
|
||||
pk=str(pk))
|
||||
try:
|
||||
mediafile = Mediafile.objects.get(pk=config_entry.get('id'))
|
||||
except Mediafile.DoesNotExist:
|
||||
# Mediafile does not exist. Just do nothing.
|
||||
pass
|
||||
else:
|
||||
yield mediafile
|
||||
|
@ -18,11 +18,9 @@ angular.module('OpenSlidesApp.mediafiles.projector', ['OpenSlidesApp.mediafiles'
|
||||
'Mediafile',
|
||||
function($scope, Mediafile) {
|
||||
// load mediafile object
|
||||
var mediafile = Mediafile.find($scope.element.id);
|
||||
mediafile.then(function(mediafile) {
|
||||
$scope.pdfName = mediafile.title;
|
||||
$scope.pdfUrl = mediafile.mediafileUrl;
|
||||
});
|
||||
var mediafile = Mediafile.get($scope.element.id);
|
||||
$scope.pdfName = mediafile.title;
|
||||
$scope.pdfUrl = mediafile.mediafileUrl;
|
||||
}
|
||||
]);
|
||||
|
||||
|
@ -19,9 +19,9 @@ class MediafileViewSet(ModelViewSet):
|
||||
"""
|
||||
Returns True if the user has required permissions.
|
||||
"""
|
||||
if self.action == 'retrieve':
|
||||
result = self.get_access_permissions().can_retrieve(self.request.user)
|
||||
elif self.action in ('metadata', 'list'):
|
||||
if self.action in ('list', 'retrieve'):
|
||||
result = self.get_access_permissions().check_permissions(self.request.user)
|
||||
elif self.action == 'metadata':
|
||||
result = self.request.user.has_perm('mediafiles.can_see')
|
||||
elif self.action == 'create':
|
||||
result = (self.request.user.has_perm('mediafiles.can_see') and
|
||||
|
@ -10,7 +10,7 @@ class MotionAccessPermissions(BaseAccessPermissions):
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
@ -43,6 +43,21 @@ class MotionAccessPermissions(BaseAccessPermissions):
|
||||
pass
|
||||
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):
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
def can_retrieve(self, user):
|
||||
def check_permissions(self, user):
|
||||
"""
|
||||
Returns True if the user has read access model instances.
|
||||
"""
|
||||
@ -129,7 +144,7 @@ class WorkflowAccessPermissions(BaseAccessPermissions):
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
|
@ -3,7 +3,6 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import jsonfield.fields
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
|
@ -1,66 +1,27 @@
|
||||
from openslides.core.exceptions import ProjectorException
|
||||
from openslides.core.views import TagViewSet
|
||||
from openslides.utils.projector import ProjectorElement, ProjectorRequirement
|
||||
|
||||
from ..core.exceptions import ProjectorException
|
||||
from ..utils.projector import ProjectorElement
|
||||
from .models import Motion
|
||||
from .views import CategoryViewSet, MotionViewSet, WorkflowViewSet
|
||||
|
||||
|
||||
class MotionSlide(ProjectorElement):
|
||||
"""
|
||||
Slide definitions for Motion model.
|
||||
|
||||
Set 'id' to get a detail slide. Omit it to get a list slide.
|
||||
"""
|
||||
name = 'motions/motion'
|
||||
|
||||
def check_data(self):
|
||||
pk = self.config_entry.get('id')
|
||||
if pk is not None:
|
||||
# Detail slide.
|
||||
if not Motion.objects.filter(pk=pk).exists():
|
||||
raise ProjectorException('Motion does not exist.')
|
||||
if not Motion.objects.filter(pk=self.config_entry.get('id')).exists():
|
||||
raise ProjectorException('Motion does not exist.')
|
||||
|
||||
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')
|
||||
try:
|
||||
motion = Motion.objects.get(pk=config_entry.get('id'))
|
||||
except Motion.DoesNotExist:
|
||||
# Motion does not exist. Just do nothing.
|
||||
pass
|
||||
else:
|
||||
# Detail slide.
|
||||
try:
|
||||
motion = Motion.objects.get(pk=pk)
|
||||
except Motion.DoesNotExist:
|
||||
# Motion does not exist. Just do nothing.
|
||||
pass
|
||||
else:
|
||||
yield ProjectorRequirement(
|
||||
view_class=MotionViewSet,
|
||||
view_action='retrieve',
|
||||
pk=str(motion.pk))
|
||||
if motion.category:
|
||||
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))
|
||||
yield motion
|
||||
yield motion.agenda_item
|
||||
yield motion.state.workflow
|
||||
yield from motion.submitters.all()
|
||||
yield from motion.supporters.all()
|
||||
|
@ -60,14 +60,6 @@ angular.module('OpenSlidesApp.motions', [
|
||||
}
|
||||
])
|
||||
|
||||
// Load all MotionWorkflows at startup
|
||||
.run([
|
||||
'Workflow',
|
||||
function (Workflow) {
|
||||
Workflow.findAll();
|
||||
}
|
||||
])
|
||||
|
||||
.factory('MotionPoll', [
|
||||
'DS',
|
||||
'gettextCatalog',
|
||||
@ -452,7 +444,8 @@ angular.module('OpenSlidesApp.motions', [
|
||||
.run([
|
||||
'Motion',
|
||||
'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
|
||||
// class.
|
||||
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');
|
||||
|
||||
// load all users
|
||||
User.findAll();
|
||||
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)
|
||||
.factory('MotionForm', [
|
||||
'gettextCatalog',
|
||||
|
@ -71,7 +71,7 @@
|
||||
|
||||
<!-- Text -->
|
||||
<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 -->
|
||||
<h3 ng-if="motion.getReason()" translate>Reason</h3>
|
||||
|
@ -53,9 +53,9 @@ class MotionViewSet(ModelViewSet):
|
||||
"""
|
||||
Returns True if the user has required permissions.
|
||||
"""
|
||||
if self.action == 'retrieve':
|
||||
result = self.get_access_permissions().can_retrieve(self.request.user)
|
||||
elif self.action in ('metadata', 'list', 'partial_update', 'update'):
|
||||
if self.action in ('list', 'retrieve'):
|
||||
result = self.get_access_permissions().check_permissions(self.request.user)
|
||||
elif self.action in ('metadata', 'partial_update', 'update'):
|
||||
result = self.request.user.has_perm('motions.can_see')
|
||||
# For partial_update and update requests the rest of the check is
|
||||
# done in the update method. See below.
|
||||
@ -373,9 +373,9 @@ class CategoryViewSet(ModelViewSet):
|
||||
"""
|
||||
Returns True if the user has required permissions.
|
||||
"""
|
||||
if self.action == 'retrieve':
|
||||
result = self.get_access_permissions().can_retrieve(self.request.user)
|
||||
elif self.action in ('metadata', 'list'):
|
||||
if self.action in ('list', 'retrieve'):
|
||||
result = self.get_access_permissions().check_permissions(self.request.user)
|
||||
elif self.action == 'metadata':
|
||||
result = self.request.user.has_perm('motions.can_see')
|
||||
elif self.action in ('create', 'partial_update', 'update', 'destroy', 'numbering'):
|
||||
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.
|
||||
"""
|
||||
if self.action == 'retrieve':
|
||||
result = self.get_access_permissions().can_retrieve(self.request.user)
|
||||
elif self.action in ('metadata', 'list'):
|
||||
if self.action in ('list', 'retrieve'):
|
||||
result = self.get_access_permissions().check_permissions(self.request.user)
|
||||
elif self.action == 'metadata':
|
||||
result = self.request.user.has_perm('motions.can_see')
|
||||
elif self.action in ('create', 'partial_update', 'update', 'destroy'):
|
||||
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 = [
|
||||
route("websocket.connect", ws_add, path='/ws/'),
|
||||
route("websocket.disconnect", ws_disconnect),
|
||||
include(projector_routing, path=r'^/ws/projector/(?P<projector_id>\d+)/$'),
|
||||
include(site_routing, path=r'^/ws/site/$'),
|
||||
route("autoupdate.send_data", send_data),
|
||||
]
|
||||
|
@ -5,7 +5,7 @@ class UserAccessPermissions(BaseAccessPermissions):
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
@ -33,12 +33,34 @@ class UserAccessPermissions(BaseAccessPermissions):
|
||||
"""
|
||||
from .serializers import USERCANSEESERIALIZER_FIELDS, USERCANSEEEXTRASERIALIZER_FIELDS
|
||||
|
||||
if user.has_perm('users.can_manage'):
|
||||
data = full_data
|
||||
else:
|
||||
NO_DATA = 0
|
||||
LITTLE_DATA = 1
|
||||
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_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
|
||||
else:
|
||||
# case == LITTLE_DATA
|
||||
fields = USERCANSEESERIALIZER_FIELDS
|
||||
# Let only some fields pass this method.
|
||||
data = {}
|
||||
@ -46,3 +68,17 @@ class UserAccessPermissions(BaseAccessPermissions):
|
||||
if key in fields:
|
||||
data[key] = full_data[key]
|
||||
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 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):
|
||||
"""
|
||||
Returns a string that can be indexed for the search.
|
||||
|
@ -1,12 +1,11 @@
|
||||
from ..core.exceptions import ProjectorException
|
||||
from ..utils.projector import ProjectorElement, ProjectorRequirement
|
||||
from ..utils.projector import ProjectorElement
|
||||
from .models import User
|
||||
from .views import GroupViewSet, UserViewSet
|
||||
|
||||
|
||||
class UserSlide(ProjectorElement):
|
||||
"""
|
||||
Slide definitions for user model.
|
||||
Slide definitions for User model.
|
||||
"""
|
||||
name = 'users/user'
|
||||
|
||||
@ -15,20 +14,10 @@ class UserSlide(ProjectorElement):
|
||||
raise ProjectorException('User does not exist.')
|
||||
|
||||
def get_requirements(self, config_entry):
|
||||
pk = config_entry.get('id')
|
||||
if pk is not None:
|
||||
try:
|
||||
user = User.objects.get(pk=pk)
|
||||
except User.DoesNotExist:
|
||||
# User does not exist. Just do nothing.
|
||||
pass
|
||||
else:
|
||||
yield ProjectorRequirement(
|
||||
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))
|
||||
try:
|
||||
user = User.objects.get(pk=config_entry.get('id'))
|
||||
except User.DoesNotExist:
|
||||
# User does not exist. Just do nothing.
|
||||
pass
|
||||
else:
|
||||
yield user
|
||||
|
@ -11,7 +11,6 @@ from ..utils.rest_api import (
|
||||
)
|
||||
from .models import Group, User
|
||||
|
||||
|
||||
USERCANSEESERIALIZER_FIELDS = (
|
||||
'id',
|
||||
'username',
|
||||
|
@ -21,7 +21,6 @@ angular.module('OpenSlidesApp.users.projector', ['OpenSlidesApp.users'])
|
||||
// Add it to the coresponding get_requirements method of the ProjectorElement
|
||||
// class.
|
||||
var id = $scope.element.id;
|
||||
User.find(id);
|
||||
User.bindOne(id, $scope, 'user');
|
||||
}
|
||||
]);
|
||||
|
@ -37,9 +37,9 @@ class UserViewSet(ModelViewSet):
|
||||
"""
|
||||
Returns True if the user has required permissions.
|
||||
"""
|
||||
if self.action == 'retrieve':
|
||||
result = self.get_access_permissions().can_retrieve(self.request.user)
|
||||
elif self.action in ('metadata', 'list', 'update', 'partial_update'):
|
||||
if self.action in ('list', 'retrieve'):
|
||||
result = self.get_access_permissions().check_permissions(self.request.user)
|
||||
elif self.action in ('metadata', 'update', 'partial_update'):
|
||||
result = self.request.user.has_perm('users.can_see_name')
|
||||
elif self.action in ('create', 'destroy', 'reset_password'):
|
||||
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':
|
||||
return cls.__name__
|
||||
|
||||
def can_retrieve(self, user):
|
||||
def check_permissions(self, user):
|
||||
"""
|
||||
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
|
||||
user has read access to model instances.
|
||||
|
||||
Hint: You should override this method if your
|
||||
get_serializer_class() method may return different serializer for
|
||||
different users or if you have access restrictions in your view or
|
||||
viewset in methods like retrieve() or check_object_permissions().
|
||||
Hint: You should override this method if your get_serializer_class()
|
||||
method returns different serializers for different users or if you
|
||||
have access restrictions in your view or viewset in methods like
|
||||
retrieve(), list() or check_object_permissions().
|
||||
"""
|
||||
if self.can_retrieve(user):
|
||||
if self.check_permissions(user):
|
||||
data = full_data
|
||||
else:
|
||||
data = None
|
||||
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 channels import Channel, Group
|
||||
from channels.auth import channel_session_user, channel_session_user_from_http
|
||||
from django.apps import apps
|
||||
from django.db import transaction
|
||||
from django.utils import timezone
|
||||
|
||||
from ..core.config import config
|
||||
from ..core.models import Projector
|
||||
from ..users.auth import AnonymousUser
|
||||
from ..users.models import User
|
||||
from .access_permissions import BaseAccessPermissions
|
||||
from .collection import CollectionElement
|
||||
|
||||
|
||||
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()
|
||||
|
||||
|
||||
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.
|
||||
|
||||
The argument projector has to be a projector instance.
|
||||
"""
|
||||
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
|
||||
output = []
|
||||
for requirement in projector.get_all_requirements():
|
||||
required_collection_element = CollectionElement.from_instance(requirement)
|
||||
element_dict = required_collection_element.as_autoupdate_for_projector()
|
||||
if element_dict is not None:
|
||||
output.append(element_dict)
|
||||
return output
|
||||
|
||||
|
||||
# Connected to websocket.connect
|
||||
@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.
|
||||
|
||||
@ -61,45 +48,107 @@ def ws_add(message):
|
||||
Group('user-{}'.format(message.user.id)).add(message.reply_channel)
|
||||
|
||||
|
||||
# Connected to websocket.disconnect
|
||||
@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)
|
||||
|
||||
|
||||
@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):
|
||||
"""
|
||||
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():
|
||||
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)
|
||||
collection_element = CollectionElement.from_values(**message)
|
||||
|
||||
# Loop over all logged in users and the anonymous user.
|
||||
for user in itertools.chain(get_logged_in_users(), [AnonymousUser()]):
|
||||
channel = Group('user-{}'.format(user.id))
|
||||
output = {
|
||||
'collection': message['collection_string'],
|
||||
'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.
|
||||
continue
|
||||
output['data'] = data
|
||||
channel.send({'text': json.dumps(output)})
|
||||
output = collection_element.as_autoupdate_for_user(user)
|
||||
if output is None:
|
||||
# There are no data for the user so he can't see the object. Skip him.
|
||||
continue
|
||||
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):
|
||||
@ -109,23 +158,20 @@ def inform_changed_data(instance, is_deleted=False):
|
||||
# Instance has no method get_root_rest_element. Just ignore it.
|
||||
pass
|
||||
else:
|
||||
message_dict = {
|
||||
'collection_string': root_instance.get_collection_string(),
|
||||
'pk': root_instance.pk,
|
||||
'is_deleted': is_deleted and instance == root_instance,
|
||||
'dispatch_uid': root_instance.get_access_permissions().get_dispatch_uid(),
|
||||
}
|
||||
collection_element = CollectionElement.from_instance(
|
||||
root_instance,
|
||||
is_deleted=is_deleted and instance == root_instance)
|
||||
|
||||
# If currently there is an open database transaction, then the following
|
||||
# function is only called, when the transaction is commited. If there
|
||||
# is currently no transaction, then the function is called immediately.
|
||||
def send_autoupdate(message):
|
||||
def send_autoupdate():
|
||||
try:
|
||||
Channel('autoupdate.send_data').send(message)
|
||||
Channel('autoupdate.send_data').send(collection_element.as_channels_message())
|
||||
except ChannelLayer.ChannelFull:
|
||||
pass
|
||||
|
||||
transaction.on_commit(lambda: send_autoupdate(message_dict))
|
||||
transaction.on_commit(send_autoupdate)
|
||||
|
||||
|
||||
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
|
||||
|
||||
def get_access_permissions(self):
|
||||
@classmethod
|
||||
def get_access_permissions(cls):
|
||||
"""
|
||||
Returns a container to handle access permissions for this model and
|
||||
its corresponding viewset.
|
||||
"""
|
||||
return self.access_permissions
|
||||
return cls.access_permissions
|
||||
|
||||
@classmethod
|
||||
def get_collection_string(cls):
|
||||
|
@ -68,36 +68,7 @@ class ProjectorElement(object, metaclass=SignalConnectMetaClass):
|
||||
|
||||
def get_requirements(self, config_entry):
|
||||
"""
|
||||
Returns an iterable of ProjectorRequirement instances to setup
|
||||
which views should be accessable for projector clients if the
|
||||
projector element is active. The config_entry has to be given.
|
||||
Returns an iterable of instances that are required for this projector
|
||||
element. The config_entry has to be given.
|
||||
"""
|
||||
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.
|
||||
|
||||
The methods check_view_permissions or check_projector_requirements are
|
||||
evaluated. If both return False self.permission_denied() is called.
|
||||
Django REST Framework's permission system is disabled.
|
||||
The method check_view_permissions is evaluated. If it returns False
|
||||
self.permission_denied() is called. Django REST Framework's permission
|
||||
system is disabled.
|
||||
|
||||
Also connects container to handle access permissions for model and
|
||||
viewset.
|
||||
@ -106,12 +106,12 @@ class PermissionMixin:
|
||||
|
||||
def get_permissions(self):
|
||||
"""
|
||||
Overridden method to check view and projector permissions. Returns an
|
||||
empty iterable so Django REST framework won't do any other
|
||||
permission checks by evaluating Django REST framework style permission
|
||||
classes and the request passes.
|
||||
Overridden method to check view permissions. Returns an empty
|
||||
iterable so Django REST framework won't do any other permission
|
||||
checks by evaluating Django REST framework style permission classes
|
||||
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)
|
||||
return ()
|
||||
|
||||
@ -120,25 +120,11 @@ class PermissionMixin:
|
||||
Override this and return True if the requesting user should be able to
|
||||
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
|
||||
|
||||
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):
|
||||
"""
|
||||
Returns a container to handle access permissions for this viewset and
|
||||
|
@ -6,7 +6,6 @@ import os
|
||||
|
||||
from openslides.global_settings import * # noqa
|
||||
|
||||
|
||||
# Path to the directory for user specific data files
|
||||
|
||||
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
|
||||
|
||||
|
||||
# Path to the directory for user specific data files
|
||||
|
||||
OPENSLIDES_USER_DATA_PATH = os.path.realpath(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
Loading…
Reference in New Issue
Block a user