Compare commits
13 Commits
main
...
stable/0.9
Author | SHA1 | Date |
---|---|---|
James Bennett | c939b2a1cb | |
James Bennett | c4c00358ee | |
Russell Keith-Magee | c60c89a76e | |
Russell Keith-Magee | 9cc89c97b9 | |
James Bennett | 40598612d1 | |
Russell Keith-Magee | da85d76fd6 | |
James Bennett | 1232a46765 | |
Jacob Kaplan-Moss | 7e0972bded | |
James Bennett | 3ca5a055a7 | |
James Bennett | 7791e5c050 | |
Jacob Kaplan-Moss | 7dd2dd08a7 | |
Jacob Kaplan-Moss | 6c1c7c9e4f | |
Jacob Kaplan-Moss | c48165765e |
|
@ -1 +1 @@
|
||||||
VERSION = (0, 96, None)
|
VERSION = (0, 96.5, None)
|
||||||
|
|
|
@ -237,7 +237,7 @@ TRANSACTIONS_MANAGED = False
|
||||||
|
|
||||||
# The User-Agent string to use when checking for URL validity through the
|
# The User-Agent string to use when checking for URL validity through the
|
||||||
# isExistingURL validator.
|
# isExistingURL validator.
|
||||||
URL_VALIDATOR_USER_AGENT = "Django/0.96pre (http://www.djangoproject.com)"
|
URL_VALIDATOR_USER_AGENT = "Django/0.96.5 (http://www.djangoproject.com)"
|
||||||
|
|
||||||
##############
|
##############
|
||||||
# MIDDLEWARE #
|
# MIDDLEWARE #
|
||||||
|
|
|
@ -19,7 +19,6 @@
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<label for="id_password">{% trans 'Password:' %}</label> <input type="password" name="password" id="id_password" />
|
<label for="id_password">{% trans 'Password:' %}</label> <input type="password" name="password" id="id_password" />
|
||||||
<input type="hidden" name="this_is_the_login_form" value="1" />
|
<input type="hidden" name="this_is_the_login_form" value="1" />
|
||||||
<input type="hidden" name="post_data" value="{{ post_data }}" /> {#<span class="help">{% trans 'Have you <a href="/password_reset/">forgotten your password</a>?' %}</span>#}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="submit-row">
|
<div class="submit-row">
|
||||||
<label> </label><input type="submit" value="{% trans 'Log in' %}" />
|
<label> </label><input type="submit" value="{% trans 'Log in' %}" />
|
||||||
|
|
|
@ -3,43 +3,21 @@ from django.conf import settings
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.contrib.auth import authenticate, login
|
from django.contrib.auth import authenticate, login
|
||||||
from django.shortcuts import render_to_response
|
from django.shortcuts import render_to_response
|
||||||
|
from django.utils.html import escape
|
||||||
from django.utils.translation import gettext_lazy
|
from django.utils.translation import gettext_lazy
|
||||||
import base64, datetime, md5
|
import base64, datetime
|
||||||
import cPickle as pickle
|
|
||||||
|
|
||||||
ERROR_MESSAGE = gettext_lazy("Please enter a correct username and password. Note that both fields are case-sensitive.")
|
ERROR_MESSAGE = gettext_lazy("Please enter a correct username and password. Note that both fields are case-sensitive.")
|
||||||
LOGIN_FORM_KEY = 'this_is_the_login_form'
|
LOGIN_FORM_KEY = 'this_is_the_login_form'
|
||||||
|
|
||||||
def _display_login_form(request, error_message=''):
|
def _display_login_form(request, error_message=''):
|
||||||
request.session.set_test_cookie()
|
request.session.set_test_cookie()
|
||||||
if request.POST and request.POST.has_key('post_data'):
|
|
||||||
# User has failed login BUT has previously saved post data.
|
|
||||||
post_data = request.POST['post_data']
|
|
||||||
elif request.POST:
|
|
||||||
# User's session must have expired; save their post data.
|
|
||||||
post_data = _encode_post_data(request.POST)
|
|
||||||
else:
|
|
||||||
post_data = _encode_post_data({})
|
|
||||||
return render_to_response('admin/login.html', {
|
return render_to_response('admin/login.html', {
|
||||||
'title': _('Log in'),
|
'title': _('Log in'),
|
||||||
'app_path': request.path,
|
'app_path': escape(request.path),
|
||||||
'post_data': post_data,
|
|
||||||
'error_message': error_message
|
'error_message': error_message
|
||||||
}, context_instance=template.RequestContext(request))
|
}, context_instance=template.RequestContext(request))
|
||||||
|
|
||||||
def _encode_post_data(post_data):
|
|
||||||
pickled = pickle.dumps(post_data)
|
|
||||||
pickled_md5 = md5.new(pickled + settings.SECRET_KEY).hexdigest()
|
|
||||||
return base64.encodestring(pickled + pickled_md5)
|
|
||||||
|
|
||||||
def _decode_post_data(encoded_data):
|
|
||||||
encoded_data = base64.decodestring(encoded_data)
|
|
||||||
pickled, tamper_check = encoded_data[:-32], encoded_data[-32:]
|
|
||||||
if md5.new(pickled + settings.SECRET_KEY).hexdigest() != tamper_check:
|
|
||||||
from django.core.exceptions import SuspiciousOperation
|
|
||||||
raise SuspiciousOperation, "User may have tampered with session cookie."
|
|
||||||
return pickle.loads(pickled)
|
|
||||||
|
|
||||||
def staff_member_required(view_func):
|
def staff_member_required(view_func):
|
||||||
"""
|
"""
|
||||||
Decorator for views that checks that the user is logged in and is a staff
|
Decorator for views that checks that the user is logged in and is a staff
|
||||||
|
@ -48,10 +26,6 @@ def staff_member_required(view_func):
|
||||||
def _checklogin(request, *args, **kwargs):
|
def _checklogin(request, *args, **kwargs):
|
||||||
if request.user.is_authenticated() and request.user.is_staff:
|
if request.user.is_authenticated() and request.user.is_staff:
|
||||||
# The user is valid. Continue to the admin page.
|
# The user is valid. Continue to the admin page.
|
||||||
if request.POST.has_key('post_data'):
|
|
||||||
# User must have re-authenticated through a different window
|
|
||||||
# or tab.
|
|
||||||
request.POST = _decode_post_data(request.POST['post_data'])
|
|
||||||
return view_func(request, *args, **kwargs)
|
return view_func(request, *args, **kwargs)
|
||||||
|
|
||||||
assert hasattr(request, 'session'), "The Django admin requires session middleware to be installed. Edit your MIDDLEWARE_CLASSES setting to insert 'django.contrib.sessions.middleware.SessionMiddleware'."
|
assert hasattr(request, 'session'), "The Django admin requires session middleware to be installed. Edit your MIDDLEWARE_CLASSES setting to insert 'django.contrib.sessions.middleware.SessionMiddleware'."
|
||||||
|
@ -59,7 +33,7 @@ def staff_member_required(view_func):
|
||||||
# If this isn't already the login page, display it.
|
# If this isn't already the login page, display it.
|
||||||
if not request.POST.has_key(LOGIN_FORM_KEY):
|
if not request.POST.has_key(LOGIN_FORM_KEY):
|
||||||
if request.POST:
|
if request.POST:
|
||||||
message = _("Please log in again, because your session has expired. Don't worry: Your submission has been saved.")
|
message = _("Please log in again, because your session has expired.")
|
||||||
else:
|
else:
|
||||||
message = ""
|
message = ""
|
||||||
return _display_login_form(request, message)
|
return _display_login_form(request, message)
|
||||||
|
@ -92,16 +66,7 @@ def staff_member_required(view_func):
|
||||||
# TODO: set last_login with an event.
|
# TODO: set last_login with an event.
|
||||||
user.last_login = datetime.datetime.now()
|
user.last_login = datetime.datetime.now()
|
||||||
user.save()
|
user.save()
|
||||||
if request.POST.has_key('post_data'):
|
return http.HttpResponseRedirect(request.path)
|
||||||
post_data = _decode_post_data(request.POST['post_data'])
|
|
||||||
if post_data and not post_data.has_key(LOGIN_FORM_KEY):
|
|
||||||
# overwrite request.POST with the saved post_data, and continue
|
|
||||||
request.POST = post_data
|
|
||||||
request.user = user
|
|
||||||
return view_func(request, *args, **kwargs)
|
|
||||||
else:
|
|
||||||
request.session.delete_test_cookie()
|
|
||||||
return http.HttpResponseRedirect(request.path)
|
|
||||||
else:
|
else:
|
||||||
return _display_login_form(request, ERROR_MESSAGE)
|
return _display_login_form(request, ERROR_MESSAGE)
|
||||||
|
|
||||||
|
|
|
@ -1192,9 +1192,7 @@ def runserver(addr, port, use_reloader=True, admin_media_dir=''):
|
||||||
print "Development server is running at http://%s:%s/" % (addr, port)
|
print "Development server is running at http://%s:%s/" % (addr, port)
|
||||||
print "Quit the server with %s." % quit_command
|
print "Quit the server with %s." % quit_command
|
||||||
try:
|
try:
|
||||||
import django
|
handler = AdminMediaHandler(WSGIHandler(), admin_media_dir)
|
||||||
path = admin_media_dir or django.__path__[0] + '/contrib/admin/media'
|
|
||||||
handler = AdminMediaHandler(WSGIHandler(), path)
|
|
||||||
run(addr, int(port), handler)
|
run(addr, int(port), handler)
|
||||||
except WSGIServerException, e:
|
except WSGIServerException, e:
|
||||||
# Use helpful error messages instead of ugly tracebacks.
|
# Use helpful error messages instead of ugly tracebacks.
|
||||||
|
|
|
@ -11,6 +11,8 @@ from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
|
||||||
from types import ListType, StringType
|
from types import ListType, StringType
|
||||||
import os, re, sys, time, urllib
|
import os, re, sys, time, urllib
|
||||||
|
|
||||||
|
from django.utils._os import safe_join
|
||||||
|
|
||||||
__version__ = "0.1"
|
__version__ = "0.1"
|
||||||
__all__ = ['WSGIServer','WSGIRequestHandler','demo_app']
|
__all__ = ['WSGIServer','WSGIRequestHandler','demo_app']
|
||||||
|
|
||||||
|
@ -599,11 +601,25 @@ class AdminMediaHandler(object):
|
||||||
self.application = application
|
self.application = application
|
||||||
if not media_dir:
|
if not media_dir:
|
||||||
import django
|
import django
|
||||||
self.media_dir = django.__path__[0] + '/contrib/admin/media'
|
self.media_dir = \
|
||||||
|
os.path.join(django.__path__[0], 'contrib', 'admin', 'media')
|
||||||
else:
|
else:
|
||||||
self.media_dir = media_dir
|
self.media_dir = media_dir
|
||||||
self.media_url = settings.ADMIN_MEDIA_PREFIX
|
self.media_url = settings.ADMIN_MEDIA_PREFIX
|
||||||
|
|
||||||
|
def file_path(self, url):
|
||||||
|
"""
|
||||||
|
Returns the path to the media file on disk for the given URL.
|
||||||
|
|
||||||
|
The passed URL is assumed to begin with ADMIN_MEDIA_PREFIX. If the
|
||||||
|
resultant file path is outside the media directory, then a ValueError
|
||||||
|
is raised.
|
||||||
|
"""
|
||||||
|
# Remove ADMIN_MEDIA_PREFIX.
|
||||||
|
relative_url = url[len(self.media_url):]
|
||||||
|
relative_path = urllib.url2pathname(relative_url)
|
||||||
|
return safe_join(self.media_dir, relative_path)
|
||||||
|
|
||||||
def __call__(self, environ, start_response):
|
def __call__(self, environ, start_response):
|
||||||
import os.path
|
import os.path
|
||||||
|
|
||||||
|
@ -614,19 +630,25 @@ class AdminMediaHandler(object):
|
||||||
return self.application(environ, start_response)
|
return self.application(environ, start_response)
|
||||||
|
|
||||||
# Find the admin file and serve it up, if it exists and is readable.
|
# Find the admin file and serve it up, if it exists and is readable.
|
||||||
relative_url = environ['PATH_INFO'][len(self.media_url):]
|
try:
|
||||||
file_path = os.path.join(self.media_dir, relative_url)
|
file_path = self.file_path(environ['PATH_INFO'])
|
||||||
|
except ValueError: # Resulting file path was not valid.
|
||||||
|
status = '404 NOT FOUND'
|
||||||
|
headers = {'Content-type': 'text/plain'}
|
||||||
|
output = ['Page not found: %s' % environ['PATH_INFO']]
|
||||||
|
start_response(status, headers.items())
|
||||||
|
return output
|
||||||
if not os.path.exists(file_path):
|
if not os.path.exists(file_path):
|
||||||
status = '404 NOT FOUND'
|
status = '404 NOT FOUND'
|
||||||
headers = {'Content-type': 'text/plain'}
|
headers = {'Content-type': 'text/plain'}
|
||||||
output = ['Page not found: %s' % file_path]
|
output = ['Page not found: %s' % environ['PATH_INFO']]
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
fp = open(file_path, 'rb')
|
fp = open(file_path, 'rb')
|
||||||
except IOError:
|
except IOError:
|
||||||
status = '401 UNAUTHORIZED'
|
status = '401 UNAUTHORIZED'
|
||||||
headers = {'Content-type': 'text/plain'}
|
headers = {'Content-type': 'text/plain'}
|
||||||
output = ['Permission denied: %s' % file_path]
|
output = ['Permission denied: %s' % environ['PATH_INFO']]
|
||||||
else:
|
else:
|
||||||
status = '200 OK'
|
status = '200 OK'
|
||||||
headers = {}
|
headers = {}
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
"""
|
||||||
|
A back-ported version of the same module from the 1.0.x branch, without the
|
||||||
|
unicode support.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from os.path import join, normcase, abspath, sep
|
||||||
|
|
||||||
|
def safe_join(base, *paths):
|
||||||
|
"""
|
||||||
|
Joins one or more path components to the base path component intelligently.
|
||||||
|
Returns a normalized, absolute version of the final path.
|
||||||
|
|
||||||
|
The final path must be located inside of the base path component (otherwise
|
||||||
|
a ValueError is raised).
|
||||||
|
"""
|
||||||
|
# We need to use normcase to ensure we don't false-negative on case
|
||||||
|
# insensitive operating systems (like Windows).
|
||||||
|
final_path = normcase(abspath(join(base, *paths)))
|
||||||
|
base_path = normcase(abspath(base))
|
||||||
|
base_path_len = len(base_path)
|
||||||
|
# Ensure final_path starts with base_path and that the next character after
|
||||||
|
# the final path is os.sep (or nothing, in which case final_path must be
|
||||||
|
# equal to base_path).
|
||||||
|
if not final_path.startswith(base_path) \
|
||||||
|
or final_path[base_path_len:base_path_len+1] not in ('', sep):
|
||||||
|
raise ValueError('the joined path is located outside of the base path'
|
||||||
|
' component')
|
||||||
|
return final_path
|
|
@ -1,6 +1,9 @@
|
||||||
"Translation helper functions"
|
"Translation helper functions"
|
||||||
|
|
||||||
import os, re, sys
|
import locale
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
import gettext as gettext_module
|
import gettext as gettext_module
|
||||||
from cStringIO import StringIO
|
from cStringIO import StringIO
|
||||||
from django.utils.functional import lazy
|
from django.utils.functional import lazy
|
||||||
|
@ -25,15 +28,25 @@ _active = {}
|
||||||
# The default translation is based on the settings file.
|
# The default translation is based on the settings file.
|
||||||
_default = None
|
_default = None
|
||||||
|
|
||||||
# This is a cache for accept-header to translation object mappings to prevent
|
# This is a cache for normalised accept-header languages to prevent multiple
|
||||||
# the accept parser to run multiple times for one user.
|
# file lookups when checking the same locale on repeated requests.
|
||||||
_accepted = {}
|
_accepted = {}
|
||||||
|
|
||||||
def to_locale(language):
|
# Format of Accept-Language header values. From RFC 2616, section 14.4 and 3.9.
|
||||||
|
accept_language_re = re.compile(r'''
|
||||||
|
([A-Za-z]{1,8}(?:-[A-Za-z]{1,8})*|\*) # "en", "en-au", "x-y-z", "*"
|
||||||
|
(?:;q=(0(?:\.\d{,3})?|1(?:.0{,3})?))? # Optional "q=1.00", "q=0.8"
|
||||||
|
(?:\s*,\s*|$) # Multiple accepts per header.
|
||||||
|
''', re.VERBOSE)
|
||||||
|
|
||||||
|
def to_locale(language, to_lower=False):
|
||||||
"Turns a language name (en-us) into a locale name (en_US)."
|
"Turns a language name (en-us) into a locale name (en_US)."
|
||||||
p = language.find('-')
|
p = language.find('-')
|
||||||
if p >= 0:
|
if p >= 0:
|
||||||
return language[:p].lower()+'_'+language[p+1:].upper()
|
if to_lower:
|
||||||
|
return language[:p].lower()+'_'+language[p+1:].lower()
|
||||||
|
else:
|
||||||
|
return language[:p].lower()+'_'+language[p+1:].upper()
|
||||||
else:
|
else:
|
||||||
return language.lower()
|
return language.lower()
|
||||||
|
|
||||||
|
@ -309,46 +322,40 @@ def get_language_from_request(request):
|
||||||
if lang_code in supported and lang_code is not None and check_for_language(lang_code):
|
if lang_code in supported and lang_code is not None and check_for_language(lang_code):
|
||||||
return lang_code
|
return lang_code
|
||||||
|
|
||||||
lang_code = request.COOKIES.get('django_language', None)
|
lang_code = request.COOKIES.get('django_language')
|
||||||
if lang_code in supported and lang_code is not None and check_for_language(lang_code):
|
if lang_code and lang_code in supported and check_for_language(lang_code):
|
||||||
return lang_code
|
return lang_code
|
||||||
|
|
||||||
accept = request.META.get('HTTP_ACCEPT_LANGUAGE', None)
|
accept = request.META.get('HTTP_ACCEPT_LANGUAGE', '')
|
||||||
if accept is not None:
|
for lang, unused in parse_accept_lang_header(accept):
|
||||||
|
if lang == '*':
|
||||||
|
break
|
||||||
|
|
||||||
t = _accepted.get(accept, None)
|
# We have a very restricted form for our language files (no encoding
|
||||||
if t is not None:
|
# specifier, since they all must be UTF-8 and only one possible
|
||||||
return t
|
# language each time. So we avoid the overhead of gettext.find() and
|
||||||
|
# look up the MO file manually.
|
||||||
|
|
||||||
def _parsed(el):
|
normalized = locale.locale_alias.get(to_locale(lang, True))
|
||||||
p = el.find(';q=')
|
if not normalized:
|
||||||
if p >= 0:
|
continue
|
||||||
lang = el[:p].strip()
|
|
||||||
order = int(float(el[p+3:].strip())*100)
|
|
||||||
else:
|
|
||||||
lang = el
|
|
||||||
order = 100
|
|
||||||
p = lang.find('-')
|
|
||||||
if p >= 0:
|
|
||||||
mainlang = lang[:p]
|
|
||||||
else:
|
|
||||||
mainlang = lang
|
|
||||||
return (lang, mainlang, order)
|
|
||||||
|
|
||||||
langs = [_parsed(el) for el in accept.split(',')]
|
# Remove the default encoding from locale_alias
|
||||||
langs.sort(lambda a,b: -1*cmp(a[2], b[2]))
|
normalized = normalized.split('.')[0]
|
||||||
|
|
||||||
for lang, mainlang, order in langs:
|
if normalized in _accepted:
|
||||||
if lang in supported or mainlang in supported:
|
# We've seen this locale before and have an MO file for it, so no
|
||||||
langfile = gettext_module.find('django', globalpath, [to_locale(lang)])
|
# need to check again.
|
||||||
if langfile:
|
return _accepted[normalized]
|
||||||
# reconstruct the actual language from the language
|
|
||||||
# filename, because otherwise we might incorrectly
|
for lang in (normalized, normalized.split('_')[0]):
|
||||||
# report de_DE if we only have de available, but
|
if lang not in supported:
|
||||||
# did find de_DE because of language normalization
|
continue
|
||||||
lang = langfile[len(globalpath):].split(os.path.sep)[1]
|
langfile = os.path.join(globalpath, lang, 'LC_MESSAGES',
|
||||||
_accepted[accept] = lang
|
'django.mo')
|
||||||
return lang
|
if os.path.exists(langfile):
|
||||||
|
_accepted[normalized] = lang
|
||||||
|
return lang
|
||||||
|
|
||||||
return settings.LANGUAGE_CODE
|
return settings.LANGUAGE_CODE
|
||||||
|
|
||||||
|
@ -494,3 +501,24 @@ def string_concat(*strings):
|
||||||
return ''.join([str(el) for el in strings])
|
return ''.join([str(el) for el in strings])
|
||||||
|
|
||||||
string_concat = lazy(string_concat, str)
|
string_concat = lazy(string_concat, str)
|
||||||
|
|
||||||
|
def parse_accept_lang_header(lang_string):
|
||||||
|
"""
|
||||||
|
Parses the lang_string, which is the body of an HTTP Accept-Language
|
||||||
|
header, and returns a list of (lang, q-value), ordered by 'q' values.
|
||||||
|
|
||||||
|
Any format errors in lang_string results in an empty list being returned.
|
||||||
|
"""
|
||||||
|
result = []
|
||||||
|
pieces = accept_language_re.split(lang_string)
|
||||||
|
if pieces[-1]:
|
||||||
|
return []
|
||||||
|
for i in range(0, len(pieces) - 1, 3):
|
||||||
|
first, lang, priority = pieces[i : i + 3]
|
||||||
|
if first:
|
||||||
|
return []
|
||||||
|
priority = priority and float(priority) or 1.0
|
||||||
|
result.append((lang, priority))
|
||||||
|
result.sort(lambda x, y: -cmp(x[1], y[1]))
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
=================================
|
===================================
|
||||||
Django version 0.96 release notes
|
Django version 0.96.1 release notes
|
||||||
=================================
|
===================================
|
||||||
|
|
||||||
Welcome to Django 0.96!
|
Welcome to Django 0.96.1!
|
||||||
|
|
||||||
The primary goal for 0.96 is a cleanup and stabilization of the features
|
The primary goal for 0.96 is a cleanup and stabilization of the features
|
||||||
introduced in 0.95. There have been a few small `backwards-incompatible
|
introduced in 0.95. There have been a few small `backwards-incompatible
|
||||||
changes`_ since 0.95, but the upgrade process should be fairly simple
|
changes since 0.95`_, but the upgrade process should be fairly simple
|
||||||
and should not require major changes to existing applications.
|
and should not require major changes to existing applications.
|
||||||
|
|
||||||
However, we're also releasing 0.96 now because we have a set of
|
However, we're also releasing 0.96 now because we have a set of
|
||||||
|
@ -17,9 +17,21 @@ next official release; then you'll be able to upgrade in one step
|
||||||
instead of needing to make incremental changes to keep up with the
|
instead of needing to make incremental changes to keep up with the
|
||||||
development version of Django.
|
development version of Django.
|
||||||
|
|
||||||
Backwards-incompatible changes
|
Changes since the 0.96 release
|
||||||
==============================
|
==============================
|
||||||
|
|
||||||
|
This release contains fixes for a security vulnerability discovered after the
|
||||||
|
initial release of Django 0.96. A bug in the i18n framework could allow an
|
||||||
|
attacker to send extremely large strings in the Accept-Language header and
|
||||||
|
cause a denial of service by filling available memory.
|
||||||
|
|
||||||
|
Because this problems wasn't discovered and fixed until after the 0.96
|
||||||
|
release, it's recommended that you use this release rather than the original
|
||||||
|
0.96.
|
||||||
|
|
||||||
|
Backwards-incompatible changes since 0.95
|
||||||
|
=========================================
|
||||||
|
|
||||||
The following changes may require you to update your code when you switch from
|
The following changes may require you to update your code when you switch from
|
||||||
0.95 to 0.96:
|
0.95 to 0.96:
|
||||||
|
|
||||||
|
|
10
setup.py
10
setup.py
|
@ -32,15 +32,13 @@ if len(sys.argv) > 1 and sys.argv[1] == 'bdist_wininst':
|
||||||
for file_info in data_files:
|
for file_info in data_files:
|
||||||
file_info[0] = '/PURELIB/%s' % file_info[0]
|
file_info[0] = '/PURELIB/%s' % file_info[0]
|
||||||
|
|
||||||
# Dynamically calculate the version based on django.VERSION.
|
|
||||||
version = "%d.%d-%s" % (__import__('django').VERSION)
|
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name = "Django",
|
name = "Django",
|
||||||
version = version,
|
version = "0.96.5",
|
||||||
url = 'http://www.djangoproject.com/',
|
url = 'http://www.djangoproject.com/',
|
||||||
author = 'Lawrence Journal-World',
|
author = 'Django Software Foundation',
|
||||||
author_email = 'holovaty@gmail.com',
|
author_email = 'foundation@djangoproject.com',
|
||||||
|
download_url = 'http://media.djangoproject.com/releases/0.96/Django-0.96.5.tar.gz',
|
||||||
description = 'A high-level Python Web framework that encourages rapid development and clean, pragmatic design.',
|
description = 'A high-level Python Web framework that encourages rapid development and clean, pragmatic design.',
|
||||||
packages = packages,
|
packages = packages,
|
||||||
data_files = data_files,
|
data_files = data_files,
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
"""
|
||||||
|
Tests for django.core.servers.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
import django
|
||||||
|
from django.test import TestCase
|
||||||
|
from django.core.handlers.wsgi import WSGIHandler
|
||||||
|
from django.core.servers.basehttp import AdminMediaHandler
|
||||||
|
|
||||||
|
|
||||||
|
class AdminMediaHandlerTests(TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.admin_media_file_path = \
|
||||||
|
os.path.join(django.__path__[0], 'contrib', 'admin', 'media')
|
||||||
|
self.handler = AdminMediaHandler(WSGIHandler())
|
||||||
|
|
||||||
|
def test_media_urls(self):
|
||||||
|
"""
|
||||||
|
Tests that URLs that look like absolute file paths after the
|
||||||
|
settings.ADMIN_MEDIA_PREFIX don't turn into absolute file paths.
|
||||||
|
"""
|
||||||
|
# Cases that should work on all platforms.
|
||||||
|
data = (
|
||||||
|
('/media/css/base.css', ('css', 'base.css')),
|
||||||
|
)
|
||||||
|
# Cases that should raise an exception.
|
||||||
|
bad_data = ()
|
||||||
|
|
||||||
|
# Add platform-specific cases.
|
||||||
|
if os.sep == '/':
|
||||||
|
data += (
|
||||||
|
# URL, tuple of relative path parts.
|
||||||
|
('/media/\\css/base.css', ('\\css', 'base.css')),
|
||||||
|
)
|
||||||
|
bad_data += (
|
||||||
|
'/media//css/base.css',
|
||||||
|
'/media////css/base.css',
|
||||||
|
'/media/../css/base.css',
|
||||||
|
)
|
||||||
|
elif os.sep == '\\':
|
||||||
|
bad_data += (
|
||||||
|
'/media/C:\css/base.css',
|
||||||
|
'/media//\\css/base.css',
|
||||||
|
'/media/\\css/base.css',
|
||||||
|
'/media/\\\\css/base.css'
|
||||||
|
)
|
||||||
|
for url, path_tuple in data:
|
||||||
|
try:
|
||||||
|
output = self.handler.file_path(url)
|
||||||
|
except ValueError:
|
||||||
|
self.fail("Got a ValueError exception, but wasn't expecting"
|
||||||
|
" one. URL was: %s" % url)
|
||||||
|
rel_path = os.path.join(*path_tuple)
|
||||||
|
desired = os.path.normcase(
|
||||||
|
os.path.join(self.admin_media_file_path, rel_path))
|
||||||
|
self.assertEqual(output, desired,
|
||||||
|
"Got: %s, Expected: %s, URL was: %s" % (output, desired, url))
|
||||||
|
for url in bad_data:
|
||||||
|
try:
|
||||||
|
output = self.handler.file_path(url)
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
self.fail('URL: %s should have caused a ValueError exception.'
|
||||||
|
% url)
|
Loading…
Reference in New Issue