Skip to content

Commit fd17760

Browse files
author
James Rutherford
committed
Merge remote-tracking branch 'upstream/master'
2 parents 3225b44 + a008428 commit fd17760

File tree

5 files changed

+136
-127
lines changed

5 files changed

+136
-127
lines changed

README.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ A drop in replacement for Django's built-in runserver command. Features include:
99
* Threaded (default) and multi-process development servers.
1010
* Ability to specify a WSGI application as your target environment.
1111

12+
.. note:: django-devserver works on Django 1.3 and newer
13+
1214
------------
1315
Installation
1416
------------

devserver/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"""
1111

1212
__all__ = ('__version__', '__build__', '__docformat__', 'get_revision')
13-
__version__ = (0, 4, 0)
13+
__version__ = (0, 6, 2)
1414
__docformat__ = 'restructuredtext en'
1515

1616
import os
Lines changed: 123 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
from django.conf import settings
2-
from django.core.management.base import BaseCommand, CommandError, handle_default_options
2+
from django.core.management.commands.runserver import Command as BaseCommand
3+
from django.core.management.base import CommandError, handle_default_options
34
from django.core.servers.basehttp import WSGIServerException, WSGIServer
45
from django.core.handlers.wsgi import WSGIHandler
56

67
import os
78
import sys
8-
import django
99
import imp
1010
import SocketServer
1111
from optparse import make_option
@@ -14,11 +14,14 @@
1414
from devserver.utils.http import SlimWSGIRequestHandler
1515

1616

17+
STATICFILES_APPS = ('django.contrib.staticfiles', 'staticfiles')
18+
19+
1720
def null_technical_500_response(request, exc_type, exc_value, tb):
1821
raise exc_type, exc_value, tb
1922

2023

21-
def run(addr, port, wsgi_handler, mixin=None):
24+
def run(addr, port, wsgi_handler, mixin=None, ipv6=False):
2225
if mixin:
2326
class new(mixin, WSGIServer):
2427
def __init__(self, *args, **kwargs):
@@ -27,29 +30,30 @@ def __init__(self, *args, **kwargs):
2730
new = WSGIServer
2831
server_address = (addr, port)
2932
new.request_queue_size = 10
30-
httpd = new(server_address, SlimWSGIRequestHandler)
33+
httpd = new(server_address, SlimWSGIRequestHandler, ipv6=ipv6)
3134
httpd.set_app(wsgi_handler)
3235
httpd.serve_forever()
3336

3437

3538
class Command(BaseCommand):
3639
option_list = BaseCommand.option_list + (
37-
make_option('--noreload', action='store_false', dest='use_reloader', default=True,
38-
help='Tells Django to NOT use the auto-reloader.'),
39-
make_option('--werkzeug', action='store_true', dest='use_werkzeug', default=False,
40+
make_option(
41+
'--werkzeug', action='store_true', dest='use_werkzeug', default=False,
4042
help='Tells Django to use the Werkzeug interactive debugger.'),
41-
make_option('--adminmedia', dest='admin_media_path', default='',
42-
help='Specifies the directory from which to serve admin media.'),
43-
make_option('--forked', action='store_true', dest='use_forked', default=False,
43+
make_option(
44+
'--forked', action='store_true', dest='use_forked', default=False,
4445
help='Use forking instead of threading for multiple web requests.'),
45-
make_option('--dozer', action='store_true', dest='use_dozer', default=False,
46+
make_option(
47+
'--dozer', action='store_true', dest='use_dozer', default=False,
4648
help='Enable the Dozer memory debugging middleware.'),
47-
make_option('--wsgi-app', dest='wsgi_app', default=None,
49+
make_option(
50+
'--wsgi-app', dest='wsgi_app', default=None,
4851
help='Load the specified WSGI app as the server endpoint.'),
4952
)
50-
if any(map(lambda app: app in settings.INSTALLED_APPS, ('django.contrib.staticfiles', 'staticfiles', ))):
51-
option_list += make_option('--nostatic', dest='use_static_files', default=True,
52-
help='Tells Django to NOT automatically serve static files at STATIC_URL.'),
53+
if any(map(lambda app: app in settings.INSTALLED_APPS, STATICFILES_APPS)):
54+
option_list += make_option(
55+
'--nostatic', dest='use_static_files', action='store_false', default=True,
56+
help='Tells Django to NOT automatically serve static files at STATIC_URL.'),
5357

5458
help = "Starts a lightweight Web server for development which outputs additional debug information."
5559
args = '[optional port number, or ipaddr:port]'
@@ -75,135 +79,132 @@ def handle(self, addrport='', *args, **options):
7579
raise CommandError('Usage is runserver %s' % self.args)
7680

7781
if not addrport:
78-
addr = getattr(settings, 'DEVSERVER_DEFAULT_ADDR', '')
82+
addr = getattr(settings, 'DEVSERVER_DEFAULT_ADDR', '127.0.0.1')
7983
port = getattr(settings, 'DEVSERVER_DEFAULT_PORT', '8000')
84+
addrport = '%s:%s' % (addr, port)
85+
86+
return super(Command, self).handle(addrport=addrport, *args, **options)
87+
88+
def get_handler(self, *args, **options):
89+
if int(options['verbosity']) < 1:
90+
handler = WSGIHandler()
8091
else:
81-
try:
82-
addr, port = addrport.split(':')
83-
except ValueError:
84-
addr, port = '', addrport
85-
if not addr:
86-
addr = '127.0.0.1'
92+
handler = DevServerHandler()
93+
94+
# AdminMediaHandler is removed in Django 1.5
95+
# Add it only when it avialable.
96+
try:
97+
from django.core.servers.basehttp import AdminMediaHandler
98+
except ImportError:
99+
pass
100+
else:
101+
handler = AdminMediaHandler(
102+
handler, options['admin_media_path'])
87103

88-
if not port.isdigit():
89-
raise CommandError("%r is not a valid port number." % port)
104+
if 'django.contrib.staticfiles' in settings.INSTALLED_APPS and options['use_static_files']:
105+
from django.contrib.staticfiles.handlers import StaticFilesHandler
106+
handler = StaticFilesHandler(handler)
107+
108+
return handler
109+
110+
def inner_run(self, *args, **options):
111+
# Flag the server as active
112+
from devserver import settings
113+
import devserver
114+
settings.DEVSERVER_ACTIVE = True
115+
settings.DEBUG = True
116+
117+
from django.conf import settings
118+
from django.utils import translation
90119

91-
use_reloader = options.get('use_reloader', True)
92-
admin_media_path = options.get('admin_media_path', '')
93120
shutdown_message = options.get('shutdown_message', '')
94121
use_werkzeug = options.get('use_werkzeug', False)
95122
quit_command = (sys.platform == 'win32') and 'CTRL-BREAK' or 'CONTROL-C'
96123
wsgi_app = options.get('wsgi_app', None)
97-
use_static_files = options.get('use_static_files', True)
98124

99125
if use_werkzeug:
100126
try:
101127
from werkzeug import run_simple, DebuggedApplication
102128
except ImportError, e:
103-
print >> sys.stderr, "WARNING: Unable to initialize werkzeug: %s" % e
129+
self.stderr.write("WARNING: Unable to initialize werkzeug: %s\n" % e)
104130
use_werkzeug = False
105131
else:
106-
use_werkzeug = True
107132
from django.views import debug
108133
debug.technical_500_response = null_technical_500_response
109134

110-
def inner_run():
111-
# Flag the server as active
112-
from devserver import settings
113-
import devserver
114-
settings.DEVSERVER_ACTIVE = True
115-
settings.DEBUG = True
116-
117-
from django.conf import settings
118-
from django.utils import translation
119-
120-
print "Validating models..."
121-
self.validate(display_num_errors=True)
122-
print "\nDjango version %s, using settings %r" % (django.get_version(), settings.SETTINGS_MODULE)
123-
print "Running django-devserver %s" % (devserver.get_version(),)
124-
if use_werkzeug:
125-
server_type = 'werkzeug'
135+
self.stdout.write("Validating models...\n\n")
136+
self.validate(display_num_errors=True)
137+
self.stdout.write((
138+
"Django version %(version)s, using settings %(settings)r\n"
139+
"Running django-devserver %(devserver_version)s\n"
140+
"%(server_model)s %(server_type)s server is running at http://%(addr)s:%(port)s/\n"
141+
"Quit the server with %(quit_command)s.\n"
142+
) % {
143+
"server_type": use_werkzeug and 'werkzeug' or 'Django',
144+
"server_model": options['use_forked'] and 'Forked' or 'Threaded',
145+
"version": self.get_version(),
146+
"devserver_version": devserver.get_version(),
147+
"settings": settings.SETTINGS_MODULE,
148+
"addr": self._raw_ipv6 and '[%s]' % self.addr or self.addr,
149+
"port": self.port,
150+
"quit_command": quit_command,
151+
})
152+
153+
# django.core.management.base forces the locale to en-us. We should
154+
# set it up correctly for the first request (particularly important
155+
# in the "--noreload" case).
156+
translation.activate(settings.LANGUAGE_CODE)
157+
158+
app = self.get_handler(*args, **options)
159+
if wsgi_app:
160+
self.stdout.write("Using WSGI application %r\n" % wsgi_app)
161+
if os.path.exists(os.path.abspath(wsgi_app)):
162+
# load from file
163+
app = imp.load_source('wsgi_app', os.path.abspath(wsgi_app)).application
126164
else:
127-
server_type = 'django'
128-
print "%s %s server is running at http://%s:%s/" % (options['use_forked'] and 'Forked' or 'Threaded', server_type, addr, port)
129-
print "Quit the server with %s." % quit_command
165+
try:
166+
app = __import__(wsgi_app, {}, {}, ['application']).application
167+
except (ImportError, AttributeError):
168+
raise
130169

131-
# django.core.management.base forces the locale to en-us. We should
132-
# set it up correctly for the first request (particularly important
133-
# in the "--noreload" case).
134-
translation.activate(settings.LANGUAGE_CODE)
170+
if options['use_forked']:
171+
mixin = SocketServer.ForkingMixIn
172+
else:
173+
mixin = SocketServer.ThreadingMixIn
135174

136-
if int(options['verbosity']) < 1:
137-
app = WSGIHandler()
138-
else:
139-
app = DevServerHandler()
140-
141-
if wsgi_app:
142-
print "Using WSGI application %r" % wsgi_app
143-
if os.path.exists(os.path.abspath(wsgi_app)):
144-
# load from file
145-
app = imp.load_source('wsgi_app', os.path.abspath(wsgi_app)).application
146-
else:
147-
try:
148-
app = __import__(wsgi_app, {}, {}, ['application']).application
149-
except (ImportError, AttributeError):
150-
raise
151-
152-
if options['use_forked']:
153-
mixin = SocketServer.ForkingMixIn
154-
else:
155-
mixin = SocketServer.ThreadingMixIn
175+
middleware = getattr(settings, 'DEVSERVER_WSGI_MIDDLEWARE', [])
176+
for middleware in middleware:
177+
module, class_name = middleware.rsplit('.', 1)
178+
app = getattr(__import__(module, {}, {}, [class_name]), class_name)(app)
156179

157-
middleware = getattr(settings, 'DEVSERVER_WSGI_MIDDLEWARE', [])
158-
for middleware in middleware:
159-
module, class_name = middleware.rsplit('.', 1)
160-
app = getattr(__import__(module, {}, {}, [class_name]), class_name)(app)
180+
if options['use_dozer']:
181+
from dozer import Dozer
182+
app = Dozer(app)
161183

162-
if 'django.contrib.staticfiles' in settings.INSTALLED_APPS and use_static_files:
163-
from django.contrib.staticfiles.handlers import StaticFilesHandler
164-
app = StaticFilesHandler(app)
184+
try:
185+
if use_werkzeug:
186+
run_simple(
187+
self.addr, int(self.port), DebuggedApplication(app, True),
188+
use_reloader=False, use_debugger=True)
165189
else:
166-
# AdminMediaHandler is removed in Django 1.5
167-
# Add it only when it avialable.
168-
try:
169-
from django.core.servers.basehttp import AdminMediaHandler
170-
except ImportError:
171-
pass
172-
else:
173-
app = AdminMediaHandler(app, admin_media_path)
174-
175-
if options['use_dozer']:
176-
from dozer import Dozer
177-
app = Dozer(app)
178-
190+
run(self.addr, int(self.port), app, mixin, ipv6=options['use_ipv6'])
191+
192+
except WSGIServerException, e:
193+
# Use helpful error messages instead of ugly tracebacks.
194+
ERRORS = {
195+
13: "You don't have permission to access that port.",
196+
98: "That port is already in use.",
197+
99: "That IP address can't be assigned-to.",
198+
}
179199
try:
180-
if use_werkzeug:
181-
run_simple(addr, int(port), DebuggedApplication(app, True),
182-
use_reloader=False, use_debugger=True)
183-
else:
184-
run(addr, int(port), app, mixin)
185-
except WSGIServerException, e:
186-
# Use helpful error messages instead of ugly tracebacks.
187-
ERRORS = {
188-
13: "You don't have permission to access that port.",
189-
98: "That port is already in use.",
190-
99: "That IP address can't be assigned-to.",
191-
}
192-
try:
193-
error_text = ERRORS[e.args[0].args[0]]
194-
except (AttributeError, KeyError):
195-
error_text = str(e)
196-
sys.stderr.write(self.style.ERROR("Error: %s" % error_text) + '\n')
197-
# Need to use an OS exit because sys.exit doesn't work in a thread
198-
os._exit(1)
199-
except KeyboardInterrupt:
200-
if shutdown_message:
201-
print shutdown_message
202-
sys.exit(0)
203-
204-
# werkzeug does its own autoreload stuff
205-
if use_reloader:
206-
from django.utils import autoreload
207-
autoreload.main(inner_run)
208-
else:
209-
inner_run()
200+
error_text = ERRORS[e.args[0].args[0]]
201+
except (AttributeError, KeyError):
202+
error_text = str(e)
203+
sys.stderr.write(self.style.ERROR("Error: %s" % error_text) + '\n')
204+
# Need to use an OS exit because sys.exit doesn't work in a thread
205+
os._exit(1)
206+
207+
except KeyboardInterrupt:
208+
if shutdown_message:
209+
self.stdout.write("%s\n" % shutdown_message)
210+
sys.exit(0)

devserver/modules/profile.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -125,15 +125,20 @@ def __init__(self, follow=[]):
125125
self.follow = follow
126126

127127
def __call__(self, func):
128-
def profiled_func(request, *args, **kwargs):
128+
def profiled_func(*args, **kwargs):
129+
request = args[0]
130+
if hasattr(request, 'request'):
131+
# We're decorating a Django class-based-view and the first argument is actually self:
132+
request = args[1]
133+
129134
try:
130135
request.devserver_profiler.add_function(func)
131136
request.devserver_profiler_run = True
132137
for f in self.follow:
133138
request.devserver_profiler.add_function(f)
134139
request.devserver_profiler.enable_by_count()
135-
retval = func(request, *args, **kwargs)
140+
return func(*args, **kwargs)
136141
finally:
137142
request.devserver_profiler.disable_by_count()
138-
return retval
143+
139144
return functools.wraps(func)(profiled_func)

devserver/modules/sql.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
connections = {'default': connection}
1414

1515
from django.db.backends import util
16+
from django.conf import settings as django_settings
1617
#from django.template import Node
1718

1819
from devserver.modules import DevServerModule
@@ -83,7 +84,7 @@ def execute(self, sql, params=()):
8384
if self.cursor.rowcount >= 0:
8485
self.logger.debug('Found %s matching rows', self.cursor.rowcount, duration=duration)
8586

86-
if not (debug_toolbar or settings.DEBUG):
87+
if not (debug_toolbar or django_settings.DEBUG):
8788
self.db.queries.append({
8889
'sql': formatted_sql,
8990
'time': duration,

0 commit comments

Comments
 (0)