Merge pull request #1626 from normanjaeckel/Chat
Added model, serializer and viewset for chat messages.
This commit is contained in:
commit
4d14a1297d
@ -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')
|
||||||
|
@ -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}
|
|
35
openslides/core/migrations/0005_add_chat_message_model.py
Normal file
35
openslides/core/migrations/0005_add_chat_message_model.py
Normal 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'))},
|
||||||
|
),
|
||||||
|
]
|
@ -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)
|
||||||
|
@ -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', )
|
||||||
|
@ -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):
|
||||||
|
@ -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 = {
|
||||||
|
@ -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()
|
||||||
|
Loading…
Reference in New Issue
Block a user