django-live / live /

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright 2010 Nicolás Echániz
# All rights reserved.
# This file is part of django-live.
# Django-Live is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# Django-Live is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <>.

import time

from django.http import HttpResponse, HttpResponseNotFound
from django.shortcuts import render_to_response, get_object_or_404, render_to_response
from django.template import RequestContext, loader
from django.contrib.auth.decorators import login_required, permission_required
from django.utils import simplejson as json
from django.views.decorators.csrf import csrf_exempt
from django.utils.translation import ugettext as _
from django.contrib.sites.models import get_current_site

from django.contrib.sessions.models import Session

from django.contrib.auth.models import User

import live.settings as s
from models import Participant, Channel

import stomp

## we set up a stomp connection from Django.
## this fails if we do it where it's later used (inside the restq method)
conn = stomp.Connection([('localhost', 61613)])
##conn.subscribe(destination='PUBLIC', ack='auto')

def json_response(json_data):
    return HttpResponse(json_data, mimetype='application/json')

def chat_html(request, channel_name=None, nickname=None,
               manager_channel="MANAGE", extra_context=None):

        session = Session.objects.get(session_key=request.session.session_key)
        print "no session yet"
        is_subscribed = Channel.objects.filter(participants__session=session, name=channel_name)
        ## if a user was participating in a channel and joins again
        ## we show a page to let him kick himself before re-joining
        if is_subscribed:
            return HttpResponse(_('You are already participating in this chat channel. You can <a href="/live/kickme/%s">close your active sessions</a> and try again' % channel_name))

    if not nickname:
        nickname = 'guest' + str(time.time())
    channel_session = "%s__%s" % (request.session.session_key, channel_name)

    ## we make sure the session gets saved
    request.session.modified = True

    req_context = RequestContext(request,
                                 {'ORBITED_HOST': s.ORBITED_HOST,
                                  'ORBITED_PORT': s.ORBITED_PORT,
                                  'STOMP_PORT': s.STOMP_PORT,
                                  'channel': channel_name,
                                  'manager_channel': manager_channel,
                                  'nickname': nickname,
                                  'username': channel_session,
                                  'channel_name': channel_name,
    if extra_context:
    t = loader.get_template('live/chat.html')
    return t.render(req_context)

def _show_chat(request, channel_name=None, nickname=None,
               manager_channel="MANAGE", extra_context=None):

    return HttpResponse(chat_html(request, channel_name, nickname,
                                   manager_channel, extra_context))
def _get_channel(name):
        channel = Channel.objects.get(name=name)
        channel = None
    return channel

def _get_or_create_channel(name):
    if Channel.objects.filter(name=name).exists():
        channel = Channel.objects.get(name=name)
        channel = Channel(name=name)
    return channel

def _join(user, destination, session, body):
    channel = _get_or_create_channel(destination)
    nickname = body['nickname']
    ## set payload to the type of user so it can be CSS styled
    if not user:
        body['message']['payload'] = 'guest'
        new_participant = Participant(name=nickname, session=session)
        new_participant = Participant(name=nickname, user=user, session=session)
        if user.is_superuser:
            body['message']['payload'] = 'superuser'
        elif user.is_staff:
            body['message']['payload'] = 'staff'
            body['message']['payload'] = 'registered'
    ## we return our modified message body
    return json.dumps({'allow': 'yes', 'body': json.dumps(body)})

def _leave(participant, channel):
    conn.send('{"message":{"action": "leave"}, "nickname": "%s"}' \

def chat(request, channel_name=None, nickname=None, manager_channel="MANAGE"):
    if channel_name is None:
        channel_name = str(time.time())
    nickname = request.user.username
    return _show_chat(request, channel_name, nickname, manager_channel)

def kickme(request, channel_name):
    """"Kick the participant/s corresponding to this session (cleanup)"""

    channel = _get_channel(channel_name)
    if channel:
        session = Session.objects.get(session_key=request.session.session_key)
        participants = channel.participants.filter(session=session)
        for participant in participants:
            _leave(participant, channel)
    import time
    return HttpResponse(_('You may now try to enter the channel.'))

def manage(request):
    ## TODO(nicoechaniz): implement proper permissions
    if request.user.is_superuser:
        return render_to_response('live/manage.html',
                                  {'ORBITED_HOST': s.ORBITED_HOST,
                                   'ORBITED_PORT': s.ORBITED_PORT,
                                   'STOMP_PORT': s.STOMP_PORT,
                                   'STOMP_BROKER': s.STOMP_BROKER.lower(),
                                   'manage_channel': "MANAGE",
                                   'nickname': request.user.username,
                                   'session_key': request.session.session_key,
        return HttpResponse(_('Not allowed'))

def public(request, channel_name="PUBLIC"):
    if request.user:
        nickname = request.user.username
    return _show_chat(request, channel_name=channel_name, nickname=nickname)

def channel_participants(request, channel_name):
    """Return a JSON list of current participants for a given channel"""
    channel = _get_channel(channel_name)
    json_data = []
    if channel:
        participants = channel.participants.all()
        for participant in participants:
            user = participant.user
            if not user:
                json_data.append((, 'guest'))
                if user.is_superuser:
                    json_data.append((, 'superuser'))
                elif user.is_staff:
                    json_data.append((, 'staff'))
                    json_data.append((, 'registered'))
    return json_response(json.dumps(json_data))

def restq(request, command=None, *args, **kwargs):
    """Morbid's RestQ view"""

    ## Morbid is asking for the command URLs
    if not command:
        site =  get_current_site(request)
        base_url = "http://"+site.domain
        rest_q_url = base_url + s.STOMP_RESTQ_URL
        print rest_q_url
        json_data = json.dumps({'unsubscribe': rest_q_url+'unsubscribe/',
                                'subscribe': rest_q_url+'subscribe/',
                                'disconnect': rest_q_url+'disconnect/',
                                'connect': rest_q_url+'connect/',
                                'send': rest_q_url+'send/'})
        return json_response(json_data)

    incoming_data = json.loads(request.raw_post_data)
    destination = incoming_data.get('destination', None)
    stomp_username = incoming_data.get('username', None)
    ## we "encoded" the session_key and channel_name in the STOMP username
    ## so here we extract that info in order to determine the real user
    session = None
    user = None
    if stomp_username:
        session_key, channel_name = stomp_username.split('__')
        session = Session.objects.get(session_key=session_key)
        uid = session.get_decoded().get('_auth_user_id')

        if uid:
            user = User.objects.get(pk=uid)

   ## only super_users are allowed to use the manage feature
    if command == "subscribe":
        if destination == 'MANAGE':
            if not user or not user.is_superuser:
                return json_response(json.dumps({'allow': 'no'}))

    json_data = json.dumps({'allow': 'yes'})
    if command == "send":
        body = json.loads(incoming_data['body'])
        if 'message' in body:
            action = body['message'].get('action', None)
            ## we only accept messages that contain an action
            if not action:
                json_data = json.dumps({'allow': 'no'})
            elif action == 'join':
                json_data = _join(user, destination, session, body)

            elif action == 'rename':
                channel = _get_channel(destination)
                participant = channel.participants.get(session=session)
                new_name = body['message']['payload']
                invalid_name = channel.participants.filter(name=new_name).exists()
                if not invalid_name:
                    existing_user = User.objects.filter(username=new_name)
                    if existing_user:
                        invalid_name = not user == existing_user[0]
                if invalid_name:
                    ## the name is invalid, so we change the action to 'failed_rename
                    ## which will inform about the invalid change and reset the nickname
                     body['message']['action'] = 'failed_rename'
                     json_data = json.dumps({'allow': 'yes', 'body': json.dumps(body)})
           = new_name

            ## TODO(nicoechaniz): this is an expensive validation to run on every message send, refactor.
            elif action == 'say':
                channel = _get_channel(destination)
                participant = channel.participants.get(session=session)
                ## someone has been fiddling with JS to talk with a different nickname
                if != body['nickname']:
                    body['nickname'] =
                    json_data = json.dumps({'allow': 'yes', 'body': json.dumps(body)})

    elif command == "disconnect":
        ## Morbid sends no destination data for disconnect command
        ## so we use the channel_name we got earlier
        channel = _get_channel(channel_name)
            participant = channel.participants.get(session=session)
            print "no participant for session", session, "on", channel_name
            print "kickeando", participant
            _leave(participant, channel)
        json_data = json.dumps({})

    return json_response(json_data)