Merge pull request #1626 from normanjaeckel/Chat

Added model, serializer and viewset for chat messages.
This commit is contained in:
Oskar Hahn 2015-09-07 17:34:43 +02:00
commit 4d14a1297d
8 changed files with 104 additions and 118 deletions

View File

@ -20,6 +20,7 @@ class CoreAppConfig(AppConfig):
from openslides.utils.rest_api import router from openslides.utils.rest_api import router
from .signals import setup_general_config from .signals import setup_general_config
from .views import ( from .views import (
ChatMessageViewSet,
ConfigViewSet, ConfigViewSet,
CustomSlideViewSet, CustomSlideViewSet,
ProjectorViewSet, ProjectorViewSet,
@ -31,6 +32,7 @@ class CoreAppConfig(AppConfig):
# Register viewsets. # Register viewsets.
router.register('core/projector', ProjectorViewSet) router.register('core/projector', ProjectorViewSet)
router.register('core/chatmessage', ChatMessageViewSet)
router.register('core/customslide', CustomSlideViewSet) router.register('core/customslide', CustomSlideViewSet)
router.register('core/tag', TagViewSet) router.register('core/tag', TagViewSet)
router.register('core/config', ConfigViewSet, 'config') router.register('core/config', ConfigViewSet, 'config')

View File

@ -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 '<small class="grey">%s</small>' % self.time.strftime('%H:%M')
def html_person(self):
"""
Returns the message sender name in html style.
"""
return "<span style='color:%(color)s;'>%(person)s:</span>" % {
'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}

View File

@ -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'))},
),
]

View File

@ -1,3 +1,4 @@
from django.conf import settings
from django.db import models from django.db import models
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy, ugettext_noop from django.utils.translation import ugettext_lazy, ugettext_noop
@ -50,8 +51,7 @@ class Projector(RESTModelMixin, models.Model):
permissions = ( permissions = (
('can_see_projector', ugettext_noop('Can see the projector')), ('can_see_projector', ugettext_noop('Can see the projector')),
('can_manage_projector', ugettext_noop('Can manage the projector')), ('can_manage_projector', ugettext_noop('Can manage the projector')),
('can_see_dashboard', ugettext_noop('Can see the dashboard')), ('can_see_dashboard', ugettext_noop('Can see the dashboard')))
('can_use_chat', ugettext_noop('Can use the chat')))
@property @property
def elements(self): def elements(self):
@ -154,3 +154,26 @@ class ConfigStore(models.Model):
class Meta: class Meta:
permissions = (('can_manage_config', ugettext_noop('Can manage configuration')),) 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)

View File

@ -1,6 +1,6 @@
from openslides.utils.rest_api import Field, ModelSerializer, ValidationError 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): class JSONSerializerField(Field):
@ -51,3 +51,13 @@ class TagSerializer(ModelSerializer):
class Meta: class Meta:
model = Tag model = Tag
fields = ('id', 'name', ) 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', )

View File

@ -28,8 +28,9 @@ from openslides.utils.rest_api import (
from .config import config from .config import config
from .exceptions import ConfigError, ConfigNotFound from .exceptions import ConfigError, ConfigNotFound
from .models import CustomSlide, Projector, Tag from .models import ChatMessage, CustomSlide, Projector, Tag
from .serializers import ( from .serializers import (
ChatMessageSerializer,
CustomSlideSerializer, CustomSlideSerializer,
ProjectorSerializer, ProjectorSerializer,
TagSerializer, TagSerializer,
@ -414,6 +415,34 @@ class ConfigViewSet(ViewSet):
return Response({'key': key, 'value': value}) 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 # Special API views
class UrlPatternsView(utils_views.APIView): class UrlPatternsView(utils_views.APIView):

View File

@ -97,7 +97,6 @@ TEMPLATE_CONTEXT_PROCESSORS = (
'django.core.context_processors.request', 'django.core.context_processors.request',
'django.core.context_processors.i18n', 'django.core.context_processors.i18n',
'django.core.context_processors.static', 'django.core.context_processors.static',
'openslides.core.chatbox.chat_messages_context_processor',
) )
CACHES = { CACHES = {

View File

@ -134,8 +134,6 @@ def run_tornado(addr, port, *args, **kwargs):
app = WSGIContainer(get_wsgi_application()) app = WSGIContainer(get_wsgi_application())
# Collect urls # Collect urls
from openslides.core.chatbox import ChatboxSocketHandler
chatbox_socket_js_router = SockJSRouter(ChatboxSocketHandler, '/core/chatbox')
sock_js_router = SockJSRouter(OpenSlidesSockJSConnection, '/sockjs') sock_js_router = SockJSRouter(OpenSlidesSockJSConnection, '/sockjs')
other_urls = [ other_urls = [
(r"%s(.*)" % settings.STATIC_URL, DjangoStaticFileHandler), (r"%s(.*)" % settings.STATIC_URL, DjangoStaticFileHandler),
@ -144,7 +142,7 @@ def run_tornado(addr, port, *args, **kwargs):
# Start the application # Start the application
debug = settings.DEBUG 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 = HTTPServer(tornado_app)
server.listen(port=port, address=addr) server.listen(port=port, address=addr)
IOLoop.instance().start() IOLoop.instance().start()