diff --git a/README b/README
index d52451d3bade..830a873a83d3 100644
--- a/README
+++ b/README
@@ -1,37 +1,49 @@
-Django is a high-level Python Web framework that encourages rapid development
-and clean, pragmatic design.
+Django is a high-level Python Web framework that encourages rapid
+development and clean, pragmatic design.
-All documentation is in the "docs" directory and online at
-http://www.djangoproject.com/documentation/. If you're just getting started,
-here's how we recommend you read the docs:
- * First, read docs/install.txt for instructions on installing Django.
+About this version
+==================
- * Next, work through the tutorials in order (docs/tutorial01.txt,
- docs/tutorial02.txt, etc.).
+This is the Django 0.91 "bugfixes" branch, which is intended to
+provide bugfix and patch support for users of Django 0.91 who have not
+been able to migrate to a more recent version. No new features will be
+added in this branch, and it is maintained solely as a means of
+providing support to legacy Django installations.
- * If you want to set up an actual deployment server, read docs/modpython.txt
- for instructions on running Django under mod_python.
+If you're completely new to Django we highly recommend that you use
+either the latest stable release or a Subversion checkout from
+Django's trunk; Django is always evolving, and the latest and greatest
+features are only available to users of newer versions of the
+framework.
- * The rest of the documentation is of the reference-manual variety.
- Read it -- and the FAQ -- as you run into problems.
-Docs are updated rigorously. If you find any problems in the docs, or think they
-should be clarified in any way, please take 30 seconds to fill out a ticket
-here:
+More information
+================
-http://code.djangoproject.com/newticket
+The complete history of bugs fixed in this branch can be viewed online
+at http://code.djangoproject.com/log/django/branches/0.91-bugfixes.
-To get more help:
+We also recommend that users of this branch subscribe to the
+"django-announce" mailing list, a low-traffic, announcements-only list
+which will send messages whenever an important (i.e.,
+security-related) bug is fixed. You can subscribe to the list via
+Google Groups at http://groups.google.com/group/django-announce.
- * Join the #django channel on irc.freenode.net. Lots of helpful people
- hang out there. Read the archives at http://loglibrary.com/179 .
+The documentation for this version of Django has been frozen, and is
+available online at http://www.djangoproject.com/documentation/0_91/.
- * Join the django-users mailing list, or read the archives, at
- http://groups-beta.google.com/group/django-users.
-To contribute to Django:
+Submitting bugs
+===============
- * Check out http://www.djangoproject.com/community/ for information
- about getting involved.
+If you run into a bug in Django 0.91, please search the Django ticket
+database to see if the issue has already been reported; if not, please
+head over to http://code.djangoproject.com/newticket and file a new
+ticket with as much information about the bug as you can provide.
+
+If you're running into a bug which has been reported but not fixed,
+feel free to update the ticket with any additional information you
+have, and to assign it to 'ubernostrum' (AKA James Bennett, the
+maintainer of this branch).
diff --git a/django/__init__.py b/django/__init__.py
index 593e2f46e474..27f57db7975d 100644
--- a/django/__init__.py
+++ b/django/__init__.py
@@ -1 +1 @@
-VERSION = (0, 9, 1, 'SVN')
+VERSION = (0, 91, 3, 'SVN')
diff --git a/django/bin/compile-messages.py b/django/bin/compile-messages.py
index e33fdd780b99..579f74364c91 100755
--- a/django/bin/compile-messages.py
+++ b/django/bin/compile-messages.py
@@ -20,7 +20,14 @@ def compile_messages():
if f.endswith('.po'):
sys.stderr.write('processing file %s in %s\n' % (f, dirpath))
pf = os.path.splitext(os.path.join(dirpath, f))[0]
- cmd = 'msgfmt -o "%s.mo" "%s.po"' % (pf, pf)
+ # Store the names of the .mo and .po files in an environment
+ # variable, rather than doing a string replacement into the
+ # command, so that we can take advantage of shell quoting, to
+ # quote any malicious characters/escaping.
+ # See http://cyberelk.net/tim/articles/cmdline/ar01s02.html
+ os.environ['djangocompilemo'] = pf + '.mo'
+ os.environ['djangocompilepo'] = pf + '.po'
+ cmd = 'msgfmt -o "$djangocompilemo" "$djangocompilepo"'
os.system(cmd)
if __name__ == "__main__":
diff --git a/django/contrib/admin/media/js/admin/RelatedObjectLookups.js b/django/contrib/admin/media/js/admin/RelatedObjectLookups.js
index 72c4e95ecab2..4ede09b54d2d 100644
--- a/django/contrib/admin/media/js/admin/RelatedObjectLookups.js
+++ b/django/contrib/admin/media/js/admin/RelatedObjectLookups.js
@@ -1,8 +1,35 @@
// Handles related-objects functionality: lookup link for raw_id_admin=True
// and Add Another links.
+function html_unescape(text) {
+ // Unescape a string that was escaped using django.utils.html.escape.
+ text = text.replace(/</g, '<');
+ text = text.replace(/>/g, '>');
+ text = text.replace(/"/g, '"');
+ text = text.replace(/'/g, "'");
+ text = text.replace(/&/g, '&');
+ return text;
+}
+
+// IE doesn't accept periods or dashes in the window name, but the element IDs
+// we use to generate popup window names may contain them, therefore we map them
+// to allowed characters in a reversible way so that we can locate the correct
+// element when the popup window is dismissed.
+function id_to_windowname(text) {
+ text = text.replace(/\./g, '__dot__');
+ text = text.replace(/\-/g, '__dash__');
+ return text;
+}
+
+function windowname_to_id(text) {
+ text = text.replace(/__dot__/g, '.');
+ text = text.replace(/__dash__/g, '-');
+ return text;
+}
+
function showRelatedObjectLookupPopup(triggeringLink) {
var name = triggeringLink.id.replace(/^lookup_/, '');
+ name = id_to_windowname(name);
var href;
if (triggeringLink.href.search(/\?/) >= 0) {
href = triggeringLink.href + '&pop=1';
@@ -15,25 +42,36 @@ function showRelatedObjectLookupPopup(triggeringLink) {
}
function dismissRelatedLookupPopup(win, chosenId) {
- var elem = document.getElementById(win.name);
+ var name = windowname_to_id(win.name);
+ var elem = document.getElementById(name);
if (elem.className.indexOf('vRawIdAdminField') != -1 && elem.value) {
elem.value += ',' + chosenId;
} else {
- document.getElementById(win.name).value = chosenId;
+ document.getElementById(name).value = chosenId;
}
win.close();
}
function showAddAnotherPopup(triggeringLink) {
var name = triggeringLink.id.replace(/^add_/, '');
- name = name.replace(/\./g, '___');
- var win = window.open(triggeringLink.href + '?_popup=1', name, 'height=500,width=800,resizable=yes,scrollbars=yes');
+ name = id_to_windowname(name);
+ href = triggeringLink.href
+ if (href.indexOf('?') == -1) {
+ href += '?_popup=1';
+ } else {
+ href += '&_popup=1';
+ }
+ var win = window.open(href, name, 'height=500,width=800,resizable=yes,scrollbars=yes');
win.focus();
return false;
}
function dismissAddAnotherPopup(win, newId, newRepr) {
- var name = win.name.replace(/___/g, '.');
+ // newId and newRepr are expected to have previously been escaped by
+ // django.utils.html.escape.
+ newId = html_unescape(newId);
+ newRepr = html_unescape(newRepr);
+ var name = windowname_to_id(win.name);
var elem = document.getElementById(name);
if (elem) {
if (elem.nodeName == 'SELECT') {
diff --git a/django/contrib/admin/templates/admin/login.html b/django/contrib/admin/templates/admin/login.html
index ea823e10200a..2445b7957356 100644
--- a/django/contrib/admin/templates/admin/login.html
+++ b/django/contrib/admin/templates/admin/login.html
@@ -17,7 +17,6 @@
{% trans 'Password:' %}
- {% comment %} {% trans 'Have you forgotten your password ?' %} {% endcomment %}
diff --git a/django/contrib/admin/views/decorators.py b/django/contrib/admin/views/decorators.py
index 5ddc17fa85f0..9b865400fc0c 100644
--- a/django/contrib/admin/views/decorators.py
+++ b/django/contrib/admin/views/decorators.py
@@ -2,43 +2,21 @@
from django.conf.settings import SECRET_KEY
from django.models.auth import users
from django.utils import httpwrappers
+from django.utils.html import escape
from django.utils.translation import gettext_lazy
-import base64, datetime, md5
-import cPickle as pickle
+import base64, datetime
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'
def _display_login_form(request, error_message=''):
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', {
'title': _('Log in'),
- 'app_path': request.path,
- 'post_data': post_data,
+ 'app_path': escape(request.path),
'error_message': error_message
}, context_instance=DjangoContext(request))
-def _encode_post_data(post_data):
- pickled = pickle.dumps(post_data)
- pickled_md5 = md5.new(pickled + 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 + 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):
"""
Decorator for views that checks that the user is logged in and is a staff
@@ -47,10 +25,6 @@ def staff_member_required(view_func):
def _checklogin(request, *args, **kwargs):
if not request.user.is_anonymous() and request.user.is_staff:
# 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)
assert hasattr(request, 'session'), "The Django admin requires session middleware to be installed. Edit your MIDDLEWARE_CLASSES setting to insert 'django.middleware.sessions.SessionMiddleware'."
@@ -58,7 +32,7 @@ def _checklogin(request, *args, **kwargs):
# If this isn't already the login page, display it.
if not request.POST.has_key(LOGIN_FORM_KEY):
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:
message = ""
return _display_login_form(request, message)
@@ -90,16 +64,7 @@ def _checklogin(request, *args, **kwargs):
request.session[users.SESSION_KEY] = user.id
user.last_login = datetime.datetime.now()
user.save()
- if request.POST.has_key('post_data'):
- 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 httpwrappers.HttpResponseRedirect(request.path)
+ return httpwrappers.HttpResponseRedirect(request.path)
else:
return _display_login_form(request, ERROR_MESSAGE)
diff --git a/django/contrib/admin/views/main.py b/django/contrib/admin/views/main.py
index 3d92a7d94992..3974f817c3f8 100644
--- a/django/contrib/admin/views/main.py
+++ b/django/contrib/admin/views/main.py
@@ -97,8 +97,16 @@ def get_modules_and_options(self, app_label, module_name, request):
self.mod, self.opts = _get_mod_opts(app_label, module_name)
if not request.user.has_perm(app_label + '.' + self.opts.get_change_permission()):
raise PermissionDenied
-
- self.lookup_mod, self.lookup_opts = self.mod, self.opts
+
+ lookup_mod, lookup_opts = self.mod, self.opts
+ if self.opts.one_to_one_field:
+ lookup_mod = self.opts.one_to_one_field.rel.to.get_model_module()
+ lookup_opts = lookup_mod.Klass._meta
+ # If lookup_opts doesn't have admin set, give it the default meta.Admin().
+ if not lookup_opts.admin:
+ lookup_opts.admin = meta.Admin()
+
+ self.lookup_mod, self.lookup_opts = lookup_mod, lookup_opts
def get_search_parameters(self, request):
# Get search parameters from the query string.
diff --git a/django/contrib/comments/feeds.py b/django/contrib/comments/feeds.py
index dd6c6ecf15af..cd38bd456712 100644
--- a/django/contrib/comments/feeds.py
+++ b/django/contrib/comments/feeds.py
@@ -43,6 +43,6 @@ def _get_lookup_kwargs(self):
kwargs = LatestFreeCommentsFeed._get_lookup_kwargs(self)
kwargs['is_removed__exact'] = False
if settings.COMMENTS_BANNED_USERS_GROUP:
- kwargs['where'] = ['user_id NOT IN (SELECT user_id FROM auth_users_group WHERE group_id = %s)']
- kwargs['params'] = [COMMENTS_BANNED_USERS_GROUP]
- return kwargs
\ No newline at end of file
+ kwargs['where'] = ['user_id NOT IN (SELECT user_id FROM auth_users_groups WHERE group_id = %s)']
+ kwargs['params'] = [settings.COMMENTS_BANNED_USERS_GROUP]
+ return kwargs
diff --git a/django/contrib/comments/views/comments.py b/django/contrib/comments/views/comments.py
index 5918db7dc83f..d6055a282ce6 100644
--- a/django/contrib/comments/views/comments.py
+++ b/django/contrib/comments/views/comments.py
@@ -107,7 +107,7 @@ def save(self, new_data):
# send the comment to the managers.
if self.user_cache.get_comments_comment_count() <= COMMENTS_FIRST_FEW:
message = ngettext('This comment was posted by a user who has posted fewer than %(count)s comment:\n\n%(text)s',
- 'This comment was posted by a user who has posted fewer than %(count)s comments:\n\n%(text)s') % \
+ 'This comment was posted by a user who has posted fewer than %(count)s comments:\n\n%(text)s', COMMENTS_FIRST_FEW) % \
{'count': COMMENTS_FIRST_FEW, 'text': c.get_as_text()}
mail_managers("Comment posted by rookie user", message)
if COMMENTS_SKETCHY_USERS_GROUP and COMMENTS_SKETCHY_USERS_GROUP in [g.id for g in self.user_cache.get_group_list()]:
diff --git a/django/core/db/backends/postgresql.py b/django/core/db/backends/postgresql.py
index 0bc799c24796..243ab8eb58ff 100644
--- a/django/core/db/backends/postgresql.py
+++ b/django/core/db/backends/postgresql.py
@@ -16,13 +16,54 @@
# Import copy of _thread_local.py from python 2.4
from django.utils._threading_local import local
+def smart_basestring(s, charset):
+ if isinstance(s, unicode):
+ return s.encode(charset)
+ return s
+
+class UnicodeCursorWrapper(object):
+ """
+ A thin wrapper around psycopg cursors that allows them to accept Unicode
+ strings as params.
+
+ This is necessary because psycopg doesn't apply any DB quoting to
+ parameters that are Unicode strings. If a param is Unicode, this will
+ convert it to a bytestring using DEFAULT_CHARSET before passing it to
+ psycopg.
+ """
+ def __init__(self, cursor, charset):
+ self.cursor = cursor
+ self.charset = charset
+
+ def execute(self, sql, params=()):
+ try:
+ params = dict([(k, smart_basestring(v, self.charset)) for (k, v) in params.items()])
+ except AttributeError:
+ params = [smart_basestring(p, self.charset) for p in params]
+ return self.cursor.execute(sql, params)
+
+ def executemany(self, sql, param_list):
+ try:
+ new_param_list = [dict([(k, smart_basestring(v, self.charset)) for (k, v) in params.items()])
+ for params in param_list]
+ except AttributeError:
+ new_param_list = [tuple([smart_basestring(p, self.charset) for p in params])
+ for params in param_list]
+ return self.cursor.executemany(sql, new_param_list)
+
+ def __getattr__(self, attr):
+ if self.__dict__.has_key(attr):
+ return self.__dict__[attr]
+ else:
+ return getattr(self.cursor, attr)
+
class DatabaseWrapper(local):
def __init__(self):
self.connection = None
self.queries = []
def cursor(self):
- from django.conf.settings import DATABASE_USER, DATABASE_NAME, DATABASE_HOST, DATABASE_PORT, DATABASE_PASSWORD, DEBUG, TIME_ZONE
+ from django.conf.settings import DATABASE_USER, DATABASE_NAME, DATABASE_HOST, DATABASE_PORT, DATABASE_PASSWORD, DEBUG, DEFAULT_CHARSET, TIME_ZONE
if self.connection is None:
if DATABASE_NAME == '':
from django.core.exceptions import ImproperlyConfigured
@@ -40,6 +81,7 @@ def cursor(self):
self.connection.set_isolation_level(1) # make transactions transparent to all cursors
cursor = self.connection.cursor()
cursor.execute("SET TIME ZONE %s", [TIME_ZONE])
+ cursor = UnicodeCursorWrapper(cursor, DEFAULT_CHARSET)
if DEBUG:
return base.CursorDebugWrapper(cursor, self)
return cursor
diff --git a/django/core/formfields.py b/django/core/formfields.py
index 167439cc0718..a25bdc16b207 100644
--- a/django/core/formfields.py
+++ b/django/core/formfields.py
@@ -325,7 +325,8 @@ def get_id(self):
class TextField(FormField):
input_type = "text"
- def __init__(self, field_name, length=30, maxlength=None, is_required=False, validator_list=[], member_name=None):
+ def __init__(self, field_name, length=30, maxlength=None, is_required=False, validator_list=None, member_name=None):
+ if validator_list is None: validator_list = []
self.field_name = field_name
self.length, self.maxlength = length, maxlength
self.is_required = is_required
@@ -362,7 +363,8 @@ class PasswordField(TextField):
input_type = "password"
class LargeTextField(TextField):
- def __init__(self, field_name, rows=10, cols=40, is_required=False, validator_list=[], maxlength=None):
+ def __init__(self, field_name, rows=10, cols=40, is_required=False, validator_list=None, maxlength=None):
+ if validator_list is None: validator_list = []
self.field_name = field_name
self.rows, self.cols, self.is_required = rows, cols, is_required
self.validator_list = validator_list[:]
@@ -380,7 +382,8 @@ def render(self, data):
self.field_name, self.rows, self.cols, escape(data))
class HiddenField(FormField):
- def __init__(self, field_name, is_required=False, validator_list=[]):
+ def __init__(self, field_name, is_required=False, validator_list=None):
+ if validator_list is None: validator_list = []
self.field_name, self.is_required = field_name, is_required
self.validator_list = validator_list[:]
@@ -410,7 +413,8 @@ def html2python(data):
html2python = staticmethod(html2python)
class SelectField(FormField):
- def __init__(self, field_name, choices=[], size=1, is_required=False, validator_list=[], member_name=None):
+ def __init__(self, field_name, choices=[], size=1, is_required=False, validator_list=None, member_name=None):
+ if validator_list is None: validator_list = []
self.field_name = field_name
# choices is a list of (value, human-readable key) tuples because order matters
self.choices, self.size, self.is_required = choices, size, is_required
@@ -446,7 +450,8 @@ def html2python(data):
html2python = staticmethod(html2python)
class RadioSelectField(FormField):
- def __init__(self, field_name, choices=[], ul_class='', is_required=False, validator_list=[], member_name=None):
+ def __init__(self, field_name, choices=[], ul_class='', is_required=False, validator_list=None, member_name=None):
+ if validator_list is None: validator_list = []
self.field_name = field_name
# choices is a list of (value, human-readable key) tuples because order matters
self.choices, self.is_required = choices, is_required
@@ -510,7 +515,8 @@ def isValidChoice(self, data, form):
class NullBooleanField(SelectField):
"This SelectField provides 'Yes', 'No' and 'Unknown', mapping results to True, False or None"
- def __init__(self, field_name, is_required=False, validator_list=[]):
+ def __init__(self, field_name, is_required=False, validator_list=None):
+ if validator_list is None: validator_list = []
SelectField.__init__(self, field_name, choices=[('1', 'Unknown'), ('2', 'Yes'), ('3', 'No')],
is_required=is_required, validator_list=validator_list)
@@ -563,7 +569,8 @@ class CheckboxSelectMultipleField(SelectMultipleField):
back into the single list that validators, renderers and save() expect.
"""
requires_data_list = True
- def __init__(self, field_name, choices=[], validator_list=[]):
+ def __init__(self, field_name, choices=[], validator_list=None):
+ if validator_list is None: validator_list = []
SelectMultipleField.__init__(self, field_name, choices, size=1, is_required=False, validator_list=validator_list)
def prepare(self, new_data):
@@ -594,7 +601,8 @@ def render(self, data):
####################
class FileUploadField(FormField):
- def __init__(self, field_name, is_required=False, validator_list=[]):
+ def __init__(self, field_name, is_required=False, validator_list=None):
+ if validator_list is None: validator_list = []
self.field_name, self.is_required = field_name, is_required
self.validator_list = [self.isNonEmptyFile] + validator_list
@@ -629,7 +637,8 @@ def isValidImage(self, field_data, all_data):
####################
class IntegerField(TextField):
- def __init__(self, field_name, length=10, maxlength=None, is_required=False, validator_list=[], member_name=None):
+ def __init__(self, field_name, length=10, maxlength=None, is_required=False, validator_list=None, member_name=None):
+ if validator_list is None: validator_list = []
validator_list = [self.isInteger] + validator_list
if member_name is not None:
self.member_name = member_name
@@ -648,7 +657,8 @@ def html2python(data):
html2python = staticmethod(html2python)
class SmallIntegerField(IntegerField):
- def __init__(self, field_name, length=5, maxlength=5, is_required=False, validator_list=[]):
+ def __init__(self, field_name, length=5, maxlength=5, is_required=False, validator_list=None):
+ if validator_list is None: validator_list = []
validator_list = [self.isSmallInteger] + validator_list
IntegerField.__init__(self, field_name, length, maxlength, is_required, validator_list)
@@ -657,7 +667,8 @@ def isSmallInteger(self, field_data, all_data):
raise validators.CriticalValidationError, _("Enter a whole number between -32,768 and 32,767.")
class PositiveIntegerField(IntegerField):
- def __init__(self, field_name, length=10, maxlength=None, is_required=False, validator_list=[]):
+ def __init__(self, field_name, length=10, maxlength=None, is_required=False, validator_list=None):
+ if validator_list is None: validator_list = []
validator_list = [self.isPositive] + validator_list
IntegerField.__init__(self, field_name, length, maxlength, is_required, validator_list)
@@ -666,7 +677,8 @@ def isPositive(self, field_data, all_data):
raise validators.CriticalValidationError, _("Enter a positive number.")
class PositiveSmallIntegerField(IntegerField):
- def __init__(self, field_name, length=5, maxlength=None, is_required=False, validator_list=[]):
+ def __init__(self, field_name, length=5, maxlength=None, is_required=False, validator_list=None):
+ if validator_list is None: validator_list = []
validator_list = [self.isPositiveSmall] + validator_list
IntegerField.__init__(self, field_name, length, maxlength, is_required, validator_list)
@@ -675,7 +687,8 @@ def isPositiveSmall(self, field_data, all_data):
raise validators.CriticalValidationError, _("Enter a whole number between 0 and 32,767.")
class FloatField(TextField):
- def __init__(self, field_name, max_digits, decimal_places, is_required=False, validator_list=[]):
+ def __init__(self, field_name, max_digits, decimal_places, is_required=False, validator_list=None):
+ if validator_list is None: validator_list = []
self.max_digits, self.decimal_places = max_digits, decimal_places
validator_list = [self.isValidFloat] + validator_list
TextField.__init__(self, field_name, max_digits+1, max_digits+1, is_required, validator_list)
@@ -700,7 +713,8 @@ def html2python(data):
class DatetimeField(TextField):
"""A FormField that automatically converts its data to a datetime.datetime object.
The data should be in the format YYYY-MM-DD HH:MM:SS."""
- def __init__(self, field_name, length=30, maxlength=None, is_required=False, validator_list=[]):
+ def __init__(self, field_name, length=30, maxlength=None, is_required=False, validator_list=None):
+ if validator_list is None: validator_list = []
self.field_name = field_name
self.length, self.maxlength = length, maxlength
self.is_required = is_required
@@ -723,7 +737,8 @@ def html2python(data):
class DateField(TextField):
"""A FormField that automatically converts its data to a datetime.date object.
The data should be in the format YYYY-MM-DD."""
- def __init__(self, field_name, is_required=False, validator_list=[]):
+ def __init__(self, field_name, is_required=False, validator_list=None):
+ if validator_list is None: validator_list = []
validator_list = [self.isValidDate] + validator_list
TextField.__init__(self, field_name, length=10, maxlength=10,
is_required=is_required, validator_list=validator_list)
@@ -747,7 +762,8 @@ def html2python(data):
class TimeField(TextField):
"""A FormField that automatically converts its data to a datetime.time object.
The data should be in the format HH:MM:SS or HH:MM:SS.mmmmmm."""
- def __init__(self, field_name, is_required=False, validator_list=[]):
+ def __init__(self, field_name, is_required=False, validator_list=None):
+ if validator_list is None: validator_list = []
validator_list = [self.isValidTime] + validator_list
TextField.__init__(self, field_name, length=8, maxlength=8,
is_required=is_required, validator_list=validator_list)
@@ -781,7 +797,8 @@ def html2python(data):
class EmailField(TextField):
"A convenience FormField for validating e-mail addresses"
- def __init__(self, field_name, length=50, maxlength=75, is_required=False, validator_list=[]):
+ def __init__(self, field_name, length=50, maxlength=75, is_required=False, validator_list=None):
+ if validator_list is None: validator_list = []
validator_list = [self.isValidEmail] + validator_list
TextField.__init__(self, field_name, length, maxlength=maxlength,
is_required=is_required, validator_list=validator_list)
@@ -794,7 +811,8 @@ def isValidEmail(self, field_data, all_data):
class URLField(TextField):
"A convenience FormField for validating URLs"
- def __init__(self, field_name, length=50, is_required=False, validator_list=[]):
+ def __init__(self, field_name, length=50, is_required=False, validator_list=None):
+ if validator_list is None: validator_list = []
validator_list = [self.isValidURL] + validator_list
TextField.__init__(self, field_name, length=length, maxlength=200,
is_required=is_required, validator_list=validator_list)
@@ -806,7 +824,8 @@ def isValidURL(self, field_data, all_data):
raise validators.CriticalValidationError, e.messages
class IPAddressField(TextField):
- def __init__(self, field_name, length=15, maxlength=15, is_required=False, validator_list=[]):
+ def __init__(self, field_name, length=15, maxlength=15, is_required=False, validator_list=None):
+ if validator_list is None: validator_list = []
validator_list = [self.isValidIPAddress] + validator_list
TextField.__init__(self, field_name, length=length, maxlength=maxlength,
is_required=is_required, validator_list=validator_list)
@@ -827,7 +846,8 @@ def html2python(data):
class FilePathField(SelectField):
"A SelectField whose choices are the files in a given directory."
- def __init__(self, field_name, path, match=None, recursive=False, is_required=False, validator_list=[]):
+ def __init__(self, field_name, path, match=None, recursive=False, is_required=False, validator_list=None):
+ if validator_list is None: validator_list = []
import os
if match is not None:
import re
@@ -850,7 +870,8 @@ def __init__(self, field_name, path, match=None, recursive=False, is_required=Fa
class PhoneNumberField(TextField):
"A convenience FormField for validating phone numbers (e.g. '630-555-1234')"
- def __init__(self, field_name, is_required=False, validator_list=[]):
+ def __init__(self, field_name, is_required=False, validator_list=None):
+ if validator_list is None: validator_list = []
validator_list = [self.isValidPhone] + validator_list
TextField.__init__(self, field_name, length=12, maxlength=12,
is_required=is_required, validator_list=validator_list)
@@ -863,7 +884,8 @@ def isValidPhone(self, field_data, all_data):
class USStateField(TextField):
"A convenience FormField for validating U.S. states (e.g. 'IL')"
- def __init__(self, field_name, is_required=False, validator_list=[]):
+ def __init__(self, field_name, is_required=False, validator_list=None):
+ if validator_list is None: validator_list = []
validator_list = [self.isValidUSState] + validator_list
TextField.__init__(self, field_name, length=2, maxlength=2,
is_required=is_required, validator_list=validator_list)
@@ -875,15 +897,13 @@ def isValidUSState(self, field_data, all_data):
raise validators.CriticalValidationError, e.messages
def html2python(data):
- if data:
- return data.upper() # Should always be stored in upper case
- else:
- return None
+ return data.upper() # Should always be stored in upper case
html2python = staticmethod(html2python)
class CommaSeparatedIntegerField(TextField):
"A convenience FormField for validating comma-separated integer fields"
- def __init__(self, field_name, maxlength=None, is_required=False, validator_list=[]):
+ def __init__(self, field_name, maxlength=None, is_required=False, validator_list=None):
+ if validator_list is None: validator_list = []
validator_list = [self.isCommaSeparatedIntegerList] + validator_list
TextField.__init__(self, field_name, length=20, maxlength=maxlength,
is_required=is_required, validator_list=validator_list)
diff --git a/django/core/handlers/base.py b/django/core/handlers/base.py
index fd3a3ccae124..9a4bb826a436 100644
--- a/django/core/handlers/base.py
+++ b/django/core/handlers/base.py
@@ -55,14 +55,14 @@ def get_response(self, path, request):
# Reset query list per request.
db.db.queries = []
- # Apply request middleware
- for middleware_method in self._request_middleware:
- response = middleware_method(request)
- if response:
- return response
-
resolver = urlresolvers.RegexURLResolver(r'^/', ROOT_URLCONF)
try:
+ # Apply request middleware
+ for middleware_method in self._request_middleware:
+ response = middleware_method(request)
+ if response:
+ return response
+
callback, callback_args, callback_kwargs = resolver.resolve(path)
# Apply view middleware
diff --git a/django/core/handlers/modpython.py b/django/core/handlers/modpython.py
index 37499c6f0651..34c21c52df59 100644
--- a/django/core/handlers/modpython.py
+++ b/django/core/handlers/modpython.py
@@ -13,9 +13,30 @@ def __init__(self, req):
self.path = req.uri
def __repr__(self):
+ # Since this is called as part of error handling, we need to be very
+ # robust against potentially malformed input.
+ try:
+ get = pformat(self.GET)
+ except:
+ get = ''
+ try:
+ post = pformat(self.POST)
+ except:
+ post = ''
+ try:
+ cookies = pformat(self.COOKIES)
+ except:
+ cookies = ''
+ try:
+ meta = pformat(self.META)
+ except:
+ meta = ''
+ try:
+ user = self.user
+ except:
+ user = ''
return '' % \
- (self.path, pformat(self.GET), pformat(self.POST), pformat(self.COOKIES),
- pformat(self.META), pformat(self.user))
+ (self.path, get, post, cookies, meta, user)
def get_full_path(self):
return '%s%s' % (self.path, self._req.args and ('?' + self._req.args) or '')
@@ -141,13 +162,12 @@ def __call__(self, req):
try:
request = ModPythonRequest(req)
response = self.get_response(req.uri, request)
+ # Apply response middleware
+ for middleware_method in self._response_middleware:
+ response = middleware_method(request, response)
finally:
db.db.close()
- # Apply response middleware
- for middleware_method in self._response_middleware:
- response = middleware_method(request, response)
-
# Convert our custom HttpResponse object back into the mod_python req.
populate_apache_request(response, req)
return 0 # mod_python.apache.OK
diff --git a/django/core/handlers/wsgi.py b/django/core/handlers/wsgi.py
index 7541a5fd9dd8..362e66b0ced0 100644
--- a/django/core/handlers/wsgi.py
+++ b/django/core/handlers/wsgi.py
@@ -55,9 +55,30 @@ def __init__(self, environ):
def __repr__(self):
from pprint import pformat
- return '' % \
- (pformat(self.GET), pformat(self.POST), pformat(self.COOKIES),
- pformat(self.META))
+ # Since this is called as part of error handling, we need to be very
+ # robust against potentially malformed input.
+ try:
+ get = pformat(self.GET)
+ except:
+ get = ''
+ try:
+ post = pformat(self.POST)
+ except:
+ post = ''
+ try:
+ cookies = pformat(self.COOKIES)
+ except:
+ cookies = ''
+ try:
+ meta = pformat(self.META)
+ except:
+ meta = ''
+ try:
+ user = self.user
+ except:
+ user = ''
+ return '' % \
+ (self.path, get, post, cookies, meta, user)
def get_full_path(self):
return '%s%s' % (self.path, self.environ['QUERY_STRING'] and ('?' + self.environ['QUERY_STRING']) or '')
@@ -157,13 +178,12 @@ def __call__(self, environ, start_response):
try:
request = WSGIRequest(environ)
response = self.get_response(request.path, request)
+ # Apply response middleware
+ for middleware_method in self._response_middleware:
+ response = middleware_method(request, response)
finally:
db.db.close()
- # Apply response middleware
- for middleware_method in self._response_middleware:
- response = middleware_method(request, response)
-
try:
status_text = STATUS_CODE_TEXT[response.status_code]
except KeyError:
diff --git a/django/core/meta/__init__.py b/django/core/meta/__init__.py
index 812173609663..eaa69b960bf3 100644
--- a/django/core/meta/__init__.py
+++ b/django/core/meta/__init__.py
@@ -151,7 +151,7 @@ class BadKeywordArguments(Exception):
class BoundRelatedObject(object):
def __init__(self, related_object, field_mapping, original):
self.relation = related_object
- self.field_mappings = field_mapping[related_object.opts.module_name]
+ self.field_mappings = field_mapping[related_object.name]
def template_name(self):
raise NotImplementedError
@@ -165,7 +165,7 @@ def __init__(self, parent_opts, opts, field):
self.opts = opts
self.field = field
self.edit_inline = field.rel.edit_inline
- self.name = opts.module_name
+ self.name = '%s_%s' % (opts.app_label, opts.module_name)
self.var_name = opts.object_name.lower()
def flatten_data(self, follow, obj=None):
@@ -1734,7 +1734,7 @@ def manipulator_init(opts, add, change, self, obj_key=None, follow=None):
# Sanity check -- Make sure the "parent" object exists.
# For example, make sure the Place exists for the Restaurant.
# Let the ObjectDoesNotExist exception propagate up.
- lookup_kwargs = opts.one_to_one_field.rel.limit_choices_to
+ lookup_kwargs = opts.one_to_one_field.rel.limit_choices_to.copy()
lookup_kwargs['%s__exact' % opts.one_to_one_field.rel.field_name] = obj_key
_ = opts.one_to_one_field.rel.to.get_model_module().get_object(**lookup_kwargs)
params = dict([(f.attname, f.get_default()) for f in opts.fields])
diff --git a/django/core/template/defaultfilters.py b/django/core/template/defaultfilters.py
index b82d54a31c35..69362fa2fad0 100644
--- a/django/core/template/defaultfilters.py
+++ b/django/core/template/defaultfilters.py
@@ -327,18 +327,26 @@ def get_digit(value, arg):
# DATES #
###################
+EMPTY_DATE_VALUES = (None, '')
+
def date(value, arg=DATE_FORMAT):
"Formats a date according to the given format"
+ if value in EMPTY_DATE_VALUES:
+ return ''
from django.utils.dateformat import format
return format(value, arg)
def time(value, arg=TIME_FORMAT):
"Formats a time according to the given format"
+ if value in EMPTY_DATE_VALUES:
+ return ''
from django.utils.dateformat import time_format
return time_format(value, arg)
def timesince(value):
'Formats a date as the time since that date (i.e. "4 days, 6 hours")'
+ if value in EMPTY_DATE_VALUES:
+ return ''
from django.utils.timesince import timesince
return timesince(value)
diff --git a/django/utils/translation.py b/django/utils/translation.py
index 56cd5426f00b..feb7824f4351 100644
--- a/django/utils/translation.py
+++ b/django/utils/translation.py
@@ -1,6 +1,9 @@
"translation helper functions"
-import os, re, sys
+import locale
+import os
+import re
+import sys
import gettext as gettext_module
from cStringIO import StringIO
from django.utils.functional import lazy
@@ -25,15 +28,25 @@ def currentThread():
# The default translation is based on the settings file.
_default = None
-# This is a cache for accept-header to translation object mappings to prevent
-# the accept parser to run multiple times for one user.
+# This is a cache for normalised accept-header languages to prevent multiple
+# file lookups when checking the same locale on repeated requests.
_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)."
p = language.find('-')
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:
return language.lower()
@@ -297,46 +310,40 @@ def get_language_from_request(request):
if lang_code in supported and lang_code is not None and check_for_language(lang_code):
return lang_code
- lang_code = request.COOKIES.get('django_language', None)
- if lang_code in supported and lang_code is not None and check_for_language(lang_code):
+ lang_code = request.COOKIES.get('django_language')
+ if lang_code and lang_code in supported and check_for_language(lang_code):
return lang_code
- accept = request.META.get('HTTP_ACCEPT_LANGUAGE', None)
- if accept is not None:
-
- t = _accepted.get(accept, None)
- if t is not None:
- return t
-
- def _parsed(el):
- p = el.find(';q=')
- if p >= 0:
- 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(',')]
- langs.sort(lambda a,b: -1*cmp(a[2], b[2]))
-
- for lang, mainlang, order in langs:
- if lang in supported or mainlang in supported:
- langfile = gettext_module.find('django', globalpath, [to_locale(lang)])
- if langfile:
- # reconstruct the actual language from the language
- # filename, because otherwise we might incorrectly
- # report de_DE if we only have de available, but
- # did find de_DE because of language normalization
- lang = langfile[len(globalpath):].split(os.path.sep)[1]
- _accepted[accept] = lang
- return lang
+ accept = request.META.get('HTTP_ACCEPT_LANGUAGE', '')
+ for lang, unused in parse_accept_lang_header(accept):
+ if lang == '*':
+ break
+
+ # We have a very restricted form for our language files (no encoding
+ # specifier, since they all must be UTF-8 and only one possible
+ # language each time. So we avoid the overhead of gettext.find() and
+ # look up the MO file manually.
+
+ normalized = locale.locale_alias.get(to_locale(lang, True))
+ if not normalized:
+ continue
+
+ # Remove the default encoding from locale_alias
+ normalized = normalized.split('.')[0]
+
+ if normalized in _accepted:
+ # We've seen this locale before and have an MO file for it, so no
+ # need to check again.
+ return _accepted[normalized]
+
+ for lang in (normalized, normalized.split('_')[0]):
+ if lang not in supported:
+ continue
+ langfile = os.path.join(globalpath, lang, 'LC_MESSAGES',
+ 'django.mo')
+ if os.path.exists(langfile):
+ _accepted[normalized] = lang
+ return lang
return settings.LANGUAGE_CODE
@@ -457,3 +464,23 @@ def templatize(src):
else:
out.write(blankout(t.contents, 'X'))
return out.getvalue()
+
+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
diff --git a/setup.py b/setup.py
index 2ff9b700fd0b..be0ef946e7a6 100644
--- a/setup.py
+++ b/setup.py
@@ -5,7 +5,7 @@
setup(
name = "Django",
- version = "0.91",
+ version = "0.91.3",
url = 'http://www.djangoproject.com/',
author = 'Lawrence Journal-World',
author_email = 'holovaty@gmail.com',