diff --git a/AUTHORS b/AUTHORS
index 43ec7dedef..cb69ebd0b0 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -246,6 +246,7 @@ answer newbie questions, and generally made Django that much better:
Brian Ray
remco@diji.biz
rhettg@gmail.com
+ Matt Riggott
Henrique Romano
Armin Ronacher
Brian Rosner
diff --git a/django/conf/locale/te/LC_MESSAGES/django.mo b/django/conf/locale/te/LC_MESSAGES/django.mo
index 8823b2015c..e86df1c91c 100644
Binary files a/django/conf/locale/te/LC_MESSAGES/django.mo and b/django/conf/locale/te/LC_MESSAGES/django.mo differ
diff --git a/django/conf/locale/te/LC_MESSAGES/django.po b/django/conf/locale/te/LC_MESSAGES/django.po
index 248baf2249..e0c600ba3e 100644
--- a/django/conf/locale/te/LC_MESSAGES/django.po
+++ b/django/conf/locale/te/LC_MESSAGES/django.po
@@ -14,7 +14,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: KBabel 1.11.4\n"
-"Plural-Forms: nplurals=2; nplurals=n>1;"
+"Plural-Forms: nplurals=2; plural=n>1;"
#: contrib/comments/models.py:67 contrib/comments/models.py:166
msgid "object ID"
diff --git a/django/contrib/admin/media/js/core.js b/django/contrib/admin/media/js/core.js
index a17ac8a4d2..c8d0db6a8d 100644
--- a/django/contrib/admin/media/js/core.js
+++ b/django/contrib/admin/media/js/core.js
@@ -1,5 +1,9 @@
// Core javascript helper functions
+// basic browser identification & version
+var isOpera = (navigator.userAgent.indexOf("Opera")>=0) && parseFloat(navigator.appVersion);
+var isIE = ((document.all) && (!isOpera)) && parseFloat(navigator.appVersion.split("MSIE ")[1].split(";")[0]);
+
// Cross-browser event handlers.
function addEvent(obj, evType, fn) {
if (obj.addEventListener) {
@@ -71,9 +75,13 @@ function findPosX(obj) {
var curleft = 0;
if (obj.offsetParent) {
while (obj.offsetParent) {
- curleft += obj.offsetLeft;
+ curleft += obj.offsetLeft - ((isOpera) ? 0 : obj.scrollLeft);
obj = obj.offsetParent;
}
+ // IE offsetParent does not include the top-level
+ if (isIE && obj.parentElement){
+ curleft += obj.offsetLeft - obj.scrollLeft;
+ }
} else if (obj.x) {
curleft += obj.x;
}
@@ -84,9 +92,13 @@ function findPosY(obj) {
var curtop = 0;
if (obj.offsetParent) {
while (obj.offsetParent) {
- curtop += obj.offsetTop;
+ curtop += obj.offsetTop - ((isOpera) ? 0 : obj.scrollTop);
obj = obj.offsetParent;
}
+ // IE offsetParent does not include the top-level
+ if (isIE && obj.parentElement){
+ curtop += obj.offsetTop - obj.scrollTop;
+ }
} else if (obj.y) {
curtop += obj.y;
}
diff --git a/django/contrib/localflavor/br/forms.py b/django/contrib/localflavor/br/forms.py
index c7082c0f9e..58bf795292 100644
--- a/django/contrib/localflavor/br/forms.py
+++ b/django/contrib/localflavor/br/forms.py
@@ -6,16 +6,21 @@ BR-specific Form helpers
from django.newforms import ValidationError
from django.newforms.fields import Field, RegexField, CharField, Select, EMPTY_VALUES
from django.utils.encoding import smart_unicode
-from django.utils.translation import ugettext
+from django.utils.translation import ugettext as _
import re
+try:
+ set
+except NameError:
+ from sets import Set as set # For Python 2.3
+
phone_digits_re = re.compile(r'^(\d{2})[-\.]?(\d{4})[-\.]?(\d{4})$')
class BRZipCodeField(RegexField):
def __init__(self, *args, **kwargs):
super(BRZipCodeField, self).__init__(r'^\d{5}-\d{3}$',
max_length=None, min_length=None,
- error_message=ugettext('Enter a zip code in the format XXXXX-XXX.'),
+ error_message=_('Enter a zip code in the format XXXXX-XXX.'),
*args, **kwargs)
class BRPhoneNumberField(Field):
@@ -27,7 +32,7 @@ class BRPhoneNumberField(Field):
m = phone_digits_re.search(value)
if m:
return u'%s-%s-%s' % (m.group(1), m.group(2), m.group(3))
- raise ValidationError(ugettext('Phone numbers must be in XX-XXXX-XXXX format.'))
+ raise ValidationError(_('Phone numbers must be in XX-XXXX-XXXX format.'))
class BRStateSelect(Select):
"""
@@ -38,6 +43,32 @@ class BRStateSelect(Select):
from br_states import STATE_CHOICES
super(BRStateSelect, self).__init__(attrs, choices=STATE_CHOICES)
+class BRStateChoiceField(Field):
+ """
+ A choice field that uses a list of Brazilian states as its choices.
+ """
+ widget = Select
+
+ def __init__(self, required=True, widget=None, label=None,
+ initial=None, help_text=None):
+ super(BRStateChoiceField, self).__init__(required, widget, label,
+ initial, help_text)
+ from br_states import STATE_CHOICES
+ self.widget.choices = STATE_CHOICES
+
+ def clean(self, value):
+ value = super(BRStateChoiceField, self).clean(value)
+ if value in EMPTY_VALUES:
+ value = u''
+ value = smart_unicode(value)
+ if value == u'':
+ return value
+ valid_values = set([smart_unicode(k) for k, v in self.widget.choices])
+ if value not in valid_values:
+ raise ValidationError(_(u'Select a valid brazilian state.'
+ u' That state is not one'
+ u' of the available states.'))
+ return value
def DV_maker(v):
if v >= 2:
@@ -69,9 +100,9 @@ class BRCPFField(CharField):
try:
int(value)
except ValueError:
- raise ValidationError(ugettext("This field requires only numbers."))
+ raise ValidationError(_("This field requires only numbers."))
if len(value) != 11:
- raise ValidationError(ugettext("This field requires at most 11 digits or 14 characters."))
+ raise ValidationError(_("This field requires at most 11 digits or 14 characters."))
orig_dv = value[-2:]
new_1dv = sum([i * int(value[idx]) for idx, i in enumerate(range(10, 1, -1))])
@@ -81,7 +112,7 @@ class BRCPFField(CharField):
new_2dv = DV_maker(new_2dv % 11)
value = value[:-1] + str(new_2dv)
if value[-2:] != orig_dv:
- raise ValidationError(ugettext("Invalid CPF number."))
+ raise ValidationError(_("Invalid CPF number."))
return orig_value
@@ -103,7 +134,7 @@ class BRCNPJField(Field):
raise ValidationError("This field requires only numbers.")
if len(value) != 14:
raise ValidationError(
- ugettext("This field requires at least 14 digits"))
+ _("This field requires at least 14 digits"))
orig_dv = value[-2:]
new_1dv = sum([i * int(value[idx]) for idx, i in enumerate(range(5, 1, -1) + range(9, 1, -1))])
@@ -113,7 +144,7 @@ class BRCNPJField(Field):
new_2dv = DV_maker(new_2dv % 11)
value = value[:-1] + str(new_2dv)
if value[-2:] != orig_dv:
- raise ValidationError(ugettext("Invalid CNPJ number."))
+ raise ValidationError(_("Invalid CNPJ number."))
return orig_value
diff --git a/django/contrib/sites/models.py b/django/contrib/sites/models.py
index 921cf4ff4d..253a2722b6 100644
--- a/django/contrib/sites/models.py
+++ b/django/contrib/sites/models.py
@@ -1,15 +1,33 @@
from django.db import models
from django.utils.translation import ugettext_lazy as _
+from django.http import get_host
+
+SITE_CACHE = {}
class SiteManager(models.Manager):
def get_current(self):
+ """
+ Returns the current ``Site`` based on the SITE_ID in the
+ project's settings. The ``Site`` object is cached the first
+ time it's retrieved from the database.
+ """
from django.conf import settings
try:
sid = settings.SITE_ID
except AttributeError:
from django.core.exceptions import ImproperlyConfigured
raise ImproperlyConfigured("You're using the Django \"sites framework\" without having set the SITE_ID setting. Create a site in your database and set the SITE_ID setting to fix this error.")
- return self.get(pk=sid)
+ try:
+ current_site = SITE_CACHE[sid]
+ except KeyError:
+ current_site = self.get(pk=sid)
+ SITE_CACHE[sid] = current_site
+ return current_site
+
+ def clear_cache(self):
+ """Clears the ``Site`` object cache."""
+ global SITE_CACHE
+ SITE_CACHE = {}
class Site(models.Model):
domain = models.CharField(_('domain name'), max_length=100)
@@ -36,7 +54,7 @@ class RequestSite(object):
The save() and delete() methods raise NotImplementedError.
"""
def __init__(self, request):
- self.domain = self.name = request.META['SERVER_NAME']
+ self.domain = self.name = get_host(request)
def __unicode__(self):
return self.domain
diff --git a/django/core/handler.py b/django/core/handler.py
deleted file mode 100644
index 039406722b..0000000000
--- a/django/core/handler.py
+++ /dev/null
@@ -1,11 +0,0 @@
-# This module is DEPRECATED!
-#
-# You should no longer be pointing your mod_python configuration
-# at "django.core.handler".
-#
-# Use "django.core.handlers.modpython" instead.
-
-from django.core.handlers.modpython import ModPythonHandler
-
-def handler(req):
- return ModPythonHandler()(req)
diff --git a/django/core/handlers/base.py b/django/core/handlers/base.py
index ca48b301d4..768fc14b00 100644
--- a/django/core/handlers/base.py
+++ b/django/core/handlers/base.py
@@ -50,6 +50,10 @@ class BaseHandler(object):
def get_response(self, request):
"Returns an HttpResponse object for the given HttpRequest"
+ response = self._real_get_response(request)
+ return fix_location_header(request, response)
+
+ def _real_get_response(self, request):
from django.core import exceptions, urlresolvers
from django.core.mail import mail_admins
from django.conf import settings
@@ -129,3 +133,16 @@ class BaseHandler(object):
"Helper function to return the traceback as a string"
import traceback
return '\n'.join(traceback.format_exception(*(exc_info or sys.exc_info())))
+
+def fix_location_header(request, response):
+ """
+ Ensure that we always use an absolute URI in any location header in the
+ response. This is required by RFC 2616, section 14.30.
+
+ Code constructing response objects is free to insert relative paths and
+ this function converts them to absolute paths.
+ """
+ if 'Location' in response.headers and http.get_host(request):
+ response['Location'] = request.build_absolute_uri(response['Location'])
+ return response
+
diff --git a/django/core/validators.py b/django/core/validators.py
index fd28ba4ef8..7611aef921 100644
--- a/django/core/validators.py
+++ b/django/core/validators.py
@@ -181,10 +181,15 @@ def isValidImage(field_data, all_data):
except TypeError:
raise ValidationError, _("No file was submitted. Check the encoding type on the form.")
try:
- Image.open(StringIO(content))
- except (IOError, OverflowError): # Python Imaging Library doesn't recognize it as an image
- # OverflowError is due to a bug in PIL with Python 2.4+ which can cause
- # it to gag on OLE files.
+ # load() is the only method that can spot a truncated JPEG,
+ # but it cannot be called sanely after verify()
+ trial_image = Image.open(StringIO(content))
+ trial_image.load()
+ # verify() is the only method that can spot a corrupt PNG,
+ # but it must be called immediately after the constructor
+ trial_image = Image.open(StringIO(content))
+ trial_image.verify()
+ except Exception: # Python Imaging Library doesn't recognize it as an image
raise ValidationError, _("Upload a valid image. The file you uploaded was either not an image or a corrupted image.")
def isValidImageURL(field_data, all_data):
diff --git a/django/db/models/base.py b/django/db/models/base.py
index 9bbac65385..beb413fc4c 100644
--- a/django/db/models/base.py
+++ b/django/db/models/base.py
@@ -241,10 +241,12 @@ class Model(object):
placeholders = ['%s'] * len(field_names)
if self._meta.order_with_respect_to:
field_names.append(qn('_order'))
- # TODO: This assumes the database supports subqueries.
- placeholders.append('(SELECT COUNT(*) FROM %s WHERE %s = %%s)' % \
- (qn(self._meta.db_table), qn(self._meta.order_with_respect_to.column)))
- db_values.append(getattr(self, self._meta.order_with_respect_to.attname))
+ placeholders.append('%s')
+ subsel = 'SELECT COUNT(*) FROM %s WHERE %s = %%s' % (
+ qn(self._meta.db_table),
+ qn(self._meta.order_with_respect_to.column))
+ cursor.execute(subsel, (getattr(self, self._meta.order_with_respect_to.attname),))
+ db_values.append(cursor.fetchone()[0])
if db_values:
cursor.execute("INSERT INTO %s (%s) VALUES (%s)" % \
(qn(self._meta.db_table), ','.join(field_names),
diff --git a/django/http/__init__.py b/django/http/__init__.py
index 20818f138b..2b68a6243a 100644
--- a/django/http/__init__.py
+++ b/django/http/__init__.py
@@ -2,6 +2,7 @@ import os
from Cookie import SimpleCookie
from pprint import pformat
from urllib import urlencode
+from urlparse import urljoin
from django.utils.datastructures import MultiValueDict, FileDict
from django.utils.encoding import smart_str, iri_to_uri, force_unicode
@@ -42,10 +43,24 @@ class HttpRequest(object):
return key in self.GET or key in self.POST
__contains__ = has_key
-
+
def get_full_path(self):
return ''
+ def build_absolute_uri(self, location=None):
+ """
+ Builds an absolute URI from the location and the variables available in
+ this request. If no location is specified, the absolute URI is built on
+ ``request.get_full_path()``.
+ """
+ if not location:
+ location = self.get_full_path()
+ if not ':' in location:
+ current_uri = '%s://%s%s' % (self.is_secure() and 'https' or 'http',
+ get_host(self), self.path)
+ location = urljoin(current_uri, location)
+ return location
+
def is_secure(self):
return os.environ.get("HTTPS") == "on"
@@ -364,9 +379,16 @@ class HttpResponseServerError(HttpResponse):
def get_host(request):
"Gets the HTTP host from the environment or request headers."
+ # We try three options, in order of decreasing preference.
host = request.META.get('HTTP_X_FORWARDED_HOST', '')
- if not host:
- host = request.META.get('HTTP_HOST', '')
+ if 'HTTP_HOST' in request.META:
+ host = request.META['HTTP_HOST']
+ else:
+ # Reconstruct the host using the algorithm from PEP 333.
+ host = request.META['SERVER_NAME']
+ server_port = request.META['SERVER_PORT']
+ if server_port != (request.is_secure() and 443 or 80):
+ host = '%s:%s' % (host, server_port)
return host
# It's neither necessary nor appropriate to use
diff --git a/django/newforms/fields.py b/django/newforms/fields.py
index fc816a842b..d83cb6cde2 100644
--- a/django/newforms/fields.py
+++ b/django/newforms/fields.py
@@ -2,6 +2,7 @@
Field classes
"""
+import copy
import datetime
import re
import time
@@ -100,6 +101,12 @@ class Field(object):
"""
return {}
+ def __deepcopy__(self, memo):
+ result = copy.copy(self)
+ memo[id(self)] = result
+ result.widget = copy.deepcopy(self.widget, memo)
+ return result
+
class CharField(Field):
def __init__(self, max_length=None, min_length=None, *args, **kwargs):
self.max_length, self.min_length = max_length, min_length
@@ -386,10 +393,15 @@ class ImageField(FileField):
from PIL import Image
from cStringIO import StringIO
try:
- Image.open(StringIO(f.content))
- except (IOError, OverflowError): # Python Imaging Library doesn't recognize it as an image
- # OverflowError is due to a bug in PIL with Python 2.4+ which can cause
- # it to gag on OLE files.
+ # load() is the only method that can spot a truncated JPEG,
+ # but it cannot be called sanely after verify()
+ trial_image = Image.open(StringIO(f.content))
+ trial_image.load()
+ # verify() is the only method that can spot a corrupt PNG,
+ # but it must be called immediately after the constructor
+ trial_image = Image.open(StringIO(f.content))
+ trial_image.verify()
+ except Exception: # Python Imaging Library doesn't recognize it as an image
raise ValidationError(ugettext(u"Upload a valid image. The file you uploaded was either not an image or a corrupted image."))
return f
@@ -409,6 +421,9 @@ class URLField(RegexField):
self.user_agent = validator_user_agent
def clean(self, value):
+ # If no URL scheme given, assume http://
+ if value and '://' not in value:
+ value = u'http://%s' % value
value = super(URLField, self).clean(value)
if value == u'':
return value
diff --git a/django/newforms/forms.py b/django/newforms/forms.py
index 5baf0a079b..ab8729be65 100644
--- a/django/newforms/forms.py
+++ b/django/newforms/forms.py
@@ -31,7 +31,7 @@ class SortedDictFromList(SortedDict):
dict.__init__(self, dict(data))
def copy(self):
- return SortedDictFromList([(k, copy.copy(v)) for k, v in self.items()])
+ return SortedDictFromList([(k, copy.deepcopy(v)) for k, v in self.items()])
class DeclarativeFieldsMetaclass(type):
"""
diff --git a/django/test/testcases.py b/django/test/testcases.py
index baa6e7bb19..6b7714ec7b 100644
--- a/django/test/testcases.py
+++ b/django/test/testcases.py
@@ -84,12 +84,8 @@ class TestCase(unittest.TestCase):
self.assertEqual(response.status_code, status_code,
("Response didn't redirect as expected: Response code was %d"
" (expected %d)" % (response.status_code, status_code)))
- scheme, netloc, path, query, fragment = urlsplit(response['Location'])
- url = path
- if query:
- url += '?' + query
- if fragment:
- url += '#' + fragment
+ url = response['Location']
+ scheme, netloc, path, query, fragment = urlsplit(url)
self.assertEqual(url, expected_url,
"Response redirected to '%s', expected '%s'" % (url, expected_url))
diff --git a/django/views/generic/date_based.py b/django/views/generic/date_based.py
index 1a8c4c611a..e6a75e63aa 100644
--- a/django/views/generic/date_based.py
+++ b/django/views/generic/date_based.py
@@ -10,7 +10,7 @@ from django.http import Http404, HttpResponse
def archive_index(request, queryset, date_field, num_latest=15,
template_name=None, template_loader=loader,
extra_context=None, allow_empty=False, context_processors=None,
- mimetype=None, allow_future=False):
+ mimetype=None, allow_future=False, template_object_name='latest'):
"""
Generic top-level archive of date-based objects.
@@ -39,7 +39,7 @@ def archive_index(request, queryset, date_field, num_latest=15,
t = template_loader.get_template(template_name)
c = RequestContext(request, {
'date_list' : date_list,
- 'latest' : latest,
+ template_object_name : latest,
}, context_processors)
for key, value in extra_context.items():
if callable(value):
diff --git a/django/views/i18n.py b/django/views/i18n.py
index 320caf37d7..5b50f75d23 100644
--- a/django/views/i18n.py
+++ b/django/views/i18n.py
@@ -9,20 +9,26 @@ def set_language(request):
"""
Redirect to a given url while setting the chosen language in the
session or cookie. The url and the language code need to be
- specified in the GET parameters.
+ specified in the request parameters.
+
+ Since this view changes how the user will see the rest of the site, it must
+ only be accessed as a POST request. If called as a GET request, it will
+ redirect to the page in the request (the 'next' parameter) without changing
+ any state.
"""
- lang_code = request.GET.get('language', None)
next = request.GET.get('next', None)
if not next:
next = request.META.get('HTTP_REFERER', None)
if not next:
next = '/'
response = http.HttpResponseRedirect(next)
- if lang_code and check_for_language(lang_code):
- if hasattr(request, 'session'):
- request.session['django_language'] = lang_code
- else:
- response.set_cookie('django_language', lang_code)
+ if request.method == 'POST':
+ lang_code = request.POST.get('language', None)
+ if lang_code and check_for_language(lang_code):
+ if hasattr(request, 'session'):
+ request.session['django_language'] = lang_code
+ else:
+ response.set_cookie('django_language', lang_code)
return response
NullSource = """
diff --git a/docs/db-api.txt b/docs/db-api.txt
index f5da56ddf9..2a1f428ae7 100644
--- a/docs/db-api.txt
+++ b/docs/db-api.txt
@@ -799,6 +799,9 @@ of the arguments is required, but you should use at least one of them.
Entry.objects.extra(where=['headline=%s'], params=['Lennon'])
+ The combined number of placeholders in the list of strings for ``select``
+ or ``where`` should equal the number of values in the ``params`` list.
+
QuerySet methods that do not return QuerySets
---------------------------------------------
diff --git a/docs/generic_views.txt b/docs/generic_views.txt
index 33c39b7e12..a00a49d8be 100644
--- a/docs/generic_views.txt
+++ b/docs/generic_views.txt
@@ -201,6 +201,10 @@ a date in the *future* are not included unless you set ``allow_future`` to
specified in ``date_field`` is greater than the current date/time. By
default, this is ``False``.
+ * **New in Django development version:** ``template_object_name``:
+ Designates the name of the template variable to use in the template
+ context. By default, this is ``'latest'``.
+
**Template name:**
If ``template_name`` isn't specified, this view will use the template
@@ -221,10 +225,16 @@ In addition to ``extra_context``, the template's context will be:
years that have objects available according to ``queryset``. These are
ordered in reverse. This is equivalent to
``queryset.dates(date_field, 'year')[::-1]``.
+
* ``latest``: The ``num_latest`` objects in the system, ordered descending
by ``date_field``. For example, if ``num_latest`` is ``10``, then
``latest`` will be a list of the latest 10 objects in ``queryset``.
+ **New in Django development version:** This variable's name depends on
+ the ``template_object_name`` parameter, which is ``'latest'`` by default.
+ If ``template_object_name`` is ``'foo'``, this variable's name will be
+ ``foo``.
+
.. _RequestContext docs: ../templates_python/#subclassing-context-requestcontext
``django.views.generic.date_based.archive_year``
@@ -764,8 +774,8 @@ If the results are paginated, the context will contain these extra variables:
* ``hits``: The total number of objects across *all* pages, not just this
page.
- * ``page_range``: A list of the page numbers that are available. This
- is 1-based.
+ * **New in Django development version:** ``page_range``: A list of the
+ page numbers that are available. This is 1-based.
Notes on pagination
~~~~~~~~~~~~~~~~~~~
@@ -788,7 +798,11 @@ specify the page number in the URL in one of two ways:
to create a link to every page of results.
These values and lists are is 1-based, not 0-based, so the first page would be
-represented as page ``1``. As a special case, you are also permitted to use
+represented as page ``1``.
+
+**New in Django development version:**
+
+As a special case, you are also permitted to use
``last`` as a value for ``page``::
/objects/?page=last
diff --git a/docs/install.txt b/docs/install.txt
index 082000149f..9300c7f0f8 100644
--- a/docs/install.txt
+++ b/docs/install.txt
@@ -127,16 +127,24 @@ Installing an official release
1. Download the latest release from our `download page`_.
- 2. Untar the downloaded file (e.g. ``tar xzvf Django-NNN.tar.gz``).
+ 2. Untar the downloaded file (e.g. ``tar xzvf Django-NNN.tar.gz``,
+ where ``NNN`` is the version number of the latest release).
+ If you're using Windows, you can download the command-line tool
+ bsdtar_ to do this, or you can use a GUI-based tool such as 7-zip_.
- 3. Change into the downloaded directory (e.g. ``cd Django-NNN``).
+ 3. Change into the directory created in step 2 (e.g. ``cd Django-NNN``).
- 4. Run ``sudo python setup.py install``.
+ 4. If you're using Linux, Mac OS X or some other flavor of Unix, enter
+ the command ``sudo python setup.py install`` at the shell prompt.
+ If you're using Windows, start up a command shell with administrator
+ privileges and run the command ``setup.py install``.
-The command will install Django in your Python installation's ``site-packages``
-directory.
+These commands will install Django in your Python installation's
+``site-packages`` directory.
.. _distribution specific notes: ../distributions/
+.. _bsdtar: http://gnuwin32.sourceforge.net/packages/bsdtar.htm
+.. _7-zip: http://www.7-zip.org/
Installing the development version
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -144,34 +152,55 @@ Installing the development version
If you'd like to be able to update your Django code occasionally with the
latest bug fixes and improvements, follow these instructions:
-1. Make sure you have Subversion_ installed.
-2. Check out the Django code into your Python ``site-packages`` directory.
+1. Make sure that you have Subversion_ installed, and that you can run its
+ commands from a shell. (Enter ``svn help`` at a shell prompt to test
+ this.)
- On Linux / Mac OSX / Unix, do this::
+2. Check out Django's main development branch (the 'trunk') like so::
- svn co http://code.djangoproject.com/svn/django/trunk/ django_src
- ln -s `pwd`/django_src/django SITE-PACKAGES-DIR/django
+ svn co http://code.djangoproject.com/svn/django/trunk/ django-trunk
+
+3. Next, make sure that the Python interpreter can load Django's code. There
+ are various ways of accomplishing this. One of the most convenient, on
+ Linux, Mac OSX or other Unix-like systems, is to use a symbolic link::
+
+ ln -s `pwd`/django-trunk/django SITE-PACKAGES-DIR/django
(In the above line, change ``SITE-PACKAGES-DIR`` to match the location of
your system's ``site-packages`` directory, as explained in the
"Where are my ``site-packages`` stored?" section above.)
- On Windows, do this::
+ Alternatively, you can define your ``PYTHONPATH`` environment variable
+ so that it includes the ``django`` subdirectory of ``django-trunk``.
+ This is perhaps the most convenient solution on Windows systems, which
+ don't support symbolic links. (Environment variables can be defined on
+ Windows systems `from the Control Panel`_.)
- svn co http://code.djangoproject.com/svn/django/trunk/django c:\Python24\lib\site-packages\django
+ .. admonition:: What about Apache and mod_python?
-3. Copy the file ``django_src/django/bin/django-admin.py`` to somewhere on your
- system path, such as ``/usr/local/bin`` (Unix) or ``C:\Python24\Scripts``
+ If you take the approach of setting ``PYTHONPATH``, you'll need to
+ remember to do the same thing in your Apache configuration once you
+ deploy your production site. Do this by setting ``PythonPath`` in your
+ Apache configuration file.
+
+ More information about deployment is available, of course, in our
+ `How to use Django with mod_python`_ documentation.
+
+ .. _How to use Django with mod_python: ../modpython/
+
+4. Copy the file ``django-trunk/django/bin/django-admin.py`` to somewhere on
+ your system path, such as ``/usr/local/bin`` (Unix) or ``C:\Python24\Scripts``
(Windows). This step simply lets you type ``django-admin.py`` from within
any directory, rather than having to qualify the command with the full path
to the file.
-You *don't* have to run ``python setup.py install``, because that command
-takes care of steps 2 and 3 for you.
+You *don't* have to run ``python setup.py install``, because you've already
+carried out the equivalent actions in steps 3 and 4.
When you want to update your copy of the Django source code, just run the
-command ``svn update`` from within the ``django`` directory. When you do this,
-Subversion will automatically download any changes.
+command ``svn update`` from within the ``django-trunk`` directory. When you do
+this, Subversion will automatically download any changes.
.. _`download page`: http://www.djangoproject.com/download/
.. _Subversion: http://subversion.tigris.org/
+.. _from the Control Panel: http://www.microsoft.com/resources/documentation/windows/xp/all/proddocs/en-us/sysdm_advancd_environmnt_addchange_variable.mspx
diff --git a/docs/model-api.txt b/docs/model-api.txt
index 44488ae1d8..27207aab6f 100644
--- a/docs/model-api.txt
+++ b/docs/model-api.txt
@@ -1550,8 +1550,8 @@ Finally, note that in order to use ``list_display_links``, you must define
Set ``list_filter`` to activate filters in the right sidebar of the change list
page of the admin. This should be a list of field names, and each specified
-field should be either a ``BooleanField``, ``DateField``, ``DateTimeField``
-or ``ForeignKey``.
+field should be either a ``BooleanField``, ``CharField``, ``DateField``,
+``DateTimeField``, ``IntegerField`` or ``ForeignKey``.
This example, taken from the ``django.contrib.auth.models.User`` model, shows
how both ``list_display`` and ``list_filter`` work::
diff --git a/docs/modpython.txt b/docs/modpython.txt
index cbed0ee8c3..4a8c169a51 100644
--- a/docs/modpython.txt
+++ b/docs/modpython.txt
@@ -83,7 +83,7 @@ need to write your ``PythonPath`` directive as::
With this path, ``import weblog`` and ``import mysite.settings`` will both
work. If you had ``import blogroll`` in your code somewhere and ``blogroll``
lived under the ``weblog/`` directory, you would *also* need to add
-``/var/production/django-apps/weblog/`` to your ``PythonPath``. Remember: the
+``/usr/local/django-apps/weblog/`` to your ``PythonPath``. Remember: the
**parent directories** of anything you import directly must be on the Python
path.
diff --git a/docs/request_response.txt b/docs/request_response.txt
index 1eef41659a..bf914fb5ff 100644
--- a/docs/request_response.txt
+++ b/docs/request_response.txt
@@ -161,6 +161,18 @@ Methods
Example: ``"/music/bands/the_beatles/?print=true"``
+``build_absolute_uri(location)``
+ **New in Django development version**
+
+ Returns the absolute URI form of ``location``. If no location is provided,
+ the location will be set to ``request.get_full_path()``.
+
+ If the location is already an absolute URI, it will not be altered.
+ Otherwise the absolute URI is built using the server variables available in
+ this request.
+
+ Example: ``"http://example.com/music/bands/the_beatles/?print=true"``
+
``is_secure()``
Returns ``True`` if the request is secure; that is, if it was made with
HTTPS.
@@ -184,8 +196,8 @@ subclass of dictionary. Exceptions are outlined here:
* ``__getitem__(key)`` -- Returns the value for the given key. If the key
has more than one value, ``__getitem__()`` returns the last value.
Raises ``django.utils.datastructure.MultiValueDictKeyError`` if the key
- does not exist (fortunately, this is a subclass of Python's standard
- ``KeyError``, so you can stick to catching ``KeyError``).
+ does not exist. (This is a subclass of Python's standard ``KeyError``,
+ so you can stick to catching ``KeyError``.)
* ``__setitem__(key, value)`` -- Sets the given key to ``[value]``
(a Python list whose single element is ``value``). Note that this, as
diff --git a/docs/sites.txt b/docs/sites.txt
index e7a8ecbfa6..5896afcf41 100644
--- a/docs/sites.txt
+++ b/docs/sites.txt
@@ -213,6 +213,31 @@ To do this, you can use the sites framework. A simple example::
>>> 'http://%s%s' % (Site.objects.get_current().domain, obj.get_absolute_url())
'http://example.com/mymodel/objects/3/'
+Caching the current ``Site`` object
+===================================
+
+**New in Django development version**
+
+As the current site is stored in the database, each call to
+``Site.objects.get_current()`` could result in a database query. But Django is a
+little cleverer than that: on the first request, the current site is cached, and
+any subsequent call returns the cached data instead of hitting the database.
+
+If for any reason you want to force a database query, you can tell Django to
+clear the cache using ``Site.objects.clear_cache()``::
+
+ # First call; current site fetched from database.
+ current_site = Site.objects.get_current()
+ # ...
+
+ # Second call; current site fetched from cache.
+ current_site = Site.objects.get_current()
+ # ...
+
+ # Force a database query for the third call.
+ Site.objects.clear_cache()
+ current_site = Site.objects.get_current()
+
The ``CurrentSiteManager``
==========================
diff --git a/docs/templates.txt b/docs/templates.txt
index ff67579b87..0c8cc79311 100644
--- a/docs/templates.txt
+++ b/docs/templates.txt
@@ -1431,4 +1431,4 @@ Read the document `The Django template language: For Python programmers`_ if
you're interested in learning the template system from a technical
perspective -- how it works and how to extend it.
-.. _The Django template language: For Python programmers: ../templates_python/
+.. _The Django template language\: For Python programmers: ../templates_python/
diff --git a/docs/templates_python.txt b/docs/templates_python.txt
index 150aa70fdf..3399639611 100644
--- a/docs/templates_python.txt
+++ b/docs/templates_python.txt
@@ -642,12 +642,12 @@ your function. Example::
"Converts a string into all lowercase"
return value.lower()
-Template filters which expect strings
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Template filters that expect strings
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-If you're writing a template filter which only expects a string as the first
-argument, you should use the included decorator ``stringfilter``. This will
-convert an object to it's string value before being passed to your function::
+If you're writing a template filter that only expects a string as the first
+argument, you should use the decorator ``stringfilter``. This will
+convert an object to its string value before being passed to your function::
from django.template.defaultfilters import stringfilter
@@ -655,6 +655,10 @@ convert an object to it's string value before being passed to your function::
def lower(value):
return value.lower()
+This way, you'll be able to pass, say, an integer to this filter, and it
+won't cause an ``AttributeError`` (because integers don't have ``lower()``
+methods).
+
Registering a custom filters
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/tests/modeltests/test_client/models.py b/tests/modeltests/test_client/models.py
index 2bc1dfe02b..32647552eb 100644
--- a/tests/modeltests/test_client/models.py
+++ b/tests/modeltests/test_client/models.py
@@ -83,23 +83,31 @@ class ClientTest(TestCase):
def test_redirect(self):
"GET a URL that redirects elsewhere"
response = self.client.get('/test_client/redirect_view/')
-
# Check that the response was a 302 (redirect)
- self.assertRedirects(response, '/test_client/get_view/')
+ self.assertRedirects(response, 'http://testserver/test_client/get_view/')
+
+ client_providing_host = Client(HTTP_HOST='django.testserver')
+ response = client_providing_host.get('/test_client/redirect_view/')
+ # Check that the response was a 302 (redirect) with absolute URI
+ self.assertRedirects(response, 'http://django.testserver/test_client/get_view/')
def test_redirect_with_query(self):
"GET a URL that redirects with given GET parameters"
response = self.client.get('/test_client/redirect_view/', {'var': 'value'})
# Check if parameters are intact
- self.assertRedirects(response, '/test_client/get_view/?var=value')
+ self.assertRedirects(response, 'http://testserver/test_client/get_view/?var=value')
def test_permanent_redirect(self):
"GET a URL that redirects permanently elsewhere"
response = self.client.get('/test_client/permanent_redirect_view/')
-
# Check that the response was a 301 (permanent redirect)
- self.assertRedirects(response, '/test_client/get_view/', status_code=301)
+ self.assertRedirects(response, 'http://testserver/test_client/get_view/', status_code=301)
+
+ client_providing_host = Client(HTTP_HOST='django.testserver')
+ response = client_providing_host.get('/test_client/permanent_redirect_view/')
+ # Check that the response was a 301 (permanent redirect) with absolute URI
+ self.assertRedirects(response, 'http://django.testserver/test_client/get_view/', status_code=301)
def test_redirect_to_strange_location(self):
"GET a URL that redirects to a non-200 page"
@@ -107,7 +115,7 @@ class ClientTest(TestCase):
# Check that the response was a 302, and that
# the attempt to get the redirection location returned 301 when retrieved
- self.assertRedirects(response, '/test_client/permanent_redirect_view/', target_status_code=301)
+ self.assertRedirects(response, 'http://testserver/test_client/permanent_redirect_view/', target_status_code=301)
def test_notfound_response(self):
"GET a URL that responds as '404:Not Found'"
@@ -231,11 +239,11 @@ class ClientTest(TestCase):
# Get the page without logging in. Should result in 302.
response = self.client.get('/test_client/login_protected_view/')
- self.assertRedirects(response, '/accounts/login/?next=/test_client/login_protected_view/')
+ self.assertRedirects(response, 'http://testserver/accounts/login/?next=/test_client/login_protected_view/')
# Log in
login = self.client.login(username='testclient', password='password')
- self.assertTrue(login, 'Could not log in')
+ self.failUnless(login, 'Could not log in')
# Request a page that requires a login
response = self.client.get('/test_client/login_protected_view/')
@@ -269,7 +277,7 @@ class ClientTest(TestCase):
# Request a page that requires a login
response = self.client.get('/test_client/login_protected_view/')
- self.assertRedirects(response, '/accounts/login/?next=/test_client/login_protected_view/')
+ self.assertRedirects(response, 'http://testserver/accounts/login/?next=/test_client/login_protected_view/')
def test_session_modifying_view(self):
"Request a page that modifies the session"
diff --git a/tests/regressiontests/forms/localflavor.py b/tests/regressiontests/forms/localflavor.py
index 01c6df8e76..1d46adfc0b 100644
--- a/tests/regressiontests/forms/localflavor.py
+++ b/tests/regressiontests/forms/localflavor.py
@@ -980,6 +980,20 @@ u'41-3562-3464'
>>> w.render('states', 'PR')
u''
+# BRStateChoiceField #########################################################
+>>> from django.contrib.localflavor.br.forms import BRStateChoiceField
+>>> f = BRStateChoiceField()
+>>> ', '.join([f.clean(s) for s, _ in f.widget.choices])
+u'AC, AL, AP, AM, BA, CE, DF, ES, GO, MA, MT, MS, MG, PA, PB, PR, PE, PI, RJ, RN, RS, RO, RR, SC, SP, SE, TO'
+>>> f.clean('')
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+>>> f.clean('pr')
+Traceback (most recent call last):
+...
+ValidationError: [u'Select a valid brazilian state. That state is not one of the available states.']
+
# DEZipCodeField ##############################################################
>>> from django.contrib.localflavor.de.forms import DEZipCodeField
diff --git a/tests/regressiontests/forms/tests.py b/tests/regressiontests/forms/tests.py
index b0a64a7854..fef8361a14 100644
--- a/tests/regressiontests/forms/tests.py
+++ b/tests/regressiontests/forms/tests.py
@@ -1623,10 +1623,6 @@ u'http://200.8.9.10:8000/test'
Traceback (most recent call last):
...
ValidationError: [u'Enter a valid URL.']
->>> f.clean('example.com')
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a valid URL.']
>>> f.clean('http://')
Traceback (most recent call last):
...
@@ -1657,10 +1653,6 @@ u'http://www.example.com'
Traceback (most recent call last):
...
ValidationError: [u'Enter a valid URL.']
->>> f.clean('example.com')
-Traceback (most recent call last):
-...
-ValidationError: [u'Enter a valid URL.']
>>> f.clean('http://')
Traceback (most recent call last):
...
@@ -1714,6 +1706,15 @@ Traceback (most recent call last):
...
ValidationError: [u'Ensure this value has at most 20 characters (it has 37).']
+URLField should prepend 'http://' if no scheme was given
+>>> f = URLField(required=False)
+>>> f.clean('example.com')
+u'http://example.com'
+>>> f.clean('')
+u''
+>>> f.clean('https://example.com')
+u'https://example.com'
+
# BooleanField ################################################################
>>> f = BooleanField()
@@ -2690,16 +2691,24 @@ to the next.
... super(Person, self).__init__(*args, **kwargs)
... if names_required:
... self.fields['first_name'].required = True
+... self.fields['first_name'].widget.attrs['class'] = 'required'
... self.fields['last_name'].required = True
+... self.fields['last_name'].widget.attrs['class'] = 'required'
>>> f = Person(names_required=False)
>>> f['first_name'].field.required, f['last_name'].field.required
(False, False)
+>>> f['first_name'].field.widget.attrs, f['last_name'].field.widget.attrs
+({}, {})
>>> f = Person(names_required=True)
>>> f['first_name'].field.required, f['last_name'].field.required
(True, True)
+>>> f['first_name'].field.widget.attrs, f['last_name'].field.widget.attrs
+({'class': 'required'}, {'class': 'required'})
>>> f = Person(names_required=False)
>>> f['first_name'].field.required, f['last_name'].field.required
(False, False)
+>>> f['first_name'].field.widget.attrs, f['last_name'].field.widget.attrs
+({}, {})
>>> class Person(Form):
... first_name = CharField(max_length=30)
... last_name = CharField(max_length=30)
diff --git a/tests/regressiontests/test_client_regress/models.py b/tests/regressiontests/test_client_regress/models.py
index a15e4f15ec..60fd909f43 100644
--- a/tests/regressiontests/test_client_regress/models.py
+++ b/tests/regressiontests/test_client_regress/models.py
@@ -119,7 +119,7 @@ class AssertRedirectsTests(TestCase):
try:
self.assertRedirects(response, '/test_client/get_view/')
except AssertionError, e:
- self.assertEquals(str(e), "Response redirected to '/test_client/get_view/?var=value', expected '/test_client/get_view/'")
+ self.assertEquals(str(e), "Response redirected to 'http://testserver/test_client/get_view/?var=value', expected '/test_client/get_view/'")
def test_incorrect_target(self):
"An assertion is raised if the response redirects to another target"
@@ -135,7 +135,7 @@ class AssertRedirectsTests(TestCase):
response = self.client.get('/test_client/double_redirect_view/')
try:
# The redirect target responds with a 301 code, not 200
- self.assertRedirects(response, '/test_client/permanent_redirect_view/')
+ self.assertRedirects(response, 'http://testserver/test_client/permanent_redirect_view/')
except AssertionError, e:
self.assertEquals(str(e), "Couldn't retrieve redirection page '/test_client/permanent_redirect_view/': response code was 301 (expected 200)")
@@ -252,7 +252,7 @@ class LoginTests(TestCase):
# Create a second client, and log in.
c = Client()
login = c.login(username='testclient', password='password')
- self.assertTrue(login, 'Could not log in')
+ self.failUnless(login, 'Could not log in')
# Get a redirection page with the second client.
response = c.get("/test_client_regress/login_protected_redirect_view/")
@@ -260,4 +260,4 @@ class LoginTests(TestCase):
# At this points, the self.client isn't logged in.
# Check that assertRedirects uses the original client, not the
# default client.
- self.assertRedirects(response, "/test_client_regress/get_view/")
+ self.assertRedirects(response, "http://testserver/test_client_regress/get_view/")
diff --git a/tests/runtests.py b/tests/runtests.py
index 85aea50180..736a9a61b3 100755
--- a/tests/runtests.py
+++ b/tests/runtests.py
@@ -4,11 +4,17 @@ import os, sys, traceback
import unittest
import django.contrib as contrib
+
+try:
+ set
+except NameError:
+ from sets import Set as set # For Python 2.3
+
+
CONTRIB_DIR_NAME = 'django.contrib'
MODEL_TESTS_DIR_NAME = 'modeltests'
REGRESSION_TESTS_DIR_NAME = 'regressiontests'
-TEST_DATABASE_NAME = 'django_test_db'
TEST_TEMPLATE_DIR = 'templates'
CONTRIB_DIR = os.path.dirname(contrib.__file__)
@@ -90,7 +96,6 @@ def django_tests(verbosity, interactive, test_labels):
old_middleware_classes = settings.MIDDLEWARE_CLASSES
# Redirect some settings for the duration of these tests.
- settings.TEST_DATABASE_NAME = TEST_DATABASE_NAME
settings.INSTALLED_APPS = ALWAYS_INSTALLED_APPS
settings.ROOT_URLCONF = 'urls'
settings.TEMPLATE_DIRS = (os.path.join(os.path.dirname(__file__), TEST_TEMPLATE_DIR),)
@@ -143,7 +148,6 @@ def django_tests(verbosity, interactive, test_labels):
# Restore the old settings.
settings.INSTALLED_APPS = old_installed_apps
- settings.TESTS_DATABASE_NAME = old_test_database_name
settings.ROOT_URLCONF = old_root_urlconf
settings.TEMPLATE_DIRS = old_template_dirs
settings.USE_I18N = old_use_i18n