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