2016-08-19 14:44:58 +02:00
|
|
|
import base64
|
|
|
|
import json
|
|
|
|
import os
|
2015-02-14 10:10:08 +01:00
|
|
|
import re
|
2015-09-05 17:52:40 +02:00
|
|
|
import uuid
|
2015-06-29 12:08:15 +02:00
|
|
|
from collections import OrderedDict
|
|
|
|
from operator import attrgetter
|
2016-09-09 00:50:27 +02:00
|
|
|
from textwrap import dedent
|
2016-01-03 15:33:51 +01:00
|
|
|
from urllib.parse import unquote
|
2015-02-14 10:10:08 +01:00
|
|
|
|
2015-07-01 17:48:41 +02:00
|
|
|
from django.apps import apps
|
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-09-14 23:16:31 +02:00
|
|
|
from django.db.models import F
|
2015-06-29 12:08:15 +02:00
|
|
|
from django.http import Http404, HttpResponse
|
2015-09-24 21:28:30 +02:00
|
|
|
from django.utils.timezone import now
|
2016-10-17 16:56:19 +02:00
|
|
|
from django.utils.translation import ugettext as _
|
2013-03-01 17:13:12 +01:00
|
|
|
|
2016-10-17 16:56:19 +02:00
|
|
|
from .. import __version__ as version
|
|
|
|
from ..utils import views as utils_views
|
2016-11-08 21:29:26 +01:00
|
|
|
from ..utils.autoupdate import inform_changed_data, inform_deleted_data
|
2016-10-17 16:56:19 +02:00
|
|
|
from ..utils.collection import Collection, CollectionElement
|
|
|
|
from ..utils.plugins import (
|
2015-06-18 21:48:20 +02:00
|
|
|
get_plugin_description,
|
|
|
|
get_plugin_verbose_name,
|
|
|
|
get_plugin_version,
|
|
|
|
)
|
2016-10-17 16:56:19 +02:00
|
|
|
from ..utils.rest_api import (
|
2015-02-18 01:45:39 +01:00
|
|
|
ModelViewSet,
|
|
|
|
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,
|
2016-10-17 16:56:19 +02:00
|
|
|
list_route,
|
2015-02-18 01:45:39 +01:00
|
|
|
)
|
2016-10-17 16:56:19 +02:00
|
|
|
from ..utils.search import search
|
2016-02-11 22:58:32 +01:00
|
|
|
from .access_permissions import (
|
|
|
|
ChatMessageAccessPermissions,
|
|
|
|
ConfigAccessPermissions,
|
2016-10-21 11:05:24 +02:00
|
|
|
CountdownAccessPermissions,
|
2016-02-11 22:58:32 +01:00
|
|
|
ProjectorAccessPermissions,
|
2016-10-21 11:05:24 +02:00
|
|
|
ProjectorMessageAccessPermissions,
|
2016-02-11 22:58:32 +01:00
|
|
|
TagAccessPermissions,
|
|
|
|
)
|
2015-06-29 12:08:15 +02:00
|
|
|
from .config import config
|
|
|
|
from .exceptions import ConfigError, ConfigNotFound
|
2016-10-21 11:05:24 +02:00
|
|
|
from .models import (
|
|
|
|
ChatMessage,
|
|
|
|
ConfigStore,
|
|
|
|
Countdown,
|
|
|
|
ProjectionDefault,
|
|
|
|
Projector,
|
|
|
|
ProjectorMessage,
|
|
|
|
Tag,
|
|
|
|
)
|
2013-12-09 23:56:01 +01:00
|
|
|
|
|
|
|
|
2015-07-01 23:18:48 +02:00
|
|
|
# Special Django views
|
|
|
|
|
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):
|
|
|
|
"""
|
2015-10-21 22:44:07 +02:00
|
|
|
The primary view for OpenSlides projector using AngularJS.
|
|
|
|
|
2016-08-25 16:40:34 +02:00
|
|
|
The projector container template is 'openslides/core/static/templates/projector-container.html'.
|
|
|
|
This container is for controlling the projector resolution.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def get(self, *args, **kwargs):
|
|
|
|
with open(finders.find('templates/projector-container.html')) as f:
|
|
|
|
content = f.read()
|
|
|
|
return HttpResponse(content)
|
|
|
|
|
|
|
|
|
|
|
|
class RealProjectorView(utils_views.View):
|
|
|
|
"""
|
|
|
|
The original view without resolutioncontrol for OpenSlides projector using AngularJS.
|
|
|
|
|
2015-10-21 22:44:07 +02:00
|
|
|
The default base template is 'openslides/core/static/templates/projector.html'.
|
|
|
|
You can override it by simply adding a custom 'templates/projector.html'
|
|
|
|
file to the custom staticfiles directory. See STATICFILES_DIRS in
|
|
|
|
settings.py.
|
2015-06-17 09:45:00 +02:00
|
|
|
"""
|
|
|
|
|
|
|
|
def get(self, *args, **kwargs):
|
|
|
|
with open(finders.find('templates/projector.html')) as f:
|
|
|
|
content = f.read()
|
|
|
|
return HttpResponse(content)
|
|
|
|
|
|
|
|
|
2016-09-09 00:50:27 +02:00
|
|
|
class WebclientJavaScriptView(utils_views.View):
|
2015-07-01 23:18:48 +02:00
|
|
|
"""
|
2016-09-09 00:50:27 +02:00
|
|
|
This view returns JavaScript code for the main entry point in the
|
|
|
|
AngularJS app for the requested realm (site or projector). Also code
|
|
|
|
for plugins is appended. The result is not uglified.
|
2015-07-01 23:18:48 +02:00
|
|
|
"""
|
|
|
|
def get(self, *args, **kwargs):
|
|
|
|
angular_modules = []
|
|
|
|
js_files = []
|
2016-09-09 00:50:27 +02:00
|
|
|
realm = kwargs.get('realm') # Result is 'site' or 'projector'
|
2015-07-01 23:18:48 +02:00
|
|
|
for app_config in apps.get_app_configs():
|
2016-09-09 00:50:27 +02:00
|
|
|
# Add the angular app if the module has one.
|
|
|
|
if getattr(app_config, 'angular_{}_module'.format(realm), False):
|
|
|
|
angular_modules.append('OpenSlidesApp.{app_name}.{realm}'.format(
|
|
|
|
app_name=app_config.label,
|
|
|
|
realm=realm))
|
|
|
|
|
|
|
|
# Add all JavaScript files that the module needs. Our core apps
|
|
|
|
# are delivered by an extra file js/openslides.js which can be
|
|
|
|
# created via gulp.
|
|
|
|
core_apps = (
|
|
|
|
'openslides.core',
|
|
|
|
'openslides.agenda',
|
|
|
|
'openslides.motions',
|
|
|
|
'openslides.assignments',
|
|
|
|
'openslides.users',
|
|
|
|
'openslides.mediafiles',
|
|
|
|
)
|
|
|
|
if app_config.name not in core_apps:
|
|
|
|
try:
|
|
|
|
app_js_files = app_config.js_files
|
|
|
|
except AttributeError:
|
|
|
|
# The app needs no JavaScript files.
|
|
|
|
pass
|
|
|
|
else:
|
|
|
|
js_files.extend(app_js_files)
|
|
|
|
|
|
|
|
# Use JavaScript loadScript function from
|
2015-07-06 09:19:42 +02:00
|
|
|
# http://balpha.de/2011/10/jquery-script-insertion-and-its-consequences-for-debugging/
|
2016-09-09 00:50:27 +02:00
|
|
|
# jQuery is required.
|
|
|
|
content = dedent(
|
2015-07-06 09:19:42 +02:00
|
|
|
"""
|
2016-09-09 00:50:27 +02:00
|
|
|
(function () {
|
|
|
|
var loadScript = function (path) {
|
|
|
|
var result = $.Deferred(),
|
|
|
|
script = document.createElement("script");
|
|
|
|
script.async = "async";
|
|
|
|
script.type = "text/javascript";
|
|
|
|
script.src = path;
|
|
|
|
script.onload = script.onreadystatechange = function(_, isAbort) {
|
|
|
|
if (!script.readyState || /loaded|complete/.test(script.readyState)) {
|
|
|
|
if (isAbort)
|
|
|
|
result.reject();
|
|
|
|
else
|
|
|
|
result.resolve();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
script.onerror = function () { result.reject(); };
|
|
|
|
$("head")[0].appendChild(script);
|
|
|
|
return result.promise();
|
2015-07-06 09:19:42 +02:00
|
|
|
};
|
|
|
|
""" +
|
2015-09-05 16:08:40 +02:00
|
|
|
"""
|
2016-09-09 00:50:27 +02:00
|
|
|
angular.module('OpenSlidesApp.{realm}', {angular_modules});
|
|
|
|
var deferres = [];
|
|
|
|
{js_files}.forEach( function(js_file) {{ deferres.push(loadScript(js_file)); }} );
|
|
|
|
$.when.apply(this,deferres).done( function() {{
|
|
|
|
angular.bootstrap(document,['OpenSlidesApp.{realm}']);
|
|
|
|
}} );
|
|
|
|
""".format(realm=realm, angular_modules=angular_modules, js_files=js_files) +
|
2015-09-05 16:08:40 +02:00
|
|
|
"""
|
2016-09-09 00:50:27 +02:00
|
|
|
}());
|
|
|
|
""")
|
|
|
|
|
|
|
|
return HttpResponse(content, content_type='application/javascript')
|
2015-07-01 23:18:48 +02:00
|
|
|
|
|
|
|
|
|
|
|
# Viewsets for the REST API
|
|
|
|
|
2016-09-12 11:05:34 +02:00
|
|
|
class ProjectorViewSet(ModelViewSet):
|
2015-02-18 01:45:39 +01:00
|
|
|
"""
|
2015-07-01 23:18:48 +02:00
|
|
|
API endpoint for the projector slide info.
|
|
|
|
|
2015-09-14 23:16:31 +02:00
|
|
|
There are the following views: metadata, list, retrieve,
|
|
|
|
activate_elements, prune_elements, update_elements,
|
|
|
|
deactivate_elements, clear_elements and control_view.
|
2015-02-18 01:45:39 +01:00
|
|
|
"""
|
2016-02-11 22:58:32 +01:00
|
|
|
access_permissions = ProjectorAccessPermissions()
|
2015-02-18 01:45:39 +01:00
|
|
|
queryset = Projector.objects.all()
|
|
|
|
|
2015-07-01 23:18:48 +02:00
|
|
|
def check_view_permissions(self):
|
2015-02-18 01:45:39 +01:00
|
|
|
"""
|
2015-07-01 23:18:48 +02:00
|
|
|
Returns True if the user has required permissions.
|
2015-02-18 01:45:39 +01:00
|
|
|
"""
|
2016-09-17 22:26:23 +02:00
|
|
|
if self.action in ('list', 'retrieve'):
|
|
|
|
result = self.get_access_permissions().check_permissions(self.request.user)
|
|
|
|
elif self.action == 'metadata':
|
2015-07-01 23:18:48 +02:00
|
|
|
result = self.request.user.has_perm('core.can_see_projector')
|
2016-09-29 15:32:58 +02:00
|
|
|
elif self.action in (
|
|
|
|
'create', 'update', 'partial_update', 'destroy',
|
|
|
|
'activate_elements', 'prune_elements', 'update_elements', 'deactivate_elements', 'clear_elements',
|
|
|
|
'control_view', 'set_resolution', 'set_scroll', 'control_blank', 'broadcast',
|
|
|
|
'set_projectiondefault',
|
|
|
|
):
|
2015-07-01 23:18:48 +02:00
|
|
|
result = (self.request.user.has_perm('core.can_see_projector') and
|
|
|
|
self.request.user.has_perm('core.can_manage_projector'))
|
|
|
|
else:
|
|
|
|
result = False
|
|
|
|
return result
|
2015-02-18 01:45:39 +01:00
|
|
|
|
2016-09-29 15:32:58 +02:00
|
|
|
# Assign all ProjectionDefault objects from this projector to the default projector (pk=1).
|
2016-09-12 11:05:34 +02:00
|
|
|
def destroy(self, *args, **kwargs):
|
|
|
|
projector_instance = self.get_object()
|
2016-09-29 15:32:58 +02:00
|
|
|
for projection_default in ProjectionDefault.objects.all():
|
|
|
|
if projection_default.projector.id == projector_instance.id:
|
|
|
|
projection_default.projector_id = 1
|
|
|
|
projection_default.save()
|
2016-09-12 11:05:34 +02:00
|
|
|
return super(ProjectorViewSet, self).destroy(*args, **kwargs)
|
|
|
|
|
2015-02-18 01:45:39 +01:00
|
|
|
@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
|
2015-09-06 13:28:25 +02:00
|
|
|
of dictionaries to be appended to the projector config entry.
|
2015-02-18 01:45:39 +01:00
|
|
|
"""
|
2015-09-06 13:28:25 +02:00
|
|
|
if not isinstance(request.data, list):
|
2016-02-06 00:02:22 +01:00
|
|
|
raise ValidationError({'detail': 'Data must be a list.'})
|
2015-09-06 13:28:25 +02:00
|
|
|
|
2015-02-18 01:45:39 +01:00
|
|
|
projector_instance = self.get_object()
|
|
|
|
projector_config = projector_instance.config
|
2015-09-06 13:28:25 +02:00
|
|
|
for element in request.data:
|
|
|
|
if element.get('name') is None:
|
2016-02-06 00:02:22 +01:00
|
|
|
raise ValidationError({'detail': 'Invalid projector element. Name is missing.'})
|
2015-09-06 13:28:25 +02:00
|
|
|
projector_config[uuid.uuid4().hex] = element
|
|
|
|
|
2015-02-18 01:45:39 +01:00
|
|
|
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.
|
|
|
|
"""
|
2015-09-06 13:28:25 +02:00
|
|
|
if not isinstance(request.data, list):
|
2016-02-06 00:02:22 +01:00
|
|
|
raise ValidationError({'detail': 'Data must be a list.'})
|
2015-09-06 13:28:25 +02:00
|
|
|
|
2015-02-18 01:45:39 +01:00
|
|
|
projector_instance = self.get_object()
|
2015-09-06 13:28:25 +02:00
|
|
|
projector_config = {}
|
|
|
|
for key, value in projector_instance.config.items():
|
|
|
|
if value.get('stable'):
|
|
|
|
projector_config[key] = value
|
|
|
|
for element in request.data:
|
|
|
|
if element.get('name') is None:
|
2016-02-06 00:02:22 +01:00
|
|
|
raise ValidationError({'detail': 'Invalid projector element. Name is missing.'})
|
2015-09-06 13:28:25 +02:00
|
|
|
projector_config[uuid.uuid4().hex] = element
|
|
|
|
|
2015-02-18 01:45:39 +01:00
|
|
|
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'])
|
2015-09-05 17:52:40 +02:00
|
|
|
def update_elements(self, request, pk):
|
2015-02-18 01:45:39 +01:00
|
|
|
"""
|
2015-09-05 17:52:40 +02:00
|
|
|
REST API operation to update projector elements. It expects a POST
|
2015-09-14 23:16:31 +02:00
|
|
|
request to /rest/core/projector/<pk>/update_elements/ with a
|
|
|
|
dictonary to update the projector config. This must be a dictionary
|
|
|
|
with UUIDs as keys and projector element dictionaries as values.
|
|
|
|
|
|
|
|
Example:
|
|
|
|
|
|
|
|
{
|
|
|
|
"191c0878cdc04abfbd64f3177a21891a": {
|
|
|
|
"name": "core/countdown",
|
|
|
|
"stable": true,
|
|
|
|
"status": "running",
|
|
|
|
"countdown_time": 1374321600.0,
|
|
|
|
"visable": true,
|
|
|
|
"default": 42
|
|
|
|
}
|
|
|
|
}
|
2015-02-18 01:45:39 +01:00
|
|
|
"""
|
2015-09-06 13:28:25 +02:00
|
|
|
if not isinstance(request.data, dict):
|
2016-02-06 00:02:22 +01:00
|
|
|
raise ValidationError({'detail': 'Data must be a dictionary.'})
|
|
|
|
error = {'detail': 'Data must be a dictionary with UUIDs as keys and dictionaries as values.'}
|
2015-09-06 13:28:25 +02:00
|
|
|
for key, value in request.data.items():
|
2015-09-05 17:52:40 +02:00
|
|
|
try:
|
2015-09-06 13:28:25 +02:00
|
|
|
uuid.UUID(hex=str(key))
|
2015-09-05 17:52:40 +02:00
|
|
|
except ValueError:
|
|
|
|
raise ValidationError(error)
|
2015-09-06 13:28:25 +02:00
|
|
|
if not isinstance(value, dict):
|
2015-09-05 17:52:40 +02:00
|
|
|
raise ValidationError(error)
|
2015-02-18 01:45:39 +01:00
|
|
|
|
|
|
|
projector_instance = self.get_object()
|
|
|
|
projector_config = projector_instance.config
|
2015-09-06 13:28:25 +02:00
|
|
|
for key, value in request.data.items():
|
|
|
|
if key not in projector_config:
|
2016-02-06 00:02:22 +01:00
|
|
|
raise ValidationError({'detail': 'Invalid projector element. Wrong UUID.'})
|
2015-09-05 23:32:10 +02:00
|
|
|
projector_config[key].update(request.data[key])
|
2015-09-06 13:28:25 +02:00
|
|
|
|
2015-09-05 17:52:40 +02:00
|
|
|
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 hex UUIDs. These are the projector_elements in the config
|
|
|
|
that should be deleted.
|
|
|
|
"""
|
|
|
|
if not isinstance(request.data, list):
|
2016-02-06 00:02:22 +01:00
|
|
|
raise ValidationError({'detail': 'Data must be a list of hex UUIDs.'})
|
2015-09-05 17:52:40 +02:00
|
|
|
for item in request.data:
|
2015-02-18 01:45:39 +01:00
|
|
|
try:
|
2015-09-05 17:52:40 +02:00
|
|
|
uuid.UUID(hex=str(item))
|
2015-02-18 01:45:39 +01:00
|
|
|
except ValueError:
|
2016-02-06 00:02:22 +01:00
|
|
|
raise ValidationError({'detail': 'Data must be a list of hex UUIDs.'})
|
2015-09-05 17:52:40 +02:00
|
|
|
|
|
|
|
projector_instance = self.get_object()
|
2015-09-06 13:28:25 +02:00
|
|
|
projector_config = projector_instance.config
|
|
|
|
for key in request.data:
|
|
|
|
try:
|
|
|
|
del projector_config[key]
|
|
|
|
except KeyError:
|
2016-02-06 00:02:22 +01:00
|
|
|
raise ValidationError({'detail': 'Invalid UUID.'})
|
2015-09-06 13:28:25 +02:00
|
|
|
|
|
|
|
serializer = self.get_serializer(projector_instance, data={'config': projector_config}, partial=False)
|
2015-02-18 01:45:39 +01:00
|
|
|
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/.
|
|
|
|
"""
|
|
|
|
projector_instance = self.get_object()
|
2015-09-06 13:28:25 +02:00
|
|
|
projector_config = {}
|
|
|
|
for key, value in projector_instance.config.items():
|
|
|
|
if value.get('stable'):
|
|
|
|
projector_config[key] = value
|
|
|
|
|
2015-02-18 01:45:39 +01:00
|
|
|
serializer = self.get_serializer(projector_instance, data={'config': projector_config}, partial=False)
|
|
|
|
serializer.is_valid(raise_exception=True)
|
|
|
|
serializer.save()
|
|
|
|
return Response(serializer.data)
|
|
|
|
|
2016-08-25 16:40:34 +02:00
|
|
|
@detail_route(methods=['post'])
|
|
|
|
def set_resolution(self, request, pk):
|
|
|
|
"""
|
|
|
|
REST API operation to set the resolution.
|
|
|
|
|
|
|
|
It is actually unused, because the resolution is currently set in the config.
|
|
|
|
But with the multiprojector feature this will become importent to set the
|
|
|
|
resolution per projector individually.
|
|
|
|
|
|
|
|
It expects a POST request to
|
|
|
|
/rest/core/projector/<pk>/set_resolution/ with a dictionary with the width
|
|
|
|
and height and the values.
|
|
|
|
|
|
|
|
Example:
|
|
|
|
|
|
|
|
{
|
|
|
|
"width": "1024",
|
|
|
|
"height": "768"
|
|
|
|
}
|
|
|
|
"""
|
|
|
|
if not isinstance(request.data, dict):
|
|
|
|
raise ValidationError({'detail': 'Data must be a dictionary.'})
|
|
|
|
if request.data.get('width') is None or request.data.get('height') is None:
|
|
|
|
raise ValidationError({'detail': 'A width and a height have to be given.'})
|
|
|
|
if not isinstance(request.data['width'], int) or not isinstance(request.data['height'], int):
|
|
|
|
raise ValidationError({'detail': 'Data has to be integers.'})
|
|
|
|
if (request.data['width'] < 800 or request.data['width'] > 3840 or
|
|
|
|
request.data['height'] < 600 or request.data['height'] > 2160):
|
|
|
|
raise ValidationError({'detail': 'The Resolution have to be between 800x600 and 3840x2160.'})
|
|
|
|
|
|
|
|
projector_instance = self.get_object()
|
|
|
|
projector_instance.width = request.data['width']
|
|
|
|
projector_instance.height = request.data['height']
|
|
|
|
projector_instance.save()
|
|
|
|
|
|
|
|
message = 'Changing resolution to {width}x{height} was successful.'.format(
|
|
|
|
width=request.data['width'],
|
|
|
|
height=request.data['height'])
|
|
|
|
return Response({'detail': message})
|
|
|
|
|
2015-09-14 23:16:31 +02:00
|
|
|
@detail_route(methods=['post'])
|
|
|
|
def control_view(self, request, pk):
|
|
|
|
"""
|
|
|
|
REST API operation to control the projector view, i. e. scale and
|
|
|
|
scroll the projector.
|
|
|
|
|
|
|
|
It expects a POST request to
|
|
|
|
/rest/core/projector/<pk>/control_view/ with a dictionary with an
|
|
|
|
action ('scale' or 'scroll') and a direction ('up', 'down' or
|
|
|
|
'reset').
|
|
|
|
|
|
|
|
Example:
|
|
|
|
|
|
|
|
{
|
|
|
|
"action": "scale",
|
|
|
|
"direction": "up"
|
|
|
|
}
|
|
|
|
"""
|
|
|
|
if not isinstance(request.data, dict):
|
2016-02-06 00:02:22 +01:00
|
|
|
raise ValidationError({'detail': 'Data must be a dictionary.'})
|
2015-09-14 23:16:31 +02:00
|
|
|
if (request.data.get('action') not in ('scale', 'scroll') or
|
|
|
|
request.data.get('direction') not in ('up', 'down', 'reset')):
|
2016-02-06 00:02:22 +01:00
|
|
|
raise ValidationError({'detail': "Data must be a dictionary with an action ('scale' or 'scroll') "
|
|
|
|
"and a direction ('up', 'down' or 'reset')."})
|
2015-09-14 23:16:31 +02:00
|
|
|
|
|
|
|
projector_instance = self.get_object()
|
|
|
|
if request.data['action'] == 'scale':
|
|
|
|
if request.data['direction'] == 'up':
|
|
|
|
projector_instance.scale = F('scale') + 1
|
|
|
|
elif request.data['direction'] == 'down':
|
|
|
|
projector_instance.scale = F('scale') - 1
|
|
|
|
else:
|
|
|
|
# request.data['direction'] == 'reset'
|
|
|
|
projector_instance.scale = 0
|
|
|
|
else:
|
|
|
|
# request.data['action'] == 'scroll'
|
|
|
|
if request.data['direction'] == 'up':
|
|
|
|
projector_instance.scroll = F('scroll') + 1
|
|
|
|
elif request.data['direction'] == 'down':
|
|
|
|
projector_instance.scroll = F('scroll') - 1
|
|
|
|
else:
|
|
|
|
# request.data['direction'] == 'reset'
|
|
|
|
projector_instance.scroll = 0
|
|
|
|
|
2016-11-08 21:29:26 +01:00
|
|
|
projector_instance.save(skip_autoupdate=True)
|
|
|
|
projector_instance.refresh_from_db()
|
|
|
|
inform_changed_data(projector_instance)
|
2015-09-14 23:16:31 +02:00
|
|
|
message = '{action} {direction} was successful.'.format(
|
|
|
|
action=request.data['action'].capitalize(),
|
|
|
|
direction=request.data['direction'])
|
|
|
|
return Response({'detail': message})
|
|
|
|
|
2016-09-05 16:23:44 +02:00
|
|
|
@detail_route(methods=['post'])
|
|
|
|
def set_scroll(self, request, pk):
|
|
|
|
"""
|
|
|
|
REST API operation to scroll the projector.
|
|
|
|
|
|
|
|
It expects a POST request to
|
|
|
|
/rest/core/projector/<pk>/set_scroll/ with a new value for scroll.
|
|
|
|
"""
|
|
|
|
if not isinstance(request.data, int):
|
|
|
|
raise ValidationError({'detail': 'Data must be an int.'})
|
|
|
|
|
|
|
|
projector_instance = self.get_object()
|
|
|
|
projector_instance.scroll = request.data
|
|
|
|
|
|
|
|
projector_instance.save()
|
|
|
|
message = 'Setting scroll to {scroll} was successful.'.format(
|
|
|
|
scroll=request.data)
|
|
|
|
return Response({'detail': message})
|
|
|
|
|
2016-09-12 11:05:34 +02:00
|
|
|
@detail_route(methods=['post'])
|
|
|
|
def control_blank(self, request, pk):
|
|
|
|
"""
|
|
|
|
REST API operation to blank the projector.
|
|
|
|
|
|
|
|
It expects a POST request to
|
|
|
|
/rest/core/projector/<pk>/control_blank/ with a value for blank.
|
|
|
|
"""
|
|
|
|
if not isinstance(request.data, bool):
|
|
|
|
raise ValidationError({'detail': 'Data must be a bool.'})
|
|
|
|
|
|
|
|
projector_instance = self.get_object()
|
|
|
|
projector_instance.blank = request.data
|
|
|
|
projector_instance.save()
|
|
|
|
message = "Setting 'blank' to {blank} was successful.".format(
|
|
|
|
blank=request.data)
|
|
|
|
return Response({'detail': message})
|
|
|
|
|
|
|
|
@detail_route(methods=['post'])
|
|
|
|
def broadcast(self, request, pk):
|
|
|
|
"""
|
|
|
|
REST API operation to (un-)broadcast the given projector.
|
|
|
|
This method takes care, that all other projectors get the new requirements.
|
|
|
|
|
|
|
|
It expects a POST request to
|
|
|
|
/rest/core/projector/<pk>/broadcast/ without an argument
|
|
|
|
"""
|
|
|
|
if config['projector_broadcast'] == 0:
|
|
|
|
config['projector_broadcast'] = pk
|
|
|
|
message = "Setting projector {id} as broadcast projector was successful.".format(
|
|
|
|
id=pk)
|
|
|
|
else:
|
|
|
|
config['projector_broadcast'] = 0
|
|
|
|
message = "Disabling broadcast was successful."
|
|
|
|
return Response({'detail': message})
|
|
|
|
|
|
|
|
@detail_route(methods=['post'])
|
|
|
|
def set_projectiondefault(self, request, pk):
|
|
|
|
"""
|
|
|
|
REST API operation to set a projectiondefault to the requested projector. The argument
|
|
|
|
has to be an int representing the pk from the projectiondefault to be set.
|
|
|
|
|
|
|
|
It expects a POST request to
|
|
|
|
/rest/core/projector/<pk>/set_projectiondefault/ with the projectiondefault id as the argument
|
|
|
|
"""
|
|
|
|
if not isinstance(request.data, int):
|
|
|
|
raise ValidationError({'detail': 'Data must be an int.'})
|
|
|
|
|
|
|
|
try:
|
|
|
|
projectiondefault = ProjectionDefault.objects.get(pk=request.data)
|
|
|
|
except ProjectionDefault.DoesNotExist:
|
|
|
|
raise ValidationError({'detail': 'The projectiondefault with pk={pk} was not found.'.format(
|
|
|
|
pk=request.data)})
|
|
|
|
else:
|
|
|
|
projector_instance = self.get_object()
|
|
|
|
projectiondefault.projector = projector_instance
|
|
|
|
projectiondefault.save()
|
|
|
|
|
|
|
|
return Response('Setting projectiondefault "{name}" to projector {projector_id} was successful.'.format(
|
|
|
|
name=projectiondefault.display_name,
|
|
|
|
projector_id=projector_instance.pk))
|
|
|
|
|
2015-02-18 01:45:39 +01:00
|
|
|
|
|
|
|
class TagViewSet(ModelViewSet):
|
|
|
|
"""
|
2015-07-01 23:18:48 +02:00
|
|
|
API endpoint for tags.
|
|
|
|
|
2015-08-31 14:07:24 +02:00
|
|
|
There are the following views: metadata, list, retrieve, create,
|
|
|
|
partial_update, update and destroy.
|
2015-02-18 01:45:39 +01:00
|
|
|
"""
|
2016-02-11 22:58:32 +01:00
|
|
|
access_permissions = TagAccessPermissions()
|
2015-02-18 01:45:39 +01:00
|
|
|
queryset = Tag.objects.all()
|
|
|
|
|
2015-07-01 23:18:48 +02:00
|
|
|
def check_view_permissions(self):
|
2015-02-18 01:45:39 +01:00
|
|
|
"""
|
2015-07-01 23:18:48 +02:00
|
|
|
Returns True if the user has required permissions.
|
2015-02-18 01:45:39 +01:00
|
|
|
"""
|
2016-09-17 22:26:23 +02:00
|
|
|
if self.action in ('list', 'retrieve'):
|
|
|
|
result = self.get_access_permissions().check_permissions(self.request.user)
|
|
|
|
elif self.action == 'metadata':
|
2016-02-11 22:58:32 +01:00
|
|
|
# Every authenticated user can see the metadata and list tags.
|
|
|
|
# Anonymous users can do so if they are enabled.
|
2015-07-01 23:18:48 +02:00
|
|
|
result = self.request.user.is_authenticated() or config['general_system_enable_anonymous']
|
|
|
|
elif self.action in ('create', 'update', 'destroy'):
|
|
|
|
result = self.request.user.has_perm('core.can_manage_tags')
|
|
|
|
else:
|
|
|
|
result = False
|
2015-06-18 21:48:20 +02:00
|
|
|
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):
|
|
|
|
# Build tree.
|
|
|
|
config_groups = []
|
2016-06-02 12:47:01 +02:00
|
|
|
for config_variable in sorted(config.config_variables.values(), key=attrgetter('weight')):
|
2016-02-14 21:38:26 +01:00
|
|
|
if config_variable.is_hidden():
|
|
|
|
# Skip hidden config variables. Do not even check groups and subgroups.
|
|
|
|
continue
|
2015-06-29 12:08:15 +02:00
|
|
|
if not config_groups or config_groups[-1]['name'] != config_variable.group:
|
2016-02-14 21:38:26 +01:00
|
|
|
# Add new group.
|
2015-06-29 12:08:15 +02:00
|
|
|
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:
|
2016-02-14 21:38:26 +01:00
|
|
|
# Add new subgroup.
|
2015-06-29 12:08:15 +02:00
|
|
|
config_groups[-1]['subgroups'].append(OrderedDict(
|
|
|
|
name=config_variable.subgroup,
|
|
|
|
items=[]))
|
2016-02-14 21:38:26 +01:00
|
|
|
# Add the config variable to the current group and subgroup.
|
2015-06-29 12:08:15 +02:00
|
|
|
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):
|
|
|
|
"""
|
2015-07-01 23:18:48 +02:00
|
|
|
API endpoint for the config.
|
|
|
|
|
2015-08-31 14:07:24 +02:00
|
|
|
There are the following views: metadata, list, retrieve and update.
|
2015-06-29 12:08:15 +02:00
|
|
|
"""
|
2016-02-11 22:58:32 +01:00
|
|
|
access_permissions = ConfigAccessPermissions()
|
2015-06-29 12:08:15 +02:00
|
|
|
metadata_class = ConfigMetadata
|
|
|
|
|
2015-07-01 23:18:48 +02:00
|
|
|
def check_view_permissions(self):
|
|
|
|
"""
|
|
|
|
Returns True if the user has required permissions.
|
|
|
|
"""
|
2016-09-17 22:26:23 +02:00
|
|
|
if self.action in ('list', 'retrieve'):
|
|
|
|
result = self.get_access_permissions().check_permissions(self.request.user)
|
|
|
|
elif self.action == 'metadata':
|
2015-08-31 14:07:24 +02:00
|
|
|
# Every authenticated user can see the metadata and list or
|
|
|
|
# retrieve the config. Anonymous users can do so if they are
|
|
|
|
# enabled.
|
2015-07-01 23:18:48 +02:00
|
|
|
result = self.request.user.is_authenticated() or config['general_system_enable_anonymous']
|
|
|
|
elif self.action == 'update':
|
|
|
|
result = self.request.user.has_perm('core.can_manage_config')
|
|
|
|
else:
|
|
|
|
result = False
|
|
|
|
return result
|
|
|
|
|
2015-06-29 12:08:15 +02:00
|
|
|
def list(self, request):
|
|
|
|
"""
|
2016-09-18 16:00:31 +02:00
|
|
|
Lists all config variables.
|
2015-06-29 12:08:15 +02:00
|
|
|
"""
|
2016-09-18 16:00:31 +02:00
|
|
|
collection = Collection(config.get_collection_string())
|
|
|
|
return Response(collection.as_list_for_user(request.user))
|
2015-06-29 12:08:15 +02:00
|
|
|
|
|
|
|
def retrieve(self, request, *args, **kwargs):
|
|
|
|
"""
|
2016-09-18 16:00:31 +02:00
|
|
|
Retrieves a config variable.
|
2015-06-29 12:08:15 +02:00
|
|
|
"""
|
|
|
|
key = kwargs['pk']
|
2016-09-18 16:00:31 +02:00
|
|
|
collection_element = CollectionElement.from_values(config.get_collection_string(), key)
|
2015-06-29 12:08:15 +02:00
|
|
|
try:
|
2016-09-18 16:00:31 +02:00
|
|
|
content = collection_element.as_dict_for_user(request.user)
|
|
|
|
except ConfigStore.DoesNotExist:
|
2015-06-29 12:08:15 +02:00
|
|
|
raise Http404
|
2016-09-18 16:00:31 +02:00
|
|
|
if content is None:
|
|
|
|
# If content is None, the user has no permissions to see the item.
|
2016-09-30 20:42:58 +02:00
|
|
|
# See ConfigAccessPermissions or rather its parent class.
|
2016-09-18 16:00:31 +02:00
|
|
|
self.permission_denied()
|
|
|
|
return Response(content)
|
2015-06-29 12:08:15 +02:00
|
|
|
|
|
|
|
def update(self, request, *args, **kwargs):
|
|
|
|
"""
|
|
|
|
Updates a config variable. Only managers can do this.
|
|
|
|
|
|
|
|
Example: {"value": 42}
|
|
|
|
"""
|
|
|
|
key = kwargs['pk']
|
2016-01-13 15:04:47 +01:00
|
|
|
value = request.data.get('value')
|
|
|
|
if value is None:
|
|
|
|
raise ValidationError({'detail': 'Invalid input. Config value is missing.'})
|
2015-06-29 12:08:15 +02:00
|
|
|
|
|
|
|
# Validate and change value.
|
|
|
|
try:
|
|
|
|
config[key] = value
|
|
|
|
except ConfigNotFound:
|
|
|
|
raise Http404
|
|
|
|
except ConfigError as e:
|
2015-08-31 14:07:24 +02:00
|
|
|
raise ValidationError({'detail': str(e)})
|
2015-06-29 12:08:15 +02:00
|
|
|
|
|
|
|
# Return response.
|
|
|
|
return Response({'key': key, 'value': value})
|
2015-07-01 17:48:41 +02:00
|
|
|
|
|
|
|
|
2015-09-07 16:46:04 +02:00
|
|
|
class ChatMessageViewSet(ModelViewSet):
|
|
|
|
"""
|
|
|
|
API endpoint for chat messages.
|
|
|
|
|
|
|
|
There are the following views: metadata, list, retrieve and create.
|
|
|
|
The views partial_update, update and destroy are disabled.
|
|
|
|
"""
|
2016-02-11 22:58:32 +01:00
|
|
|
access_permissions = ChatMessageAccessPermissions()
|
2015-09-07 16:46:04 +02:00
|
|
|
queryset = ChatMessage.objects.all()
|
|
|
|
|
|
|
|
def check_view_permissions(self):
|
|
|
|
"""
|
|
|
|
Returns True if the user has required permissions.
|
|
|
|
"""
|
2016-09-17 22:26:23 +02:00
|
|
|
if self.action in ('list', 'retrieve'):
|
|
|
|
result = self.get_access_permissions().check_permissions(self.request.user)
|
2016-10-17 16:56:19 +02:00
|
|
|
elif self.action in ('metadata', 'create'):
|
2016-02-11 22:58:32 +01:00
|
|
|
# We do not want anonymous users to use the chat even the anonymous
|
|
|
|
# group has the permission core.can_use_chat.
|
|
|
|
result = (
|
2015-09-07 16:46:04 +02:00
|
|
|
self.request.user.is_authenticated() and
|
|
|
|
self.request.user.has_perm('core.can_use_chat'))
|
2016-10-17 16:56:19 +02:00
|
|
|
elif self.action == 'clear':
|
|
|
|
result = (
|
|
|
|
self.request.user.has_perm('core.can_use_chat') and
|
|
|
|
self.request.user.has_perm('core.can_manage_chat'))
|
|
|
|
else:
|
|
|
|
result = False
|
2016-02-11 22:58:32 +01:00
|
|
|
return result
|
2015-09-07 16:46:04 +02:00
|
|
|
|
|
|
|
def perform_create(self, serializer):
|
|
|
|
"""
|
|
|
|
Customized method to inject the request.user into serializer's save
|
|
|
|
method so that the request.user can be saved into the model field.
|
|
|
|
"""
|
|
|
|
serializer.save(user=self.request.user)
|
|
|
|
|
2016-10-17 16:56:19 +02:00
|
|
|
@list_route(methods=['post'])
|
|
|
|
def clear(self, request):
|
|
|
|
"""
|
|
|
|
Deletes all chat messages.
|
|
|
|
"""
|
|
|
|
# Collect all chat messages with their collection_string and id
|
|
|
|
chatmessages = ChatMessage.objects.all()
|
|
|
|
args = []
|
|
|
|
for chatmessage in chatmessages:
|
|
|
|
args.append(chatmessage.get_collection_string())
|
|
|
|
args.append(chatmessage.pk)
|
|
|
|
chatmessages.delete()
|
|
|
|
# Trigger autoupdate and setup response.
|
|
|
|
inform_deleted_data(*args)
|
|
|
|
return Response({'detail': _('All chat messages deleted successfully.')})
|
|
|
|
|
2015-09-07 16:46:04 +02:00
|
|
|
|
2016-10-21 11:05:24 +02:00
|
|
|
class ProjectorMessageViewSet(ModelViewSet):
|
|
|
|
"""
|
|
|
|
API endpoint for messages.
|
|
|
|
|
|
|
|
There are the following views: list, retrieve, create, update and destroy.
|
|
|
|
"""
|
|
|
|
access_permissions = ProjectorMessageAccessPermissions()
|
|
|
|
queryset = ProjectorMessage.objects.all()
|
|
|
|
|
|
|
|
def check_view_permissions(self):
|
|
|
|
"""
|
|
|
|
Returns True if the user has required permissions.
|
|
|
|
"""
|
|
|
|
if self.action in ('list', 'retrieve'):
|
|
|
|
result = self.get_access_permissions().check_permissions(self.request.user)
|
|
|
|
elif self.action in ('create', 'update', 'destroy'):
|
|
|
|
result = self.request.user.has_perm('core.can_manage_projector')
|
|
|
|
else:
|
|
|
|
result = False
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
|
|
class CountdownViewSet(ModelViewSet):
|
|
|
|
"""
|
|
|
|
API endpoint for Countdown.
|
|
|
|
|
|
|
|
There are the following views: list, retrieve, create, update and destroy.
|
|
|
|
"""
|
|
|
|
access_permissions = CountdownAccessPermissions()
|
|
|
|
queryset = Countdown.objects.all()
|
|
|
|
|
|
|
|
def check_view_permissions(self):
|
|
|
|
"""
|
|
|
|
Returns True if the user has required permissions.
|
|
|
|
"""
|
|
|
|
if self.action in ('list', 'retrieve'):
|
|
|
|
result = self.get_access_permissions().check_permissions(self.request.user)
|
|
|
|
elif self.action in ('create', 'update', 'destroy'):
|
|
|
|
result = self.request.user.has_perm('core.can_manage_projector')
|
|
|
|
else:
|
|
|
|
result = False
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
2015-07-01 23:18:48 +02:00
|
|
|
# Special API views
|
2015-07-01 17:48:41 +02:00
|
|
|
|
2015-07-01 23:18:48 +02:00
|
|
|
class UrlPatternsView(utils_views.APIView):
|
2015-07-01 17:48:41 +02:00
|
|
|
"""
|
2015-07-01 23:18:48 +02:00
|
|
|
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']
|
2015-07-01 17:48:41 +02:00
|
|
|
|
2015-07-01 23:18:48 +02:00
|
|
|
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-07-01 17:48:41 +02:00
|
|
|
|
2015-07-01 23:18:48 +02:00
|
|
|
|
2015-09-24 21:28:30 +02:00
|
|
|
class ServerTime(utils_views.APIView):
|
|
|
|
"""
|
|
|
|
Returns the server time as UNIX timestamp.
|
|
|
|
"""
|
|
|
|
http_method_names = ['get']
|
|
|
|
|
|
|
|
def get_context_data(self, **context):
|
|
|
|
return now().timestamp()
|
|
|
|
|
|
|
|
|
2015-07-01 23:18:48 +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
|
2016-01-03 15:33:51 +01:00
|
|
|
|
|
|
|
|
|
|
|
class SearchView(utils_views.APIView):
|
|
|
|
"""
|
|
|
|
Accepts a search string and returns a list of objects where each object
|
|
|
|
is a dictonary with the keywords collection and id.
|
|
|
|
|
|
|
|
This view expects a get argument 'q' with a search string.
|
|
|
|
|
|
|
|
See: https://pythonhosted.org/Whoosh/querylang.html for the format of the
|
|
|
|
search string.
|
|
|
|
"""
|
|
|
|
http_method_names = ['get']
|
|
|
|
|
|
|
|
def get_context_data(self, **context):
|
|
|
|
query = self.request.GET.get('q', '')
|
|
|
|
return super().get_context_data(
|
|
|
|
elements=search(unquote(query)),
|
|
|
|
**context)
|
2016-08-19 14:44:58 +02:00
|
|
|
|
|
|
|
|
|
|
|
class MediaEncoder(utils_views.APIView):
|
|
|
|
"""
|
|
|
|
MediaEncoder is a class based view to prepare encoded media for pdfMake
|
|
|
|
"""
|
|
|
|
http_method_names = ['post']
|
|
|
|
|
|
|
|
def post(self, request, *args, **kwargs):
|
|
|
|
"""
|
|
|
|
Encode_image is used in the context of PDF-Generation
|
|
|
|
Takes an array of IMG.src - Paths
|
|
|
|
Retrieves the according images
|
|
|
|
Encodes the images to BASE64
|
|
|
|
Puts it into a key-value structure
|
|
|
|
|
|
|
|
{
|
|
|
|
"images": {
|
|
|
|
"media/file/ubuntu.png":"$ENCODED_IMAGE"
|
2016-11-02 22:45:43 +01:00
|
|
|
}
|
2016-08-19 14:44:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
:param request:
|
|
|
|
:return: Response of the resulting dictionary
|
|
|
|
|
|
|
|
Calling e.g.
|
|
|
|
$.ajax({ type: "POST", url: "/motions/encode_images/",
|
|
|
|
data: JSON.stringify(["$FILEPATH"]),
|
|
|
|
success: function(data){ console.log(data); },
|
|
|
|
dataType: 'application/json' });
|
|
|
|
"""
|
|
|
|
body_unicode = request.body.decode('utf-8')
|
|
|
|
file_paths = json.loads(body_unicode)
|
|
|
|
images = {file_path: self.encode_image_from(file_path) for file_path in file_paths}
|
|
|
|
return Response({
|
2016-11-02 22:45:43 +01:00
|
|
|
"images": images
|
2016-08-19 14:44:58 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
def encode_image_from(self, file_path):
|
|
|
|
"""
|
|
|
|
Returns the BASE64 encoded version of an image-file for a given path
|
|
|
|
:param file_path:
|
|
|
|
:return:
|
|
|
|
"""
|
|
|
|
path = os.path.join(settings.MEDIA_ROOT, 'file', os.path.basename(file_path))
|
|
|
|
try:
|
|
|
|
with open(path, "rb") as file:
|
|
|
|
string_representation = "data:image/{};base64,{}".format(os.path.splitext(file_path)[1][1:],
|
|
|
|
base64.b64encode(file.read()).decode())
|
|
|
|
except Exception:
|
|
|
|
# If any error occurs ignore it and return an empty string
|
|
|
|
return ""
|
|
|
|
else:
|
|
|
|
return string_representation
|