Skip to content

Commit

Permalink
Merge pull request #29 from mbegel/app_chat
Browse files Browse the repository at this point in the history
App chat
  • Loading branch information
SRLKilling committed Nov 30, 2016
2 parents 2750572 + 489b724 commit 223d4e5
Show file tree
Hide file tree
Showing 28 changed files with 1,540 additions and 192 deletions.
3 changes: 3 additions & 0 deletions requirements/prod.txt
Expand Up @@ -10,6 +10,7 @@ dry-rest-permissions == 0.1.6
django-cors-middleware # Fork of django-cors-header with Django 1.9 support
django-extensions >= 1.5.9
django-filter == 0.10.0
django-tornado-websockets >= 0.1.3

django-oauth-toolkit == 0.10.0

Expand All @@ -18,3 +19,5 @@ Pillow >= 3.1.0
mysqlclient >= 1.3.7
timeout-decorator
jsonfield
requests >= 2.11.1
tornado >= 4.4.2
28 changes: 26 additions & 2 deletions sigma/settings_default.py
Expand Up @@ -45,9 +45,11 @@
'rest_framework_swagger',
'oauth2_provider',
'django_nose',
'tornado_websockets',

'sigma_core.apps.SigmaCoreConfig',
'sigma_files.apps.SigmaFilesConfig',
'sigma_chat.apps.SigmaChatConfig',
)

MIDDLEWARE_CLASSES = (
Expand Down Expand Up @@ -145,9 +147,11 @@

# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.8/howto/static-files/
ENV_PATH = os.path.abspath(os.path.dirname(__file__))

STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(ENV_PATH, 'static')

ENV_PATH = os.path.abspath(os.path.dirname(__file__))
MEDIA_ROOT = os.path.join(ENV_PATH, '../media/')
MEDIA_URL = '/media/'

Expand All @@ -156,6 +160,26 @@

# Test (django_nose conf)
TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'
NOSE_ARGS = ['--nocapture', '--nologcapture', '--with-coverage', '--cover-package=sigma,sigma_core,sigma_files', ] # To display debug during testing
NOSE_ARGS = ['--nocapture', '--nologcapture', '--with-coverage', '--cover-package=sigma,sigma_core,sigma_files,sigma_chat', ] # To display debug during testing

AUTH_USER_MODEL = 'sigma_core.User'

import tornado_websockets
from tornado import websocket
from tornado.web import StaticFileHandler
from tornado_chat import MainHandler, ClientWSConnection, ChatHandler

ch = ChatHandler()

TORNADO = {
'port': 8000, # 8000 by default
'handlers': [
(r'/tornado/chat/', MainHandler, {'chat_handler': ch}),
(r"/tornado/ws/(.*)", ClientWSConnection, {'chat_handler': ch}),
(r'%s(.*)' % STATIC_URL, StaticFileHandler, {'path': STATIC_ROOT}),
tornado_websockets.django_app
], # [] by default
'settings': {
'debug': True,
}, # {} by default
}
6 changes: 6 additions & 0 deletions sigma/urls.py
Expand Up @@ -28,6 +28,9 @@
from sigma_core.views.group_member_value import GroupMemberValueViewSet
from sigma_core.views.group_field import GroupFieldViewSet
from sigma_core.views.validator import ValidatorViewSet
from sigma_chat.views.chat_member import ChatMemberViewSet
from sigma_chat.views.chat import ChatViewSet
from sigma_chat.views.message import MessageViewSet

router.register(r'group', GroupViewSet)
router.register(r'group-field', GroupFieldViewSet)
Expand All @@ -36,6 +39,9 @@
router.register(r'cluster', ClusterViewSet)
router.register(r'user', UserViewSet)
router.register(r'validator', ValidatorViewSet)
router.register(r'chatmember', ChatMemberViewSet)
router.register(r'chat', ChatViewSet)
router.register(r'message', MessageViewSet)

from sigma_files.views import ImageViewSet

Expand Down
Empty file added sigma_chat/__init__.py
Empty file.
3 changes: 3 additions & 0 deletions sigma_chat/admin.py
@@ -0,0 +1,3 @@
from django.contrib import admin

# Register your models here.
5 changes: 5 additions & 0 deletions sigma_chat/apps.py
@@ -0,0 +1,5 @@
from django.apps import AppConfig


class SigmaChatConfig(AppConfig):
name = 'sigma_chat'
50 changes: 50 additions & 0 deletions sigma_chat/migrations/0001_initial.py
@@ -0,0 +1,50 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9 on 2016-10-04 09:37
from __future__ import unicode_literals

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import sigma_chat.models.message


class Migration(migrations.Migration):

initial = True

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]

operations = [
migrations.CreateModel(
name='Chat',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=50)),
],
),
migrations.CreateModel(
name='ChatMember',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('is_creator', models.BooleanField()),
('is_admin', models.BooleanField()),
('is_member', models.BooleanField(default=True)),
('is_banned', models.BooleanField(default=False)),
('chat', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='chatmember', to='sigma_chat.Chat')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user_chatmember', to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='Message',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('text', models.TextField(blank=True)),
('date', models.DateTimeField(auto_now=True)),
('attachment', models.FileField(blank=True, upload_to=sigma_chat.models.message.chat_directory_path)),
('chat_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='message', to='sigma_chat.Chat')),
('chatmember_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='chatmember_message', to='sigma_chat.ChatMember')),
],
),
]
Empty file.
Empty file added sigma_chat/models/__init__.py
Empty file.
29 changes: 29 additions & 0 deletions sigma_chat/models/chat.py
@@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
from django.db import models


class Chat(models.Model):
name = models.CharField(max_length=50)

# Related fields :
# - chatmember (model ChatMember.chat)
# - message (model Message.chat)

################################################################
# PERMISSIONS #
################################################################

@staticmethod
def has_read_permission(request):
return True

def has_object_read_permission(self, request):
return request.user.is_chat_member(self)

@staticmethod
def has_write_permission(request):
return True

def has_object_write_permission(self, request):
return request.user.is_chat_admin(self)

36 changes: 36 additions & 0 deletions sigma_chat/models/chat_member.py
@@ -0,0 +1,36 @@
# -*- coding: utf-8 -*-
from django.db import models

from sigma_chat.models.chat import Chat


class ChatMember(models.Model):
is_creator = models.BooleanField()
is_admin = models.BooleanField()
is_member = models.BooleanField(default=True)
is_banned = models.BooleanField(default=False)
user = models.ForeignKey('sigma_core.User', related_name='user_chatmember')
chat = models.ForeignKey(Chat, related_name='chatmember')

# Related fields :
# - member_message (model Message.member)

################################################################
# PERMISSIONS #
################################################################

@staticmethod
def has_read_permission(request):
return True

def has_object_read_permission(self, request):
# Return True if user is the one of this ChatMember or if user is admin on the related chat
return request.user.is_chat_member(self.chat)

@staticmethod
def has_write_permission(request):
return True

def has_object_write_permission(self, request):
# Return True if user is the one of this ChatMember or if user is admin on the related chat
return (not self.is_creator) and (request.user == self.user or request.user.is_chat_admin(self.chat))
36 changes: 36 additions & 0 deletions sigma_chat/models/message.py
@@ -0,0 +1,36 @@
# -*- coding: utf-8 -*-
from django.db import models

from sigma_chat.models.chat_member import ChatMember
from sigma_chat.models.chat import Chat


def chat_directory_path(instance, filename):
# file will be uploaded to MEDIA_ROOT/user_<id>/<filename>
return 'uploads/chats/{0}/{1}'.format(instance.chat_id.id, filename)


class Message(models.Model):
text = models.TextField(blank=True)
chatmember_id = models.ForeignKey(ChatMember, related_name='chatmember_message')
chat_id = models.ForeignKey(Chat, related_name='message')
date = models.DateTimeField(auto_now=True)
attachment = models.FileField(upload_to=chat_directory_path, blank=True)

################################################################
# PERMISSIONS #
################################################################

@staticmethod
def has_read_permission(request):
return True

def has_object_read_permission(self, request):
return request.user.is_member(self.chat)

@staticmethod
def has_write_permission(request):
return True

def has_object_write_permission(self, request):
return request.user == self.chatmember.user and self.chatmember.is_member
Empty file.
28 changes: 28 additions & 0 deletions sigma_chat/serializers/chat.py
@@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
from rest_framework import serializers

from sigma_chat.models.chat import Chat
from sigma_chat.models.chat_member import ChatMember
from sigma_core.models.user import User

class ChatSerializer(serializers.ModelSerializer):
"""
Serialize ChatMember model.
"""
class Meta:
model = Chat

def create(self, data):
chat = Chat(**data)
if 'user' in self.initial_data:
try:
user = User.objects.get(pk=self.initial_data['user'])
chat.save()
creator = ChatMember(user=user, is_creator=True, is_admin=True, chat=chat)
creator.save()
chat.chatmember.add(creator)
chat.save()
return chat
except User.DoesNotExist:
return None
return None
16 changes: 16 additions & 0 deletions sigma_chat/serializers/chat_member.py
@@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
from rest_framework import serializers

from sigma_chat.models.chat_member import ChatMember

class ChatMemberSerializer(serializers.ModelSerializer):
"""
Serialize ChatMember model.
"""
class Meta:
model = ChatMember
exclude = ('user', 'chat')

user_id = serializers.PrimaryKeyRelatedField(read_only=True, source="user")
chat_id = serializers.PrimaryKeyRelatedField(read_only=True, source="chat")
chatmember_message_id = serializers.PrimaryKeyRelatedField(read_only=True, many=True, source="chatmember_message")
50 changes: 50 additions & 0 deletions sigma_chat/serializers/message.py
@@ -0,0 +1,50 @@
# -*- coding: utf-8 -*-
from rest_framework import serializers

from sigma_chat.models.chat import Chat
from sigma_chat.models.message import Message
from sigma_core.models.user import User
from rest_framework.serializers import ValidationError

import requests
import json

class MessageSerializer(serializers.ModelSerializer):
"""
Serialize Message model.
"""
class Meta:
model = Message

def validate(self, data):
if "chatmember_id" not in data:
raise ValidationError("No user given.")
if "chat_id" not in data:
raise ValidationError("No chat given.")
if data['chat_id'].id != data['chatmember_id'].chat.id:
raise ValidationError("ChatMember not allowed to publish on this chat.")
if (not "text" in data or data['text'] is None or data['text'] == "") and (not 'attachment' in data or data['attachment'] is None):
raise ValidationError("You must send either a text or a file.")

return data

################################################################
# CHAT #
################################################################

def save(self, *args, **kwargs):
super(MessageSerializer, self).save(*args, **kwargs)
"""
NO_PROXY = {
'no': 'pass',
}
message = {'message': json.dumps({
'chat':{'id': self.data['chat_id']},
'chatmember':{'id': self.data['chatmember_id']},
'text': self.data['text'],
'attachment': self.data['attachment'],
'date': self.data['date']
})
}
requests.post('http://localhost:8000/tornado/chat/?secret_key=', data=message, proxies=NO_PROXY)
"""
Empty file added sigma_chat/tests/__init__.py
Empty file.
37 changes: 37 additions & 0 deletions sigma_chat/tests/factories.py
@@ -0,0 +1,37 @@
import factory
from faker import Factory as FakerFactory

from sigma_core.tests.factories import UserFactory

from sigma_chat.models.chat import Chat
from sigma_chat.models.chat_member import ChatMember
from sigma_chat.models.message import Message

faker = FakerFactory.create('fr_FR')

class ChatFactory(factory.django.DjangoModelFactory):
class Meta:
model = Chat

name = factory.LazyAttribute(lambda obj: faker.name())


class ChatMemberFactory(factory.django.DjangoModelFactory):
class Meta:
model = ChatMember

is_creator = False
is_admin = False
user = factory.SubFactory(UserFactory)
chat = factory.SubFactory(ChatFactory)


class MessageFactory(factory.django.DjangoModelFactory):
class Meta:
model = Message

text = factory.LazyAttribute(lambda obj: faker.text())
chatmember_id = factory.SubFactory(ChatMemberFactory)
chat_id = factory.SubFactory(ChatFactory)
date = factory.LazyAttribute(lambda obj: faker.date())
attachment = ""

0 comments on commit 223d4e5

Please sign in to comment.