2015-02-14 10:10:08 +01:00
|
|
|
import re
|
2015-06-29 12:08:15 +02:00
|
|
|
from collections import OrderedDict
|
|
|
|
from operator import attrgetter
|
2015-02-14 10:10:08 +01:00
|
|
|
|
2015-06-18 21:48:20 +02:00
|
|
|
from django.conf import settings
|
2015-01-30 11:58:36 +01:00
|
|
|
from django.contrib.staticfiles import finders
|
2015-06-16 10:37:23 +02:00
|
|
|
from django.core.urlresolvers import get_resolver
|
2015-06-29 12:08:15 +02:00
|
|
|
from django.http import Http404, HttpResponse
|
2013-03-01 17:13:12 +01:00
|
|
|
|
2015-06-18 21:48:20 +02:00
|
|
|
from openslides import __version__ as version
|
2015-01-06 00:04:36 +01:00
|
|
|
from openslides.utils import views as utils_views
|
2015-06-18 21:48:20 +02:00
|
|
|
from openslides.utils.plugins import (
|
|
|
|
get_plugin_description,
|
|
|
|
get_plugin_verbose_name,
|
|
|
|
get_plugin_version,
|
|
|
|
)
|
2015-02-18 01:45:39 +01:00
|
|
|
from openslides.utils.rest_api import (
|
|
|
|
ModelViewSet,
|
|
|
|
ReadOnlyModelViewSet,
|
|
|
|
Response,
|
2015-06-29 12:08:15 +02:00
|
|
|
SimpleMetadata,
|
2015-02-18 01:45:39 +01:00
|
|
|
ValidationError,
|
2015-06-29 12:08:15 +02:00
|
|
|
ViewSet,
|
2015-06-16 10:37:23 +02:00
|
|
|
detail_route,
|
2015-02-18 01:45:39 +01:00
|
|
|
)
|
2013-12-09 23:56:01 +01:00
|
|
|
|
2015-06-29 12:08:15 +02:00
|
|
|
from .config import config
|
|
|
|
from .exceptions import ConfigError, ConfigNotFound
|
2015-02-18 01:45:39 +01:00
|
|
|
from .models import CustomSlide, Projector, Tag
|
|
|
|
from .serializers import (
|
|
|
|
CustomSlideSerializer,
|
|
|
|
ProjectorSerializer,
|
|
|
|
TagSerializer,
|
|
|
|
)
|
2013-12-09 23:56:01 +01:00
|
|
|
|
|
|
|
|
2015-02-17 20:07:44 +01:00
|
|
|
class IndexView(utils_views.CSRFMixin, utils_views.View):
|
2015-01-30 11:58:36 +01:00
|
|
|
"""
|
|
|
|
The primary view for OpenSlides using AngularJS.
|
|
|
|
|
|
|
|
The default base template is 'openslides/core/static/templates/index.html'.
|
|
|
|
You can override it by simply adding a custom 'templates/index.html' file
|
|
|
|
to the custom staticfiles directory. See STATICFILES_DIRS in settings.py.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def get(self, *args, **kwargs):
|
|
|
|
with open(finders.find('templates/index.html')) as f:
|
|
|
|
content = f.read()
|
|
|
|
return HttpResponse(content)
|
|
|
|
|
|
|
|
|
2015-06-17 09:45:00 +02:00
|
|
|
class ProjectorView(utils_views.View):
|
|
|
|
"""
|
|
|
|
Access the projector.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def get(self, *args, **kwargs):
|
|
|
|
with open(finders.find('templates/projector.html')) as f:
|
|
|
|
content = f.read()
|
|
|
|
return HttpResponse(content)
|
|
|
|
|
|
|
|
|
2015-02-18 01:45:39 +01:00
|
|
|
class ProjectorViewSet(ReadOnlyModelViewSet):
|
|
|
|
"""
|
|
|
|
API endpoint to list, retrieve and update the projector slide info.
|
|
|
|
"""
|
|
|
|
queryset = Projector.objects.all()
|
|
|
|
serializer_class = ProjectorSerializer
|
|
|
|
|
|
|
|
def check_permissions(self, request):
|
|
|
|
"""
|
|
|
|
Calls self.permission_denied() if the requesting user has not the
|
|
|
|
permission to see the projector and in case of an update request the
|
|
|
|
permission to manage the projector.
|
|
|
|
"""
|
|
|
|
manage_methods = (
|
|
|
|
'activate_elements',
|
|
|
|
'prune_elements',
|
|
|
|
'deactivate_elements',
|
|
|
|
'clear_elements')
|
|
|
|
if (not request.user.has_perm('core.can_see_projector') or
|
|
|
|
(self.action in manage_methods and
|
|
|
|
not request.user.has_perm('core.can_manage_projector'))):
|
|
|
|
self.permission_denied(request)
|
|
|
|
|
|
|
|
@detail_route(methods=['post'])
|
|
|
|
def activate_elements(self, request, pk):
|
|
|
|
"""
|
|
|
|
REST API operation to activate projector elements. It expects a POST
|
|
|
|
request to /rest/core/projector/<pk>/activate_elements/ with a list
|
|
|
|
of dictionaries to append to the projector config entry.
|
|
|
|
"""
|
|
|
|
# Get config entry from projector model, add new elements and try to
|
|
|
|
# serialize. This raises ValidationErrors if the data is invalid.
|
|
|
|
projector_instance = self.get_object()
|
|
|
|
projector_config = projector_instance.config
|
|
|
|
for projector_element in request.data:
|
|
|
|
projector_config.append(projector_element)
|
|
|
|
serializer = self.get_serializer(projector_instance, data={'config': projector_config}, partial=False)
|
|
|
|
serializer.is_valid(raise_exception=True)
|
|
|
|
serializer.save()
|
|
|
|
return Response(serializer.data)
|
|
|
|
|
|
|
|
@detail_route(methods=['post'])
|
|
|
|
def prune_elements(self, request, pk):
|
|
|
|
"""
|
|
|
|
REST API operation to activate projector elements. It expects a POST
|
|
|
|
request to /rest/core/projector/<pk>/prune_elements/ with a list of
|
|
|
|
dictionaries to write them to the projector config entry. All old
|
|
|
|
entries are deleted but not entries with stable == True.
|
|
|
|
"""
|
|
|
|
# Get config entry from projector model, delete old and add new
|
|
|
|
# elements and try to serialize. This raises ValidationErrors if the
|
|
|
|
# data is invalid. Do not filter 'stable' elements.
|
|
|
|
projector_instance = self.get_object()
|
|
|
|
projector_config = [element for element in projector_instance.config if element.get('stable')]
|
|
|
|
projector_config.extend(request.data)
|
|
|
|
serializer = self.get_serializer(projector_instance, data={'config': projector_config}, partial=False)
|
|
|
|
serializer.is_valid(raise_exception=True)
|
|
|
|
serializer.save()
|
|
|
|
return Response(serializer.data)
|
|
|
|
|
|
|
|
@detail_route(methods=['post'])
|
|
|
|
def deactivate_elements(self, request, pk):
|
|
|
|
"""
|
|
|
|
REST API operation to deactivate projector elements. It expects a
|
|
|
|
POST request to /rest/core/projector/<pk>/deactivate_elements/ with
|
|
|
|
a list of dictionaries. These are exactly the projector_elements in
|
|
|
|
the config that should be deleted.
|
|
|
|
"""
|
|
|
|
# Check the data. It must be a list of dictionaries. Get config
|
|
|
|
# entry from projector model. Pop out the entries that should be
|
|
|
|
# deleted and try to serialize. This raises ValidationErrors if the
|
|
|
|
# data is invalid.
|
|
|
|
if not isinstance(request.data, list) or list(filter(lambda item: not isinstance(item, dict), request.data)):
|
|
|
|
raise ValidationError({'config': ['Data must be a list of dictionaries.']})
|
|
|
|
|
|
|
|
projector_instance = self.get_object()
|
|
|
|
projector_config = projector_instance.config
|
|
|
|
for entry_to_be_deleted in request.data:
|
|
|
|
try:
|
|
|
|
projector_config.remove(entry_to_be_deleted)
|
|
|
|
except ValueError:
|
|
|
|
# The entry that should be deleted is not on the projector.
|
|
|
|
pass
|
|
|
|
serializer = self.get_serializer(projector_instance, data={'config': projector_config}, partial=False)
|
|
|
|
serializer.is_valid(raise_exception=True)
|
|
|
|
serializer.save()
|
|
|
|
return Response(serializer.data)
|
|
|
|
|
|
|
|
@detail_route(methods=['post'])
|
|
|
|
def clear_elements(self, request, pk):
|
|
|
|
"""
|
|
|
|
REST API operation to deactivate all projector elements but not
|
|
|
|
entries with stable == True. It expects a POST request to
|
|
|
|
/rest/core/projector/<pk>/clear_elements/.
|
|
|
|
"""
|
|
|
|
# Get config entry from projector model. Then clear the config field
|
|
|
|
# and try to serialize. Do not remove 'stable' elements.
|
|
|
|
projector_instance = self.get_object()
|
|
|
|
projector_config = [element for element in projector_instance.config if element.get('stable')]
|
|
|
|
serializer = self.get_serializer(projector_instance, data={'config': projector_config}, partial=False)
|
|
|
|
serializer.is_valid(raise_exception=True)
|
|
|
|
serializer.save()
|
|
|
|
return Response(serializer.data)
|
|
|
|
|
|
|
|
|
|
|
|
class CustomSlideViewSet(ModelViewSet):
|
|
|
|
"""
|
|
|
|
API endpoint to list, retrieve, create, update and destroy custom slides.
|
|
|
|
"""
|
|
|
|
queryset = CustomSlide.objects.all()
|
|
|
|
serializer_class = CustomSlideSerializer
|
|
|
|
|
|
|
|
def check_permissions(self, request):
|
|
|
|
"""
|
|
|
|
Calls self.permission_denied() if the requesting user has not the
|
|
|
|
permission to manage projector.
|
|
|
|
"""
|
|
|
|
if not request.user.has_perm('core.can_manage_projector'):
|
|
|
|
self.permission_denied(request)
|
|
|
|
|
|
|
|
|
|
|
|
class TagViewSet(ModelViewSet):
|
|
|
|
"""
|
|
|
|
API endpoint to list, retrieve, create, update and destroy tags.
|
|
|
|
"""
|
|
|
|
queryset = Tag.objects.all()
|
|
|
|
serializer_class = TagSerializer
|
|
|
|
|
|
|
|
def check_permissions(self, request):
|
|
|
|
"""
|
|
|
|
Calls self.permission_denied() if the requesting user has not the
|
|
|
|
permission to manage tags and it is a create, update or detroy request.
|
|
|
|
Users without permissions are able to list and retrieve tags.
|
|
|
|
"""
|
|
|
|
if (self.action in ('create', 'update', 'destroy') and
|
|
|
|
not request.user.has_perm('core.can_manage_tags')):
|
|
|
|
self.permission_denied(request)
|
|
|
|
|
|
|
|
|
|
|
|
class UrlPatternsView(utils_views.APIView):
|
|
|
|
"""
|
|
|
|
Returns a dictionary with all url patterns as json. The patterns kwargs
|
|
|
|
are transformed using a colon.
|
|
|
|
"""
|
|
|
|
URL_KWARGS_REGEX = re.compile(r'%\((\w*)\)s')
|
|
|
|
http_method_names = ['get']
|
|
|
|
|
|
|
|
def get_context_data(self, **context):
|
|
|
|
result = {}
|
|
|
|
url_dict = get_resolver(None).reverse_dict
|
|
|
|
for pattern_name in filter(lambda key: isinstance(key, str), url_dict.keys()):
|
|
|
|
normalized_regex_bits, p_pattern, pattern_default_args = url_dict[pattern_name]
|
|
|
|
url, url_kwargs = normalized_regex_bits[0]
|
|
|
|
result[pattern_name] = self.URL_KWARGS_REGEX.sub(r':\1', url)
|
|
|
|
return result
|
2015-06-18 21:48:20 +02:00
|
|
|
|
|
|
|
|
|
|
|
class VersionView(utils_views.APIView):
|
|
|
|
"""
|
|
|
|
Returns a dictionary with the OpenSlides version and the version of all
|
|
|
|
plugins.
|
|
|
|
"""
|
|
|
|
http_method_names = ['get']
|
|
|
|
|
|
|
|
def get_context_data(self, **context):
|
|
|
|
result = dict(openslides_version=version, plugins=[])
|
|
|
|
# Versions of plugins.
|
|
|
|
for plugin in settings.INSTALLED_PLUGINS:
|
|
|
|
result['plugins'].append({
|
|
|
|
'verbose_name': get_plugin_verbose_name(plugin),
|
|
|
|
'description': get_plugin_description(plugin),
|
|
|
|
'version': get_plugin_version(plugin)})
|
|
|
|
return result
|
2015-06-29 12:08:15 +02:00
|
|
|
|
|
|
|
|
|
|
|
class ConfigMetadata(SimpleMetadata):
|
|
|
|
"""
|
|
|
|
Custom metadata class to add config info to responses on OPTIONS requests.
|
|
|
|
"""
|
|
|
|
def determine_metadata(self, request, view):
|
|
|
|
# Sort config variables by weight.
|
|
|
|
config_variables = sorted(config.get_config_variables().values(), key=attrgetter('weight'))
|
|
|
|
|
|
|
|
# Build tree.
|
|
|
|
config_groups = []
|
|
|
|
for config_variable in config_variables:
|
|
|
|
if not config_groups or config_groups[-1]['name'] != config_variable.group:
|
|
|
|
config_groups.append(OrderedDict(
|
|
|
|
name=config_variable.group,
|
|
|
|
subgroups=[]))
|
|
|
|
if not config_groups[-1]['subgroups'] or config_groups[-1]['subgroups'][-1]['name'] != config_variable.subgroup:
|
|
|
|
config_groups[-1]['subgroups'].append(OrderedDict(
|
|
|
|
name=config_variable.subgroup,
|
|
|
|
items=[]))
|
|
|
|
config_groups[-1]['subgroups'][-1]['items'].append(config_variable.data)
|
|
|
|
|
|
|
|
# Add tree to metadata.
|
|
|
|
metadata = super().determine_metadata(request, view)
|
|
|
|
metadata['config_groups'] = config_groups
|
|
|
|
return metadata
|
|
|
|
|
|
|
|
|
|
|
|
class ConfigViewSet(ViewSet):
|
|
|
|
"""
|
|
|
|
API endpoint to list, retrieve and update the config.
|
|
|
|
"""
|
|
|
|
metadata_class = ConfigMetadata
|
|
|
|
|
|
|
|
def list(self, request):
|
|
|
|
"""
|
|
|
|
Lists all config variables. Everybody can see them.
|
|
|
|
"""
|
|
|
|
return Response([{'key': key, 'value': value} for key, value in config.items()])
|
|
|
|
|
|
|
|
def retrieve(self, request, *args, **kwargs):
|
|
|
|
"""
|
|
|
|
Retrieves a config variable. Everybody can see it.
|
|
|
|
"""
|
|
|
|
key = kwargs['pk']
|
|
|
|
try:
|
|
|
|
value = config[key]
|
|
|
|
except ConfigNotFound:
|
|
|
|
raise Http404
|
|
|
|
return Response({'key': key, 'value': value})
|
|
|
|
|
|
|
|
def update(self, request, *args, **kwargs):
|
|
|
|
"""
|
|
|
|
Updates a config variable. Only managers can do this.
|
|
|
|
|
|
|
|
Example: {"value": 42}
|
|
|
|
"""
|
|
|
|
# Check permission.
|
|
|
|
if not request.user.has_perm('core.can_manage_config'):
|
|
|
|
self.permission_denied(request)
|
|
|
|
|
|
|
|
key = kwargs['pk']
|
|
|
|
value = request.data['value']
|
|
|
|
|
|
|
|
# Validate and change value.
|
|
|
|
try:
|
|
|
|
config[key] = value
|
|
|
|
except ConfigNotFound:
|
|
|
|
raise Http404
|
|
|
|
except ConfigError as e:
|
|
|
|
raise ValidationError({'detail': e})
|
|
|
|
|
|
|
|
# Return response.
|
|
|
|
return Response({'key': key, 'value': value})
|