├── .hgignore
├── README.md
└── myproject
├── __init__.py
├── chat
├── __init__.py
├── templates
│ └── chat
│ │ ├── client.js
│ │ └── index.html
├── urls.py
└── views.py
├── django_tornado
├── __init__.py
├── decorator.py
├── management
│ ├── __init__.py
│ └── commands
│ │ ├── __init__.py
│ │ └── runtornado.py
├── models.py
├── tests.py
└── views.py
├── manage.py
├── settings.py
└── urls.py
/.hgignore:
--------------------------------------------------------------------------------
1 | syntax: glob
2 | *.pyc
3 | *.pyo
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Requirements
2 | ============
3 | * Django_
4 | * Tornado_
5 |
6 | .. _Django: http://www.djangoproject.com/
7 | .. _Tornado: http://www.tornadoweb.org/
8 |
9 | Instructions
10 | ============
11 |
12 | 1. ``cd myproject``
13 | 2. ``python manage.py runtornado --reload 8000``
14 | 3. Go to: http://localhost:8000/
15 |
16 | Now you have a working chat server -- this is based on the NodeJS chat server, UI primarily.
17 |
18 | Quick Tutorial
19 | ==============
20 |
21 | Django request/response is untouched. To utilize Tornado's async capabilities, a decorator
22 | is avilable. Which adds an extra argument to your request handler, which is the Tornado
23 | handler class -- all functions are available.
24 |
25 | If you return a value it is assumed to be a Django Response, if nothing is returned then
26 | it is assumed that you're doing async response processing.
27 |
28 | from django_tornado.decorator import asynchronous
29 |
30 | @asynchronous
31 | def recv(request, handler) :
32 | response = {}
33 |
34 | if 'since' not in request.GET :
35 | return ChatResponseError('Must supply since parameter')
36 | if 'id' not in request.GET :
37 | return ChatResponseError('Must supply id parameter')
38 |
39 | id = request.GET['id']
40 | session = Session.get(id)
41 | if session :
42 | session.poke()
43 |
44 | since = int(request.GET['since'])
45 |
46 | def on_new_messages(messages) :
47 | if handler.request.connection.stream.closed():
48 | return
49 | handler.finish({ 'messages': messages, 'rss' : channel.size() })
50 |
51 | channel.query(handler.async_callback(on_new_messages), since)
52 |
53 | Advanced Usage
54 | ==============
55 |
56 | Since Tornado is a single threaded server which blocks on long operations (e.g. slow DB queries) it''s useful to
57 | run multiple servers at the same time. That''s now supported on the command line, for
58 |
59 | python manage.py runtornado 8000 8001 8002 8003
60 |
61 | Which enables the following nginx configuration (skipping lots of details)
62 |
63 | upstream frontends {
64 | server 127.0.0.1:8000;
65 | server 127.0.0.1:8001;
66 | server 127.0.0.1:8002;
67 | server 127.0.0.1:8003;
68 | }
69 |
70 | ...
71 |
72 | location / {
73 | proxy_pass http://frontends;
74 | }
75 |
76 | Acknowledgements
77 | ================
78 |
79 | Idea and code snippets borrowed from http://geekscrap.com/2010/02/integrate-tornado-in-django/
80 | Chat server http://github.com/ry/node_chat
81 |
--------------------------------------------------------------------------------
/myproject/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/koblas/django-on-tornado/d53b28ee642d929240918f857112f857671d0a60/myproject/__init__.py
--------------------------------------------------------------------------------
/myproject/chat/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/koblas/django-on-tornado/d53b28ee642d929240918f857112f857671d0a60/myproject/chat/__init__.py
--------------------------------------------------------------------------------
/myproject/chat/templates/chat/client.js:
--------------------------------------------------------------------------------
1 | var CONFIG = { debug: false
2 | , nick: "#" // set in onConnect
3 | , id: null // set in onConnect
4 | , last_message_time: 1
5 | , focus: true //event listeners bound in onConnect
6 | , unread: 0 //updated in the message-processing loop
7 | };
8 |
9 | var nicks = [];
10 |
11 | // CUT ///////////////////////////////////////////////////////////////////
12 | /* This license and copyright apply to all code until the next "CUT"
13 | http://github.com/jherdman/javascript-relative-time-helpers/
14 |
15 | The MIT License
16 |
17 | Copyright (c) 2009 James F. Herdman
18 |
19 | Permission is hereby granted, free of charge, to any person obtaining a copy of
20 | this software and associated documentation files (the "Software"), to deal in
21 | the Software without restriction, including without limitation the rights to
22 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
23 | of the Software, and to permit persons to whom the Software is furnished to do
24 | so, subject to the following conditions:
25 |
26 | The above copyright notice and this permission notice shall be included in all
27 | copies or substantial portions of the Software.
28 |
29 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
30 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
31 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
32 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
33 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
34 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
35 | SOFTWARE.
36 |
37 |
38 | * Returns a description of this past date in relative terms.
39 | * Takes an optional parameter (default: 0) setting the threshold in ms which
40 | * is considered "Just now".
41 | *
42 | * Examples, where new Date().toString() == "Mon Nov 23 2009 17:36:51 GMT-0500 (EST)":
43 | *
44 | * new Date().toRelativeTime()
45 | * --> 'Just now'
46 | *
47 | * new Date("Nov 21, 2009").toRelativeTime()
48 | * --> '2 days ago'
49 | *
50 | * // One second ago
51 | * new Date("Nov 23 2009 17:36:50 GMT-0500 (EST)").toRelativeTime()
52 | * --> '1 second ago'
53 | *
54 | * // One second ago, now setting a now_threshold to 5 seconds
55 | * new Date("Nov 23 2009 17:36:50 GMT-0500 (EST)").toRelativeTime(5000)
56 | * --> 'Just now'
57 | *
58 | */
59 | Date.prototype.toRelativeTime = function(now_threshold) {
60 | var delta = new Date() - this;
61 |
62 | now_threshold = parseInt(now_threshold, 10);
63 |
64 | if (isNaN(now_threshold)) {
65 | now_threshold = 0;
66 | }
67 |
68 | if (delta <= now_threshold) {
69 | return 'Just now';
70 | }
71 |
72 | var units = null;
73 | var conversions = {
74 | millisecond: 1, // ms -> ms
75 | second: 1000, // ms -> sec
76 | minute: 60, // sec -> min
77 | hour: 60, // min -> hour
78 | day: 24, // hour -> day
79 | month: 30, // day -> month (roughly)
80 | year: 12 // month -> year
81 | };
82 |
83 | for (var key in conversions) {
84 | if (delta < conversions[key]) {
85 | break;
86 | } else {
87 | units = key; // keeps track of the selected key over the iteration
88 | delta = delta / conversions[key];
89 | }
90 | }
91 |
92 | // pluralize a unit when the difference is greater than 1.
93 | delta = Math.floor(delta);
94 | if (delta !== 1) { units += "s"; }
95 | return [delta, units].join(" ");
96 | };
97 |
98 | /*
99 | * Wraps up a common pattern used with this plugin whereby you take a String
100 | * representation of a Date, and want back a date object.
101 | */
102 | Date.fromString = function(str) {
103 | return new Date(Date.parse(str));
104 | };
105 |
106 | // CUT ///////////////////////////////////////////////////////////////////
107 |
108 |
109 |
110 | //updates the users link to reflect the number of active users
111 | function updateUsersLink ( ) {
112 | var t = nicks.length.toString() + " user";
113 | if (nicks.length != 1) t += "s";
114 | $("#usersLink").text(t);
115 | }
116 |
117 | //handles another person joining chat
118 | function userJoin(nick, timestamp) {
119 | //put it in the stream
120 | addMessage(nick, "joined", timestamp, "join");
121 | //if we already know about this user, ignore it
122 | for (var i = 0; i < nicks.length; i++)
123 | if (nicks[i] == nick) return;
124 | //otherwise, add the user to the list
125 | nicks.push(nick);
126 | //update the UI
127 | updateUsersLink();
128 | }
129 |
130 | //handles someone leaving
131 | function userPart(nick, timestamp) {
132 | //put it in the stream
133 | addMessage(nick, "left", timestamp, "part");
134 | //remove the user from the list
135 | for (var i = 0; i < nicks.length; i++) {
136 | if (nicks[i] == nick) {
137 | nicks.splice(i,1)
138 | break;
139 | }
140 | }
141 | //update the UI
142 | updateUsersLink();
143 | }
144 |
145 | // utility functions
146 |
147 | util = {
148 | urlRE: /https?:\/\/([-\w\.]+)+(:\d+)?(\/([^\s]*(\?\S+)?)?)?/g,
149 |
150 | // html sanitizer
151 | toStaticHTML: function(inputHtml) {
152 | inputHtml = inputHtml.toString();
153 | return inputHtml.replace(/&/g, "&")
154 | .replace(//g, ">");
156 | },
157 |
158 | //pads n with zeros on the left,
159 | //digits is minimum length of output
160 | //zeroPad(3, 5); returns "005"
161 | //zeroPad(2, 500); returns "500"
162 | zeroPad: function (digits, n) {
163 | n = n.toString();
164 | while (n.length < digits)
165 | n = '0' + n;
166 | return n;
167 | },
168 |
169 | //it is almost 8 o'clock PM here
170 | //timeString(new Date); returns "19:49"
171 | timeString: function (date) {
172 | var minutes = date.getMinutes().toString();
173 | var hours = date.getHours().toString();
174 | return this.zeroPad(2, hours) + ":" + this.zeroPad(2, minutes);
175 | },
176 |
177 | //does the argument only contain whitespace?
178 | isBlank: function(text) {
179 | var blank = /^\s*$/;
180 | return (text.match(blank) !== null);
181 | }
182 | };
183 |
184 | //used to keep the most recent messages visible
185 | function scrollDown () {
186 | window.scrollBy(0, 100000000000000000);
187 | $("#entry").focus();
188 | }
189 |
190 | //inserts an event into the stream for display
191 | //the event may be a msg, join or part type
192 | //from is the user, text is the body and time is the timestamp, defaulting to now
193 | //_class is a css class to apply to the message, usefull for system events
194 | function addMessage (from, text, time, _class) {
195 | if (text === null)
196 | return;
197 |
198 | if (time == null) {
199 | // if the time is null or undefined, use the current time.
200 | time = new Date();
201 | } else if ((time instanceof Date) === false) {
202 | // if it's a timestamp, interpret it
203 | time = new Date(time);
204 | }
205 |
206 | //every message you see is actually a table with 3 cols:
207 | // the time,
208 | // the person who caused the event,
209 | // and the content
210 | var messageElement = $(document.createElement("table"));
211 |
212 | messageElement.addClass("message");
213 | if (_class)
214 | messageElement.addClass(_class);
215 |
216 | // sanitize
217 | text = util.toStaticHTML(text);
218 |
219 | // If the current user said this, add a special css class
220 | var nick_re = new RegExp(CONFIG.nick);
221 | if (nick_re.exec(text))
222 | messageElement.addClass("personal");
223 |
224 | // replace URLs with links
225 | text = text.replace(util.urlRE, '$&');
226 |
227 | var content = '
121 |
137 |
138 |
139 |
18:58 | TTilus |
140 | x6a616e: i think you can, there was some weird #send trick to do that |
141 |
142 |
18:58 | TTilus |
143 | (or i could just be terribly wrong) |
144 |
145 |
19:02 | x6a616e |
146 | TTilus: with #send you can invoke private methods |
147 |
148 |
19:03 | x6a616e |
149 | dunno how to leverage it to access instance var :-/ |
150 |
151 |
19:05 | x6a616e |
152 | i3d: usually I use rspec::mocks |
153 |
154 |
19:05 | dlisboa |
155 | x6a616e: #instance_variable_get ? |
156 |
157 |
19:06 | x6a616e |
158 | dlisboa: phew I forgot that .. |
159 |
160 |
19:19 | UrbanVegan |
161 | How can I use "%" in a string as just another character (meaning "percent")? |
162 |
163 |
19:20 | ddfreyne |
164 | "%" |
165 |
166 |
19:20 | ddfreyne |
167 | :) |
168 |
169 |
19:20 | ddfreyne |
170 | no need to escape it |
171 |
172 |
19:20 | dominikh |
173 | %% |
174 |
175 |
19:21 | dominikh |
176 | ddfreyne: if you use something like "%string" % 1 |
177 |
178 |
19:21 | dominikh |
179 | eh |
180 |
181 |
19:21 | dominikh |
182 | you get the idea |
183 |
184 |
19:21 | ddfreyne |
185 | "foo %s bar" % [ 'hello' ] # => "foo hello bar" |
186 |
187 |
19:21 | dominikh |
188 | lets assume he has some other % stuff he wants to be replaced |
189 |
190 |
19:21 | ddfreyne |
191 | "foo %% %s bar" % [ 'hello' ] # => "foo % hello bar" |
192 |
193 |
19:21 | dominikh |
194 | and some he doesnt want to |
195 |
196 |
20:07 | bougyman |
197 | docs should be in /usr/share, not /usr/lib/ruby/gems/1.8/doc, too |
198 |
199 |
20:07 | bougyman |
200 | FHS is OS agnostic. |
201 |
202 |
20:08 | drbrain |
203 | bougyman: FreeBSD doesn't follow the FHS |
204 |
205 |
20:08 | drbrain |
206 | Apple doesn't follow the FHS, and windows doesn't follow the FHS |
207 |
208 |
20:08 | drbrain |
209 | I really don't care about people who say "you don't X, Y or Z!" and won't pony up patches |
210 |
211 |
20:11 | bougyman |
212 | the fbsd list seems split over FHS compliance |
213 |
214 |
20:11 | bougyman |
215 | some of em want it, some give it the finger. |
216 |
217 |
20:11 | drbrain |
218 | that's because they already have the heir man page |
219 |
220 |
20:12 | bougyman |
221 | looks like they gave in on mounts to FHS 2.2 (freebsd did) |
222 |
223 |
20:12 | bougyman |
224 | winFS was said to be FHS compliant. |
225 |
226 |
20:12 | bougyman |
227 | maybe we'll see that in the next MS product. |
228 |
229 |
20:13 | bougyman |
230 | it was supposed to be in Vista, but got scrapped. |
231 |
232 |
20:13 | ddfreyne |
233 | stuff in /bin should have config stuff in /etc, stuff in /usr/bin should have their configs in
234 | /usr/etc, ... IMO
235 | |
236 |
237 |
20:13 | ddfreyne |
238 | stuff in ~/bin should have their configs in ~/etc |
239 |
240 |
20:13 | ddfreyne |
241 | that would make a lot more sense than it does now |
242 |
243 |
20:13 | ddfreyne |
244 | ... what kind of names are "etc" and "var" anyway? |
245 |
246 |
20:13 | ddfreyne |
247 | "config" and "data" would have made more sense |
248 |
249 |
20:14 | bougyman |
250 | they make sense to me. |
251 |
252 |
20:14 | ddfreyne |
253 | even 'etc'? etcetera? "all the rest of the stuff goes here"? |
254 |
255 |
20:14 | bougyman |
256 | etc. and variable are how I read them. |
257 |
258 |
20:14 | catalystmediastu |
259 | Does anyone know of a gem or Rails plugin that converts rtf documents to HTML? I've |
260 |
261 |
20:15 | wmoxam |
262 | catalystmediastu: I doubt it, you'll probably have to find a tool that does it, and call the tool |
263 |
264 |
20:15 | ddfreyne |
265 | bougyman: you can't really say that 'etc' is a better name than 'config' |
266 |
267 |
20:16 | catalystmediastu |
268 | wmoxam: I'll start looking for a generic tool for linux then. Thanks! |
269 |
270 |
20:16 | wmoxam |
271 | catalystmediastu: http://sourceforge.net/projects/rtf2html/ <-- might work |
272 |
273 |
20:17 | catalystmediastu |
274 | wmoxam: Ahh that looks like it might. Thank you! |
275 |
276 |
20:17 | wmoxam |
277 | np |
278 |
279 |
20:17 | bougyman |
280 | catalystmediastu: unrtf works well for that. |
281 |
282 |
20:17 | bougyman |
283 | http://www.gnu.org/software/unrtf/unrtf.html |
284 |
285 |
20:20 | catalystmediastu |
286 | bougyman: Thanks, that looks like a good tool too. I'll look into them both a little
287 | more.
288 | |
289 |
290 |
291 |
299 |
300 |
301 |
--------------------------------------------------------------------------------
/myproject/chat/urls.py:
--------------------------------------------------------------------------------
1 | from django.conf.urls.defaults import *
2 |
3 | urlpatterns = patterns('chat.views',
4 | url(r'^$', 'index'),
5 | url(r'^client.js$', 'clientjs'),
6 | url(r'^join', 'join'),
7 | url(r'^recv', 'recv'),
8 | url(r'^send', 'send'),
9 | url(r'^who', 'who'),
10 | )
11 |
--------------------------------------------------------------------------------
/myproject/chat/views.py:
--------------------------------------------------------------------------------
1 | from django.template import RequestContext
2 | from django.shortcuts import render_to_response
3 | from django.http import HttpResponse
4 | import json
5 | import time
6 | import tornado.web
7 | from django_tornado.decorator import asynchronous
8 |
9 | #
10 | #
11 | #
12 | class Channel(object) :
13 | def __init__(self) :
14 | self._messages = []
15 | self._callbacks = []
16 |
17 | def message(self, type, nick, text="") :
18 | m = { 'type': type, 'timestamp' : int(time.time()), 'text' : text, 'nick' : nick }
19 |
20 | for cb in self._callbacks :
21 | cb([m])
22 | self._callbacks = []
23 |
24 | self._messages.append(m)
25 |
26 | def query(self, cb, since) :
27 | msgs = [m for m in self._messages if m['timestamp'] > since]
28 | if msgs :
29 | return cb(msgs)
30 | self._callbacks.append(cb)
31 |
32 | def size(self) :
33 | return 1024
34 |
35 | channel = Channel()
36 |
37 | class Session(object) :
38 | SESSIONS = {}
39 | CUR_ID = 100
40 |
41 | def __init__(self, nick) :
42 | for v in self.SESSIONS.values() :
43 | if v.nick == nick :
44 | raise "In use"
45 |
46 | self.nick = nick
47 | Session.CUR_ID += 1
48 | self.id = Session.CUR_ID
49 |
50 | Session.SESSIONS[str(self.id)] = self
51 |
52 | def poke(self) :
53 | pass
54 |
55 | @classmethod
56 | def who(cls) :
57 | return [ s.nick for s in Session.SESSIONS.values() ]
58 |
59 | @classmethod
60 | def get(cls, id) :
61 | return Session.SESSIONS.get(str(id), None)
62 |
63 | @classmethod
64 | def remove(cls, id) :
65 | if id in cls.SESSIONS :
66 | del Session.SESSIONS[id]
67 |
68 | #
69 | #
70 | #
71 | class ChatResponseError(HttpResponse) :
72 | def __init__(self, message) :
73 | super(ChatResponseError, self).__init__(status=400, content=json.dumps({ 'error' : message }))
74 |
75 | class ChatResponse(HttpResponse) :
76 | def __init__(self, data) :
77 | super(ChatResponse, self).__init__(content=json.dumps(data))
78 |
79 | #
80 | #
81 | #
82 | def index(request) :
83 | return render_to_response('chat/index.html')
84 |
85 | def clientjs(request) :
86 | return render_to_response('chat/client.js')
87 |
88 | def join(request) :
89 | nick = request.GET['nick']
90 |
91 | if not nick :
92 | return ChatResponseError("Bad nickname");
93 |
94 | try :
95 | session = Session(nick)
96 | except Exception, e:
97 | return ChatResponseError("Nickname in use");
98 |
99 | channel.message('join', nick, "%s joined" % nick)
100 |
101 | return ChatResponse({
102 | 'id' : session.id,
103 | 'nick': session.nick,
104 | 'rss': channel.size(),
105 | 'starttime': int(time.time()),
106 | })
107 |
108 | def part(request) :
109 | id = request.GET['id']
110 |
111 | session = Session.get(id)
112 | if not session :
113 | return ChatResponseError('session expired')
114 |
115 | channel.message('part', session.nick)
116 |
117 | Session.remove(id)
118 |
119 | return ChatResponse({ 'rss': 0 })
120 |
121 | def send(request) :
122 | id = request.GET['id']
123 | session = Session.get(id)
124 | if not session :
125 | return ChatResponseError('session expired')
126 |
127 | channel.message('msg', session.nick, request.GET['text'])
128 |
129 | return ChatResponse({ 'rss' : channel.size() })
130 |
131 | def who(request) :
132 | return ChatResponse({ 'nicks': Session.who(), 'rss' : channel.size() })
133 |
134 | @asynchronous
135 | def recv(request, handler) :
136 | response = {}
137 |
138 | if 'since' not in request.GET :
139 | return ChatResponseError('Must supply since parameter')
140 | if 'id' not in request.GET :
141 | return ChatResponseError('Must supply id parameter')
142 |
143 | id = request.GET['id']
144 | session = Session.get(id)
145 | if session :
146 | session.poke()
147 |
148 | since = int(request.GET['since'])
149 |
150 | def on_new_messages(messages) :
151 | if handler.request.connection.stream.closed():
152 | return
153 | handler.finish({ 'messages': messages, 'rss' : channel.size() })
154 |
155 | channel.query(handler.async_callback(on_new_messages), since)
156 |
--------------------------------------------------------------------------------
/myproject/django_tornado/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/koblas/django-on-tornado/d53b28ee642d929240918f857112f857671d0a60/myproject/django_tornado/__init__.py
--------------------------------------------------------------------------------
/myproject/django_tornado/decorator.py:
--------------------------------------------------------------------------------
1 | import tornado.web
2 | import types
3 | import functools
4 |
5 | class TornadoAsyncException(Exception) : pass
6 |
7 | class _DefGen_Return(BaseException):
8 | def __init__(self, value):
9 | self.value = value
10 |
11 | def returnResponse(value) :
12 | raise _DefGen_Return(value)
13 |
14 | def asynchronous(method) :
15 | def wrapper(request, *args, **kwargs):
16 | try :
17 | v = method(request, request._tornado_handler, *args, **kwargs)
18 | if v == None or type(v) == types.GeneratorType :
19 | raise TornadoAsyncException
20 | except _DefGen_Return, e :
21 | request._tornado_handler.finish(e.value.content)
22 | return v
23 | return wrapper
24 |
--------------------------------------------------------------------------------
/myproject/django_tornado/management/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/koblas/django-on-tornado/d53b28ee642d929240918f857112f857671d0a60/myproject/django_tornado/management/__init__.py
--------------------------------------------------------------------------------
/myproject/django_tornado/management/commands/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/koblas/django-on-tornado/d53b28ee642d929240918f857112f857671d0a60/myproject/django_tornado/management/commands/__init__.py
--------------------------------------------------------------------------------
/myproject/django_tornado/management/commands/runtornado.py:
--------------------------------------------------------------------------------
1 | from django.core.management.base import BaseCommand, CommandError
2 | from optparse import make_option
3 | import settings
4 | import os
5 | import sys
6 | import tornado.web
7 |
8 | class Command(BaseCommand):
9 | option_list = BaseCommand.option_list + (
10 | make_option('--reload', action='store_true',
11 | dest='use_reloader', default=False,
12 | help="Tells Tornado to use auto-reloader."),
13 | #make_option('--admin', action='store_true',
14 | # dest='admin_media', default=False,
15 | # help="Serve admin media."),
16 | #make_option('--adminmedia', dest='admin_media_path', default='',
17 | # help="Specifies the directory from which to serve admin media."),
18 | make_option('--noxheaders', action='store_false',
19 | dest='xheaders', default=True,
20 | help="Tells Tornado to NOT override remote IP with X-Real-IP."),
21 | make_option('--nokeepalive', action='store_true',
22 | dest='no_keep_alive', default=False,
23 | help="Tells Tornado to NOT keep alive http connections."),
24 | )
25 | help = "Starts a Tornado Web."
26 | args = '[optional port number or ipaddr:port] (one or more, will start multiple servers)'
27 |
28 | # Validation is called explicitly each time the server is reloaded.
29 | requires_model_validation = False
30 |
31 | def handle(self, *addrport, **options):
32 | # reopen stdout/stderr file descriptor with write mode
33 | # and 0 as the buffer size (unbuffered).
34 | # XXX: why?
35 | sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)
36 | sys.stderr = os.fdopen(sys.stderr.fileno(), 'w', 0)
37 |
38 | if len(addrport) == 0 :
39 | raise CommandError('Usage is runserver %s' % self.args)
40 |
41 | if len(addrport) == 1 :
42 | self.run_one(addrport[0], **options)
43 | else :
44 | from multiprocessing import Process
45 |
46 | plist = []
47 | for ap in addrport :
48 | p = Process(target=self.run_one, args=(ap,), kwargs=options)
49 | p.start()
50 | plist.append(p)
51 |
52 | # for p in plist : plist.terminate()
53 |
54 | while plist :
55 | if plist[0].exitcode is None :
56 | plist.pop(0)
57 | else :
58 | plist[0].join()
59 |
60 |
61 | def run_one(self, addrport, **options) :
62 | import django
63 | from django.core.handlers.wsgi import WSGIHandler
64 | from tornado import httpserver, wsgi, ioloop, web
65 |
66 | if not addrport:
67 | addr = ''
68 | port = '8000'
69 | else:
70 | try:
71 | addr, port = addrport.split(':')
72 | except ValueError:
73 | addr, port = '', addrport
74 | if not addr:
75 | addr = '127.0.0.1'
76 |
77 | if not port.isdigit():
78 | raise CommandError("%r is not a valid port number." % port)
79 |
80 | use_reloader = options.get('use_reloader', False)
81 |
82 | serve_admin_media = options.get('admin_media', False)
83 | admin_media_path = options.get('admin_media_path', '')
84 |
85 | xheaders = options.get('xheaders', True)
86 | no_keep_alive = options.get('no_keep_alive', False)
87 |
88 | shutdown_message = options.get('shutdown_message', '')
89 | quit_command = (sys.platform == 'win32') and 'CTRL-BREAK' or 'CONTROL-C'
90 |
91 | if settings.DEBUG :
92 | import logging
93 | logger = logging.getLogger()
94 | logger.setLevel(logging.DEBUG)
95 |
96 | def inner_run():
97 | from django.conf import settings
98 | from django.utils import translation
99 | print "Validating models..."
100 | self.validate(display_num_errors=True)
101 | print "\nDjango version %s, using settings %r" % (django.get_version(), settings.SETTINGS_MODULE)
102 | print "Server is running at http://%s:%s/" % (addr, port)
103 | print "Quit the server with %s." % quit_command
104 |
105 | # django.core.management.base forces the locate to en-us. We
106 | # should set it up correctly for the first request
107 | # (particularly important in the not "--reload" case).
108 | translation.activate(settings.LANGUAGE_CODE)
109 |
110 | try:
111 | # Instance Django's wsgi handler.
112 | application = web.Application([
113 | (r".*", DjangoHandler),
114 | ])
115 |
116 | # start tornado web server in single-threaded mode
117 | # instead auto pre-fork mode with bind/start.
118 | http_server = httpserver.HTTPServer(application,
119 | xheaders=xheaders,
120 | no_keep_alive=no_keep_alive)
121 | http_server.listen(int(port), address=addr)
122 |
123 | #
124 | #
125 | #
126 | if hasattr(settings, 'TORNADO_STARTUP') :
127 | from django.utils.importlib import import_module
128 | for obj in settings.TORNADO_STARTUP :
129 | # TODO - check to see if string or object
130 | idx = obj.rindex('.')
131 | func = getattr(import_module(obj[:idx]), obj[idx+1:])
132 | func()
133 |
134 | ioloop.IOLoop.instance().start()
135 | except KeyboardInterrupt:
136 | if shutdown_message:
137 | print shutdown_message
138 | sys.exit(0)
139 |
140 | if use_reloader:
141 | # Use tornado reload to handle IOLoop restarting.
142 | from tornado import autoreload
143 | autoreload.start()
144 |
145 | inner_run()
146 |
147 | #
148 | # Modify copy of the base handeler with Tornado changes
149 | #
150 | from threading import Lock
151 | from django.core.handlers import base
152 | from django.core.urlresolvers import set_script_prefix
153 | from django.core import signals
154 |
155 | class DjangoHandler(tornado.web.RequestHandler, base.BaseHandler) :
156 | initLock = Lock()
157 |
158 | def __init__(self, *args, **kwargs) :
159 | super(DjangoHandler, self).__init__(*args, **kwargs)
160 |
161 | # Set up middleware if needed. We couldn't do this earlier, because
162 | # settings weren't available.
163 | self._request_middleware = None
164 | self.initLock.acquire()
165 | # Check that middleware is still uninitialised.
166 | if self._request_middleware is None:
167 | self.load_middleware()
168 | self.initLock.release()
169 | self._auto_finish = False
170 |
171 | def head(self) :
172 | self.get()
173 |
174 | def get(self) :
175 | from tornado.wsgi import HTTPRequest, WSGIContainer
176 | from django.core.handlers.wsgi import WSGIRequest, STATUS_CODE_TEXT
177 | import urllib
178 |
179 | environ = WSGIContainer.environ(self.request)
180 | environ['PATH_INFO'] = urllib.unquote(environ['PATH_INFO'])
181 | request = WSGIRequest(environ)
182 |
183 | request._tornado_handler = self
184 |
185 | set_script_prefix(base.get_script_name(environ))
186 | signals.request_started.send(sender=self.__class__)
187 | try:
188 | response = self.get_response(request)
189 |
190 | if not response :
191 | return
192 |
193 | # Apply response middleware
194 | for middleware_method in self._response_middleware:
195 | response = middleware_method(request, response)
196 | response = self.apply_response_fixes(request, response)
197 | finally:
198 | signals.request_finished.send(sender=self.__class__)
199 |
200 | try:
201 | status_text = STATUS_CODE_TEXT[response.status_code]
202 | except KeyError:
203 | status_text = 'UNKNOWN STATUS CODE'
204 | status = '%s %s' % (response.status_code, status_text)
205 |
206 | self.set_status(response.status_code)
207 | for h in response.items() :
208 | self.set_header(h[0], h[1])
209 |
210 | if not hasattr(self, "_new_cookies"):
211 | self._new_cookies = []
212 | self._new_cookies.append(response.cookies)
213 |
214 | self.write(response.content)
215 | self.finish()
216 |
217 | def post(self) :
218 | self.get()
219 |
220 |
221 | #
222 | #
223 | #
224 | def get_response(self, request):
225 | "Returns an HttpResponse object for the given HttpRequest"
226 | from django import http
227 | from django.core import exceptions, urlresolvers
228 | from django.conf import settings
229 |
230 | try:
231 | try:
232 | # Setup default url resolver for this thread.
233 | urlconf = settings.ROOT_URLCONF
234 | urlresolvers.set_urlconf(urlconf)
235 | resolver = urlresolvers.RegexURLResolver(r'^/', urlconf)
236 |
237 | # Apply request middleware
238 | for middleware_method in self._request_middleware:
239 | response = middleware_method(request)
240 | if response:
241 | return response
242 |
243 | if hasattr(request, "urlconf"):
244 | # Reset url resolver with a custom urlconf.
245 | urlconf = request.urlconf
246 | urlresolvers.set_urlconf(urlconf)
247 | resolver = urlresolvers.RegexURLResolver(r'^/', urlconf)
248 |
249 | callback, callback_args, callback_kwargs = resolver.resolve(
250 | request.path_info)
251 |
252 | # Apply view middleware
253 | for middleware_method in self._view_middleware:
254 | response = middleware_method(request, callback, callback_args, callback_kwargs)
255 | if response:
256 | return response
257 |
258 | from ...decorator import TornadoAsyncException
259 |
260 | try:
261 | response = callback(request, *callback_args, **callback_kwargs)
262 | except TornadoAsyncException, e:
263 | #
264 | # Running under Tornado, so a null return is ok... means that the
265 | # data is not finished
266 | #
267 | return
268 | except Exception, e:
269 | # If the view raised an exception, run it through exception
270 | # middleware, and if the exception middleware returns a
271 | # response, use that. Otherwise, reraise the exception.
272 | for middleware_method in self._exception_middleware:
273 | response = middleware_method(request, e)
274 | if response:
275 | return response
276 | raise
277 |
278 | # Complain if the view returned None (a common error).
279 | if response is None:
280 | try:
281 | view_name = callback.func_name # If it's a function
282 | except AttributeError:
283 | view_name = callback.__class__.__name__ + '.__call__' # If it's a class
284 | raise ValueError("The view %s.%s didn't return an HttpResponse object." % (callback.__module__, view_name))
285 |
286 | return response
287 | except http.Http404, e:
288 | if settings.DEBUG:
289 | from django.views import debug
290 | return debug.technical_404_response(request, e)
291 | else:
292 | try:
293 | callback, param_dict = resolver.resolve404()
294 | return callback(request, **param_dict)
295 | except:
296 | try:
297 | return self.handle_uncaught_exception(request, resolver, sys.exc_info())
298 | finally:
299 | receivers = signals.got_request_exception.send(sender=self.__class__, request=request)
300 | except exceptions.PermissionDenied:
301 | return http.HttpResponseForbidden('
Permission denied
')
302 | except SystemExit:
303 | # Allow sys.exit() to actually exit. See tickets #1023 and #4701
304 | raise
305 | except Exception, e: # Handle everything else, including SuspiciousOperation, etc.
306 | # Get the exception info now, in case another exception is thrown later.
307 | exc_info = sys.exc_info()
308 | receivers = signals.got_request_exception.send(sender=self.__class__, request=request)
309 | return self.handle_uncaught_exception(request, resolver, exc_info)
310 | finally:
311 | # Reset URLconf for this thread on the way out for complete
312 | # isolation of request.urlconf
313 | urlresolvers.set_urlconf(None)
314 |
--------------------------------------------------------------------------------
/myproject/django_tornado/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 |
3 | # Create your models here.
4 |
--------------------------------------------------------------------------------
/myproject/django_tornado/tests.py:
--------------------------------------------------------------------------------
1 | """
2 | This file demonstrates two different styles of tests (one doctest and one
3 | unittest). These will both pass when you run "manage.py test".
4 |
5 | Replace these with more appropriate tests for your application.
6 | """
7 |
8 | from django.test import TestCase
9 |
10 | class SimpleTest(TestCase):
11 | def test_basic_addition(self):
12 | """
13 | Tests that 1 + 1 always equals 2.
14 | """
15 | self.failUnlessEqual(1 + 1, 2)
16 |
17 | __test__ = {"doctest": """
18 | Another way to test that 1 + 1 is equal to 2.
19 |
20 | >>> 1 + 1 == 2
21 | True
22 | """}
23 |
24 |
--------------------------------------------------------------------------------
/myproject/django_tornado/views.py:
--------------------------------------------------------------------------------
1 | # Create your views here.
2 |
--------------------------------------------------------------------------------
/myproject/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | from django.core.management import execute_manager
3 | try:
4 | import settings # Assumed to be in the same directory.
5 | except ImportError:
6 | import sys
7 | sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
8 | sys.exit(1)
9 |
10 | if __name__ == "__main__":
11 | execute_manager(settings)
12 |
--------------------------------------------------------------------------------
/myproject/settings.py:
--------------------------------------------------------------------------------
1 | # Django settings for myproject project.
2 |
3 | DEBUG = True
4 | TEMPLATE_DEBUG = DEBUG
5 |
6 | ADMINS = (
7 | # ('Your Name', 'your_email@domain.com'),
8 | )
9 |
10 | MANAGERS = ADMINS
11 |
12 | DATABASES = {
13 | 'default': {
14 | 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
15 | 'NAME': 'dev.db', # Or path to database file if using sqlite3.
16 | 'USER': '', # Not used with sqlite3.
17 | 'PASSWORD': '', # Not used with sqlite3.
18 | 'HOST': '', # Set to empty string for localhost. Not used with sqlite3.
19 | 'PORT': '', # Set to empty string for default. Not used with sqlite3.
20 | }
21 | }
22 |
23 | # Local time zone for this installation. Choices can be found here:
24 | # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
25 | # although not all choices may be available on all operating systems.
26 | # On Unix systems, a value of None will cause Django to use the same
27 | # timezone as the operating system.
28 | # If running in a Windows environment this must be set to the same as your
29 | # system time zone.
30 | TIME_ZONE = 'America/Chicago'
31 |
32 | # Language code for this installation. All choices can be found here:
33 | # http://www.i18nguy.com/unicode/language-identifiers.html
34 | LANGUAGE_CODE = 'en-us'
35 |
36 | SITE_ID = 1
37 |
38 | # If you set this to False, Django will make some optimizations so as not
39 | # to load the internationalization machinery.
40 | USE_I18N = True
41 |
42 | # Absolute path to the directory that holds media.
43 | # Example: "/home/media/media.lawrence.com/"
44 | MEDIA_ROOT = ''
45 |
46 | # URL that handles the media served from MEDIA_ROOT. Make sure to use a
47 | # trailing slash if there is a path component (optional in other cases).
48 | # Examples: "http://media.lawrence.com", "http://example.com/media/"
49 | MEDIA_URL = ''
50 |
51 | # URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
52 | # trailing slash.
53 | # Examples: "http://foo.com/media/", "/media/".
54 | ADMIN_MEDIA_PREFIX = '/media/'
55 |
56 | # Make this unique, and don't share it with anybody.
57 | SECRET_KEY = 's01412zamfz#o6^3dekfk+unpp7h_3uno!b(c2e_o+qc+nb(#c'
58 |
59 | # List of callables that know how to import templates from various sources.
60 | TEMPLATE_LOADERS = (
61 | 'django.template.loaders.filesystem.Loader',
62 | 'django.template.loaders.app_directories.Loader',
63 | # 'django.template.loaders.eggs.Loader',
64 | )
65 |
66 | MIDDLEWARE_CLASSES = (
67 | 'django.middleware.common.CommonMiddleware',
68 | 'django.contrib.sessions.middleware.SessionMiddleware',
69 | 'django.middleware.csrf.CsrfViewMiddleware',
70 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
71 | 'django.contrib.messages.middleware.MessageMiddleware',
72 | )
73 |
74 | ROOT_URLCONF = 'myproject.urls'
75 |
76 | TEMPLATE_DIRS = (
77 | # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
78 | # Always use forward slashes, even on Windows.
79 | # Don't forget to use absolute paths, not relative paths.
80 | )
81 |
82 | INSTALLED_APPS = (
83 | 'django.contrib.auth',
84 | 'django.contrib.contenttypes',
85 | 'django.contrib.sessions',
86 | 'django.contrib.sites',
87 | 'django.contrib.messages',
88 | # Uncomment the next line to enable the admin:
89 | 'django.contrib.admin',
90 | 'django_tornado',
91 | 'chat',
92 | )
93 |
--------------------------------------------------------------------------------
/myproject/urls.py:
--------------------------------------------------------------------------------
1 | from django.conf.urls.defaults import *
2 |
3 | # Uncomment the next two lines to enable the admin:
4 | #from django.contrib import admin
5 | #admin.autodiscover()
6 |
7 | urlpatterns = patterns('',
8 | # Example:
9 | # (r'^myproject/', include('myproject.foo.urls')),
10 |
11 | # Uncomment the admin/doc line below and add 'django.contrib.admindocs'
12 | # to INSTALLED_APPS to enable admin documentation:
13 | # (r'^admin/doc/', include('django.contrib.admindocs.urls')),
14 |
15 | # Uncomment the next line to enable the admin:
16 | #(r'^admin/', include(admin.site.urls)),
17 | (r'^', include('chat.urls')),
18 | )
19 |
--------------------------------------------------------------------------------