diff --git a/openslides/core/apps.py b/openslides/core/apps.py
index d180b7109..08f80bcc2 100644
--- a/openslides/core/apps.py
+++ b/openslides/core/apps.py
@@ -20,6 +20,7 @@ class CoreAppConfig(AppConfig):
from openslides.utils.rest_api import router
from .signals import setup_general_config
from .views import (
+ ChatMessageViewSet,
ConfigViewSet,
CustomSlideViewSet,
ProjectorViewSet,
@@ -31,6 +32,7 @@ class CoreAppConfig(AppConfig):
# Register viewsets.
router.register('core/projector', ProjectorViewSet)
+ router.register('core/chatmessage', ChatMessageViewSet)
router.register('core/customslide', CustomSlideViewSet)
router.register('core/tag', TagViewSet)
router.register('core/config', ConfigViewSet, 'config')
diff --git a/openslides/core/chatbox.py b/openslides/core/chatbox.py
deleted file mode 100644
index a84314e9c..000000000
--- a/openslides/core/chatbox.py
+++ /dev/null
@@ -1,110 +0,0 @@
-from datetime import datetime
-
-from django.conf import settings
-from django.utils.html import urlize
-from django.utils.importlib import import_module
-from sockjs.tornado import SockJSConnection
-
-
-class ChatboxSocketHandler(SockJSConnection):
- """
- Websocket handler for the chatbox.
- """
- clients = set()
-
- def on_open(self, info):
- """
- Checks connecting user and adds his client to the clients list.
- """
- from openslides.users.models import User
-
- # get the session (compatible with other auth-backends)
- engine = import_module(settings.SESSION_ENGINE)
- try:
- session_key = info.get_cookie(settings.SESSION_COOKIE_NAME).value
- session_store = engine.SessionStore(session_key)
- pk = session_store.get('_auth_user_id')
- except AttributeError:
- return False
-
- try:
- self.user = User.objects.get(pk=pk)
- except User.DoesNotExist:
- return False
-
- if self.user.has_perm('core.can_use_chat'):
- self.clients.add(self)
- return True
- else:
- return False
-
- def on_message(self, message):
- """
- Sends the given message to all clients.
-
- Also appends the message to the cache and removes old messages if there
- are more than 100.
- """
- if self.user.has_perm('core.can_use_chat') and message:
- message_object = ChatMessage(person=self.user, message=message)
- chat_messages.append(message_object)
- if len(chat_messages) > 100:
- chat_messages.pop(0)
- self.broadcast(
- self.clients,
- '%s %s %s' % (message_object.html_time(),
- message_object.html_person(),
- urlize(message_object.message)))
-
- def on_close(self):
- """
- Removes client from the clients list.
- """
- self.clients.remove(self)
-
-
-class ChatMessage(object):
- """
- Class for all chat messages. They are stored in the chat_messages object.
-
- The argument person has to be a Person object, the argument message has to
- be the message as string. The argument color can be a three-tuple of RGB
- color values. Default is black (0, 0, 0).
- """
- def __init__(self, person, message, color=None):
- self.person = person
- self.message = message
- self.color = color or (0, 0, 0)
- self.time = datetime.now()
-
- def html_time(self):
- """
- Returns the message time in html style.
- """
- return '%s' % self.time.strftime('%H:%M')
-
- def html_person(self):
- """
- Returns the message sender name in html style.
- """
- return "%(person)s:" % {
- 'color': 'rgb(%d,%d,%d)' % self.color,
- 'person': self.person.clean_name}
-
-
-chat_messages = []
-"""
-Cache with all messages during livetime of the server.
-"""
-
-
-def chat_messages_context_processor(request):
- """
- Adds all chat messages to the request context as template context processor.
- Returns None if the request user has no permission to use the chat.
- """
- if request.user.has_perm('core.can_use_chat'):
- value = chat_messages
- else:
- value = None
- return {'chat_messages': value}
diff --git a/openslides/core/migrations/0005_add_chat_message_model.py b/openslides/core/migrations/0005_add_chat_message_model.py
new file mode 100644
index 000000000..6fec317ef
--- /dev/null
+++ b/openslides/core/migrations/0005_add_chat_message_model.py
@@ -0,0 +1,35 @@
+from django.conf import settings
+from django.db import migrations, models
+
+import openslides.utils.models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ('core', '0004_clear_all_and_make_it_new'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='ChatMessage',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)),
+ ('message', models.TextField(verbose_name='Message')),
+ ('timestamp', models.DateTimeField(auto_now_add=True)),
+ ('user', models.ForeignKey(verbose_name='User', to=settings.AUTH_USER_MODEL)),
+ ],
+ options={
+ 'permissions': (('can_use_chat', 'Can use the chat'),),
+ },
+ bases=(openslides.utils.models.RESTModelMixin, models.Model),
+ ),
+ migrations.AlterModelOptions(
+ name='projector',
+ options={'permissions': (
+ ('can_see_projector', 'Can see the projector'),
+ ('can_manage_projector', 'Can manage the projector'),
+ ('can_see_dashboard', 'Can see the dashboard'))},
+ ),
+ ]
diff --git a/openslides/core/models.py b/openslides/core/models.py
index ac4fe7ac1..09bbade37 100644
--- a/openslides/core/models.py
+++ b/openslides/core/models.py
@@ -1,3 +1,4 @@
+from django.conf import settings
from django.db import models
from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy, ugettext_noop
@@ -50,8 +51,7 @@ class Projector(RESTModelMixin, models.Model):
permissions = (
('can_see_projector', ugettext_noop('Can see the projector')),
('can_manage_projector', ugettext_noop('Can manage the projector')),
- ('can_see_dashboard', ugettext_noop('Can see the dashboard')),
- ('can_use_chat', ugettext_noop('Can use the chat')))
+ ('can_see_dashboard', ugettext_noop('Can see the dashboard')))
@property
def elements(self):
@@ -154,3 +154,26 @@ class ConfigStore(models.Model):
class Meta:
permissions = (('can_manage_config', ugettext_noop('Can manage configuration')),)
+
+
+class ChatMessage(RESTModelMixin, models.Model):
+ """
+ Model for chat messages.
+
+ At the moment we only have one global chat room for managers.
+ """
+ message = models.TextField(
+ verbose_name=ugettext_lazy('Message'))
+
+ timestamp = models.DateTimeField(auto_now_add=True)
+
+ user = models.ForeignKey(
+ settings.AUTH_USER_MODEL,
+ verbose_name=ugettext_lazy('User'))
+
+ class Meta:
+ permissions = (
+ ('can_use_chat', ugettext_noop('Can use the chat')),)
+
+ def __str__(self):
+ return 'Message {}'.format(self.timestamp)
diff --git a/openslides/core/serializers.py b/openslides/core/serializers.py
index 622e9655b..7fb6db583 100644
--- a/openslides/core/serializers.py
+++ b/openslides/core/serializers.py
@@ -1,6 +1,6 @@
from openslides.utils.rest_api import Field, ModelSerializer, ValidationError
-from .models import CustomSlide, Projector, Tag
+from .models import ChatMessage, CustomSlide, Projector, Tag
class JSONSerializerField(Field):
@@ -51,3 +51,13 @@ class TagSerializer(ModelSerializer):
class Meta:
model = Tag
fields = ('id', 'name', )
+
+
+class ChatMessageSerializer(ModelSerializer):
+ """
+ Serializer for core.models.ChatMessage objects.
+ """
+ class Meta:
+ model = ChatMessage
+ fields = ('id', 'message', 'timestamp', 'user', )
+ read_only_fields = ('user', )
diff --git a/openslides/core/views.py b/openslides/core/views.py
index e36728528..6ec3572e9 100644
--- a/openslides/core/views.py
+++ b/openslides/core/views.py
@@ -28,8 +28,9 @@ from openslides.utils.rest_api import (
from .config import config
from .exceptions import ConfigError, ConfigNotFound
-from .models import CustomSlide, Projector, Tag
+from .models import ChatMessage, CustomSlide, Projector, Tag
from .serializers import (
+ ChatMessageSerializer,
CustomSlideSerializer,
ProjectorSerializer,
TagSerializer,
@@ -414,6 +415,34 @@ class ConfigViewSet(ViewSet):
return Response({'key': key, 'value': value})
+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.
+ """
+ queryset = ChatMessage.objects.all()
+ serializer_class = ChatMessageSerializer
+
+ def check_view_permissions(self):
+ """
+ Returns True if the user has required permissions.
+ """
+ # We do not want anonymous users to use the chat even the anonymous
+ # group has the permission core.can_use_chat.
+ return (self.action in ('metadata', 'list', 'retrieve', 'create') and
+ self.request.user.is_authenticated() and
+ self.request.user.has_perm('core.can_use_chat'))
+
+ 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)
+
+
# Special API views
class UrlPatternsView(utils_views.APIView):
diff --git a/openslides/global_settings.py b/openslides/global_settings.py
index 7b87d62d8..62b15d902 100644
--- a/openslides/global_settings.py
+++ b/openslides/global_settings.py
@@ -97,7 +97,6 @@ TEMPLATE_CONTEXT_PROCESSORS = (
'django.core.context_processors.request',
'django.core.context_processors.i18n',
'django.core.context_processors.static',
- 'openslides.core.chatbox.chat_messages_context_processor',
)
CACHES = {
diff --git a/openslides/utils/autoupdate.py b/openslides/utils/autoupdate.py
index f9c093fb2..3bb33c2b5 100644
--- a/openslides/utils/autoupdate.py
+++ b/openslides/utils/autoupdate.py
@@ -134,8 +134,6 @@ def run_tornado(addr, port, *args, **kwargs):
app = WSGIContainer(get_wsgi_application())
# Collect urls
- from openslides.core.chatbox import ChatboxSocketHandler
- chatbox_socket_js_router = SockJSRouter(ChatboxSocketHandler, '/core/chatbox')
sock_js_router = SockJSRouter(OpenSlidesSockJSConnection, '/sockjs')
other_urls = [
(r"%s(.*)" % settings.STATIC_URL, DjangoStaticFileHandler),
@@ -144,7 +142,7 @@ def run_tornado(addr, port, *args, **kwargs):
# Start the application
debug = settings.DEBUG
- tornado_app = Application(sock_js_router.urls + chatbox_socket_js_router.urls + other_urls, autoreload=debug, debug=debug)
+ tornado_app = Application(sock_js_router.urls + other_urls, autoreload=debug, debug=debug)
server = HTTPServer(tornado_app)
server.listen(port=port, address=addr)
IOLoop.instance().start()