├── demo ├── demo │ ├── __init__.py │ ├── views.py │ ├── templates │ │ └── index.html │ ├── urls.py │ ├── wsgi.py │ └── settings.py ├── live_support └── manage.py ├── live_support ├── migrations │ ├── __init__.py │ ├── 0003_auto__chg_field_chat_hash_key.py │ ├── 0002_auto__add_supportgroup__add_field_chat_support_group.py │ └── 0001_initial.py ├── templatetags │ ├── __init__.py │ └── live_support_tags.py ├── __init__.py ├── static │ └── live_support │ │ ├── js │ │ ├── live_support_admin.js │ │ └── live_support.js │ │ └── css │ │ └── live_support.css ├── locale │ └── pt_BR │ │ └── LC_MESSAGES │ │ ├── django.mo │ │ └── django.po ├── fixtures │ └── users.json ├── forms.py ├── templates │ ├── live_support │ │ ├── start_chat.html │ │ ├── live_support.html │ │ └── chat_iframe.html │ └── admin │ │ └── live_support │ │ └── chat │ │ └── change_list.html ├── urls.py ├── admin.py ├── models.py ├── tests.py └── views.py ├── AUTHORS ├── MANIFEST.in ├── .gitignore ├── tox.ini ├── setup.py ├── LICENSE └── README.rst /demo/demo/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /demo/live_support: -------------------------------------------------------------------------------- 1 | ../live_support/ -------------------------------------------------------------------------------- /live_support/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /live_support/templatetags/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | * Mark Ransom 2 | -------------------------------------------------------------------------------- /live_support/__init__.py: -------------------------------------------------------------------------------- 1 | VERSION = (0,1,9) 2 | __version__ = "0.1.9" 3 | 4 | -------------------------------------------------------------------------------- /live_support/static/live_support/js/live_support_admin.js: -------------------------------------------------------------------------------- 1 | var $ = django.jQuery; 2 | -------------------------------------------------------------------------------- /live_support/locale/pt_BR/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MegaMark16/django-live-support/HEAD/live_support/locale/pt_BR/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /demo/demo/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render_to_response 2 | 3 | def iframe(request): 4 | return render_to_response('index.html', {'request': request}) 5 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include AUTHORS 2 | include LICENSE 3 | include README.rst 4 | recursive-include live_support/templates * 5 | recursive-include live_support/static * 6 | 7 | 8 | -------------------------------------------------------------------------------- /demo/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "demo.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /demo/demo/templates/index.html: -------------------------------------------------------------------------------- 1 | {% load live_support_tags %} 2 | 3 | 4 | 5 | Index 6 | 7 | 8 | {% chat_iframe 1 %} 9 | 10 | 11 | -------------------------------------------------------------------------------- /demo/demo/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import patterns, include, url 2 | from django.views.generic import TemplateView 3 | from django.contrib import admin 4 | admin.autodiscover() 5 | 6 | urlpatterns = patterns('', 7 | url(r'^admin/', include(admin.site.urls)), 8 | url('^iframe/', 'demo.views.iframe'), 9 | url('^', include('live_support.urls')), 10 | ) 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[co] 2 | *.sw[l-p] 3 | *.db 4 | # Packages 5 | *.egg 6 | *.egg-info 7 | dist 8 | build 9 | eggs 10 | parts 11 | bin 12 | var 13 | sdist 14 | develop-eggs 15 | .installed.cfg 16 | 17 | # Installer logs 18 | pip-log.txt 19 | 20 | # Unit test / coverage reports 21 | .coverage 22 | .tox 23 | 24 | #Translations 25 | #*.mo 26 | 27 | #Mr Developer 28 | .mr.developer.cfg 29 | -------------------------------------------------------------------------------- /live_support/fixtures/users.json: -------------------------------------------------------------------------------- 1 | [{"pk": 1, "model": "auth.user", "fields": {"username": "test", "first_name": "", "last_name": "", "is_active": true, "is_superuser": true, "is_staff": true, "last_login": "2012-12-27T16:42:11.882", "groups": [], "user_permissions": [], "password": "pbkdf2_sha256$10000$O8jQ4PUZtofq$c5WGcqB+Luz7gUZreXF8APn1yKXIq0+93dZhEreUvIQ=", "email": "test@example.com", "date_joined": "2012-12-27T16:42:11.882"}}] -------------------------------------------------------------------------------- /demo/demo/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for demo project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.6/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "demo.settings") 12 | 13 | from django.core.wsgi import get_wsgi_application 14 | application = get_wsgi_application() 15 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = django13 3 | 4 | 5 | [base] 6 | sitepackages = False 7 | setenv = 8 | DJANGO_SETTINGS_MODULE=demo.settings 9 | PYTHONPATH={toxinidir} 10 | 11 | [testenv:django13] 12 | deps = 13 | django==1.4 14 | south 15 | ipdb 16 | sitepackages = {[base]sitepackages} 17 | setenv = {[base]setenv} 18 | commands = 19 | {envbindir}/django-admin.py syncdb --noinput 20 | {envbindir}/django-admin.py migrate --noinput 21 | {envbindir}/django-admin.py test live_support 22 | -------------------------------------------------------------------------------- /live_support/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from django.utils.translation import ugettext_lazy as _ 3 | from live_support.models import Chat, ChatMessage 4 | 5 | 6 | class ChatForm(forms.ModelForm): 7 | details = forms.CharField(widget=forms.Textarea, label=_('Question')) 8 | class Meta: 9 | model = Chat 10 | fields = ('name','details',) 11 | 12 | class ChatMessageForm(forms.ModelForm): 13 | message = forms.CharField() 14 | class Meta: 15 | model = ChatMessage 16 | fields = ('message',) 17 | -------------------------------------------------------------------------------- /live_support/templates/live_support/start_chat.html: -------------------------------------------------------------------------------- 1 | {% load url from future %} 2 | {% load i18n %} 3 | 4 |
5 |
6 | {% csrf_token %} 7 | {{ chat_form.as_p }} 8 | {% if admin_active %} 9 |

10 | {% else %} 11 |

12 | {% endif %} 13 |
14 |
15 | -------------------------------------------------------------------------------- /live_support/urls.py: -------------------------------------------------------------------------------- 1 | try: 2 | from django.conf.urls.defaults import patterns, include, url 3 | except ImportError: 4 | from django.conf.urls import patterns, include, url 5 | from live_support import views 6 | 7 | urlpatterns = patterns('', 8 | url('^$', views.start_chat, name='start_chat'), 9 | url('^(?P\d+)/$', views.start_chat, name='start_chat_for_group'), 10 | url('^ajax/get_messages/$', views.get_messages, name='get_messages'), 11 | url('^ajax/(?P\d+)/post_message/$', views.post_message, name='post_message'), 12 | url('^ajax/(?P\d+)/end_chat/$', views.end_chat, name='end_chat'), 13 | url('^ajax/(?P\d+)/join_chat/$', views.join_chat, name='join_chat'), 14 | url('^(?P[\w-]+)/end_chat/$', views.client_end_chat, name='client_end_chat'), 15 | url('^(?P[\w-]+)/get_messages/$', views.client_get_messages, name='client_get_messages'), 16 | url('^(?P[\w-]+)/post_message/$', views.client_post_message, name='client_post_message'), 17 | url('^(?P[\w-]+)/$', views.client_chat, name='client_chat'), 18 | ) 19 | 20 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | import os 3 | 4 | version = __import__('live_support').__version__ 5 | 6 | install_requires = [ 7 | 'setuptools', 8 | 'simplejson', 9 | 'django', 10 | ] 11 | 12 | setup( 13 | name = "django-live-support", 14 | version = version, 15 | url = 'http://github.com/megamark16/django-live-support', 16 | license = 'BSD', 17 | platforms=['OS Independent'], 18 | description = "A django app that lets you chat with visitors to your site.", 19 | author = "Mark Ransom", 20 | author_email = 'megamark16@gmail.com', 21 | packages=find_packages(), 22 | install_requires = install_requires, 23 | include_package_data=True, 24 | zip_safe=False, 25 | classifiers = [ 26 | 'Development Status :: 4 - Beta', 27 | 'Framework :: Django', 28 | 'Intended Audience :: Developers', 29 | 'License :: OSI Approved :: BSD License', 30 | 'Operating System :: OS Independent', 31 | 'Programming Language :: Python', 32 | 'Topic :: Internet :: WWW/HTTP', 33 | 'Topic :: Communications :: Chat', 34 | ], 35 | package_dir={ 36 | 'live_support': 'live_support', 37 | }, 38 | ) 39 | -------------------------------------------------------------------------------- /live_support/templatetags/live_support_tags.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | from django.conf import settings 3 | from django.core.cache import cache 4 | from datetime import timedelta, datetime 5 | from django.core.urlresolvers import reverse 6 | 7 | from live_support.models import Chat 8 | 9 | register = template.Library() 10 | 11 | def chat_iframe(context, support_group_id=None): 12 | request = context['request'] 13 | # The default url is the Start Chat page 14 | if support_group_id: 15 | iframe_url = reverse('live_support.views.start_chat', args=[support_group_id,]) 16 | cache_key = 'admin_active_%s' % support_group_id 17 | else: 18 | iframe_url = reverse('live_support.views.start_chat') 19 | cache_key = 'admin_active' 20 | if request.session.get('chat_hash_key'): 21 | chat = Chat.objects.filter(hash_key=request.session['chat_hash_key']) 22 | if chat and not chat[0].ended: 23 | # If the user currently has an active chat session and it has not 24 | # ended, display that instead. 25 | iframe_url = reverse('live_support.views.client_chat', args=[chat[0].hash_key]) 26 | 27 | return { 28 | 'STATIC_URL': settings.STATIC_URL, 29 | 'url': iframe_url, 30 | 'admin_active': cache.get(cache_key, False), 31 | 'request': request, 32 | } 33 | 34 | register.inclusion_tag('live_support/chat_iframe.html', takes_context=True)(chat_iframe) 35 | 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011, Mark Ransom 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | * Neither the name of the Salva O'Renick nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 9 | 10 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 11 | -------------------------------------------------------------------------------- /live_support/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.db.models import Q 3 | from django.conf import settings 4 | 5 | from live_support.models import Chat, ChatMessage, SupportGroup 6 | 7 | 8 | class SupportGroupAdmin(admin.ModelAdmin): 9 | list_display = ('id', 'name',) 10 | filter_horizontal = ('agents','supervisors',) 11 | 12 | class ChatAdmin(admin.ModelAdmin): 13 | list_display = ('id', 'name', 'started',) 14 | list_display_links = ('id', 'name',) 15 | list_filter = ('started',) 16 | 17 | class Media: 18 | js = ( 19 | "live_support/js/live_support_admin.js", 20 | "live_support/js/live_support.js", 21 | ) 22 | css = { 23 | 'all': ('live_support/css/live_support.css',), 24 | } 25 | 26 | def changelist_view(self, request, extra_context=None): 27 | user = request.user 28 | pending_chats = Chat.objects.filter(ended=None)\ 29 | .exclude(agents=user)\ 30 | .order_by('-started') 31 | groups = SupportGroup.objects.filter( 32 | Q(supervisors=user) | 33 | Q(agents=user) 34 | ) 35 | if groups: 36 | pending_chats = pending_chats.filter(support_group__in=groups) 37 | active_chats = Chat.objects.filter(ended=None)\ 38 | .filter(agents=request.user) 39 | c = { 40 | 'pending_chats': pending_chats, 41 | 'active_chats': active_chats, 42 | } 43 | return super(ChatAdmin, self).changelist_view(request, extra_context=c) 44 | 45 | 46 | admin.site.register(Chat, ChatAdmin) 47 | admin.site.register(ChatMessage) 48 | admin.site.register(SupportGroup, SupportGroupAdmin) 49 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | django-live-support 2 | ================= 3 | A live support chat app for django that lets you chat with visitors to your 4 | site through the Django Admin interface. 5 | 6 | Dependancies 7 | ============ 8 | 9 | - django (tested with 1.3) 10 | - simplejson (required if using python 2.5, suggested otherwise) 11 | 12 | Getting Started 13 | ============= 14 | 15 | To get started simply install using ``pip``: 16 | :: 17 | pip install django-live-support 18 | 19 | Add ``live_support`` to your installed apps and ``syncdb`` (or migrate, if 20 | you have south installed). 21 | 22 | Your installed apps should look something like this: 23 | :: 24 | INSTALLED_APPS = ( 25 | 'django.contrib.auth', 26 | 'django.contrib.contenttypes', 27 | 'django.contrib.sessions', 28 | 'django.contrib.sites', 29 | 'django.contrib.messages', 30 | 'django.contrib.admin', 31 | 'live_support', 32 | ) 33 | 34 | Add ``live_support.urls`` to your urls.py, like so: 35 | :: 36 | from django.conf.urls.defaults import patterns, include, url 37 | 38 | from django.contrib import admin 39 | admin.autodiscover() 40 | 41 | urlpatterns = patterns('', 42 | url(r'^admin/', include(admin.site.urls)), 43 | url(r'^support/', include('live_support.urls')), 44 | ) 45 | 46 | 47 | If you are going to use the chat_iframe templatetag, be sure that you have 48 | 'django.core.context_processors.request' in your TEMPLATE_CONTEXT_PROCESSORS. 49 | 50 | Usage 51 | ============= 52 | 53 | You can either override the template for the ``start_chat`` 54 | (live_support/start_chat.html) and ``client_chat`` 55 | (live_support/live_support.html) views and just point users to the root 56 | of the live_support app as defined in your urls.py file, or you can drop 57 | the ``{% chat_iframe %}`` templatetag into your base template, but be sure 58 | to include {% load live_support_tags %} at the top of your template, which 59 | will render the chat sidebar (which pops out into a chat window) on every 60 | page. 61 | 62 | -------------------------------------------------------------------------------- /live_support/templates/live_support/live_support.html: -------------------------------------------------------------------------------- 1 | {% load url from future %} 2 | {% load i18n %} 3 | 4 | 5 | 8 | 9 |
10 |
11 |
12 |
    13 |
  • {{ chat.name }}: {{ chat.details }}
  • 14 | {% for message in chat.messages.all %} 15 | {% if message.name %} 16 |
  • {{ message.name }}: {{ message.message }}
  • 17 | {% else %} 18 |
  • {{ message.message }}
  • 19 | {% endif %} 20 | {% endfor %} 21 |
22 |
23 |
24 | {% csrf_token %} 25 | 26 | 27 | 28 |
29 |
32 | {% csrf_token %} 33 | 34 | 35 | 36 |
37 |
38 |
39 |
40 | 41 | -------------------------------------------------------------------------------- /demo/demo/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for demo project. 3 | 4 | For more information on this file, see 5 | https://docs.djangoproject.com/en/1.6/topics/settings/ 6 | 7 | For the full list of settings and their values, see 8 | https://docs.djangoproject.com/en/1.6/ref/settings/ 9 | """ 10 | 11 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 12 | import os 13 | BASE_DIR = os.path.dirname(os.path.dirname(__file__)) 14 | 15 | 16 | # Quick-start development settings - unsuitable for production 17 | # See https://docs.djangoproject.com/en/1.6/howto/deployment/checklist/ 18 | 19 | # SECURITY WARNING: keep the secret key used in production secret! 20 | SECRET_KEY = 'xkrui=h^a7&!p^ey9k8)o4@6s36j%dr)j7=!s!ehzpk14^9#5g' 21 | 22 | # SECURITY WARNING: don't run with debug turned on in production! 23 | DEBUG = True 24 | 25 | TEMPLATE_DEBUG = True 26 | 27 | ALLOWED_HOSTS = [] 28 | 29 | 30 | # Application definition 31 | 32 | INSTALLED_APPS = ( 33 | 'django.contrib.admin', 34 | 'django.contrib.auth', 35 | 'django.contrib.contenttypes', 36 | 'django.contrib.sessions', 37 | 'django.contrib.messages', 38 | 'django.contrib.staticfiles', 39 | 'live_support', 40 | 'south', 41 | 'demo', 42 | ) 43 | 44 | MIDDLEWARE_CLASSES = ( 45 | 'django.contrib.sessions.middleware.SessionMiddleware', 46 | 'django.middleware.common.CommonMiddleware', 47 | 'django.middleware.csrf.CsrfViewMiddleware', 48 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 49 | 'django.contrib.messages.middleware.MessageMiddleware', 50 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 51 | ) 52 | 53 | ROOT_URLCONF = 'demo.urls' 54 | 55 | WSGI_APPLICATION = 'demo.wsgi.application' 56 | 57 | 58 | # Database 59 | # https://docs.djangoproject.com/en/1.6/ref/settings/#databases 60 | 61 | DATABASES = { 62 | 'default': { 63 | 'ENGINE': 'django.db.backends.sqlite3', 64 | 'NAME': os.path.join(BASE_DIR, 'db.db'), 65 | } 66 | } 67 | 68 | # Internationalization 69 | # https://docs.djangoproject.com/en/1.6/topics/i18n/ 70 | 71 | LANGUAGE_CODE = 'en-us' 72 | 73 | TIME_ZONE = 'UTC' 74 | 75 | USE_I18N = True 76 | 77 | USE_L10N = True 78 | 79 | USE_TZ = True 80 | 81 | 82 | # Static files (CSS, JavaScript, Images) 83 | # https://docs.djangoproject.com/en/1.6/howto/static-files/ 84 | 85 | STATIC_URL = '/static/' 86 | -------------------------------------------------------------------------------- /live_support/static/live_support/css/live_support.css: -------------------------------------------------------------------------------- 1 | .clear_left { 2 | clear: left; 3 | } 4 | 5 | ul.chat_names { 6 | margin: 0px; 7 | } 8 | 9 | ul.chat_names li a.selected { 10 | background: #EDF3FE; 11 | } 12 | 13 | ul.chat_names li a.disconnected { 14 | background: #B0B0B0; 15 | } 16 | 17 | ul.chat_names li a.new_message { 18 | border-color: #CC0000; 19 | } 20 | 21 | ul.chat_names li { 22 | float: left; 23 | list-style: none; 24 | white-space: nowrap; 25 | } 26 | 27 | ul.chat_names li a { 28 | padding: 5px 10px; 29 | /*margin-right: -1px;*/ 30 | border: 1px solid #EDF3FE; 31 | cursor: pointer; 32 | } 33 | 34 | 35 | .message_list { 36 | margin: 10px; 37 | display: none; 38 | border: 1px solid #EDF3FE; 39 | width: 500px; 40 | height: 200px; 41 | position: relative; 42 | } 43 | 44 | .message_list ul { 45 | padding: 5px; 46 | margin: 0; 47 | overflow-y: scroll; 48 | height: 165px; 49 | } 50 | 51 | .message_list li { 52 | list-style: none; 53 | } 54 | 55 | .pending_chats ul li { 56 | list-style: none; 57 | } 58 | .pending_chats ul li a.inactive { 59 | color: #CC0000; 60 | } 61 | 62 | .pending_chats ul { 63 | margin-left: 5px; 64 | } 65 | 66 | .pending_chats { 67 | float: left; 68 | width: 200px; 69 | } 70 | 71 | .active_chats { 72 | float: left; 73 | } 74 | 75 | .chat .status { 76 | padding: 5px; 77 | color: #CC0000; 78 | background: #fff; 79 | right: 15px; 80 | position: absolute; 81 | display: none; 82 | } 83 | 84 | .message_box_container { 85 | position: absolute; 86 | bottom: 0; 87 | width: 500px; 88 | height: 25px; 89 | } 90 | .message_box_container form { 91 | display: inline; 92 | } 93 | .message_box_container input { 94 | margin: 2px 0; 95 | } 96 | .message_box_container .message_box { 97 | width: 406px; 98 | padding: 3px 0; 99 | } 100 | 101 | .message_box_container .send_message_button { 102 | width: 45px; 103 | } 104 | 105 | .message_box_container .end_chat_button { 106 | width: 39px; 107 | } 108 | 109 | .start_chat p { 110 | padding: 0; 111 | margin: 10px 0; 112 | } 113 | 114 | .start_chat textarea { 115 | height: 85px; 116 | } 117 | 118 | .start_chat label { 119 | display: block; 120 | } 121 | 122 | .system_message { 123 | color: #D14836; 124 | } 125 | -------------------------------------------------------------------------------- /live_support/templates/live_support/chat_iframe.html: -------------------------------------------------------------------------------- 1 | {% load url from future %} 2 | {% load i18n %} 3 | 27 | 63 | 64 |
65 |
66 | {% if admin_active %} 67 | {% trans 'Chat Now' %} 68 | {% else %} 69 | {% trans 'Leave a Message' %} 70 | {% endif %} 71 |
72 |
73 | {% trans 'Welcome to Django Live Support' %} 74 |
75 | 77 |
78 | -------------------------------------------------------------------------------- /live_support/models.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from uuid import uuid4 3 | from django.db import models 4 | from django.contrib.auth.models import User 5 | from django.utils.translation import ugettext_lazy as _ 6 | from django.core.cache import cache 7 | 8 | 9 | class SupportGroup(models.Model): 10 | name = models.CharField(_("name"), max_length=255) 11 | agents = models.ManyToManyField( 12 | User, blank=True, related_name='agent_support_groups' 13 | ) 14 | supervisors = models.ManyToManyField( 15 | User, blank=True, related_name='supervisor_support_groups' 16 | ) 17 | 18 | def __unicode__(self): 19 | return self.name 20 | 21 | class Meta: 22 | verbose_name = _('Support group') 23 | verbose_name_plural = _('Support groups') 24 | 25 | 26 | class ChatManager(models.Manager): 27 | def get_query_set(self): 28 | return super(ChatManager, self).get_query_set().filter(ended=None) 29 | 30 | 31 | class Chat(models.Model): 32 | name = models.CharField(_("name"), max_length=255) 33 | hash_key = models.CharField(unique=True, max_length=64, null=True, 34 | editable=False, blank=True, default=uuid4) 35 | details = models.TextField(_("question"), blank=True) 36 | started = models.DateTimeField(auto_now_add=True) 37 | ended = models.DateTimeField(null=True, blank=True) 38 | agents = models.ManyToManyField(User, blank=True, related_name='chats') 39 | objects = models.Manager() 40 | active = ChatManager() 41 | support_group = models.ForeignKey(SupportGroup, null=True, blank=True) 42 | 43 | def __unicode__(self): 44 | return '%s: %s' % (self.started, self.name) 45 | 46 | def end(self): 47 | self.ended = datetime.now() 48 | self.save() 49 | 50 | def is_active(self): 51 | return cache.get('chat_%s' % self.id, 'inactive') 52 | 53 | class Meta: 54 | permissions = ( 55 | ("chat_admin", "Chat Admin"), 56 | ) 57 | verbose_name = _('Chat') 58 | verbose_name_plural = _('Chats') 59 | 60 | 61 | class ChatMessage(models.Model): 62 | chat = models.ForeignKey(Chat, related_name='messages') 63 | name = models.CharField(max_length=255, blank=True) 64 | agent = models.ForeignKey(User, blank=True, null=True) 65 | message = models.TextField() 66 | sent = models.DateTimeField(auto_now_add=True) 67 | 68 | def get_name(self): 69 | if self.agent: 70 | return self.agent.first_name or self.agent.username 71 | else: 72 | return self.name 73 | 74 | def __unicode__(self): 75 | return '%s: %s' % (self.sent, self.message) 76 | 77 | class Meta: 78 | verbose_name = _('Chat message') 79 | verbose_name_plural = _('Chat messages') 80 | -------------------------------------------------------------------------------- /live_support/locale/pt_BR/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: \n" 9 | "Report-Msgid-Bugs-To: \n" 10 | "POT-Creation-Date: 2014-02-26 11:40-0300\n" 11 | "PO-Revision-Date: 2014-02-26 11:43-0300\n" 12 | "Last-Translator: FULL NAME \n" 13 | "Language-Team: LANGUAGE \n" 14 | "Language: pt_BR\n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=UTF-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | "Plural-Forms: nplurals=2; plural=(n > 1);\n" 19 | "X-Generator: Poedit 1.6.4\n" 20 | 21 | #: forms.py:7 22 | msgid "Question" 23 | msgstr "Pergunta" 24 | 25 | #: models.py:10 models.py:32 26 | msgid "name" 27 | msgstr "nome" 28 | 29 | #: models.py:22 30 | msgid "Support group" 31 | msgstr "Grupo de apoio" 32 | 33 | #: models.py:23 34 | msgid "Support groups" 35 | msgstr "Grupos de apoio" 36 | 37 | #: models.py:35 38 | msgid "question" 39 | msgstr "pergunta" 40 | 41 | #: models.py:57 42 | msgid "Chat" 43 | msgstr "Bate-papo" 44 | 45 | #: models.py:58 46 | msgid "Chats" 47 | msgstr "Bate-papos" 48 | 49 | #: models.py:78 50 | msgid "Chat message" 51 | msgstr "Mensagem de bate-papo" 52 | 53 | #: models.py:79 54 | msgid "Chat messages" 55 | msgstr "Mensagens de bate-papo" 56 | 57 | #: templates/admin/live_support/chat/change_list.html:6 58 | msgid "Chat Admin" 59 | msgstr "Administração de Bate-papo" 60 | 61 | #: templates/admin/live_support/chat/change_list.html:15 62 | msgid "Pending Chats" 63 | msgstr "Bate-papos pendentes" 64 | 65 | #: templates/admin/live_support/chat/change_list.html:23 66 | msgid "Your Chats" 67 | msgstr "Seus Bate-papos" 68 | 69 | #: templates/admin/live_support/chat/change_list.html:33 70 | msgid "The user seems to have disconnected." 71 | msgstr "O usuário parece ter desconectado." 72 | 73 | #: templates/admin/live_support/chat/change_list.html:49 74 | #: templates/live_support/live_support.html:26 75 | msgid "Send" 76 | msgstr "Enviar" 77 | 78 | #: templates/admin/live_support/chat/change_list.html:56 79 | #: templates/live_support/live_support.html:34 80 | msgid "End" 81 | msgstr "Fim" 82 | 83 | #: templates/live_support/chat_iframe.html:66 84 | msgid "Chat Now" 85 | msgstr "Iniciar Bate-papo" 86 | 87 | #: templates/live_support/chat_iframe.html:68 88 | msgid "Leave a Message" 89 | msgstr "Deixe uma Mensagem" 90 | 91 | #: templates/live_support/chat_iframe.html:72 92 | msgid "Welcome to Django Live Support" 93 | msgstr "Bem Vindo ao Django Live Support" 94 | 95 | #: templates/live_support/start_chat.html:8 96 | msgid "Start Chat" 97 | msgstr "Iniciar Bate-papo" 98 | 99 | #: templates/live_support/start_chat.html:10 100 | msgid "Leave Message" 101 | msgstr "Deixe sua Mensagem" 102 | -------------------------------------------------------------------------------- /live_support/templates/admin/live_support/chat/change_list.html: -------------------------------------------------------------------------------- 1 | {% extends "admin/change_list.html" %} 2 | {% load url from future %} 3 | {% load i18n admin_list %} 4 | 5 | {% block content_title %} 6 |

{% trans 'Chat Admin' %}

7 | {% endblock %} 8 | 9 | {% block content %} 10 | 13 |
14 |
15 |

{% trans 'Pending Chats' %}

16 |
    17 | {% for chat in pending_chats %} 18 |
  • {{ chat.name }}
  • 19 | {% endfor %} 20 |
21 |
22 |
23 |

{% trans 'Your Chats' %}

24 |
    25 | {% for chat in active_chats %} 26 |
  • {{ chat.name }}
  • 27 | {% endfor %} 28 |
29 | {% for chat in active_chats %} 30 |
31 |
32 |
33 |
{% trans 'The user seems to have disconnected.' %}
34 |
    35 |
  • Question: {{ chat.details }}
  • 36 | {% for message in chat.messages.all %} 37 | {% if message.name %} 38 |
  • {{ message.name }}: {{ message.message }}
  • 39 | {% else %} 40 |
  • {{ message.message }}
  • 41 | {% endif %} 42 | {% endfor %} 43 |
44 |
45 |
46 | {% csrf_token %} 47 | 48 | 49 | 50 |
51 |
54 | {% csrf_token %} 55 | 56 | 57 |
58 |
59 |
60 |
61 | {% endfor %} 62 |
63 |
64 |
65 |
66 |
67 | {# block.super #} 68 | {% endblock %} 69 | 70 | -------------------------------------------------------------------------------- /live_support/tests.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from django.test import TestCase 4 | from django.test.client import Client, ClientHandler 5 | from django.core.urlresolvers import reverse 6 | from django.core.cache import cache 7 | from models import Chat 8 | 9 | class ClientTests(TestCase): 10 | urls = 'live_support.urls' 11 | fixtures = ['live_support/fixtures/users.json',] 12 | 13 | def setUp(self): 14 | pass 15 | 16 | def test_start_chat(self): 17 | # Login and call get_messages to set the admin_active cache 18 | login = self.client.login(username='test', password='test') 19 | self.client.get(reverse('get_messages')) 20 | 21 | resp = self.client.post(reverse('start_chat'), { 22 | 'name': 'Test Name', 23 | 'details': 'Test Details' 24 | }) 25 | self.assertEqual(resp.status_code, 302) 26 | resp2 = self.client.get(resp['location']) 27 | self.assertEqual(resp2.status_code, 200) 28 | self.assertTrue(resp2.context['chat'].hash_key in resp['location']) 29 | 30 | def test_leave_message(self): 31 | # Make sure no admins are logged in 32 | cache.set('admin_active', None) 33 | # Because no admin is logged in calling start_chat just leaves 34 | # a message and returns a thank you response 35 | resp = self.client.post(reverse('start_chat'), { 36 | 'name': 'Test Name', 37 | 'details': 'Test Message' 38 | }) 39 | self.assertEqual(resp.status_code, 200) 40 | self.assertEqual(resp.content, 'Thank you for contacting us') 41 | 42 | def test_get_messages_prompts_login(self): 43 | # If you aren't logged in you cannot call get_messages without 44 | # a chat hash_key or you will be directed to log in 45 | resp = self.client.get(reverse('get_messages')) 46 | self.assertEqual(resp.status_code, 302) 47 | self.assertIn('/login/', resp['location']) 48 | 49 | def test_get_messages_with_hash_key_returns_messages(self): 50 | chat = Chat.objects.create(name='Test Chat', details='Details') 51 | chat.messages.create(name=chat.name, message='new message text') 52 | resp = self.client.get(reverse('client_get_messages', args=[chat.hash_key])) 53 | self.assertTrue('new message text' in resp.content) 54 | 55 | def test_get_latest_message(self): 56 | chat = Chat.objects.create(name='Test Chat', details='Details') 57 | message1 = chat.messages.create(name=chat.name, message='message one') 58 | message2 = chat.messages.create(name=chat.name, message='message two') 59 | url = reverse('client_get_messages', args=[chat.hash_key]) 60 | resp = self.client.get('%s?%s=%s' % (url, chat.id, message1.id)) 61 | self.assertIn('two', resp.content) 62 | self.assertNotIn('one', resp.content) 63 | 64 | 65 | class AdminTests(TestCase): 66 | urls = 'live_support.urls' 67 | fixtures = ['live_support/fixtures/users.json',] 68 | 69 | def setUp(self): 70 | pass 71 | 72 | def test_get_all_messages(self): 73 | login = self.client.login(username='test', password='test') 74 | chat = Chat.objects.create(name='Test Chat', details='Details') 75 | chat.messages.create(name=chat.name, message='new message text') 76 | url = reverse('get_messages') 77 | resp = self.client.get('%s?%s=0' % (url, chat.id)) 78 | self.assertTrue('new message text' in resp.content) 79 | 80 | def test_getting_messages_with_invalid_args(self): 81 | login = self.client.login(username='test', password='test') 82 | chat = Chat.objects.create(name='Test Chat', details='Details') 83 | message1 = chat.messages.create(name=chat.name, message='message one') 84 | message2 = chat.messages.create(name=chat.name, message='message two') 85 | url = reverse('get_messages') 86 | resp = self.client.get('%s?bad=good&%s=%s' % (url, chat.id, message1.id)) 87 | self.assertIn('two', resp.content) 88 | self.assertNotIn('one', resp.content) 89 | 90 | def test_send_admin_message(self): 91 | login = self.client.login(username='test', password='test') 92 | chat = Chat.objects.create(name='Test Chat', details='Details') 93 | resp = self.client.post(reverse('post_message', args=[chat.id]), 94 | { 'message': 'admin test message'}, 95 | **{'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'}) 96 | self.assertIn('admin test message', resp.content) 97 | -------------------------------------------------------------------------------- /live_support/migrations/0003_auto__chg_field_chat_hash_key.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | # Adding M2M table for field supervisors on 'SupportGroup' 12 | db.create_table('live_support_supportgroup_supervisors', ( 13 | ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), 14 | ('supportgroup', models.ForeignKey(orm['live_support.supportgroup'], null=False)), 15 | ('user', models.ForeignKey(orm['auth.user'], null=False)) 16 | )) 17 | db.create_unique('live_support_supportgroup_supervisors', ['supportgroup_id', 'user_id']) 18 | 19 | 20 | # Changing field 'Chat.hash_key' 21 | db.alter_column('live_support_chat', 'hash_key', self.gf('django.db.models.fields.CharField')(max_length=64, unique=True, null=True)) 22 | 23 | def backwards(self, orm): 24 | # Removing M2M table for field supervisors on 'SupportGroup' 25 | db.delete_table('live_support_supportgroup_supervisors') 26 | 27 | 28 | # Changing field 'Chat.hash_key' 29 | db.alter_column('live_support_chat', 'hash_key', self.gf('django.db.models.fields.CharField')(default='', max_length=64, unique=True)) 30 | 31 | models = { 32 | 'auth.group': { 33 | 'Meta': {'object_name': 'Group'}, 34 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 35 | 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), 36 | 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) 37 | }, 38 | 'auth.permission': { 39 | 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, 40 | 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 41 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), 42 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 43 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) 44 | }, 45 | 'auth.user': { 46 | 'Meta': {'object_name': 'User'}, 47 | 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 48 | 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), 49 | 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 50 | 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), 51 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 52 | 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 53 | 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 54 | 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 55 | 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 56 | 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 57 | 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 58 | 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), 59 | 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) 60 | }, 61 | 'contenttypes.contenttype': { 62 | 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, 63 | 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 64 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 65 | 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 66 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) 67 | }, 68 | 'live_support.chat': { 69 | 'Meta': {'object_name': 'Chat'}, 70 | 'agents': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'chats'", 'blank': 'True', 'to': "orm['auth.User']"}), 71 | 'details': ('django.db.models.fields.TextField', [], {'blank': 'True'}), 72 | 'ended': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), 73 | 'hash_key': ('django.db.models.fields.CharField', [], {'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'}), 74 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 75 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), 76 | 'started': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), 77 | 'support_group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['live_support.SupportGroup']", 'null': 'True', 'blank': 'True'}) 78 | }, 79 | 'live_support.chatmessage': { 80 | 'Meta': {'object_name': 'ChatMessage'}, 81 | 'agent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), 82 | 'chat': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'messages'", 'to': "orm['live_support.Chat']"}), 83 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 84 | 'message': ('django.db.models.fields.TextField', [], {}), 85 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), 86 | 'sent': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}) 87 | }, 88 | 'live_support.supportgroup': { 89 | 'Meta': {'object_name': 'SupportGroup'}, 90 | 'agents': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'agent_support_groups'", 'blank': 'True', 'to': "orm['auth.User']"}), 91 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 92 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), 93 | 'supervisors': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'supervisor_support_groups'", 'blank': 'True', 'to': "orm['auth.User']"}) 94 | } 95 | } 96 | 97 | complete_apps = ['live_support'] 98 | -------------------------------------------------------------------------------- /live_support/migrations/0002_auto__add_supportgroup__add_field_chat_support_group.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | # Adding model 'SupportGroup' 12 | db.create_table('live_support_supportgroup', ( 13 | ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 14 | ('name', self.gf('django.db.models.fields.CharField')(max_length=255)), 15 | )) 16 | db.send_create_signal('live_support', ['SupportGroup']) 17 | 18 | # Adding M2M table for field agents on 'SupportGroup' 19 | db.create_table('live_support_supportgroup_agents', ( 20 | ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), 21 | ('supportgroup', models.ForeignKey(orm['live_support.supportgroup'], null=False)), 22 | ('user', models.ForeignKey(orm['auth.user'], null=False)) 23 | )) 24 | db.create_unique('live_support_supportgroup_agents', ['supportgroup_id', 'user_id']) 25 | 26 | # Adding field 'Chat.support_group' 27 | db.add_column('live_support_chat', 'support_group', 28 | self.gf('django.db.models.fields.related.ForeignKey')(to=orm['live_support.SupportGroup'], null=True, blank=True), 29 | keep_default=False) 30 | 31 | 32 | def backwards(self, orm): 33 | # Deleting model 'SupportGroup' 34 | db.delete_table('live_support_supportgroup') 35 | 36 | # Removing M2M table for field agents on 'SupportGroup' 37 | db.delete_table('live_support_supportgroup_agents') 38 | 39 | # Deleting field 'Chat.support_group' 40 | db.delete_column('live_support_chat', 'support_group_id') 41 | 42 | 43 | models = { 44 | 'auth.group': { 45 | 'Meta': {'object_name': 'Group'}, 46 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 47 | 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), 48 | 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) 49 | }, 50 | 'auth.permission': { 51 | 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, 52 | 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 53 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), 54 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 55 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) 56 | }, 57 | 'auth.user': { 58 | 'Meta': {'object_name': 'User'}, 59 | 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 60 | 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), 61 | 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 62 | 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), 63 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 64 | 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 65 | 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 66 | 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 67 | 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 68 | 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 69 | 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 70 | 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), 71 | 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) 72 | }, 73 | 'contenttypes.contenttype': { 74 | 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, 75 | 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 76 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 77 | 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 78 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) 79 | }, 80 | 'live_support.chat': { 81 | 'Meta': {'object_name': 'Chat'}, 82 | 'agents': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'chats'", 'blank': 'True', 'to': "orm['auth.User']"}), 83 | 'details': ('django.db.models.fields.TextField', [], {'blank': 'True'}), 84 | 'ended': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), 85 | 'hash_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64', 'blank': 'True'}), 86 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 87 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), 88 | 'started': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), 89 | 'support_group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['live_support.SupportGroup']", 'null': 'True', 'blank': 'True'}) 90 | }, 91 | 'live_support.chatmessage': { 92 | 'Meta': {'object_name': 'ChatMessage'}, 93 | 'agent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), 94 | 'chat': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'messages'", 'to': "orm['live_support.Chat']"}), 95 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 96 | 'message': ('django.db.models.fields.TextField', [], {}), 97 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), 98 | 'sent': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}) 99 | }, 100 | 'live_support.supportgroup': { 101 | 'Meta': {'object_name': 'SupportGroup'}, 102 | 'agents': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'support_groups'", 'blank': 'True', 'to': "orm['auth.User']"}), 103 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 104 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}) 105 | } 106 | } 107 | 108 | complete_apps = ['live_support'] 109 | -------------------------------------------------------------------------------- /live_support/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | # Adding model 'Chat' 12 | db.create_table('live_support_chat', ( 13 | ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 14 | ('name', self.gf('django.db.models.fields.CharField')(max_length=255)), 15 | ('hash_key', self.gf('django.db.models.fields.CharField')(unique=True, max_length=64, blank=True)), 16 | ('details', self.gf('django.db.models.fields.TextField')(blank=True)), 17 | ('started', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), 18 | ('ended', self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True)), 19 | )) 20 | db.send_create_signal('live_support', ['Chat']) 21 | 22 | # Adding M2M table for field agents on 'Chat' 23 | db.create_table('live_support_chat_agents', ( 24 | ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), 25 | ('chat', models.ForeignKey(orm['live_support.chat'], null=False)), 26 | ('user', models.ForeignKey(orm['auth.user'], null=False)) 27 | )) 28 | db.create_unique('live_support_chat_agents', ['chat_id', 'user_id']) 29 | 30 | # Adding model 'ChatMessage' 31 | db.create_table('live_support_chatmessage', ( 32 | ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 33 | ('chat', self.gf('django.db.models.fields.related.ForeignKey')(related_name='messages', to=orm['live_support.Chat'])), 34 | ('name', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)), 35 | ('agent', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True, blank=True)), 36 | ('message', self.gf('django.db.models.fields.TextField')()), 37 | ('sent', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), 38 | )) 39 | db.send_create_signal('live_support', ['ChatMessage']) 40 | 41 | 42 | def backwards(self, orm): 43 | # Deleting model 'Chat' 44 | db.delete_table('live_support_chat') 45 | 46 | # Removing M2M table for field agents on 'Chat' 47 | db.delete_table('live_support_chat_agents') 48 | 49 | # Deleting model 'ChatMessage' 50 | db.delete_table('live_support_chatmessage') 51 | 52 | 53 | models = { 54 | 'auth.group': { 55 | 'Meta': {'object_name': 'Group'}, 56 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 57 | 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), 58 | 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) 59 | }, 60 | 'auth.permission': { 61 | 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, 62 | 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 63 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), 64 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 65 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) 66 | }, 67 | 'auth.user': { 68 | 'Meta': {'object_name': 'User'}, 69 | 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 70 | 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), 71 | 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 72 | 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), 73 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 74 | 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 75 | 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 76 | 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 77 | 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 78 | 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 79 | 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 80 | 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), 81 | 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) 82 | }, 83 | 'contenttypes.contenttype': { 84 | 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, 85 | 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 86 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 87 | 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 88 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) 89 | }, 90 | 'live_support.chat': { 91 | 'Meta': {'object_name': 'Chat'}, 92 | 'agents': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'chats'", 'blank': 'True', 'to': "orm['auth.User']"}), 93 | 'details': ('django.db.models.fields.TextField', [], {'blank': 'True'}), 94 | 'ended': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), 95 | 'hash_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64', 'blank': 'True'}), 96 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 97 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), 98 | 'started': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}) 99 | }, 100 | 'live_support.chatmessage': { 101 | 'Meta': {'object_name': 'ChatMessage'}, 102 | 'agent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), 103 | 'chat': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'messages'", 'to': "orm['live_support.Chat']"}), 104 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 105 | 'message': ('django.db.models.fields.TextField', [], {}), 106 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), 107 | 'sent': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}) 108 | } 109 | } 110 | 111 | complete_apps = ['live_support'] 112 | -------------------------------------------------------------------------------- /live_support/views.py: -------------------------------------------------------------------------------- 1 | try: 2 | import simplejson as json 3 | except ImportError: 4 | import json 5 | from django.core.urlresolvers import reverse 6 | from django.http import HttpResponse, HttpResponseRedirect 7 | from django.shortcuts import render_to_response, get_object_or_404 8 | from django.template import RequestContext 9 | from django.contrib.auth.decorators import permission_required 10 | from django.db.models import Q 11 | from django.core.cache import cache 12 | from django.utils.html import escape 13 | 14 | from live_support.models import Chat, ChatMessage, SupportGroup 15 | from live_support.forms import ChatMessageForm, ChatForm 16 | 17 | 18 | @permission_required('live_support.chat_admin') 19 | def join_chat(request, chat_id): 20 | chat = get_object_or_404(Chat, id=chat_id) 21 | if request.user.is_authenticated(): 22 | chat.agents.add(request.user) 23 | message = ChatMessage() 24 | name = request.user.first_name or request.user.username 25 | message.message = '%s has joined the chat' % name 26 | chat.messages.add(message) 27 | return HttpResponseRedirect(request.META['HTTP_REFERER']) 28 | 29 | 30 | @permission_required('live_support.chat_admin') 31 | def post_message(request, chat_id): 32 | chat = get_object_or_404(Chat, id=chat_id) 33 | last_message_id = request.POST.get('last_message_id') 34 | message_form = ChatMessageForm(request.POST or None) 35 | if message_form.is_valid(): 36 | message = message_form.save(commit=False) 37 | message.chat = chat 38 | message.agent = request.user 39 | message.name = message.get_name() 40 | message.save() 41 | if request.is_ajax(): 42 | if last_message_id: 43 | new_messages = chat.messages.filter(id__gt=last_message_id) 44 | else: 45 | new_messages = chat.messages.all() 46 | new_message_list = [] 47 | for message in new_messages: 48 | new_message_list.append({ 49 | 'name': escape(message.name), 50 | 'message': escape(message.message), 51 | 'pk': message.pk, 52 | 'chat': chat.id, 53 | }) 54 | return HttpResponse(json.dumps(new_message_list)) 55 | return HttpResponseRedirect(request.META['HTTP_REFERER']) 56 | 57 | 58 | @permission_required('live_support.chat_admin') 59 | def end_chat(request, chat_id): 60 | chat = get_object_or_404(Chat, id=chat_id) 61 | message = ChatMessage() 62 | name = request.user.first_name or request.user.username 63 | message.message = '%s has left the chat. This chat has ended.' % name 64 | chat.messages.add(message) 65 | if request.POST.get('end_chat') == 'true': 66 | chat.end() 67 | return HttpResponseRedirect(request.META['HTTP_REFERER']) 68 | 69 | 70 | @permission_required('live_support.chat_admin') 71 | def get_messages(request): 72 | """ 73 | For each chat id passed in via querystring, get the last message id and 74 | query for any new messages for that chat session. Also include all 75 | pending chat sessions. 76 | """ 77 | user = request.user 78 | chats = {} 79 | for k, v in request.GET.iteritems(): 80 | alive = True 81 | try: 82 | messages = ChatMessage.objects.filter(chat__id=k) 83 | except ValueError: 84 | next 85 | if v: 86 | try: 87 | messages = messages.filter(id__gt=v) 88 | except ValueError: 89 | pass 90 | # Check to see if the end user has recently checked for new messages 91 | # for this chat session by checking for the cache entry using the 92 | # chat id. If they haven't asked for new messages in the past 30 93 | # seconds they probably navigated away or closed the window. 94 | if not cache.get('chat_%s' % k): 95 | alive = False 96 | 97 | message_list = [] 98 | for message in messages: 99 | message_list.append({ 100 | 'name': escape(message.name), 101 | 'message': escape(message.message), 102 | 'pk': message.pk, 103 | }) 104 | chats[k] = { 105 | 'messages': message_list, 106 | 'alive': alive 107 | } 108 | 109 | # Get the list of pending chat sessions, and for each one get a url for 110 | # joining that chat session. 111 | pending_chats = Chat.objects.filter(ended=None)\ 112 | .exclude(agents=user)\ 113 | .order_by('-started') 114 | groups = SupportGroup.objects.filter( 115 | Q(supervisors=user) | 116 | Q(agents=user) 117 | ) 118 | if groups: 119 | pending_chats = pending_chats.filter(support_group__in=groups) 120 | 121 | pending_chats = list(pending_chats) 122 | pending_chats_list = [{ 123 | 'name': escape(chat.name), 124 | 'url': reverse('live_support.views.join_chat', args=[chat.id]), 125 | 'active': chat.is_active(), 126 | } for chat in pending_chats] 127 | 128 | output = { 129 | 'chats': chats, 130 | 'pending_chats': pending_chats_list, 131 | } 132 | if groups: 133 | for group in groups: 134 | cache.set('admin_active_%s' % group.id, True, 20) 135 | else: 136 | cache.set('admin_active', True, 20) 137 | # Dump the whole thing to json and return it to the browser. 138 | return HttpResponse(json.dumps(output)) 139 | 140 | 141 | def client_get_messages(request, chat_uuid): 142 | """ 143 | Get any new chat messages for the chat current chat session (based on 144 | hash_key) and return them to the browser as json. 145 | """ 146 | chat = get_object_or_404(Chat, hash_key=chat_uuid) 147 | cache.set('chat_%s' % chat.id, 'active', 20) 148 | last_message_id = request.GET.get(str(chat.id)) 149 | if last_message_id: 150 | messages = chat.messages.filter(id__gt=last_message_id) 151 | else: 152 | messages = chat.messages.all() 153 | 154 | message_list = [] 155 | for message in messages: 156 | message_list.append({ 157 | 'name': escape(message.name), 158 | 'message': escape(message.message), 159 | 'pk': message.pk, 160 | }) 161 | chats = { 162 | chat.id: { 163 | 'messages': message_list, 164 | } 165 | } 166 | output = { 167 | 'chats': chats, 168 | 'pending_chats': [], 169 | } 170 | return HttpResponse(json.dumps(output)) 171 | 172 | 173 | def client_end_chat(request, chat_uuid): 174 | chat = get_object_or_404(Chat, hash_key=chat_uuid) 175 | if request.POST.get('end_chat') == 'true': 176 | message = ChatMessage() 177 | name = request.POST.get('name', 'the user') 178 | message.message = '%s has left the chat. This chat has ended.' % name 179 | chat.messages.add(message) 180 | chat.end() 181 | return HttpResponse('Thank you') 182 | 183 | 184 | def client_post_message(request, chat_uuid): 185 | """ 186 | Post the message from the end user and return a list of any 187 | new messages based on the last_messag_id specified. 188 | """ 189 | chat = get_object_or_404(Chat, hash_key=chat_uuid) 190 | last_message_id = request.POST.get('last_message_id') 191 | message_form = ChatMessageForm(request.POST or None) 192 | if message_form.is_valid(): 193 | message = message_form.save(commit=False) 194 | message.chat = chat 195 | message.name = chat.name 196 | message.save() 197 | if last_message_id: 198 | new_messages = chat.messages.filter(id__gt=last_message_id) 199 | else: 200 | new_messages = chat.messages.all() 201 | new_message_list = [] 202 | for message in new_messages: 203 | new_message_list.append({ 204 | 'name': escape(message.name), 205 | 'message': escape(message.message), 206 | 'pk': message.pk, 207 | 'chat': chat.id, 208 | }) 209 | return HttpResponse(json.dumps(new_message_list)) 210 | 211 | 212 | def client_chat(request, chat_uuid): 213 | chat = get_object_or_404(Chat, hash_key=chat_uuid) 214 | message_form = ChatMessageForm(request.POST or None) 215 | if message_form.is_valid(): 216 | message = message_form.save(commit=False) 217 | message.chat = chat 218 | message.name = chat.name 219 | message.save() 220 | message_form = ChatMessageForm() 221 | 222 | params = { 223 | 'chat': chat, 224 | 'message_form': message_form, 225 | } 226 | return render_to_response('live_support/live_support.html', params, 227 | context_instance=RequestContext(request)) 228 | 229 | 230 | def start_chat(request, support_group_id=None): 231 | chat_form = ChatForm(request.POST or None) 232 | admin_active = cache.get('admin_active', False) 233 | if support_group_id: 234 | admin_active = cache.get('admin_active_%s' % support_group_id, False) 235 | if chat_form.is_valid(): 236 | chat = chat_form.save(commit=False) 237 | chat.support_group_id = support_group_id 238 | chat.save() 239 | if admin_active: 240 | request.session['chat_hash_key'] = chat.hash_key.hex 241 | return HttpResponseRedirect(reverse( 242 | 'client_chat', 243 | args=[chat.hash_key,]) 244 | ) 245 | else: 246 | return HttpResponse('Thank you for contacting us') 247 | params = { 248 | 'chat_form': chat_form, 249 | 'admin_active': admin_active, 250 | } 251 | return render_to_response('live_support/start_chat.html', params, 252 | context_instance=RequestContext(request)) 253 | -------------------------------------------------------------------------------- /live_support/static/live_support/js/live_support.js: -------------------------------------------------------------------------------- 1 | var check_messages_interval = 2000; 2 | var current_check_messages_interval = check_messages_interval; 3 | 4 | $(document).ready(function() { 5 | // Bind events for changing the currently active chat and sending a message 6 | $('.chat_names a').click(changeChat); 7 | $('.send_message_button').click(sendMessage); 8 | 9 | // show the first active chat and set its label to active 10 | $('.message_list:first:hidden').show(); 11 | $('.chat_names a:first').addClass('selected'); 12 | 13 | // Scroll all of the active chats to the bottom of the conversation 14 | scrollAll(); 15 | 16 | // Set the timeout for pulling for new messages. 17 | setTimeout(getMessages, current_check_messages_interval); 18 | }); 19 | 20 | // Check for new messages for any of the currently active chat sessions by 21 | // passing the chat id and the id of the last message received for each chat 22 | // session. Also update the list of pending chats. 23 | function getMessages() { 24 | var args = {}; 25 | $('.chat').each(function(index, item) { 26 | var chat_id = $(item).find('.chat_id').val(); 27 | var last_message_control = $(item).find('.message_list li:last'); 28 | // Are there any existing messages for this chat session? If so, grab 29 | // the id of the last message received. 30 | if (last_message_control.length > 0) { 31 | var last_message_id = $(item).find('.message_list li:last').attr('id').replace('message_', ''); 32 | args[chat_id] = last_message_id; 33 | } 34 | else { 35 | args[chat_id] = 0; 36 | } 37 | }); 38 | 39 | // document.get_messages_url is set in the template using a {% url %} tag 40 | var ajax_args = { 41 | url: document.get_messages_url, 42 | data: args, 43 | success: gotMessages, 44 | error: getMessagesFailed, 45 | dataType: 'json' 46 | }; 47 | // Make the call. 48 | $.ajax(ajax_args); 49 | } 50 | 51 | // If there was an error retrieving messages, we don't want to just keep 52 | // hitting the server over and over again, so we make the interval double 53 | // every time it continues to fail. We set this interval value back to 54 | // the default as soon as a successful call goes through. 55 | function getMessagesFailed() { 56 | current_check_messages_interval = current_check_messages_interval * 2; 57 | // TODO: show a message ot the user letting them know that message 58 | // checking will be delayed by x seconds... 59 | setTimeout(getMessages, current_check_messages_interval); 60 | } 61 | 62 | // Handle the response from the server with the list of new messages for 63 | // each active chat session and the list of currently pending chat sessions. 64 | function gotMessages(resp) { 65 | // Weird thing, sometimes jQuery will call this method even when the 66 | // request failed, so if the resp is null we need to call getMessagesFailed 67 | if (resp == null) { 68 | getMessagesFailed(); 69 | return; 70 | } 71 | // If the request was actually successful, reset the wait interval to 72 | // the default wait time. 73 | current_check_messages_interval = check_messages_interval; 74 | 75 | // Loop through each active chat session and add all new messages to the 76 | // message list for this chat session. 77 | for (var chat_id in resp.chats) { 78 | var chat = resp.chats[chat_id]; 79 | var message_list = $('#chat_' + chat_id).find('ul'); 80 | for (var index in chat.messages) { 81 | var message = chat.messages[index]; 82 | var new_message_element = $( document.createElement('li')); 83 | if (message.name == '') { 84 | $(new_message_element).addClass('system_message'); 85 | $(new_message_element).html(message.message); 86 | } 87 | else { 88 | $(new_message_element).html(message.name + ': ' + message.message); 89 | } 90 | $(new_message_element).attr('id', 'message_' + message.pk); 91 | $(message_list).append(new_message_element); 92 | // Add a "new_message" class to the current chat session label 93 | // if this chat session is not currently selected, that way 94 | // the admin user can see that there are new messages. 95 | $('.chat_names a[href="' + chat_id + '"]:not(".selected")').addClass('new_message'); 96 | } 97 | // If the session is active (based on recent requests by the user's 98 | // browser for new messages) we will make sure that the "The user has 99 | // disconnected" message is hidden. 100 | if (chat.alive) { 101 | // user still connected or reconnected 102 | $('#chat_' + chat_id + ' .status').hide(); 103 | } 104 | else { 105 | // user timed out 106 | $('#chat_' + chat_id + ' .status').show(); 107 | } 108 | } 109 | 110 | // Remove all of the existing Pending Chats and add only the ones 111 | // received in the response from the server. 112 | $('.pending_chats ul').children().remove(); 113 | for (var index in resp.pending_chats) { 114 | var chat = resp.pending_chats[index]; 115 | $('.pending_chats ul').append('
  • ' + chat.name + '
  • '); 116 | } 117 | 118 | // Scroll all of the chat sessions to the bottom of the message list. 119 | scrollAll(); 120 | 121 | // Set the timeout to check for new messages in a few seconds. 122 | setTimeout(getMessages, current_check_messages_interval); 123 | } 124 | 125 | // Change the currently active chat session being displayed. 126 | function changeChat(event) { 127 | $('.message_list').hide(); 128 | $('.chat_names a').removeClass('selected'); 129 | $(this).addClass('selected').removeClass('new_message'); 130 | $('#chat_' + $(event.target).attr('href')).show(); 131 | $('#chat_' + $(event.target).attr('href')).find('.message_box').focus(); 132 | scrollAll(); 133 | return false; 134 | } 135 | 136 | // Send the message (if there is one) for the currently active chat session. 137 | function sendMessage(event) { 138 | var message = $(event.target).parent().find('.message_box').val(); 139 | if (message.trim() != '') { 140 | var chat_id = $(event.target).parent().find('.chat_id').val(); 141 | var url = $(event.target).parents('form').attr('action'); 142 | var last_message_id = $(event.target).parents('.chat').find('.message_list li:last').attr('id').replace('message_', ''); 143 | var args = { 144 | 'message': message, 145 | 'last_message_id': last_message_id, 146 | 'chat_id': chat_id 147 | }; 148 | $.post(url, args, messageSent, 'json'); 149 | } 150 | return false; 151 | } 152 | 153 | // Message sent, and in the resp is a list of new messages from the server 154 | // (including the one you just sent). Add those messages to the message list 155 | function messageSent(response, code) { 156 | if (code == 'success') { 157 | var chat_id = response[0].chat; 158 | var message_list = $('#chat_' + chat_id).find('ul'); 159 | var message_box = $('#chat_' + chat_id).find('.message_box'); 160 | $(message_box).val(''); 161 | for (var index in response) { 162 | var message = response[index]; 163 | var new_message_element = $( document.createElement('li')); 164 | $(new_message_element).html(message.name + ': ' + message.message); 165 | $(new_message_element).attr('id', 'message_' + message.pk); 166 | $(message_list).append(new_message_element); 167 | } 168 | // Scroll to the bottom of the message list. 169 | scrollAll(); 170 | } 171 | } 172 | 173 | // Scroll all of the active chat sessions message lists to the bottom. 174 | function scrollAll() { 175 | $('.message_list ul').each(function(index, control) { 176 | $(control).scrollTop(control.scrollHeight - $(control).height()); 177 | }); 178 | } 179 | 180 | // Boilerplate jquery config stuff to make sure that ajax requests include 181 | // the Django csrf token. 182 | $(document).ajaxSend(function(event, xhr, settings) { 183 | function getCookie(name) { 184 | var cookieValue = null; 185 | if (document.cookie && document.cookie != '') { 186 | var cookies = document.cookie.split(';'); 187 | for (var i = 0; i < cookies.length; i++) { 188 | var cookie = $.trim(cookies[i]); 189 | // Does this cookie string begin with the name we want? 190 | if (cookie.substring(0, name.length + 1) == (name + '=')) { 191 | cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); 192 | break; 193 | } 194 | } 195 | } 196 | return cookieValue; 197 | } 198 | function sameOrigin(url) { 199 | // url could be relative or scheme relative or absolute 200 | var host = document.location.host; // host + port 201 | var protocol = document.location.protocol; 202 | var sr_origin = '//' + host; 203 | var origin = protocol + sr_origin; 204 | // Allow absolute or scheme relative URLs to same origin 205 | return (url == origin || url.slice(0, origin.length + 1) == origin + '/') || 206 | (url == sr_origin || url.slice(0, sr_origin.length + 1) == sr_origin + '/') || 207 | // or any other URL that isn't scheme relative or absolute i.e relative. 208 | !(/^(\/\/|http:|https:).*/.test(url)); 209 | } 210 | function safeMethod(method) { 211 | return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); 212 | } 213 | 214 | if (!safeMethod(settings.type) && sameOrigin(settings.url)) { 215 | xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken')); 216 | } 217 | }); 218 | --------------------------------------------------------------------------------