├── .gitignore ├── README.md ├── django_and_rethinkdb ├── __init__.py ├── change_feed.py ├── forms │ ├── __init__.py │ ├── login.py │ └── register.py ├── settings.py ├── static │ ├── app │ │ ├── index.js │ │ ├── main │ │ │ ├── main.html │ │ │ └── main.js │ │ └── services.js │ └── main.css ├── templates │ └── index.html ├── tornado_main.py ├── urls.py ├── views.py └── wsgi.py ├── manage.py └── requirements.pip /.gitignore: -------------------------------------------------------------------------------- 1 | !**/* 2 | 3 | venv 4 | *.pyc 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Django and RethinkDB 2 | 3 | Small repo showing how to use Django and RethinkDB together. App uses Tornado for websockets, RethinkDB for messages, and Django users (with MySQL) for authentication. 4 | 5 | ## Setup 6 | 7 | ``` 8 | git clone ... 9 | virtualenv venv 10 | pip install 11 | python manage.py migrate 12 | ``` 13 | 14 | ### Running 15 | 16 | ``` 17 | export PYTHONPATH=.;export DJANGO_SETTINGS_MODULE=django_and_rethinkdb.settings; python django_and_rethinkdb/tornado_main.py 18 | ``` 19 | -------------------------------------------------------------------------------- /django_and_rethinkdb/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejsj/django-and-rethinkdb/b5680b6bbc98ac32e5812adaca079ce07a1e11df/django_and_rethinkdb/__init__.py -------------------------------------------------------------------------------- /django_and_rethinkdb/change_feed.py: -------------------------------------------------------------------------------- 1 | import tornado.websocket 2 | import tornado.gen 3 | import tornado.web 4 | import rethinkdb as r 5 | import json 6 | 7 | clients = [] 8 | 9 | r.set_loop_type('tornado') 10 | 11 | @tornado.gen.coroutine 12 | def print_changes(): 13 | conn = yield r.connect(host="localhost", port=28015) 14 | feed = yield r.db("rethinkdb_chat").table('messages').changes(include_states=False).run(conn) 15 | while (yield feed.fetch_next()): 16 | change = yield feed.next() 17 | for client in clients: 18 | client.write_message(change) 19 | 20 | class MessageHandler(tornado.web.RequestHandler): 21 | 22 | @tornado.gen.coroutine 23 | def get(self): 24 | conn = yield r.connect(host='localhost', port=28015, db='rethinkdb_chat') 25 | messages = yield r.db('rethinkdb_chat').table('messages').order_by('created').coerce_to('array').run(conn) 26 | self.write(json.dumps(messages)) 27 | 28 | class SocketHandler(tornado.websocket.WebSocketHandler): 29 | 30 | def open(self): 31 | self.stream.set_nodelay(True) 32 | if self not in clients: 33 | clients.append(self) 34 | print len(clients) 35 | 36 | @tornado.gen.coroutine 37 | def on_message(self, message): 38 | new_message_object = json.loads(message) 39 | conn = yield r.connect(host="localhost", port=28015) 40 | new_message = yield r.db("rethinkdb_chat").table('messages').insert(new_message_object).run(conn) 41 | 42 | def on_close(self): 43 | for i, client in enumerate(clients): 44 | if client is self: 45 | del clients[i] 46 | return 47 | -------------------------------------------------------------------------------- /django_and_rethinkdb/forms/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejsj/django-and-rethinkdb/b5680b6bbc98ac32e5812adaca079ce07a1e11df/django_and_rethinkdb/forms/__init__.py -------------------------------------------------------------------------------- /django_and_rethinkdb/forms/login.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | 3 | class LoginForm(forms.Form): 4 | """ 5 | Login form 6 | """ 7 | username = forms.EmailField(widget=forms.widgets.TextInput) 8 | password = forms.CharField(widget=forms.widgets.PasswordInput) 9 | 10 | class Meta: 11 | fields = ['username', 'password'] 12 | -------------------------------------------------------------------------------- /django_and_rethinkdb/forms/register.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from django.contrib.auth.models import User 3 | 4 | class RegistrationForm(forms.ModelForm): 5 | """ 6 | Form for registering a new account. 7 | """ 8 | username = forms.EmailField(widget=forms.TextInput,label="Email") 9 | password = forms.CharField( 10 | widget=forms.PasswordInput, 11 | label="Password" 12 | ) 13 | 14 | class Meta: 15 | model = User 16 | fields = ['username', 'password'] 17 | 18 | def clean(self): 19 | """ 20 | Verifies that the values entered into the password fields match 21 | 22 | NOTE: Errors here will appear in ``non_field_errors()`` because it applies to more than one field. 23 | """ 24 | cleaned_data = super(RegistrationForm, self).clean() 25 | return self.cleaned_data 26 | 27 | def save(self, commit=True): 28 | user = super(RegistrationForm, self).save(commit=False) 29 | user.set_password(self.cleaned_data['password']) 30 | if commit: 31 | user.save() 32 | return user 33 | -------------------------------------------------------------------------------- /django_and_rethinkdb/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for django_and_rethinkdb project. 3 | 4 | Generated by 'django-admin startproject' using Django 1.8.1. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.8/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/1.8/ref/settings/ 11 | """ 12 | 13 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 14 | import os 15 | 16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = '=1gnq3++4*3to$cun@-(f86j_4f^!-k96#f%eej3k$be1o^17o' 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = [] 29 | 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = ( 34 | 'django.contrib.admin', 35 | 'django.contrib.auth', 36 | 'django.contrib.contenttypes', 37 | 'django.contrib.sessions', 38 | 'django.contrib.messages', 39 | 'django.contrib.staticfiles', 40 | 'django_and_rethinkdb' 41 | ) 42 | 43 | MIDDLEWARE_CLASSES = ( 44 | 'django.contrib.sessions.middleware.SessionMiddleware', 45 | 'django.middleware.common.CommonMiddleware', 46 | 'django.middleware.csrf.CsrfViewMiddleware', 47 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 48 | 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', 49 | 'django.contrib.messages.middleware.MessageMiddleware', 50 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 51 | 'django.middleware.security.SecurityMiddleware', 52 | ) 53 | 54 | ROOT_URLCONF = 'django_and_rethinkdb.urls' 55 | 56 | TEMPLATES = [ 57 | { 58 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 59 | 'DIRS': [], 60 | 'APP_DIRS': True, 61 | 'OPTIONS': { 62 | 'context_processors': [ 63 | 'django.template.context_processors.debug', 64 | 'django.template.context_processors.request', 65 | 'django.contrib.auth.context_processors.auth', 66 | 'django.contrib.messages.context_processors.messages', 67 | ], 68 | }, 69 | }, 70 | ] 71 | 72 | WSGI_APPLICATION = 'django_and_rethinkdb.wsgi.application' 73 | 74 | 75 | # Database 76 | # https://docs.djangoproject.com/en/1.8/ref/settings/#databases 77 | 78 | DATABASES = { 79 | 'default': { 80 | 'ENGINE': 'django.db.backends.mysql', 81 | 'NAME': 'django_and_rethinkdb', 82 | 'USER': 'django_and_rdb', 83 | 'PASSWORD': 'django_and_rethinkdb', 84 | 'HOST': 'localhost', 85 | 'PORT': '3306' 86 | } 87 | } 88 | 89 | 90 | # Internationalization 91 | # https://docs.djangoproject.com/en/1.8/topics/i18n/ 92 | 93 | LANGUAGE_CODE = 'en-us' 94 | 95 | TIME_ZONE = 'UTC' 96 | 97 | USE_I18N = True 98 | 99 | USE_L10N = True 100 | 101 | USE_TZ = True 102 | 103 | 104 | # Static files (CSS, JavaScript, Images) 105 | # https://docs.djangoproject.com/en/1.8/howto/static-files/ 106 | 107 | STATIC_URL = '/static/' 108 | -------------------------------------------------------------------------------- /django_and_rethinkdb/static/app/index.js: -------------------------------------------------------------------------------- 1 | /*global angular:true */ 2 | (function () { 3 | 'use strict'; 4 | angular.module('rethinkDBWorkshop', [ 5 | 'ui.router', 6 | 'rethinkDBWorkshop.services', 7 | 'rethinkDBWorkshop.messages' 8 | ]) 9 | .config(function ($stateProvider, $urlRouterProvider) { 10 | $urlRouterProvider.otherwise('/'); 11 | $stateProvider 12 | .state('main', { 13 | templateUrl: '/static/app/main/main.html', 14 | url: '/' 15 | }); 16 | }); 17 | })(); 18 | -------------------------------------------------------------------------------- /django_and_rethinkdb/static/app/main/main.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |

{{ message.email }}: {{ message.text }}

6 |
7 |
8 |
9 |
10 |
11 | 12 | 13 |
14 |
15 |
16 | -------------------------------------------------------------------------------- /django_and_rethinkdb/static/app/main/main.js: -------------------------------------------------------------------------------- 1 | /*global angular:true */ 2 | (function () { 3 | 'use strict'; 4 | 5 | angular.module('rethinkDBWorkshop.messages', ['ui.router']) 6 | .controller('MessagesController', MessagesController); 7 | 8 | MessagesController.$inject = ['$scope', '$window', 'MessageFactory']; 9 | 10 | function MessagesController($scope, $window, MessageFactory) { 11 | var vm = this; 12 | vm.messages = []; 13 | vm.submit = submit; 14 | 15 | $scope.$watch(function() { 16 | return vm.messages; 17 | }, function (n, o) { }); 18 | 19 | MessageFactory.getMessageCollection() 20 | .then(function (coll) { 21 | vm.messages = coll; 22 | }); 23 | 24 | function submit() { 25 | if (vm.text.length > 0) { 26 | MessageFactory.addMessage(vm.text); 27 | vm.text = ''; 28 | } 29 | } 30 | 31 | } 32 | })(); 33 | -------------------------------------------------------------------------------- /django_and_rethinkdb/static/app/services.js: -------------------------------------------------------------------------------- 1 | /*global angular:true, moment:true, _:true */ 2 | (function () { 3 | 'use strict'; 4 | 5 | angular.module('rethinkDBWorkshop.services', []) 6 | .factory('MessageFactory', MessageFactory); 7 | 8 | MessageFactory.$inject = ['$http', '$state', '$q', '$rootScope']; 9 | 10 | function MessageFactory ($http, $state, $q, $rootScope) { 11 | 12 | var ws = new WebSocket('ws://' + window.config.url + ':' + window.config.ports.http + '/new-messages/'); 13 | var messageCollection = []; 14 | 15 | ws.onopen = function () { 16 | console.log('Socket Connection Open'); 17 | }; 18 | 19 | ws.onmessage = function (evt) { 20 | var message = JSON.parse(evt.data).new_val; 21 | console.log('message', message); 22 | $rootScope.$apply(function () { 23 | messageCollection.push(message); 24 | }); 25 | }; 26 | 27 | var factory = { 28 | getMessageCollection: getMessageCollection, 29 | addMessage: addMessage, 30 | }; 31 | 32 | return factory; 33 | 34 | function getMessageCollection() { 35 | return $http.get('/messages/') 36 | .then(function (res) { 37 | console.log(res); 38 | res.data.forEach(function (row) { 39 | messageCollection.push(row); 40 | }); 41 | return messageCollection; 42 | }); 43 | } 44 | 45 | function addMessage(text) { 46 | ws.send(JSON.stringify({ 47 | text: text, 48 | email: window.config.email, 49 | created: (new Date()).getTime() 50 | })); 51 | } 52 | 53 | } 54 | 55 | })(); 56 | -------------------------------------------------------------------------------- /django_and_rethinkdb/static/main.css: -------------------------------------------------------------------------------- 1 | #container { 2 | position: relative; 3 | height: 100vh; 4 | overflow: hidden; 5 | } 6 | 7 | #container #message-form { 8 | position: absolute; 9 | bottom: 0; 10 | width: 100%; 11 | padding-top: 5px; 12 | padding-bottom: 10px; 13 | background: white; 14 | height: 40px; 15 | border-top: solid 1px #f2f2f2; 16 | } 17 | 18 | #container #message-form input[type='text'] { 19 | margin-bottom: 10px; 20 | width: calc(100% - 100px); 21 | } 22 | 23 | #container #message-form input[type='submit'] { 24 | width: 100px; 25 | float: right; 26 | } 27 | 28 | #container .message-collection-container { 29 | padding-top: 10px; 30 | height: calc(100vh - 40px); 31 | overflow-y: scroll; 32 | } 33 | -------------------------------------------------------------------------------- /django_and_rethinkdb/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | {% if user.is_authenticated %} 9 | 10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | 20 | 21 | 22 | 23 | 24 | {% endif %} 25 | {% if not user.is_authenticated %} 26 | 27 |
28 |
29 |
30 |

Login

31 |
32 | {% csrf_token %} 33 | 34 |
35 | 36 | 37 |
38 |
39 | 40 | 41 |
42 |
43 | 44 |
45 |
46 |

Signup

47 |
48 | {% csrf_token %} 49 |
50 | 51 | 52 |
53 |
54 | 55 | 56 |
57 |
58 | 59 |
60 |
61 |
62 |
63 |
64 | 65 | {% endif %} 66 | 67 | -------------------------------------------------------------------------------- /django_and_rethinkdb/tornado_main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Run this with 4 | # export PYTHONPATH=.; export DJANGO_SETTINGS_MODULE=django_and_rethinkdb.settings; python django_and_rethinkdb/tornado_main.py 5 | 6 | import django.core.handlers.wsgi 7 | from django.conf import settings 8 | import os 9 | from tornado.options import options, define, parse_command_line 10 | import tornado.httpserver 11 | import tornado.ioloop 12 | import tornado.web 13 | import tornado.wsgi 14 | import tornado.websocket 15 | import tornado.gen 16 | from change_feed import print_changes, SocketHandler, MessageHandler 17 | 18 | django.setup() 19 | 20 | def main(): 21 | parse_command_line() 22 | wsgi_app = tornado.wsgi.WSGIContainer( 23 | django.core.handlers.wsgi.WSGIHandler() 24 | ) 25 | current_dir = os.path.dirname(os.path.abspath(__file__)) 26 | static_folder = os.path.join(current_dir, 'static') 27 | tornado_app = tornado.web.Application([ 28 | ('/new-messages/', SocketHandler), 29 | ('/messages/', MessageHandler), 30 | (r'/static/(.*)', tornado.web.StaticFileHandler, { 'path': static_folder }), 31 | ('.*', tornado.web.FallbackHandler, dict(fallback=wsgi_app)), 32 | ]) 33 | server = tornado.httpserver.HTTPServer(tornado_app) 34 | server.listen(8000) 35 | tornado.ioloop.IOLoop.current().add_callback(print_changes) 36 | tornado.ioloop.IOLoop.instance().start() 37 | 38 | if __name__ == '__main__': 39 | main() 40 | -------------------------------------------------------------------------------- /django_and_rethinkdb/urls.py: -------------------------------------------------------------------------------- 1 | """django_and_rethinkdb URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/1.8/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Add an import: from blog import urls as blog_urls 14 | 2. Add a URL to urlpatterns: url(r'^blog/', include(blog_urls)) 15 | """ 16 | from django.conf.urls import include, url 17 | from django_and_rethinkdb.views import * 18 | from django.contrib import admin 19 | 20 | urlpatterns = [ 21 | url(r'^auth/', include([ 22 | url(r'^signup/', 'django_and_rethinkdb.views.signup', name='signup'), 23 | url(r'^login/', 'django_and_rethinkdb.views.login', name='login'), 24 | ])), 25 | url(r'^admin/', include(admin.site.urls)), 26 | url(r'^$', 'django_and_rethinkdb.views.main', name='main'), 27 | url(r'^config.js$', 'django_and_rethinkdb.views.config', name='config') 28 | ] 29 | -------------------------------------------------------------------------------- /django_and_rethinkdb/views.py: -------------------------------------------------------------------------------- 1 | from django.http import HttpResponse 2 | from django.shortcuts import render 3 | from django.contrib import auth 4 | from django.core.exceptions import PermissionDenied 5 | from django_and_rethinkdb.forms.register import RegistrationForm 6 | from django_and_rethinkdb.forms.login import LoginForm 7 | import rethinkdb as r 8 | import json 9 | 10 | def main(request): 11 | return render(request, 'index.html') 12 | 13 | def login(request): 14 | username = password = '' 15 | if request.POST: 16 | form = LoginForm(request.POST) 17 | if form.is_valid(): 18 | user = auth.authenticate( 19 | username=form.cleaned_data['username'], 20 | password=form.cleaned_data['password'] 21 | ) 22 | if user is not None and user.is_active: 23 | auth.login(request, user) 24 | return HttpResponse('User %s logged in' % form.cleaned_data['username']) 25 | return HttpResonse('Error logging user in') 26 | raise PermissionDenied 27 | 28 | def signup(request): 29 | if request.POST: 30 | form = RegistrationForm(request.POST) 31 | if form.is_valid(): 32 | form.save() 33 | return HttpResponse('User %s Created' % form.cleaned_data['username']) 34 | return HttpResonse('Error in SignUp') 35 | raise PermissionDenied 36 | 37 | def config(request): 38 | config = { 39 | 'ports': { 40 | 'http': '8000', 41 | }, 42 | 'url': '127.0.0.1', 43 | 'email': request.user.username 44 | } 45 | return HttpResponse('window.config = %s;' % json.dumps(config)) 46 | -------------------------------------------------------------------------------- /django_and_rethinkdb/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for django_and_rethinkdb 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.8/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_and_rethinkdb.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /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", "django_and_rethinkdb.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /requirements.pip: -------------------------------------------------------------------------------- 1 | Django (1.8.1) 2 | pip (6.1.1) 3 | rethinkdb (2.0.0.post1) 4 | setuptools (12.0.5) 5 | Django (1.8.1) 6 | MySQL-python (1.2.5) 7 | pip (6.1.1) 8 | rethinkdb (2.0.0.post1) 9 | setuptools (12.0.5) 10 | backports.ssl-match-hostname (3.4.0.2) 11 | certifi (2015.4.28) 12 | Django (1.8.1) 13 | MySQL-python (1.2.5) 14 | pip (6.1.1) 15 | rethinkdb (2.0.0.post1) 16 | setuptools (12.0.5) 17 | tornado (4.1) 18 | --------------------------------------------------------------------------------