"durchstich" for autoupdate optimization
This commit is contained in:
parent
e598b308f0
commit
3db2f2fc16
9
openslides/agenda/access_permissions.py
Normal file
9
openslides/agenda/access_permissions.py
Normal file
@ -0,0 +1,9 @@
|
||||
class AccessPermissions:
|
||||
def get_serializer_class(self, user):
|
||||
return None
|
||||
|
||||
def can_retrieve(self, user):
|
||||
"""
|
||||
TODO
|
||||
"""
|
||||
return user.has_perm('assignments.can_see')
|
@ -7,6 +7,7 @@ from django.utils.translation import ugettext_lazy
|
||||
from reportlab.platypus import Paragraph
|
||||
|
||||
from openslides.core.config import config
|
||||
from openslides.agenda.access_permissions import AccessPermissions
|
||||
from openslides.utils.exceptions import OpenSlidesError
|
||||
from openslides.utils.pdf import stylesheet
|
||||
from openslides.utils.rest_api import (
|
||||
@ -36,12 +37,15 @@ class ItemViewSet(ListModelMixin, RetrieveModelMixin, UpdateModelMixin, GenericV
|
||||
"""
|
||||
queryset = Item.objects.all()
|
||||
serializer_class = ItemSerializer
|
||||
access_permissions = AccessPermissions()
|
||||
|
||||
def check_view_permissions(self):
|
||||
"""
|
||||
Returns True if the user has required permissions.
|
||||
"""
|
||||
if self.action in ('metadata', 'list', 'retrieve', 'manage_speaker', 'tree'):
|
||||
if self.action == 'retrieve':
|
||||
result = self.access_permissions.can_retrieve(self.request.user)
|
||||
elif self.action in ('metadata', 'list', '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.
|
||||
|
18
openslides/assignments/access_permissions.py
Normal file
18
openslides/assignments/access_permissions.py
Normal file
@ -0,0 +1,18 @@
|
||||
|
||||
class AccessPermissions:
|
||||
def get_serializer_class(self, user):
|
||||
"""
|
||||
Returns different serializer classes according to users permissions.
|
||||
"""
|
||||
from openslides.assignments.serializers import AssignmentFullSerializer, AssignmentShortSerializer
|
||||
if user.has_perm('assignments.can_manage'):
|
||||
serializer_class = AssignmentFullSerializer
|
||||
else:
|
||||
serializer_class = AssignmentShortSerializer
|
||||
return serializer_class
|
||||
|
||||
def can_retrieve(self, user):
|
||||
"""
|
||||
TODO
|
||||
"""
|
||||
return user.has_perm('agenda.can_see')
|
@ -12,7 +12,6 @@ class AssignmentsAppConfig(AppConfig):
|
||||
# Load projector elements.
|
||||
# Do this by just importing all from these files.
|
||||
from . import projector # noqa
|
||||
|
||||
# Import all required stuff.
|
||||
from openslides.core.signals import config_signal
|
||||
from openslides.utils.rest_api import router
|
||||
@ -23,5 +22,5 @@ class AssignmentsAppConfig(AppConfig):
|
||||
config_signal.connect(setup_assignment_config, dispatch_uid='setup_assignment_config')
|
||||
|
||||
# Register viewsets.
|
||||
router.register('assignments/assignment', AssignmentViewSet)
|
||||
router.register(self.get_model('Assignment').get_collection_name(), AssignmentViewSet)
|
||||
router.register('assignments/poll', AssignmentPollViewSet)
|
||||
|
@ -7,6 +7,7 @@ from django.utils.translation import ugettext as _
|
||||
from django.utils.translation import ugettext_lazy, ugettext_noop
|
||||
|
||||
from openslides.agenda.models import Item, Speaker
|
||||
from openslides.assignments.access_permissions import AccessPermissions
|
||||
from openslides.core.config import config
|
||||
from openslides.core.models import Tag
|
||||
from openslides.poll.models import (
|
||||
@ -49,6 +50,7 @@ class AssignmentRelatedUser(RESTModelMixin, models.Model):
|
||||
|
||||
|
||||
class Assignment(RESTModelMixin, models.Model):
|
||||
access_permissions = AccessPermissions()
|
||||
|
||||
PHASE_SEARCH = 0
|
||||
PHASE_VOTING = 1
|
||||
|
@ -17,6 +17,7 @@ from reportlab.platypus import (
|
||||
TableStyle,
|
||||
)
|
||||
|
||||
from openslides.assignments.access_permissions import AccessPermissions
|
||||
from openslides.core.config import config
|
||||
from openslides.utils.pdf import stylesheet
|
||||
from openslides.utils.rest_api import (
|
||||
@ -49,12 +50,15 @@ class AssignmentViewSet(ModelViewSet):
|
||||
mark_elected and create_poll.
|
||||
"""
|
||||
queryset = Assignment.objects.all()
|
||||
access_permissions = AccessPermissions()
|
||||
|
||||
def check_view_permissions(self):
|
||||
"""
|
||||
Returns True if the user has required permissions.
|
||||
"""
|
||||
if self.action in ('metadata', 'list', 'retrieve'):
|
||||
if self.action == 'retrieve':
|
||||
result = self.access_permissions.can_retrieve(self.request.user)
|
||||
elif self.action in ('metadata', 'list'):
|
||||
result = self.request.user.has_perm('assignments.can_see')
|
||||
elif self.action in ('create', 'partial_update', 'update', 'destroy',
|
||||
'mark_elected', 'create_poll'):
|
||||
@ -70,16 +74,6 @@ class AssignmentViewSet(ModelViewSet):
|
||||
result = False
|
||||
return result
|
||||
|
||||
def get_serializer_class(self):
|
||||
"""
|
||||
Returns different serializer classes according to users permissions.
|
||||
"""
|
||||
if self.request.user.has_perm('assignments.can_manage'):
|
||||
serializer_class = AssignmentFullSerializer
|
||||
else:
|
||||
serializer_class = AssignmentShortSerializer
|
||||
return serializer_class
|
||||
|
||||
@detail_route(methods=['post', 'delete'])
|
||||
def candidature_self(self, request, pk=None):
|
||||
"""
|
||||
|
9
openslides/core/access_permissions.py
Normal file
9
openslides/core/access_permissions.py
Normal file
@ -0,0 +1,9 @@
|
||||
class AccessPermissions:
|
||||
def get_serializer_class(self, user):
|
||||
return None
|
||||
|
||||
def can_retrieve(self, user):
|
||||
"""
|
||||
TODO
|
||||
"""
|
||||
return user.has_perm('core.can_see_projector')
|
@ -12,11 +12,11 @@ class CoreAppConfig(AppConfig):
|
||||
# Load projector elements.
|
||||
# Do this by just importing all from these files.
|
||||
from . import projector # noqa
|
||||
|
||||
# Import all required stuff.
|
||||
from django.db.models import signals
|
||||
from openslides.core.signals import config_signal, post_permission_creation
|
||||
from openslides.utils.autoupdate import inform_changed_data_receiver
|
||||
from openslides.utils.autoupdate import inform_deleted_data_receiver
|
||||
from openslides.utils.rest_api import router
|
||||
from openslides.utils.search import index_add_instance, index_del_instance
|
||||
from .signals import delete_django_app_permissions, setup_general_config
|
||||
@ -49,8 +49,8 @@ class CoreAppConfig(AppConfig):
|
||||
inform_changed_data_receiver,
|
||||
dispatch_uid='inform_changed_data_receiver')
|
||||
signals.post_delete.connect(
|
||||
inform_changed_data_receiver,
|
||||
dispatch_uid='inform_changed_data_receiver')
|
||||
inform_deleted_data_receiver,
|
||||
dispatch_uid='inform_deleted_data_receiver')
|
||||
|
||||
# Update the search when a model is saved or deleted
|
||||
signals.post_save.connect(
|
||||
|
@ -13,6 +13,7 @@ from django.http import Http404, HttpResponse
|
||||
from django.utils.timezone import now
|
||||
|
||||
from openslides import __version__ as version
|
||||
from openslides.core.access_permissions import AccessPermissions
|
||||
from openslides.utils import views as utils_views
|
||||
from openslides.utils.plugins import (
|
||||
get_plugin_description,
|
||||
@ -154,12 +155,15 @@ class ProjectorViewSet(ReadOnlyModelViewSet):
|
||||
"""
|
||||
queryset = Projector.objects.all()
|
||||
serializer_class = ProjectorSerializer
|
||||
access_permissions = AccessPermissions()
|
||||
|
||||
def check_view_permissions(self):
|
||||
"""
|
||||
Returns True if the user has required permissions.
|
||||
"""
|
||||
if self.action in ('metadata', 'list', 'retrieve'):
|
||||
if self.action == 'retrieve':
|
||||
result = self.access_permissions.can_retrieve(self.request.user)
|
||||
elif self.action in ('metadata', 'list'):
|
||||
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'):
|
||||
|
@ -2,9 +2,10 @@ import json
|
||||
import os
|
||||
import posixpath
|
||||
from urllib.parse import unquote
|
||||
|
||||
from django.conf import settings
|
||||
from openslides.users.auth import get_user
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
from django.utils.importlib import import_module
|
||||
from sockjs.tornado import SockJSConnection, SockJSRouter
|
||||
from tornado.httpclient import AsyncHTTPClient, HTTPRequest
|
||||
from tornado.httpserver import HTTPServer
|
||||
@ -18,7 +19,6 @@ from tornado.web import (
|
||||
StaticFileHandler,
|
||||
)
|
||||
from tornado.wsgi import WSGIContainer
|
||||
|
||||
from .rest_api import get_collection_and_id_from_url
|
||||
|
||||
RUNNING_HOST = None
|
||||
@ -59,6 +59,9 @@ class DjangoStaticFileHandler(StaticFileHandler):
|
||||
return absolute_path
|
||||
|
||||
|
||||
class FakeRequest:
|
||||
pass
|
||||
|
||||
class OpenSlidesSockJSConnection(SockJSConnection):
|
||||
"""
|
||||
SockJS connection for OpenSlides.
|
||||
@ -72,78 +75,41 @@ class OpenSlidesSockJSConnection(SockJSConnection):
|
||||
def on_close(self):
|
||||
OpenSlidesSockJSConnection.waiters.remove(self)
|
||||
|
||||
def forward_rest_response(self, response):
|
||||
"""
|
||||
Sends data to the client of the connection instance.
|
||||
|
||||
This method is called after succesful response of AsyncHTTPClient().
|
||||
See send_object().
|
||||
"""
|
||||
if response.code in (200, 404):
|
||||
# Only send something to the client in case of one of these status
|
||||
# codes. You have to change the client code (autoupdate.onMessage)
|
||||
# if you want to handle some more codes.
|
||||
collection, obj_id = get_collection_and_id_from_url(response.request.url)
|
||||
data = {
|
||||
'url': response.request.url,
|
||||
'status_code': response.code,
|
||||
'collection': collection,
|
||||
'id': obj_id,
|
||||
'data': json.loads(response.body.decode())}
|
||||
self.send(data)
|
||||
|
||||
@classmethod
|
||||
def send_object(cls, object_url):
|
||||
def send_object(cls, instance, is_delete):
|
||||
"""
|
||||
Sends an OpenSlides object to all connected clients (waiters).
|
||||
|
||||
First, retrieve the object from the OpenSlides REST api using the given
|
||||
object_url.
|
||||
"""
|
||||
# Join network location with object URL.
|
||||
if settings.OPENSLIDES_WSGI_NETWORK_LOCATION:
|
||||
wsgi_network_location = settings.OPENSLIDES_WSGI_NETWORK_LOCATION
|
||||
else:
|
||||
if RUNNING_HOST == '0.0.0.0':
|
||||
# Windows can not connect to 0.0.0.0, so connect to localhost instead.
|
||||
wsgi_network_location = 'http://localhost:{}'.format(RUNNING_PORT)
|
||||
else:
|
||||
wsgi_network_location = 'http://{}:{}'.format(RUNNING_HOST, RUNNING_PORT)
|
||||
url = ''.join((wsgi_network_location, object_url))
|
||||
|
||||
# Send out internal HTTP request to get data from the REST api.
|
||||
for waiter in cls.waiters:
|
||||
# Initiat new headers object.
|
||||
headers = HTTPHeaders()
|
||||
|
||||
# Read waiter's former cookies and parse session cookie to new header object.
|
||||
headers = HTTPHeaders()
|
||||
try:
|
||||
session_cookie = waiter.connection_info.cookies[settings.SESSION_COOKIE_NAME]
|
||||
engine = import_module(settings.SESSION_ENGINE)
|
||||
session = engine.SessionStore(session_cookie)
|
||||
|
||||
request = FakeRequest()
|
||||
request.session = session
|
||||
|
||||
user = get_user(request)
|
||||
serializer_class = instance.access_permissions.get_serializer_class(user)
|
||||
serialized_instance_data = serializer_class(instance).data
|
||||
|
||||
data = {
|
||||
'url': "foobar",
|
||||
'status_code': 404 if is_delete else 200,
|
||||
'collection': instance.get_collection_name(),
|
||||
'id': instance.id,
|
||||
'data': serialized_instance_data}
|
||||
waiter.send(data)
|
||||
except KeyError:
|
||||
# There is no session cookie
|
||||
pass
|
||||
else:
|
||||
headers.add('Cookie', '%s=%s' % (settings.SESSION_COOKIE_NAME, session_cookie.value))
|
||||
|
||||
# Read waiter's language header.
|
||||
try:
|
||||
languages = waiter.connection_info.headers['Accept-Language']
|
||||
except KeyError:
|
||||
# There is no language header
|
||||
pass
|
||||
else:
|
||||
headers.parse_line('Accept-Language: ' + languages)
|
||||
|
||||
# Setup uncompressed request.
|
||||
request = HTTPRequest(
|
||||
url=url,
|
||||
headers=headers,
|
||||
decompress_response=False)
|
||||
# Setup non-blocking HTTP client
|
||||
http_client = AsyncHTTPClient()
|
||||
# Executes the request, asynchronously returning an HTTPResponse
|
||||
# and calling waiter's forward_rest_response() method.
|
||||
http_client.fetch(request, waiter.forward_rest_response)
|
||||
|
||||
|
||||
def run_tornado(addr, port, *args, **kwargs):
|
||||
@ -182,23 +148,23 @@ def run_tornado(addr, port, *args, **kwargs):
|
||||
RUNNING_PORT = None
|
||||
|
||||
|
||||
def inform_changed_data(*args):
|
||||
def inform_changed_data(is_delete, *args):
|
||||
"""
|
||||
Informs all users about changed data.
|
||||
|
||||
The arguments are Django/OpenSlides models.
|
||||
"""
|
||||
rest_urls = set()
|
||||
root_instances = set()
|
||||
for instance in args:
|
||||
try:
|
||||
rest_urls.add(instance.get_root_rest_url())
|
||||
root_instances.add(instance.get_root_rest_element())
|
||||
except AttributeError:
|
||||
# Instance has no method get_root_rest_url. Just skip it.
|
||||
pass
|
||||
|
||||
if settings.USE_TORNADO_AS_WSGI_SERVER:
|
||||
for url in rest_urls:
|
||||
OpenSlidesSockJSConnection.send_object(url)
|
||||
for root_instance in root_instances:
|
||||
OpenSlidesSockJSConnection.send_object(root_instance, is_delete)
|
||||
else:
|
||||
pass
|
||||
# TODO: Implement big varainte with Apache or Nginx as wsgi webserver.
|
||||
@ -208,4 +174,11 @@ def inform_changed_data_receiver(sender, instance, **kwargs):
|
||||
"""
|
||||
Receiver for the inform_changed_data function to use in a signal.
|
||||
"""
|
||||
inform_changed_data(instance)
|
||||
inform_changed_data(False, instance)
|
||||
|
||||
|
||||
def inform_deleted_data_receiver(sender, instance, **kwargs):
|
||||
"""
|
||||
Receiver for the inform_changed_data function to use in a signal.
|
||||
"""
|
||||
inform_changed_data(True, instance)
|
||||
|
@ -22,6 +22,12 @@ class RESTModelMixin:
|
||||
Mixin for django models which are used in our rest api.
|
||||
"""
|
||||
|
||||
access_permissions = None
|
||||
|
||||
@classmethod
|
||||
def get_collection_name(cls):
|
||||
return "{0}/{1}".format(cls._meta.app_label.lower(), cls._meta.object_name.lower())
|
||||
|
||||
def get_root_rest_element(self):
|
||||
"""
|
||||
Returns the root rest instance.
|
||||
|
@ -95,28 +95,6 @@ class IdPrimaryKeyRelatedField(PrimaryKeyRelatedField):
|
||||
return IdManyRelatedField(**list_kwargs)
|
||||
|
||||
|
||||
class ModelSerializer(_ModelSerializer):
|
||||
"""
|
||||
ModelSerializer that changes the field names of related fields to
|
||||
FIELD_NAME_id.
|
||||
"""
|
||||
serializer_related_field = IdPrimaryKeyRelatedField
|
||||
|
||||
def get_fields(self):
|
||||
"""
|
||||
Returns all fields of the serializer.
|
||||
"""
|
||||
fields = OrderedDict()
|
||||
|
||||
for field_name, field in super().get_fields().items():
|
||||
try:
|
||||
field_name += field.field_name_suffix
|
||||
except AttributeError:
|
||||
pass
|
||||
fields[field_name] = field
|
||||
return fields
|
||||
|
||||
|
||||
class PermissionMixin:
|
||||
"""
|
||||
Mixin for subclasses of APIView like GenericViewSet and ModelViewSet.
|
||||
@ -126,6 +104,13 @@ class PermissionMixin:
|
||||
Django REST framework's permission system is disabled.
|
||||
"""
|
||||
|
||||
def get_serializer_class(self):
|
||||
"""
|
||||
TODO
|
||||
"""
|
||||
serializer_class = self.access_permissions.get_serializer_class(self.request.user) if self.access_permissions is not None else None
|
||||
return super().get_serializer_class() if serializer_class is None else serializer_class
|
||||
|
||||
def get_permissions(self):
|
||||
"""
|
||||
Overriden method to check view and projector permissions. Returns an
|
||||
@ -160,12 +145,34 @@ class PermissionMixin:
|
||||
return result
|
||||
|
||||
|
||||
class ModelSerializer(_ModelSerializer):
|
||||
"""
|
||||
ModelSerializer that changes the field names of related fields to
|
||||
FIELD_NAME_id.
|
||||
"""
|
||||
serializer_related_field = IdPrimaryKeyRelatedField
|
||||
|
||||
def get_fields(self):
|
||||
"""
|
||||
Returns all fields of the serializer.
|
||||
"""
|
||||
fields = OrderedDict()
|
||||
|
||||
for field_name, field in super().get_fields().items():
|
||||
try:
|
||||
field_name += field.field_name_suffix
|
||||
except AttributeError:
|
||||
pass
|
||||
fields[field_name] = field
|
||||
return fields
|
||||
|
||||
|
||||
class GenericViewSet(PermissionMixin, _GenericViewSet):
|
||||
pass
|
||||
|
||||
|
||||
class ModelViewSet(PermissionMixin, _ModelViewSet):
|
||||
pass
|
||||
access_permissions = None
|
||||
|
||||
|
||||
class ReadOnlyModelViewSet(PermissionMixin, _ReadOnlyModelViewSet):
|
||||
|
@ -10,3 +10,7 @@ def to_roman(number):
|
||||
return roman.toRoman(number)
|
||||
except (roman.NotIntegerError, roman.OutOfRangeError):
|
||||
return None
|
||||
|
||||
|
||||
def collection_name(model_class):
|
||||
return "{1}/{2}".format(model_class.Meta.app_label.lower(), model_class.Meta.object_name)
|
||||
|
Loading…
Reference in New Issue
Block a user