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 .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')
|
||||
|
@ -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.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)
|
||||
|
@ -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', )
|
||||
|
@ -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):
|
||||
|
@ -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 = {
|
||||
|
@ -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()
|
||||
|
Loading…
Reference in New Issue
Block a user