OpenSlides/openslides/core/views.py
2015-06-29 15:15:33 +02:00

311 lines
11 KiB
Python

import re
from collections import OrderedDict
from operator import attrgetter
from django.conf import settings
from django.contrib.staticfiles import finders
from django.core.urlresolvers import get_resolver
from django.http import Http404, HttpResponse
from openslides import __version__ as version
from openslides.utils import views as utils_views
from openslides.utils.plugins import (
get_plugin_description,
get_plugin_verbose_name,
get_plugin_version,
)
from openslides.utils.rest_api import (
ModelViewSet,
ReadOnlyModelViewSet,
Response,
SimpleMetadata,
ValidationError,
ViewSet,
detail_route,
)
from .config import config
from .exceptions import ConfigError, ConfigNotFound
from .models import CustomSlide, Projector, Tag
from .serializers import (
CustomSlideSerializer,
ProjectorSerializer,
TagSerializer,
)
class IndexView(utils_views.CSRFMixin, utils_views.View):
"""
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)
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)
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
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
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})