summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAdrian Holovaty <adrian@holovaty.com>2007-09-05 20:01:24 +0000
committerAdrian Holovaty <adrian@holovaty.com>2007-09-05 20:01:24 +0000
commit976acdc7743180993dc8257f5d256206857e8160 (patch)
treede6c056010fac0e747043b34dcfdb5f4b7510d6d
parentbd01c6ee5703f8efec06f65374c7961458de6ea6 (diff)
newforms-admin: Merged to [6050]
git-svn-id: http://code.djangoproject.com/svn/django/branches/newforms-admin@6051 bcc190cf-cafb-0310-a4f2-bffc1f526a37
-rw-r--r--AUTHORS1
-rw-r--r--django/contrib/csrf/middleware.py2
-rw-r--r--django/contrib/localflavor/it/it_province.py2
-rw-r--r--django/core/management/base.py16
-rw-r--r--django/core/management/color.py2
-rw-r--r--django/core/management/commands/runserver.py2
-rw-r--r--django/core/management/commands/startproject.py5
-rw-r--r--django/core/management/commands/validate.py2
-rw-r--r--django/db/backends/sqlite3/base.py6
-rw-r--r--django/template/defaultfilters.py65
-rw-r--r--django/test/client.py25
-rw-r--r--django/test/testcases.py208
-rw-r--r--docs/authentication.txt2
-rw-r--r--docs/cache.txt8
-rw-r--r--docs/db-api.txt2
-rw-r--r--docs/distributions.txt35
-rw-r--r--docs/django-admin.txt3
-rw-r--r--docs/modpython.txt3
-rw-r--r--docs/templates.txt12
-rw-r--r--docs/testing.txt29
-rw-r--r--docs/tutorial01.txt4
-rw-r--r--tests/modeltests/test_client/models.py14
-rw-r--r--tests/modeltests/test_client/views.py52
-rw-r--r--tests/regressiontests/defaultfilters/tests.py19
-rw-r--r--tests/regressiontests/test_client_regress/fixtures/testdata.json20
-rw-r--r--tests/regressiontests/test_client_regress/models.py54
-rw-r--r--tests/regressiontests/test_client_regress/urls.py2
-rw-r--r--tests/regressiontests/test_client_regress/views.py12
28 files changed, 431 insertions, 176 deletions
diff --git a/AUTHORS b/AUTHORS
index 3b8a583e4a..7b20bebc92 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -260,6 +260,7 @@ answer newbie questions, and generally made Django that much better:
Vasiliy Stavenko <stavenko@gmail.com>
Thomas Steinacher <http://www.eggdrop.ch/>
nowell strite
+ Thomas Stromberg <tstromberg@google.com>
Sundance
SuperJared
Radek Švarz <http://www.svarz.cz/translate/>
diff --git a/django/contrib/csrf/middleware.py b/django/contrib/csrf/middleware.py
index 93a9484ca6..1ef0e6cefd 100644
--- a/django/contrib/csrf/middleware.py
+++ b/django/contrib/csrf/middleware.py
@@ -40,7 +40,7 @@ class CsrfMiddleware(object):
"""
def process_request(self, request):
- if request.POST:
+ if request.method == 'POST':
try:
session_id = request.COOKIES[settings.SESSION_COOKIE_NAME]
except KeyError:
diff --git a/django/contrib/localflavor/it/it_province.py b/django/contrib/localflavor/it/it_province.py
index 669ecd7f95..db041d719e 100644
--- a/django/contrib/localflavor/it/it_province.py
+++ b/django/contrib/localflavor/it/it_province.py
@@ -45,7 +45,7 @@ PROVINCE_CHOICES = (
('IM', 'Imperia'),
('IS', 'Isernia'),
('SP', 'La Spezia'),
- ('AQ', u'L’Acquila'),
+ ('AQ', u'L’Aquila'),
('LT', 'Latina'),
('LE', 'Lecce'),
('LC', 'Lecco'),
diff --git a/django/core/management/base.py b/django/core/management/base.py
index 866dec4407..978aa7902a 100644
--- a/django/core/management/base.py
+++ b/django/core/management/base.py
@@ -1,6 +1,7 @@
from django.core.exceptions import ImproperlyConfigured
from django.core.management.color import color_style
import sys
+import os
class CommandError(Exception):
pass
@@ -44,7 +45,7 @@ class BaseCommand(object):
sys.stderr.write(self.style.ERROR(str('Error: %s\n' % e)))
sys.exit(1)
- def validate(self, app=None):
+ def validate(self, app=None, display_num_errors=False):
"""
Validates the given app, raising CommandError for any errors.
@@ -61,6 +62,8 @@ class BaseCommand(object):
s.seek(0)
error_text = s.read()
raise CommandError("One or more models did not validate:\n%s" % error_text)
+ if display_num_errors:
+ print "%s error%s found" % (num_errors, num_errors != 1 and 's' or '')
def handle(self, *args, **options):
raise NotImplementedError()
@@ -119,7 +122,6 @@ class NoArgsCommand(BaseCommand):
def copy_helper(style, app_or_project, name, directory, other_name=''):
import django
- import os
import re
import shutil
other = {'project': 'app', 'app': 'project'}[app_or_project]
@@ -155,5 +157,15 @@ def copy_helper(style, app_or_project, name, directory, other_name=''):
fp_new.close()
try:
shutil.copymode(path_old, path_new)
+ _make_writeable(path_new)
except OSError:
sys.stderr.write(style.NOTICE("Notice: Couldn't set permission bits on %s. You're probably using an uncommon filesystem setup. No problem.\n" % path_new))
+
+def _make_writeable(filename):
+ "Makes sure that the file is writeable. Useful if our source is read-only."
+ import stat
+ if not os.access(filename, os.W_OK):
+ st = os.stat(filename)
+ new_permissions = stat.S_IMODE(st.st_mode) | stat.S_IWUSR
+ os.chmod(filename, new_permissions)
+
diff --git a/django/core/management/color.py b/django/core/management/color.py
index 06edb6066b..ce95401491 100644
--- a/django/core/management/color.py
+++ b/django/core/management/color.py
@@ -7,7 +7,7 @@ import sys
def color_style():
"Returns a Style object with the Django color scheme."
- if sys.platform == 'win32' or sys.platform == 'Pocket PC' or not sys.stdout.isatty():
+ if sys.platform == 'win32' or sys.platform == 'Pocket PC' or sys.platform.startswith('java') or not sys.stdout.isatty():
return no_style()
class dummy: pass
style = dummy()
diff --git a/django/core/management/commands/runserver.py b/django/core/management/commands/runserver.py
index d06744e9fa..272ca18c07 100644
--- a/django/core/management/commands/runserver.py
+++ b/django/core/management/commands/runserver.py
@@ -37,7 +37,7 @@ class Command(BaseCommand):
def inner_run():
from django.conf import settings
print "Validating models..."
- self.validate()
+ self.validate(display_num_errors=True)
print "\nDjango version %s, using settings %r" % (django.get_version(), settings.SETTINGS_MODULE)
print "Development server is running at http://%s:%s/" % (addr, port)
print "Quit the server with %s." % quit_command
diff --git a/django/core/management/commands/startproject.py b/django/core/management/commands/startproject.py
index 0d9e4bd381..ab4f409f15 100644
--- a/django/core/management/commands/startproject.py
+++ b/django/core/management/commands/startproject.py
@@ -28,11 +28,6 @@ class Command(LabelCommand):
# Create a random SECRET_KEY hash, and put it in the main settings.
main_settings_file = os.path.join(directory, project_name, 'settings.py')
settings_contents = open(main_settings_file, 'r').read()
-
- # If settings.py was copied from a read-only source, make it writeable.
- if not os.access(main_settings_file, os.W_OK):
- os.chmod(main_settings_file, 0600)
-
fp = open(main_settings_file, 'w')
secret_key = ''.join([choice('abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)') for i in range(50)])
settings_contents = re.sub(r"(?<=SECRET_KEY = ')'", secret_key + "'", settings_contents)
diff --git a/django/core/management/commands/validate.py b/django/core/management/commands/validate.py
index 2751f41abd..760d41c5bf 100644
--- a/django/core/management/commands/validate.py
+++ b/django/core/management/commands/validate.py
@@ -6,4 +6,4 @@ class Command(NoArgsCommand):
requires_model_validation = False
def handle_noargs(self, **options):
- self.validate()
+ self.validate(display_num_errors=True)
diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py
index 7919e1cc50..a482a240cf 100644
--- a/django/db/backends/sqlite3/base.py
+++ b/django/db/backends/sqlite3/base.py
@@ -1,5 +1,9 @@
"""
-SQLite3 backend for django. Requires pysqlite2 (http://pysqlite.org/).
+SQLite3 backend for django.
+
+Python 2.3 and 2.4 require pysqlite2 (http://pysqlite.org/).
+
+Python 2.5 and later use the sqlite3 module in the standard library.
"""
from django.db.backends import BaseDatabaseWrapper, BaseDatabaseFeatures, BaseDatabaseOperations, util
diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py
index 6779b9b5f2..22babfd6c0 100644
--- a/django/template/defaultfilters.py
+++ b/django/template/defaultfilters.py
@@ -355,8 +355,8 @@ def unordered_list(value):
Recursively takes a self-nested list and returns an HTML unordered list --
WITHOUT opening and closing <ul> tags.
- The list is assumed to be in the proper format. For example, if ``var`` contains
- ``['States', [['Kansas', [['Lawrence', []], ['Topeka', []]]], ['Illinois', []]]]``,
+ The list is assumed to be in the proper format. For example, if ``var``
+ contains: ``['States', ['Kansas', ['Lawrence', 'Topeka'], 'Illinois']]``,
then ``{{ var|unordered_list }}`` would return::
<li>States
@@ -371,14 +371,61 @@ def unordered_list(value):
</ul>
</li>
"""
- def _helper(value, tabs):
+ def convert_old_style_list(list_):
+ """
+ Converts old style lists to the new easier to understand format.
+
+ The old list format looked like:
+ ['Item 1', [['Item 1.1', []], ['Item 1.2', []]]
+
+ And it is converted to:
+ ['Item 1', ['Item 1.1', 'Item 1.2]]
+ """
+ if not isinstance(list_, (tuple, list)) or len(list_) != 2:
+ return list_, False
+ first_item, second_item = list_
+ if second_item == []:
+ return [first_item], True
+ old_style_list = True
+ new_second_item = []
+ for sublist in second_item:
+ item, old_style_list = convert_old_style_list(sublist)
+ if not old_style_list:
+ break
+ new_second_item.extend(item)
+ if old_style_list:
+ second_item = new_second_item
+ return [first_item, second_item], old_style_list
+ def _helper(list_, tabs=1):
indent = u'\t' * tabs
- if value[1]:
- return u'%s<li>%s\n%s<ul>\n%s\n%s</ul>\n%s</li>' % (indent, force_unicode(value[0]), indent,
- u'\n'.join([_helper(v, tabs+1) for v in value[1]]), indent, indent)
- else:
- return u'%s<li>%s</li>' % (indent, force_unicode(value[0]))
- return _helper(value, 1)
+ output = []
+
+ list_length = len(list_)
+ i = 0
+ while i < list_length:
+ title = list_[i]
+ sublist = ''
+ sublist_item = None
+ if isinstance(title, (list, tuple)):
+ sublist_item = title
+ title = ''
+ elif i < list_length - 1:
+ next_item = list_[i+1]
+ if next_item and isinstance(next_item, (list, tuple)):
+ # The next item is a sub-list.
+ sublist_item = next_item
+ # We've processed the next item now too.
+ i += 1
+ if sublist_item:
+ sublist = _helper(sublist_item, tabs+1)
+ sublist = '\n%s<ul>\n%s\n%s</ul>\n%s' % (indent, sublist,
+ indent, indent)
+ output.append('%s<li>%s%s</li>' % (indent, force_unicode(title),
+ sublist))
+ i += 1
+ return '\n'.join(output)
+ value, converted = convert_old_style_list(value)
+ return _helper(value)
###################
# INTEGERS #
diff --git a/django/test/client.py b/django/test/client.py
index 9ec3615973..c3e221554f 100644
--- a/django/test/client.py
+++ b/django/test/client.py
@@ -11,6 +11,7 @@ from django.core.handlers.wsgi import WSGIRequest
from django.core.signals import got_request_exception
from django.dispatch import dispatcher
from django.http import SimpleCookie, HttpRequest
+from django.template import TemplateDoesNotExist
from django.test import signals
from django.utils.functional import curry
from django.utils.encoding import smart_str
@@ -165,7 +166,25 @@ class Client:
# Capture exceptions created by the handler
dispatcher.connect(self.store_exc_info, signal=got_request_exception)
- response = self.handler(environ)
+ try:
+ response = self.handler(environ)
+ except TemplateDoesNotExist, e:
+ # If the view raises an exception, Django will attempt to show
+ # the 500.html template. If that template is not available,
+ # we should ignore the error in favor of re-raising the
+ # underlying exception that caused the 500 error. Any other
+ # template found to be missing during view error handling
+ # should be reported as-is.
+ if e.args != ('500.html',):
+ raise
+
+ # Look for a signalled exception and reraise it
+ if self.exc_info:
+ raise self.exc_info[1], None, self.exc_info[2]
+
+ # Save the client and request that stimulated the response
+ response.client = self
+ response.request = request
# Add any rendered template detail to the response
# If there was only one template rendered (the most likely case),
@@ -179,10 +198,6 @@ class Client:
else:
setattr(response, detail, None)
- # Look for a signalled exception and reraise it
- if self.exc_info:
- raise self.exc_info[1], None, self.exc_info[2]
-
# Update persistent cookie data
if response.cookies:
self.cookies.update(response.cookies)
diff --git a/django/test/testcases.py b/django/test/testcases.py
index 21c429271c..baa6e7bb19 100644
--- a/django/test/testcases.py
+++ b/django/test/testcases.py
@@ -1,14 +1,28 @@
-import re, unittest
-from urlparse import urlparse
+import re
+import unittest
+from urlparse import urlsplit
+
+from django.http import QueryDict
from django.db import transaction
from django.core import mail
from django.core.management import call_command
-from django.db.models import get_apps
from django.test import _doctest as doctest
from django.test.client import Client
normalize_long_ints = lambda s: re.sub(r'(?<![\w])(\d+)L(?![\w])', '\\1', s)
+def to_list(value):
+ """
+ Puts value into a list if it's not already one.
+ Returns an empty list if value is None.
+ """
+ if value is None:
+ value = []
+ elif not isinstance(value, list):
+ value = [value]
+ return value
+
+
class OutputChecker(doctest.OutputChecker):
def check_output(self, want, got, optionflags):
ok = doctest.OutputChecker.check_output(self, want, got, optionflags)
@@ -20,28 +34,27 @@ class OutputChecker(doctest.OutputChecker):
if not ok:
return normalize_long_ints(want) == normalize_long_ints(got)
return ok
-
+
class DocTestRunner(doctest.DocTestRunner):
def __init__(self, *args, **kwargs):
doctest.DocTestRunner.__init__(self, *args, **kwargs)
self.optionflags = doctest.ELLIPSIS
-
+
def report_unexpected_exception(self, out, test, example, exc_info):
- doctest.DocTestRunner.report_unexpected_exception(self,out,test,example,exc_info)
-
+ doctest.DocTestRunner.report_unexpected_exception(self, out, test,
+ example, exc_info)
# Rollback, in case of database errors. Otherwise they'd have
# side effects on other tests.
- from django.db import transaction
transaction.rollback_unless_managed()
-class TestCase(unittest.TestCase):
+class TestCase(unittest.TestCase):
def _pre_setup(self):
- """Perform any pre-test setup. This includes:
-
- * If the Test Case class has a 'fixtures' member, clearing the
- database and installing the named fixtures at the start of each test.
+ """Performs any pre-test setup. This includes:
+
+ * If the Test Case class has a 'fixtures' member, clearing the
+ database and installing the named fixtures at the start of each
+ test.
* Clearing the mail test outbox.
-
"""
call_command('flush', verbosity=0, interactive=False)
if hasattr(self, 'fixtures'):
@@ -60,97 +73,120 @@ class TestCase(unittest.TestCase):
self._pre_setup()
super(TestCase, self).__call__(result)
- def assertRedirects(self, response, expected_path, status_code=302, target_status_code=200):
- """Assert that a response redirected to a specific URL, and that the
+ def assertRedirects(self, response, expected_url, status_code=302,
+ target_status_code=200):
+ """Asserts that a response redirected to a specific URL, and that the
redirect URL can be loaded.
-
+
+ Note that assertRedirects won't work for external links since it uses
+ TestClient to do a request.
"""
- 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, params, query, fragment = urlparse(response['Location'])
- self.assertEqual(path, expected_path,
- "Response redirected to '%s', expected '%s'" % (path, expected_path))
- redirect_response = self.client.get(path)
- self.assertEqual(redirect_response.status_code, target_status_code,
- "Couldn't retrieve redirection page '%s': response code was %d (expected %d)" %
- (path, redirect_response.status_code, target_status_code))
-
+ 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
+ self.assertEqual(url, expected_url,
+ "Response redirected to '%s', expected '%s'" % (url, expected_url))
+
+ # Get the redirection page, using the same client that was used
+ # to obtain the original response.
+ redirect_response = response.client.get(path, QueryDict(query))
+ self.assertEqual(redirect_response.status_code, target_status_code,
+ ("Couldn't retrieve redirection page '%s': response code was %d"
+ " (expected %d)") %
+ (path, redirect_response.status_code, target_status_code))
+
def assertContains(self, response, text, count=None, status_code=200):
- """Assert that a response indicates that a page was retreived successfully,
- (i.e., the HTTP status code was as expected), and that ``text`` occurs ``count``
- times in the content of the response. If ``count`` is None, the count doesn't
- matter - the assertion is true if the text occurs at least once in the response.
-
+ """
+ Asserts that a response indicates that a page was retreived
+ successfully, (i.e., the HTTP status code was as expected), and that
+ ``text`` occurs ``count`` times in the content of the response.
+ If ``count`` is None, the count doesn't matter - the assertion is true
+ if the text occurs at least once in the response.
"""
self.assertEqual(response.status_code, status_code,
- "Couldn't retrieve page: Response code was %d (expected %d)'" %
+ "Couldn't retrieve page: Response code was %d (expected %d)'" %
(response.status_code, status_code))
real_count = response.content.count(text)
if count is not None:
self.assertEqual(real_count, count,
- "Found %d instances of '%s' in response (expected %d)" % (real_count, text, count))
+ "Found %d instances of '%s' in response (expected %d)" %
+ (real_count, text, count))
else:
- self.failUnless(real_count != 0, "Couldn't find '%s' in response" % text)
-
+ self.failUnless(real_count != 0,
+ "Couldn't find '%s' in response" % text)
+
def assertFormError(self, response, form, field, errors):
- "Assert that a form used to render the response has a specific field error"
- if not response.context:
- self.fail('Response did not use any contexts to render the response')
+ """
+ Asserts that a form used to render the response has a specific field
+ error.
+ """
+ # Put context(s) into a list to simplify processing.
+ contexts = to_list(response.context)
+ if not contexts:
+ self.fail('Response did not use any contexts to render the'
+ ' response')
- # If there is a single context, put it into a list to simplify processing
- if not isinstance(response.context, list):
- contexts = [response.context]
- else:
- contexts = response.context
+ # Put error(s) into a list to simplify processing.
+ errors = to_list(errors)
- # If a single error string is provided, make it a list to simplify processing
- if not isinstance(errors, list):
- errors = [errors]
-
# Search all contexts for the error.
found_form = False
for i,context in enumerate(contexts):
- if form in context:
- found_form = True
- for err in errors:
- if field:
- if field in context[form].errors:
- self.failUnless(err in context[form].errors[field],
- "The field '%s' on form '%s' in context %d does not contain the error '%s' (actual errors: %s)" %
- (field, form, i, err, list(context[form].errors[field])))
- elif field in context[form].fields:
- self.fail("The field '%s' on form '%s' in context %d contains no errors" %
- (field, form, i))
- else:
- self.fail("The form '%s' in context %d does not contain the field '%s'" % (form, i, field))
+ if form not in context:
+ continue
+ found_form = True
+ for err in errors:
+ if field:
+ if field in context[form].errors:
+ field_errors = context[form].errors[field]
+ self.failUnless(err in field_errors,
+ "The field '%s' on form '%s' in"
+ " context %d does not contain the"
+ " error '%s' (actual errors: %s)" %
+ (field, form, i, err,
+ list(field_errors)))
+ elif field in context[form].fields:
+ self.fail("The field '%s' on form '%s' in context %d"
+ " contains no errors" % (field, form, i))
else:
- self.failUnless(err in context[form].non_field_errors(),
- "The form '%s' in context %d does not contain the non-field error '%s' (actual errors: %s)" %
- (form, i, err, list(context[form].non_field_errors())))
+ self.fail("The form '%s' in context %d does not"
+ " contain the field '%s'" %
+ (form, i, field))
+ else:
+ non_field_errors = context[form].non_field_errors()
+ self.failUnless(err in non_field_errors,
+ "The form '%s' in context %d does not contain the"
+ " non-field error '%s' (actual errors: %s)" %
+ (form, i, err, non_field_errors))
if not found_form:
- self.fail("The form '%s' was not used to render the response" % form)
-
+ self.fail("The form '%s' was not used to render the response" %
+ form)
+
def assertTemplateUsed(self, response, template_name):
- "Assert that the template with the provided name was used in rendering the response"
- if isinstance(response.template, list):
- template_names = [t.name for t in response.template]
- self.failUnless(template_name in template_names,
- u"Template '%s' was not one of the templates used to render the response. Templates used: %s" %
- (template_name, u', '.join(template_names)))
- elif response.template:
- self.assertEqual(template_name, response.template.name,
- u"Template '%s' was not used to render the response. Actual template was '%s'" %
- (template_name, response.template.name))
- else:
+ """
+ Asserts that the template with the provided name was used in rendering
+ the response.
+ """
+ template_names = [t.name for t in to_list(response.template)]
+ if not template_names:
self.fail('No templates used to render the response')
+ self.failUnless(template_name in template_names,
+ (u"Template '%s' was not a template used to render the response."
+ u" Actual template(s) used: %s") % (template_name,
+ u', '.join(template_names)))
def assertTemplateNotUsed(self, response, template_name):
- "Assert that the template with the provided name was NOT used in rendering the response"
- if isinstance(response.template, list):
- self.failIf(template_name in [t.name for t in response.template],
- u"Template '%s' was used unexpectedly in rendering the response" % template_name)
- elif response.template:
- self.assertNotEqual(template_name, response.template.name,
- u"Template '%s' was used unexpectedly in rendering the response" % template_name)
-
+ """
+ Asserts that the template with the provided name was NOT used in
+ rendering the response.
+ """
+ template_names = [t.name for t in to_list(response.template)]
+ self.failIf(template_name in template_names,
+ (u"Template '%s' was used unexpectedly in rendering the"
+ u" response") % template_name)
diff --git a/docs/authentication.txt b/docs/authentication.txt
index 7860b59d7d..131a8930b5 100644
--- a/docs/authentication.txt
+++ b/docs/authentication.txt
@@ -190,7 +190,7 @@ function that comes with Django::
>>> from django.contrib.auth.models import User
>>> user = User.objects.create_user('john', 'lennon@thebeatles.com', 'johnpassword')
- # At this point, user is a User object ready to be saved
+ # At this point, user is a User object that has already been saved
# to the database. You can continue to change its attributes
# if you want to change other fields.
>>> user.is_staff = True
diff --git a/docs/cache.txt b/docs/cache.txt
index d13352b025..92b5c1b43d 100644
--- a/docs/cache.txt
+++ b/docs/cache.txt
@@ -176,9 +176,11 @@ just implements the cache interface without doing anything.
This is useful if you have a production site that uses heavy-duty caching in
various places but a development/test environment on which you don't want to
-cache. In that case, set ``CACHE_BACKEND`` to ``"dummy:///"`` in the settings
-file for your development environment. As a result, your development
-environment won't use caching and your production environment still will.
+cache. As a result, your development environment won't use caching and your
+production environment still will. To activate dummy caching, set
+``CACHE_BACKEND`` like so::
+
+ CACHE_BACKEND = 'dummy:///'
CACHE_BACKEND arguments
-----------------------
diff --git a/docs/db-api.txt b/docs/db-api.txt
index 3198f335c4..2a90b2d171 100644
--- a/docs/db-api.txt
+++ b/docs/db-api.txt
@@ -160,7 +160,7 @@ When you save an object, Django performs the following steps:
is used to provide notification that an object has been successfully
saved. (These signals are not yet documented.)
-Raw Saves
+Raw saves
~~~~~~~~~
**New in Django development version**
diff --git a/docs/distributions.txt b/docs/distributions.txt
index f9b9cbe9f8..d65e047276 100644
--- a/docs/distributions.txt
+++ b/docs/distributions.txt
@@ -15,6 +15,18 @@ repository.
.. _installing the development version: ../install/#installing-the-development-version
+FreeBSD
+=======
+
+The `FreeBSD`_ ports system offers both Django 0.96 (`py-django`_) and a more
+recent, but not current, version based on Django's trunk (`py-django-devel`_).
+These are installed in the normal FreeBSD way; for Django 0.96, for example, type:
+``cd /usr/ports/www/py-django && sudo make install clean``.
+
+.. _FreeBSD: http://www.freebsd.org/
+.. _py-django: http://www.freebsd.org/cgi/cvsweb.cgi/ports/www/py-django/
+.. _py-django-devel: http://www.freebsd.org/cgi/cvsweb.cgi/ports/www/py-django-devel/
+
Linux distributions
===================
@@ -33,17 +45,6 @@ plan to use with Django.
.. _Debian GNU/Linux: http://www.debian.org/
.. _packaged version of Django: http://packages.debian.org/stable/python/python-django
-Ubuntu
-------
-
-The Debian ``python-django`` package is also available for `Ubuntu Linux`_, in
-the "universe" repository for Ubuntu 7.04 ("Feisty Fawn"). The `current Ubuntu
-package`_ is also based on Django 0.95.1 and can be installed in the same
-fashion as for Debian.
-
-.. _Ubuntu Linux: http://www.ubuntu.com/
-.. _current Ubuntu package: http://packages.ubuntu.com/feisty/python/python-django
-
Fedora
------
@@ -65,6 +66,18 @@ The `current Gentoo build`_ can be installed by typing ``emerge django``.
.. _Gentoo Linux: http://www.gentoo.org/
.. _current Gentoo build: http://packages.gentoo.org/packages/?category=dev-python;name=django
+Ubuntu
+------
+
+The Debian ``python-django`` package is also available for `Ubuntu Linux`_, in
+the "universe" repository for Ubuntu 7.04 ("Feisty Fawn"). The `current Ubuntu
+package`_ is also based on Django 0.95.1 and can be installed in the same
+fashion as for Debian.
+
+.. _Ubuntu Linux: http://www.ubuntu.com/
+.. _current Ubuntu package: http://packages.ubuntu.com/feisty/python/python-django
+
+
Mac OS X
========
diff --git a/docs/django-admin.txt b/docs/django-admin.txt
index 366483a47a..e3d1067dd3 100644
--- a/docs/django-admin.txt
+++ b/docs/django-admin.txt
@@ -479,6 +479,9 @@ This is useful in a number of ways:
Note that this server can only run on the default port on localhost; it does
not yet accept a ``host`` or ``port`` parameter.
+Also note that it does *not* automatically detect changes to your Python source
+code (as ``runserver`` does). It does, however, detect changes to templates.
+
.. _unit tests: ../testing/
validate
diff --git a/docs/modpython.txt b/docs/modpython.txt
index c90296bd9a..5a1a3f0506 100644
--- a/docs/modpython.txt
+++ b/docs/modpython.txt
@@ -262,7 +262,8 @@ else. This is done using the PythonImport_ directive to mod_python. You need
to ensure that you have specified the ``PythonInterpreter`` directive to
mod_python as described above__ (you need to do this even if you aren't
serving multiple installations in this case). Then add the ``PythonImport``
-line inside the ``Location`` or ``VirtualHost`` section. For example::
+line in the main server configuration (i.e., outside the ``Location`` or
+``VirtualHost`` sections). For example::
PythonInterpreter my_django
PythonImport /path/to/my/project/file.py my_django
diff --git a/docs/templates.txt b/docs/templates.txt
index 6cebd3b7bd..8bfa40dc5f 100644
--- a/docs/templates.txt
+++ b/docs/templates.txt
@@ -1301,9 +1301,14 @@ unordered_list
Recursively takes a self-nested list and returns an HTML unordered list --
WITHOUT opening and closing <ul> tags.
+**Changed in Django development version**
+
+The format accepted by ``unordered_list`` has changed to an easier to
+understand format.
+
The list is assumed to be in the proper format. For example, if ``var`` contains
-``['States', [['Kansas', [['Lawrence', []], ['Topeka', []]]], ['Illinois', []]]]``,
-then ``{{ var|unordered_list }}`` would return::
+``['States', ['Kansas', ['Lawrence', 'Topeka'], 'Illinois']]``, then
+``{{ var|unordered_list }}`` would return::
<li>States
<ul>
@@ -1317,6 +1322,9 @@ then ``{{ var|unordered_list }}`` would return::
</ul>
</li>
+Note: the previous more restrictive and verbose format is still supported:
+``['States', [['Kansas', [['Lawrence', []], ['Topeka', []]]], ['Illinois', []]]]``,
+
upper
~~~~~
diff --git a/docs/testing.txt b/docs/testing.txt
index e27479cc34..22a7e48a7a 100644
--- a/docs/testing.txt
+++ b/docs/testing.txt
@@ -577,13 +577,25 @@ Specifically, a ``Response`` object has the following attributes:
=============== ==========================================================
Attribute Description
=============== ==========================================================
- ``status_code`` The HTTP status of the response, as an integer. See
- RFC2616_ for a full list of HTTP status codes.
+ ``client`` The test client that was used to make the request that
+ resulted in the response.
``content`` The body of the response, as a string. This is the final
page content as rendered by the view, or any error
message (such as the URL for a 302 redirect).
+ ``context`` The template ``Context`` instance that was used to render
+ the template that produced the response content.
+
+ If the rendered page used multiple templates, then
+ ``context`` will be a list of ``Context``
+ objects, in the order in which they were rendered.
+
+ ``request`` The request data that stimulated the response.
+
+ ``status_code`` The HTTP status of the response, as an integer. See
+ RFC2616_ for a full list of HTTP status codes.
+
``template`` The ``Template`` instance that was used to render the
final content. Use ``template.name`` to get the
template's file name, if the template was loaded from a
@@ -594,13 +606,6 @@ Specifically, a ``Response`` object has the following attributes:
using `template inheritance`_ -- then ``template`` will
be a list of ``Template`` instances, in the order in
which they were rendered.
-
- ``context`` The template ``Context`` instance that was used to render
- the template that produced the response content.
-
- As with ``template``, if the rendered page used multiple
- templates, then ``context`` will be a list of ``Context``
- objects, in the order in which they were rendered.
=============== ==========================================================
.. _RFC2616: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
@@ -826,10 +831,10 @@ useful for testing Web applications:
Asserts that the template with the given name was *not* used in rendering
the response.
-``assertRedirects(response, expected_path, status_code=302, target_status_code=200)``
+``assertRedirects(response, expected_url, status_code=302, target_status_code=200)``
Asserts that the response return a ``status_code`` redirect status,
- it redirected to ``expected_path`` and the subsequent page was received with
- ``target_status_code``.
+ it redirected to ``expected_url`` (including any GET data), and the subsequent
+ page was received with ``target_status_code``.
``assertTemplateUsed(response, template_name)``
Asserts that the template with the given name was used in rendering the
diff --git a/docs/tutorial01.txt b/docs/tutorial01.txt
index 65ea7503d7..77b5b11103 100644
--- a/docs/tutorial01.txt
+++ b/docs/tutorial01.txt
@@ -259,7 +259,7 @@ These concepts are represented by simple Python classes. Edit the
choice = models.CharField(max_length=200)
votes = models.IntegerField()
-.. adminition:: Errors about ``max_length``
+.. admonition:: Errors about ``max_length``
If Django gives you an error message saying that ``max_length`` is
not a valid argument, you're most likely using an old version of
@@ -383,7 +383,7 @@ Note the following:
the SQL to the database.
If you're interested, also run the following commands:
- * ``python manage.py validate polls`` -- Checks for any errors in the
+ * ``python manage.py validate`` -- Checks for any errors in the
construction of your models.
* ``python manage.py sqlcustom polls`` -- Outputs any custom SQL statements
diff --git a/tests/modeltests/test_client/models.py b/tests/modeltests/test_client/models.py
index e1f3987847..2bc1dfe02b 100644
--- a/tests/modeltests/test_client/models.py
+++ b/tests/modeltests/test_client/models.py
@@ -86,6 +86,13 @@ class ClientTest(TestCase):
# Check that the response was a 302 (redirect)
self.assertRedirects(response, '/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')
def test_permanent_redirect(self):
"GET a URL that redirects permanently elsewhere"
@@ -224,10 +231,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/')
+ self.assertRedirects(response, '/accounts/login/?next=/test_client/login_protected_view/')
# Log in
- self.client.login(username='testclient', password='password')
+ login = self.client.login(username='testclient', password='password')
+ self.assertTrue(login, 'Could not log in')
# Request a page that requires a login
response = self.client.get('/test_client/login_protected_view/')
@@ -261,7 +269,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/')
+ self.assertRedirects(response, '/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/modeltests/test_client/views.py b/tests/modeltests/test_client/views.py
index 81b4a2f283..e2a9081fb2 100644
--- a/tests/modeltests/test_client/views.py
+++ b/tests/modeltests/test_client/views.py
@@ -1,4 +1,5 @@
from xml.dom.minidom import parseString
+
from django.core.mail import EmailMessage, SMTPConnection
from django.template import Context, Template
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseNotFound
@@ -11,7 +12,7 @@ def get_view(request):
"A simple view that expects a GET request, and returns a rendered template"
t = Template('This is a test. {{ var }} is the value.', name='GET Template')
c = Context({'var': request.GET.get('var', 42)})
-
+
return HttpResponse(t.render(c))
def post_view(request):
@@ -28,9 +29,9 @@ def post_view(request):
else:
t = Template('Viewing GET page.', name='Empty GET Template')
c = Context()
-
+
return HttpResponse(t.render(c))
-
+
def raw_post_view(request):
"""A view which expects raw XML to be posted and returns content extracted
from the XML"""
@@ -48,7 +49,12 @@ def raw_post_view(request):
def redirect_view(request):
"A view that redirects all requests to the GET view"
- return HttpResponseRedirect('/test_client/get_view/')
+ if request.GET:
+ from urllib import urlencode
+ query = '?' + urlencode(request.GET, True)
+ else:
+ query = ''
+ return HttpResponseRedirect('/test_client/get_view/' + query)
def double_redirect_view(request):
"A view that redirects all requests to a redirection view"
@@ -72,7 +78,7 @@ class TestForm(Form):
value = fields.IntegerField()
single = fields.ChoiceField(choices=TestChoices)
multi = fields.MultipleChoiceField(choices=TestChoices)
-
+
def form_view(request):
"A view that tests a simple form"
if request.method == 'POST':
@@ -87,7 +93,7 @@ def form_view(request):
form = TestForm(request.GET)
t = Template('Viewing base form. {{ form }}.', name='Form GET Template')
c = Context({'form': form})
-
+
return HttpResponse(t.render(c))
def form_view_with_template(request):
@@ -101,26 +107,26 @@ def form_view_with_template(request):
else:
form = TestForm()
message = 'GET form page'
- return render_to_response('form_view.html',
- {
+ return render_to_response('form_view.html',
+ {
'form': form,
'message': message
}
)
-
+
def login_protected_view(request):
"A simple view that is login protected."
t = Template('This is a login protected test. Username is {{ user.username }}.', name='Login Template')
c = Context({'user': request.user})
-
+
return HttpResponse(t.render(c))
login_protected_view = login_required(login_protected_view)
def session_view(request):
"A view that modifies the session"
request.session['tobacconist'] = 'hovercraft'
-
- t = Template('This is a view that modifies the session.',
+
+ t = Template('This is a view that modifies the session.',
name='Session Modifying View Template')
c = Context()
return HttpResponse(t.render(c))
@@ -131,25 +137,25 @@ def broken_view(request):
def mail_sending_view(request):
EmailMessage(
- "Test message",
- "This is a test email",
- "from@example.com",
+ "Test message",
+ "This is a test email",
+ "from@example.com",
['first@example.com', 'second@example.com']).send()
return HttpResponse("Mail sent")
def mass_mail_sending_view(request):
m1 = EmailMessage(
- 'First Test message',
- 'This is the first test email',
- 'from@example.com',
+ 'First Test message',
+ 'This is the first test email',
+ 'from@example.com',
['first@example.com', 'second@example.com'])
m2 = EmailMessage(
- 'Second Test message',
- 'This is the second test email',
- 'from@example.com',
+ 'Second Test message',
+ 'This is the second test email',
+ 'from@example.com',
['second@example.com', 'third@example.com'])
-
+
c = SMTPConnection()
c.send_messages([m1,m2])
-
+
return HttpResponse("Mail sent")
diff --git a/tests/regressiontests/defaultfilters/tests.py b/tests/regressiontests/defaultfilters/tests.py
index a1efae66f6..9482f1cc9f 100644
--- a/tests/regressiontests/defaultfilters/tests.py
+++ b/tests/regressiontests/defaultfilters/tests.py
@@ -266,6 +266,22 @@ u'bc'
>>> slice_(u'abcdefg', u'0::2')
u'aceg'
+>>> unordered_list([u'item 1', u'item 2'])
+u'\t<li>item 1</li>\n\t<li>item 2</li>'
+
+>>> unordered_list([u'item 1', [u'item 1.1']])
+u'\t<li>item 1\n\t<ul>\n\t\t<li>item 1.1</li>\n\t</ul>\n\t</li>'
+
+>>> unordered_list([u'item 1', [u'item 1.1', u'item1.2'], u'item 2'])
+u'\t<li>item 1\n\t<ul>\n\t\t<li>item 1.1</li>\n\t\t<li>item1.2</li>\n\t</ul>\n\t</li>\n\t<li>item 2</li>'
+
+>>> unordered_list([u'item 1', [u'item 1.1', [u'item 1.1.1', [u'item 1.1.1.1']]]])
+u'\t<li>item 1\n\t<ul>\n\t\t<li>item 1.1\n\t\t<ul>\n\t\t\t<li>item 1.1.1\n\t\t\t<ul>\n\t\t\t\t<li>item 1.1.1.1</li>\n\t\t\t</ul>\n\t\t\t</li>\n\t\t</ul>\n\t\t</li>\n\t</ul>\n\t</li>'
+
+>>> unordered_list(['States', ['Kansas', ['Lawrence', 'Topeka'], 'Illinois']])
+u'\t<li>States\n\t<ul>\n\t\t<li>Kansas\n\t\t<ul>\n\t\t\t<li>Lawrence</li>\n\t\t\t<li>Topeka</li>\n\t\t</ul>\n\t\t</li>\n\t\t<li>Illinois</li>\n\t</ul>\n\t</li>'
+
+# Old format for unordered lists should still work
>>> unordered_list([u'item 1', []])
u'\t<li>item 1</li>'
@@ -275,6 +291,9 @@ u'\t<li>item 1\n\t<ul>\n\t\t<li>item 1.1</li>\n\t</ul>\n\t</li>'
>>> unordered_list([u'item 1', [[u'item 1.1', []], [u'item 1.2', []]]])
u'\t<li>item 1\n\t<ul>\n\t\t<li>item 1.1</li>\n\t\t<li>item 1.2</li>\n\t</ul>\n\t</li>'
+>>> unordered_list(['States', [['Kansas', [['Lawrence', []], ['Topeka', []]]], ['Illinois', []]]])
+u'\t<li>States\n\t<ul>\n\t\t<li>Kansas\n\t\t<ul>\n\t\t\t<li>Lawrence</li>\n\t\t\t<li>Topeka</li>\n\t\t</ul>\n\t\t</li>\n\t\t<li>Illinois</li>\n\t</ul>\n\t</li>'
+
>>> add(u'1', u'2')
3
diff --git a/tests/regressiontests/test_client_regress/fixtures/testdata.json b/tests/regressiontests/test_client_regress/fixtures/testdata.json
new file mode 100644
index 0000000000..5c9e415240
--- /dev/null
+++ b/tests/regressiontests/test_client_regress/fixtures/testdata.json
@@ -0,0 +1,20 @@
+[
+ {
+ "pk": "1",
+ "model": "auth.user",
+ "fields": {
+ "username": "testclient",
+ "first_name": "Test",
+ "last_name": "Client",
+ "is_active": true,
+ "is_superuser": false,
+ "is_staff": false,
+ "last_login": "2006-12-17 07:03:31",
+ "groups": [],
+ "user_permissions": [],
+ "password": "sha1$6efc0$f93efe9fd7542f25a7be94871ea45aa95de57161",
+ "email": "testclient@example.com",
+ "date_joined": "2006-12-17 07:03:31"
+ }
+ }
+] \ No newline at end of file
diff --git a/tests/regressiontests/test_client_regress/models.py b/tests/regressiontests/test_client_regress/models.py
index e17770cd03..a15e4f15ec 100644
--- a/tests/regressiontests/test_client_regress/models.py
+++ b/tests/regressiontests/test_client_regress/models.py
@@ -75,7 +75,7 @@ class AssertTemplateUsedTests(TestCase):
try:
self.assertTemplateUsed(response, 'Empty POST Template')
except AssertionError, e:
- self.assertEquals(str(e), "Template 'Empty POST Template' was not used to render the response. Actual template was 'Empty GET Template'")
+ self.assertEquals(str(e), "Template 'Empty POST Template' was not a template used to render the response. Actual template(s) used: Empty GET Template")
def test_multiple_context(self):
"Template assertions work when there are multiple contexts"
@@ -101,7 +101,7 @@ class AssertTemplateUsedTests(TestCase):
try:
self.assertTemplateUsed(response, "Valid POST Template")
except AssertionError, e:
- self.assertEquals(str(e), "Template 'Valid POST Template' was not one of the templates used to render the response. Templates used: form_view.html, base.html")
+ self.assertEquals(str(e), "Template 'Valid POST Template' was not a template used to render the response. Actual template(s) used: form_view.html, base.html")
class AssertRedirectsTests(TestCase):
def test_redirect_page(self):
@@ -112,6 +112,14 @@ class AssertRedirectsTests(TestCase):
self.assertRedirects(response, '/test_client/get_view/')
except AssertionError, e:
self.assertEquals(str(e), "Response didn't redirect as expected: Response code was 301 (expected 302)")
+
+ def test_lost_query(self):
+ "An assertion is raised if the redirect location doesn't preserve GET parameters"
+ response = self.client.get('/test_client/redirect_view/', {'var': 'value'})
+ 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/'")
def test_incorrect_target(self):
"An assertion is raised if the response redirects to another target"
@@ -203,8 +211,29 @@ class AssertFormErrorTests(TestCase):
self.assertFormError(response, 'form', 'email', 'Some error.')
except AssertionError, e:
self.assertEqual(str(e), "The field 'email' on form 'form' in context 0 does not contain the error 'Some error.' (actual errors: [u'Enter a valid e-mail address.'])")
+
+ def test_unknown_nonfield_error(self):
+ """
+ Checks that an assertion is raised if the form's non field errors
+ doesn't contain the provided error.
+ """
+ post_data = {
+ 'text': 'Hello World',
+ 'email': 'not an email address',
+ 'value': 37,
+ 'single': 'b',
+ 'multi': ('b','c','e')
+ }
+ response = self.client.post('/test_client/form_view/', post_data)
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, "Invalid POST Template")
+
+ try:
+ self.assertFormError(response, 'form', None, 'Some error.')
+ except AssertionError, e:
+ self.assertEqual(str(e), "The form 'form' in context 0 does not contain the non-field error 'Some error.' (actual errors: )")
-class AssertFileUploadTests(TestCase):
+class FileUploadTests(TestCase):
def test_simple_upload(self):
fd = open(os.path.join(os.path.dirname(__file__), "views.py"))
post_data = {
@@ -213,3 +242,22 @@ class AssertFileUploadTests(TestCase):
}
response = self.client.post('/test_client_regress/file_upload/', post_data)
self.assertEqual(response.status_code, 200)
+
+class LoginTests(TestCase):
+ fixtures = ['testdata']
+
+ def test_login_different_client(self):
+ "Check that using a different test client doesn't violate authentication"
+
+ # Create a second client, and log in.
+ c = Client()
+ login = c.login(username='testclient', password='password')
+ self.assertTrue(login, 'Could not log in')
+
+ # Get a redirection page with the second client.
+ response = c.get("/test_client_regress/login_protected_redirect_view/")
+
+ # 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/")
diff --git a/tests/regressiontests/test_client_regress/urls.py b/tests/regressiontests/test_client_regress/urls.py
index 160f9f992d..e771707f61 100644
--- a/tests/regressiontests/test_client_regress/urls.py
+++ b/tests/regressiontests/test_client_regress/urls.py
@@ -4,4 +4,6 @@ import views
urlpatterns = patterns('',
(r'^no_template_view/$', views.no_template_view),
(r'^file_upload/$', views.file_upload_view),
+ (r'^get_view/$', views.get_view),
+ (r'^login_protected_redirect_view/$', views.login_protected_redirect_view)
)
diff --git a/tests/regressiontests/test_client_regress/views.py b/tests/regressiontests/test_client_regress/views.py
index d3fbcf8448..75efd212e1 100644
--- a/tests/regressiontests/test_client_regress/views.py
+++ b/tests/regressiontests/test_client_regress/views.py
@@ -1,5 +1,6 @@
+from django.contrib.auth.decorators import login_required
from django.core.mail import EmailMessage, SMTPConnection
-from django.http import HttpResponse, HttpResponseServerError
+from django.http import HttpResponse, HttpResponseRedirect, HttpResponseServerError
from django.shortcuts import render_to_response
def no_template_view(request):
@@ -18,3 +19,12 @@ def file_upload_view(request):
else:
return HttpResponseServerError()
+def get_view(request):
+ "A simple login protected view"
+ return HttpResponse("Hello world")
+get_view = login_required(get_view)
+
+def login_protected_redirect_view(request):
+ "A view that redirects all requests to the GET view"
+ return HttpResponseRedirect('/test_client_regress/get_view/')
+login_protected_redirect_view = login_required(login_protected_redirect_view) \ No newline at end of file