1
0
mirror of https://github.com/django/django.git synced 2025-07-04 01:39:20 +00:00

[multi-db] Merge trunk to [3737]. Some tests still failing.

git-svn-id: http://code.djangoproject.com/svn/django/branches/multiple-db-support@3739 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Jason Pellerin 2006-09-08 16:35:39 +00:00
parent ae3896cb74
commit 84f7a2133c
34 changed files with 610 additions and 157 deletions

View File

@ -104,7 +104,6 @@ answer newbie questions, and generally made Django that much better:
mattycakes@gmail.com
Jason McBrayer <http://www.carcosa.net/jason/>
michael.mcewan@gmail.com
mir@noris.de
mmarshall
Eric Moritz <http://eric.themoritzfamily.com/>
Robin Munn <http://www.geekforgod.com/>
@ -121,12 +120,14 @@ answer newbie questions, and generally made Django that much better:
plisk
Daniel Poelzleithner <http://poelzi.org/>
J. Rademaker
Michael Radziej <mir@noris.de>
Brian Ray <http://brianray.chipy.org/>
rhettg@gmail.com
Oliver Rutherfurd <http://rutherfurd.net/>
Ivan Sagalaev (Maniac) <http://www.softwaremaniacs.org/>
David Schein
Pete Shinners <pete@shinners.org>
SmileyChris <smileychris@gmail.com>
sopel
Thomas Steinacher <tom@eggdrop.ch>
Radek Švarz <http://www.svarz.cz/translate/>

View File

@ -1,3 +1,4 @@
from django.contrib.admin.views.decorators import staff_member_required
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User
from django import forms, template
@ -5,6 +6,8 @@ from django.shortcuts import render_to_response
from django.http import HttpResponseRedirect
def user_add_stage(request):
if not request.user.has_perm('auth.change_user'):
raise PermissionDenied
manipulator = UserCreationForm()
if request.method == 'POST':
new_data = request.POST.copy()
@ -37,3 +40,4 @@ def user_add_stage(request):
'opts': User._meta,
'username_help_text': User._meta.get_field('username').help_text,
}, context_instance=template.RequestContext(request))
user_add_stage = staff_member_required(user_add_stage)

View File

@ -16,11 +16,11 @@ def ping_google(sitemap_url=None, ping_url=PING_URL):
if sitemap_url is None:
try:
# First, try to get the "index" sitemap URL.
sitemap_url = urlresolvers.reverse('django.contrib.sitemap.views.index')
sitemap_url = urlresolvers.reverse('django.contrib.sitemaps.views.index')
except urlresolvers.NoReverseMatch:
try:
# Next, try for the "global" sitemap URL.
sitemap_url = urlresolvers.reverse('django.contrib.sitemap.views.sitemap')
sitemap_url = urlresolvers.reverse('django.contrib.sitemaps.views.sitemap')
except urlresolvers.NoReverseMatch:
pass

View File

@ -8,7 +8,7 @@ def index(request, sitemaps):
sites = []
protocol = request.is_secure() and 'https' or 'http'
for section in sitemaps.keys():
sitemap_url = urlresolvers.reverse('django.contrib.sitemap.views.sitemap', kwargs={'section': section})
sitemap_url = urlresolvers.reverse('django.contrib.sitemaps.views.sitemap', kwargs={'section': section})
sites.append('%s://%s%s' % (protocol, current_site.domain, sitemap_url))
xml = loader.render_to_string('sitemap_index.xml', {'sitemaps': sites})
return HttpResponse(xml, mimetype='application/xml')

View File

@ -755,27 +755,32 @@ def get_validation_errors(outfile, app=None):
rel_name = RelatedObject(f.rel.to, cls, f).get_accessor_name()
rel_query_name = f.related_query_name()
for r in rel_opts.fields:
if r.name == rel_name:
e.add(opts, "Accessor for m2m field '%s' clashes with field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
if r.name == rel_query_name:
e.add(opts, "Reverse query name for m2m field '%s' clashes with field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
for r in rel_opts.many_to_many:
if r.name == rel_name:
e.add(opts, "Accessor for m2m field '%s' clashes with m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
if r.name == rel_query_name:
e.add(opts, "Reverse query name for m2m field '%s' clashes with m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
for r in rel_opts.get_all_related_many_to_many_objects():
if r.field is not f:
# If rel_name is none, there is no reverse accessor.
# (This only occurs for symmetrical m2m relations to self).
# If this is the case, there are no clashes to check for this field, as
# there are no reverse descriptors for this field.
if rel_name is not None:
for r in rel_opts.fields:
if r.name == rel_name:
e.add(opts, "Accessor for m2m field '%s' clashes with field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
if r.name == rel_query_name:
e.add(opts, "Reverse query name for m2m field '%s' clashes with field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
for r in rel_opts.many_to_many:
if r.name == rel_name:
e.add(opts, "Accessor for m2m field '%s' clashes with m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
if r.name == rel_query_name:
e.add(opts, "Reverse query name for m2m field '%s' clashes with m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
for r in rel_opts.get_all_related_many_to_many_objects():
if r.field is not f:
if r.get_accessor_name() == rel_name:
e.add(opts, "Accessor for m2m field '%s' clashes with related m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name))
if r.get_accessor_name() == rel_query_name:
e.add(opts, "Reverse query name for m2m field '%s' clashes with related m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name))
for r in rel_opts.get_all_related_objects():
if r.get_accessor_name() == rel_name:
e.add(opts, "Accessor for m2m field '%s' clashes with related m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name))
e.add(opts, "Accessor for m2m field '%s' clashes with related field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name))
if r.get_accessor_name() == rel_query_name:
e.add(opts, "Reverse query name for m2m field '%s' clashes with related m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name))
for r in rel_opts.get_all_related_objects():
if r.get_accessor_name() == rel_name:
e.add(opts, "Accessor for m2m field '%s' clashes with related field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name))
if r.get_accessor_name() == rel_query_name:
e.add(opts, "Reverse query name for m2m field '%s' clashes with related field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name))
e.add(opts, "Reverse query name for m2m field '%s' clashes with related field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name))
# Check admin attribute.
if opts.admin is not None:

View File

@ -69,23 +69,9 @@ def quote_name(name):
return name # Quoting once is enough.
return '"%s"' % name
def dictfetchone(cursor):
"Returns a row from the cursor as a dict"
# TODO: cursor.dictfetchone() doesn't exist in psycopg2,
# but no Django code uses this. Safe to remove?
return cursor.dictfetchone()
def dictfetchmany(cursor, number):
"Returns a certain number of rows from a cursor as a dict"
# TODO: cursor.dictfetchmany() doesn't exist in psycopg2,
# but no Django code uses this. Safe to remove?
return cursor.dictfetchmany(number)
def dictfetchall(cursor):
"Returns all rows from a cursor as a dict"
# TODO: cursor.dictfetchall() doesn't exist in psycopg2,
# but no Django code uses this. Safe to remove?
return cursor.dictfetchall()
dictfetchone = util.dictfetchone
dictfetchmany = util.dictfetchmany
dictfetchall = util.dictfetchall
def get_last_insert_id(cursor, table_name, pk_name):
cursor.execute("SELECT CURRVAL('\"%s_%s_seq\"')" % (table_name, pk_name))

View File

@ -63,7 +63,10 @@ class DatabaseWrapper(local):
self.connection.rollback()
def close(self):
if self.connection is not None:
from django.conf import settings
# If database is in memory, closing the connection destroys the database.
# To prevent accidental data loss, ignore close requests on an in-memory db.
if self.connection is not None and settings.DATABASE_NAME != ":memory:":
self.connection.close()
self.connection = None

View File

@ -131,6 +131,9 @@ class RelatedObject(object):
# many-to-many objects. It uses the lower-cased object_name + "_set",
# but this can be overridden with the "related_name" option.
if self.field.rel.multiple:
# If this is a symmetrical m2m relation on self, there is no reverse accessor.
if getattr(self.field.rel, 'symmetrical', False) and self.model == self.parent_model:
return None
return self.field.rel.related_name or (self.opts.object_name.lower() + '_set')
else:
return self.field.rel.related_name or (self.opts.object_name.lower())

View File

@ -137,13 +137,14 @@ class StringOrigin(Origin):
return self.source
class Template(object):
def __init__(self, template_string, origin=None):
def __init__(self, template_string, origin=None, name='<Unknown Template>'):
"Compilation stage"
if settings.TEMPLATE_DEBUG and origin == None:
origin = StringOrigin(template_string)
# Could do some crazy stack-frame stuff to record where this string
# came from...
self.nodelist = compile_string(template_string, origin)
self.name = name
def __iter__(self):
for node in self.nodelist:
@ -434,7 +435,7 @@ class TokenParser(object):
while i < len(subject) and subject[i] != subject[p]:
i += 1
if i >= len(subject):
raise TemplateSyntaxError, "Searching for value. Unexpected end of string in column %d: %s" % subject
raise TemplateSyntaxError, "Searching for value. Unexpected end of string in column %d: %s" % (i, subject)
i += 1
res = subject[p:i]
while i < len(subject) and subject[i] in (' ', '\t'):
@ -548,9 +549,12 @@ class FilterExpression(object):
obj = resolve_variable(self.var, context)
except VariableDoesNotExist:
if ignore_failures:
return None
obj = None
else:
return settings.TEMPLATE_STRING_IF_INVALID
if settings.TEMPLATE_STRING_IF_INVALID:
return settings.TEMPLATE_STRING_IF_INVALID
else:
obj = settings.TEMPLATE_STRING_IF_INVALID
for func, args in self.filters:
arg_vals = []
for lookup, arg in args:

View File

@ -86,7 +86,7 @@ class ForNode(Node):
parentloop = {}
context.push()
try:
values = self.sequence.resolve(context)
values = self.sequence.resolve(context, True)
except VariableDoesNotExist:
values = []
if values is None:
@ -212,13 +212,13 @@ class RegroupNode(Node):
self.var_name = var_name
def render(self, context):
obj_list = self.target.resolve(context)
if obj_list == '': # target_var wasn't found in context; fail silently
obj_list = self.target.resolve(context, True)
if obj_list == None: # target_var wasn't found in context; fail silently
context[self.var_name] = []
return ''
output = [] # list of dictionaries in the format {'grouper': 'key', 'list': [list of contents]}
for obj in obj_list:
grouper = self.expression.resolve(Context({'var': obj}))
grouper = self.expression.resolve(Context({'var': obj}), True)
# TODO: Is this a sensible way to determine equality?
if output and repr(output[-1]['grouper']) == repr(grouper):
output[-1]['list'].append(obj)

View File

@ -51,7 +51,7 @@ class ExtendsNode(Node):
error_msg += " Got this from the %r variable." % self.parent_name_expr #TODO nice repr.
raise TemplateSyntaxError, error_msg
if hasattr(parent, 'render'):
return parent
return parent # parent is a Template object
try:
source, origin = find_template_source(parent, self.template_dirs)
except TemplateDoesNotExist:

View File

@ -1,10 +1,9 @@
from cStringIO import StringIO
from django.contrib.admin.views.decorators import LOGIN_FORM_KEY, _encode_post_data
from django.core.handlers.base import BaseHandler
from django.core.handlers.wsgi import WSGIRequest
from django.dispatch import dispatcher
from django.http import urlencode, SimpleCookie
from django.template import signals
from django.test import signals
from django.utils.functional import curry
class ClientHandler(BaseHandler):
@ -96,7 +95,7 @@ class Client:
HTML rendered to the end-user.
"""
def __init__(self, **defaults):
self.handler = TestHandler()
self.handler = ClientHandler()
self.defaults = defaults
self.cookie = SimpleCookie()
@ -126,7 +125,7 @@ class Client:
data = {}
on_template_render = curry(store_rendered_templates, data)
dispatcher.connect(on_template_render, signal=signals.template_rendered)
response = self.handler(environ)
# Add any rendered template detail to the response
@ -180,29 +179,38 @@ class Client:
def login(self, path, username, password, **extra):
"""
A specialized sequence of GET and POST to log into a view that
is protected by @login_required or a similar access decorator.
is protected by a @login_required access decorator.
path should be the URL of the login page, or of any page that
is login protected.
path should be the URL of the page that is login protected.
Returns True if login was successful; False if otherwise.
Returns the response from GETting the requested URL after
login is complete. Returns False if login process failed.
"""
# First, GET the login page.
# This is required to establish the session.
# First, GET the page that is login protected.
# This page will redirect to the login page.
response = self.get(path)
if response.status_code != 302:
return False
login_path, data = response['Location'].split('?')
next = data.split('=')[1]
# Second, GET the login page; required to set up cookies
response = self.get(login_path, **extra)
if response.status_code != 200:
return False
# Set up the block of form data required by the login page.
# Last, POST the login data.
form_data = {
'username': username,
'password': password,
'this_is_the_login_form': 1,
'post_data': _encode_post_data({LOGIN_FORM_KEY: 1})
'next' : next,
}
response = self.post(path, data=form_data, **extra)
# login page should give response 200 (if you requested the login
# page specifically), or 302 (if you requested a login
# protected page, to which the login can redirect).
return response.status_code in (200,302)
response = self.post(login_path, data=form_data, **extra)
# Login page should 302 redirect to the originally requested page
if response.status_code != 302 or response['Location'] != path:
return False
# Since we are logged in, request the actual page again
return self.get(path)

1
django/test/signals.py Normal file
View File

@ -0,0 +1 @@
template_rendered = object()

View File

@ -1,6 +1,7 @@
import unittest, doctest
from django.conf import settings
from django.core import management
from django.test.utils import setup_test_environment, teardown_test_environment
from django.test.utils import create_test_db, destroy_test_db
from django.test.testcases import OutputChecker, DocTestRunner
@ -51,6 +52,7 @@ def run_tests(module_list, verbosity=1, extra_tests=[]):
the module. A list of 'extra' tests may also be provided; these tests
will be added to the test suite.
"""
setup_test_environment()
settings.DEBUG = False
suite = unittest.TestSuite()
@ -66,3 +68,5 @@ def run_tests(module_list, verbosity=1, extra_tests=[]):
management.syncdb(verbosity, interactive=False)
unittest.TextTestRunner(verbosity=verbosity).run(suite)
destroy_test_db(old_name, verbosity)
teardown_test_environment()

View File

@ -1,11 +1,41 @@
import sys, time
from django.conf import settings
from django.db import backend, connect, connection, connection_info, connections
from django.dispatch import dispatcher
from django.test import signals
from django.template import Template
# The prefix to put on the default database name when creating
# the test database.
TEST_DATABASE_PREFIX = 'test_'
def instrumented_test_render(self, context):
"""An instrumented Template render method, providing a signal
that can be intercepted by the test system Client
"""
dispatcher.send(signal=signals.template_rendered, sender=self, template=self, context=context)
return self.nodelist.render(context)
def setup_test_environment():
"""Perform any global pre-test setup. This involves:
- Installing the instrumented test renderer
"""
Template.original_render = Template.render
Template.render = instrumented_test_render
def teardown_test_environment():
"""Perform any global post-test teardown. This involves:
- Restoring the original test renderer
"""
Template.render = Template.original_render
del Template.original_render
def _set_autocommit(connection):
"Make sure a connection is in autocommit mode."
if hasattr(connection.connection, "autocommit"):
@ -55,12 +85,13 @@ def create_test_db(verbosity=1, autoclobber=False):
else:
print "Tests cancelled."
sys.exit(1)
# Close the old connection
connection.close()
connection.close()
settings.DATABASE_NAME = TEST_DATABASE_NAME
# Get a cursor (even though we don't need one yet). This has
# the side effect of initializing the test database.
cursor = connection.cursor()
# Get a cursor (even though we don't need one yet). This has
# the side effect of initializing the test database.
cursor = connection.cursor()
# Fill OTHER_DATABASES with the TEST_DATABASES settings,
# and connect each named connection to the test database, using
@ -87,15 +118,14 @@ def destroy_test_db(old_database_name, old_databases, verbosity=1):
# connected to it.
if verbosity >= 1:
print "Destroying test database..."
if settings.DATABASE_ENGINE != "sqlite3":
connection.close()
TEST_DATABASE_NAME = settings.DATABASE_NAME
settings.DATABASE_NAME = old_database_name
connection.close()
TEST_DATABASE_NAME = settings.DATABASE_NAME
settings.DATABASE_NAME = old_database_name
if settings.DATABASE_ENGINE != "sqlite3":
settings.OTHER_DATABASES = old_databases
cursor = connection.cursor()
_set_autocommit(connection)
time.sleep(1) # To avoid "database is being accessed by other users" errors.
cursor.execute("DROP DATABASE %s" % backend.quote_name(TEST_DATABASE_NAME))
connection.close()

View File

@ -1,6 +1,6 @@
def curry(*args, **kwargs):
def curry(_curried_func, *args, **kwargs):
def _curried(*moreargs, **morekwargs):
return args[0](*(args[1:]+moreargs), **dict(kwargs.items() + morekwargs.items()))
return _curried_func(*(args+moreargs), **dict(kwargs, **morekwargs))
return _curried
class Promise:

View File

@ -115,7 +115,7 @@ def technical_500_response(request, exc_type, exc_value, tb):
'function': '?',
'lineno': '?',
}]
t = Template(TECHNICAL_500_TEMPLATE)
t = Template(TECHNICAL_500_TEMPLATE, name='Technical 500 template')
c = Context({
'exception_type': exc_type.__name__,
'exception_value': exc_value,
@ -141,7 +141,7 @@ def technical_404_response(request, exception):
# tried exists but is an empty list. The URLconf must've been empty.
return empty_urlconf(request)
t = Template(TECHNICAL_404_TEMPLATE)
t = Template(TECHNICAL_404_TEMPLATE, name='Technical 404 template')
c = Context({
'root_urlconf': settings.ROOT_URLCONF,
'urlpatterns': tried,
@ -154,7 +154,7 @@ def technical_404_response(request, exception):
def empty_urlconf(request):
"Create an empty URLconf 404 error response."
t = Template(EMPTY_URLCONF_TEMPLATE)
t = Template(EMPTY_URLCONF_TEMPLATE, name='Empty URLConf template')
c = Context({
'project_name': settings.SETTINGS_MODULE.split('.')[0]
})
@ -189,7 +189,7 @@ TECHNICAL_500_TEMPLATE = """
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<meta name="robots" content="NONE,NOARCHIVE" />
<title>{{ exception_type }} at {{ request.path }}</title>
<title>{{ exception_type }} at {{ request.path|escape }}</title>
<style type="text/css">
html * { padding:0; margin:0; }
body * { padding:10px 20px; }
@ -292,7 +292,7 @@ TECHNICAL_500_TEMPLATE = """
<body>
<div id="summary">
<h1>{{ exception_type }} at {{ request.path }}</h1>
<h1>{{ exception_type }} at {{ request.path|escape }}</h1>
<h2>{{ exception_value|escape }}</h2>
<table class="meta">
<tr>
@ -301,7 +301,7 @@ TECHNICAL_500_TEMPLATE = """
</tr>
<tr>
<th>Request URL:</th>
<td>{{ request_protocol }}://{{ request.META.HTTP_HOST }}{{ request.path }}</td>
<td>{{ request_protocol }}://{{ request.META.HTTP_HOST }}{{ request.path|escape }}</td>
</tr>
<tr>
<th>Exception Type:</th>
@ -309,7 +309,7 @@ TECHNICAL_500_TEMPLATE = """
</tr>
<tr>
<th>Exception Value:</th>
<td>{{ exception_value }}</td>
<td>{{ exception_value|escape }}</td>
</tr>
<tr>
<th>Exception Location:</th>
@ -412,7 +412,7 @@ Traceback (most recent call last):<br/>
&nbsp;&nbsp;{{ frame.lineno }}. {{ frame.context_line|escape }}<br/>
{% endif %}
{% endfor %}<br/>
&nbsp;&nbsp;{{ exception_type }} at {{ request.path }}<br/>
&nbsp;&nbsp;{{ exception_type }} at {{ request.path|escape }}<br/>
&nbsp;&nbsp;{{ exception_value|escape }}</code>
</td>
</tr>
@ -546,7 +546,7 @@ TECHNICAL_404_TEMPLATE = """
<html lang="en">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>Page not found at {{ request.path }}</title>
<title>Page not found at {{ request.path|escape }}</title>
<meta name="robots" content="NONE,NOARCHIVE" />
<style type="text/css">
html * { padding:0; margin:0; }
@ -576,7 +576,7 @@ TECHNICAL_404_TEMPLATE = """
</tr>
<tr>
<th>Request URL:</th>
<td>{{ request_protocol }}://{{ request.META.HTTP_HOST }}{{ request.path }}</td>
<td>{{ request_protocol }}://{{ request.META.HTTP_HOST }}{{ request.path|escape }}</td>
</tr>
</table>
</div>
@ -591,7 +591,7 @@ TECHNICAL_404_TEMPLATE = """
<li>{{ pattern|escape }}</li>
{% endfor %}
</ol>
<p>The current URL, <code>{{ request.path }}</code>, didn't match any of these.</p>
<p>The current URL, <code>{{ request.path|escape }}</code>, didn't match any of these.</p>
{% else %}
<p>{{ reason|escape }}</p>
{% endif %}

View File

@ -81,7 +81,7 @@ def directory_index(path, fullpath):
try:
t = loader.get_template('static/directory_index')
except TemplateDoesNotExist:
t = Template(DEFAULT_DIRECTORY_INDEX_TEMPLATE)
t = Template(DEFAULT_DIRECTORY_INDEX_TEMPLATE, name='Default directory index template')
files = []
for f in os.listdir(fullpath):
if not f.startswith('.'):

View File

@ -707,7 +707,7 @@ This example creates an Atom 1.0 feed and prints it to standard output::
... title=u"My Weblog",
... link=u"http://www.example.com/",
... description=u"In which I write about what I ate today.",
... language=u"en"),
... language=u"en")
>>> f.add_item(title=u"Hot dog today",
... link=u"http://www.example.com/entries/1/",
... description=u"<p>Today I had a Vienna Beef hot dog. It was pink, plump and perfect.</p>")

View File

@ -198,9 +198,19 @@ some things to keep in mind:
How invalid variables are handled
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If a variable doesn't exist, the template system inserts the value of the
``TEMPLATE_STRING_IF_INVALID`` setting, which is set to ``''`` (the empty
string) by default.
Generally, if a variable doesn't exist, the template system inserts the
value of the ``TEMPLATE_STRING_IF_INVALID`` setting, which is set to ``''``
(the empty string) by default.
Filters that are applied to an invalid variable will only be applied if
``TEMPLATE_STRING_IF_INVALID`` is set to ``''`` (the empty string). If
``TEMPLATE_STRING_IF_INVALID`` is set to any other value, variable
filters will be ignored.
This behavior is slightly different for the ``if``, ``for`` and ``regroup``
template tags. If an invalid variable is provided to one of these template
tags, the variable will be interpreted as ``None``. Filters are always
applied to invalid variables within these template tags.
Playing with Context objects
----------------------------

View File

@ -92,7 +92,8 @@ Writing unittests
Like doctests, Django's unit tests use a standard library module: unittest_.
As with doctests, Django's test runner looks for any unit test cases defined
in ``models.py``, or in a ``tests.py`` file in your application directory.
in ``models.py``, or in a ``tests.py`` file stored in the application
directory.
An equivalent unittest test case for the above example would look like::
@ -110,8 +111,9 @@ An equivalent unittest test case for the above example would look like::
self.assertEquals(self.cat.speak(), 'The cat says "meow"')
When you `run your tests`_, the test utility will find all the test cases
(that is, subclasses of ``unittest.TestCase``) in ``tests.py``, automatically
build a test suite out of those test cases, and run that suite.
(that is, subclasses of ``unittest.TestCase``) in ``models.py`` and
``tests.py``, automatically build a test suite out of those test cases,
and run that suite.
For more details about ``unittest``, see the `standard library unittest
documentation`_.
@ -159,20 +161,164 @@ Again, remember that you can use both systems side-by-side (even in the same
app). In the end, most projects will eventually end up using both; each shines
in different circumstances.
Testing utilities
=================
Testing Tools
=============
To assist in testing various features of your application, Django provides
tools that can be used to establish tests and test conditions.
* `Test Client`_
* Fixtures_
Test Client
-----------
A dummy browser; instruments the template generation process...
The Test Client is a simple dummy browser. It allows you to simulate
GET and POST requests on a URL, and observe the response that is received.
This allows you to test that the correct view is executed for a given URL,
and that the view constructs the correct response.
As the response is generated, the Test Client gathers details on the
Template and Context objects that were used to generate the response. These
Templates and Contexts are then provided as part of the response, and can be
used as test conditions.
.. admonition:: Test Client vs Browser Automation?
The Test Client is not intended as a replacement for Twill_, Selenium_,
or other browser automation frameworks - it is intended to allow
testing of the contexts and templates produced by a view,
rather than the HTML rendered to the end-user.
A comprehensive test suite should use a combination of both: Test Client
tests to establish that the correct view is being called and that
the view is collecting the correct context data, and Browser Automation
tests to check that user interface behaves as expected.
.. _Twill: http://twill.idyll.org/
.. _Selenium: http://www.openqa.org/selenium/
The Test Client is stateful; if a cookie is returned as part of a response,
that cookie is provided as part of the next request issued to that Client
instance. Expiry policies for these cookies are not followed; if you want
a cookie to expire, either delete it manually from ``client.cookies``, or
create a new Client instance (which will effectively delete all cookies).
Making requests
~~~~~~~~~~~~~~~
Creating an instance of ``Client`` (``django.test.client.Client``) requires
no arguments at time of construction. Once constructed, the following methods
can be invoked on the ``Client`` instance.
``get(path, data={})``
Make a GET request on the provided ``path``. The key-value pairs in the
data dictionary will be used to create a GET data payload. For example::
c = Client()
c.get('/customers/details/', {'name':'fred', 'age':7})
will result in the evaluation of a GET request equivalent to::
http://yoursite.com/customers/details/?name='fred'&age=7
``post(path, data={})``
Make a POST request on the provided ``path``. The key-value pairs in the
data dictionary will be used to create the POST data payload. This payload
will be transmitted with the mimetype ``multipart/form-data``.
However submitting files is a special case. To POST a file, you need only
provide the file field name as a key, and a file handle to the file you wish to
upload as a value. The Test Client will populate the two POST fields (i.e.,
``field`` and ``field_file``) required by FileField. For example::
c = Client()
f = open('wishlist.doc')
c.post('/customers/wishes/', {'name':'fred', 'attachment':f})
f.close()
will result in the evaluation of a POST request on ``/customers/wishes/``,
with a POST dictionary that contains `name`, `attachment` (containing the
file name), and `attachment_file` (containing the file data). Note that you
need to manually close the file after it has been provided to the POST.
``login(path, username, password)``
In a production site, it is likely that some views will be protected with
the @login_required URL provided by ``django.contrib.auth``. Interacting
with a URL that has been login protected is a slightly complex operation,
so the Test Client provides a simple URL to automate the login process. A
call to ``login()`` stimulates the series of GET and POST calls required
to log a user into a @login_required protected URL.
If login is possible, the final return value of ``login()`` is the response
that is generated by issuing a GET request on the protected URL. If login
is not possible, ``login()`` returns False.
Note that since the test suite will be executed using the test database,
which contains no users by default. As a result, logins for your production
site will not work. You will need to create users as part of the test suite
to be able to test logins to your application.
Testing Responses
~~~~~~~~~~~~~~~~~
The ``get()``, ``post()`` and ``login()`` methods all return a Response
object. This Response object has the following properties that can be used
for testing purposes:
=============== ==========================================================
Property Description
=============== ==========================================================
``status_code`` The HTTP status of the response. See RFC2616_ for a
full list of HTTP status codes.
``content`` The body of the response. The is the final page
content as rendered by the view, or any error message
(such as the URL for a 302 redirect).
``template`` The Template instance that was used to render the final
content. Testing ``template.name`` can be particularly
useful; if the template was loaded from a file,
``template.name`` will be the file name that was loaded.
If multiple templates were rendered, (e.g., if one
template includes another template),``template`` will
be a list of Template objects, in the order in which
they were rendered.
``context`` The Context that was used to render the template that
produced the response content.
As with ``template``, if multiple templates were rendered
``context`` will be a list of Context objects, stored in
the order in which they were rendered.
=============== ==========================================================
.. _RFC2616: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
The following is a simple unit test using the Test Client::
import unittest
from django.test.client import Client
class SimpleTest(unittest.TestCase):
def setUp(self):
# Every test needs a client
self.client = Client()
def test_details(self):
# Issue a GET request
response = self.client.get('/customer/details/')
# Check that the respose is 200 OK
self.failUnlessEqual(response.status_code, 200)
# Check that the rendered context contains 5 customers
self.failUnlessEqual(len(response.context['customers']), 5)
Fixtures
--------
Feature still to come...
Running tests
=============
@ -245,11 +391,13 @@ When you run ``./manage.py test``, Django looks at the ``TEST_RUNNER``
setting to determine what to do. By default, ``TEST_RUNNER`` points to ``django.test.simple.run_tests``. This method defines the default Django
testing behaviour. This behaviour involves:
#. Performing global pre-test setup
#. Creating the test database
#. Running ``syncdb`` to install models and initial data into the test database
#. Looking for Unit Tests and Doctests in ``models.py`` and ``tests.py`` file for each installed application
#. Running the Unit Tests and Doctests that are found
#. Destroying the test database.
#. Performing global post-test teardown
If you define your own test runner method and point ``TEST_RUNNER``
at that method, Django will execute your test runner whenever you run
@ -263,14 +411,12 @@ can call it anything you want. The only requirement is that it accept two
arguments:
``run_tests(module_list, verbosity=1)``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The module list is the list of Python modules that contain the models to be
tested. This is the same format returned by ``django.db.models.get_apps()``
The module list is the list of Python modules that contain the models to be
tested. This is the same format returned by ``django.db.models.get_apps()``
Verbosity determines the amount of notification and debug information that
will be printed to the console; '0' is no output, '1' is normal output,
and `2` is verbose output.
Verbosity determines the amount of notification and debug information that
will be printed to the console; '0' is no output, '1' is normal output,
and `2` is verbose output.
Testing utilities
-----------------
@ -278,26 +424,30 @@ Testing utilities
To assist in the creation of your own test runner, Django provides
a number of utility methods in the ``django.test.utils`` module.
``create_test_db(verbosity=1, autoclobber=False)``:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
``setup_test_environment()``
Performs any global pre-test setup, such as the installing the
instrumentation of the template rendering system.
Creates a new test database, and run ``syncdb`` against it.
``teardown_test_environment()``
Performs any global post-test teardown, such as removing the instrumentation
of the template rendering system.
``verbosity`` has the same behaviour as in the test runner.
``create_test_db(verbosity=1, autoclobber=False)``
Creates a new test database, and run ``syncdb`` against it.
``Autoclobber`` describes the behavior that will occur if a database with
the same name as the test database is discovered. If ``autoclobber`` is False,
the user will be asked to approve destroying the existing database. ``sys.exit``
is called if the user does not approve. If autoclobber is ``True``, the database
will be destroyed without consulting the user.
``verbosity`` has the same behaviour as in the test runner.
``create_test_db()`` has the side effect of modifying
``settings.DATABASE_NAME`` to match the name of the test database.
``Autoclobber`` describes the behavior that will occur if a database with
the same name as the test database is discovered. If ``autoclobber`` is False,
the user will be asked to approve destroying the existing database. ``sys.exit``
is called if the user does not approve. If autoclobber is ``True``, the database
will be destroyed without consulting the user.
``destroy_test_db(old_database_name, verbosity=1)``:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
``create_test_db()`` has the side effect of modifying
``settings.DATABASE_NAME`` to match the name of the test database.
Destroys the database with the name ``settings.DATABASE_NAME`` matching,
and restores the value of ``settings.DATABASE_NAME`` to the provided name.
``destroy_test_db(old_database_name, verbosity=1)``
Destroys the database with the name ``settings.DATABASE_NAME`` matching,
and restores the value of ``settings.DATABASE_NAME`` to the provided name.
``verbosity`` has the same behaviour as in the test runner.
``verbosity`` has the same behaviour as in the test runner.

View File

@ -68,15 +68,34 @@ class SelfClashForeign(models.Model):
foreign_1 = models.ForeignKey("SelfClashForeign", related_name='id')
foreign_2 = models.ForeignKey("SelfClashForeign", related_name='src_safe')
class ValidM2M(models.Model):
src_safe = models.CharField(maxlength=10)
validm2m = models.CharField(maxlength=10)
# M2M fields are symmetrical by default. Symmetrical M2M fields
# on self don't require a related accessor, so many potential
# clashes are avoided.
validm2m_set = models.ManyToManyField("ValidM2M")
m2m_1 = models.ManyToManyField("ValidM2M", related_name='id')
m2m_2 = models.ManyToManyField("ValidM2M", related_name='src_safe')
m2m_3 = models.ManyToManyField('self')
m2m_4 = models.ManyToManyField('self')
class SelfClashM2M(models.Model):
src_safe = models.CharField(maxlength=10)
selfclashm2m = models.CharField(maxlength=10)
selfclashm2m_set = models.ManyToManyField("SelfClashM2M")
m2m_1 = models.ManyToManyField("SelfClashM2M", related_name='id')
m2m_2 = models.ManyToManyField("SelfClashM2M", related_name='src_safe')
# Non-symmetrical M2M fields _do_ have related accessors, so
# there is potential for clashes.
selfclashm2m_set = models.ManyToManyField("SelfClashM2M", symmetrical=False)
m2m_1 = models.ManyToManyField("SelfClashM2M", related_name='id', symmetrical=False)
m2m_2 = models.ManyToManyField("SelfClashM2M", related_name='src_safe', symmetrical=False)
m2m_3 = models.ManyToManyField('self', symmetrical=False)
m2m_4 = models.ManyToManyField('self', symmetrical=False)
model_errors = """invalid_models.fielderrors: "charfield": CharFields require a "maxlength" attribute.
invalid_models.fielderrors: "floatfield": FloatFields require a "decimal_places" attribute.
@ -147,9 +166,17 @@ invalid_models.selfclashforeign: Accessor for field 'foreign_2' clashes with fie
invalid_models.selfclashforeign: Reverse query name for field 'foreign_2' clashes with field 'SelfClashForeign.src_safe'. Add a related_name argument to the definition for 'foreign_2'.
invalid_models.selfclashm2m: Accessor for m2m field 'selfclashm2m_set' clashes with m2m field 'SelfClashM2M.selfclashm2m_set'. Add a related_name argument to the definition for 'selfclashm2m_set'.
invalid_models.selfclashm2m: Reverse query name for m2m field 'selfclashm2m_set' clashes with field 'SelfClashM2M.selfclashm2m'. Add a related_name argument to the definition for 'selfclashm2m_set'.
invalid_models.selfclashm2m: Accessor for m2m field 'selfclashm2m_set' clashes with related m2m field 'SelfClashM2M.selfclashm2m_set'. Add a related_name argument to the definition for 'selfclashm2m_set'.
invalid_models.selfclashm2m: Accessor for m2m field 'm2m_1' clashes with field 'SelfClashM2M.id'. Add a related_name argument to the definition for 'm2m_1'.
invalid_models.selfclashm2m: Accessor for m2m field 'm2m_2' clashes with field 'SelfClashM2M.src_safe'. Add a related_name argument to the definition for 'm2m_2'.
invalid_models.selfclashm2m: Reverse query name for m2m field 'm2m_1' clashes with field 'SelfClashM2M.id'. Add a related_name argument to the definition for 'm2m_1'.
invalid_models.selfclashm2m: Reverse query name for m2m field 'm2m_2' clashes with field 'SelfClashM2M.src_safe'. Add a related_name argument to the definition for 'm2m_2'.
invalid_models.selfclashm2m: Accessor for m2m field 'm2m_3' clashes with m2m field 'SelfClashM2M.selfclashm2m_set'. Add a related_name argument to the definition for 'm2m_3'.
invalid_models.selfclashm2m: Accessor for m2m field 'm2m_3' clashes with related m2m field 'SelfClashM2M.selfclashm2m_set'. Add a related_name argument to the definition for 'm2m_3'.
invalid_models.selfclashm2m: Accessor for m2m field 'm2m_3' clashes with related m2m field 'SelfClashM2M.selfclashm2m_set'. Add a related_name argument to the definition for 'm2m_3'.
invalid_models.selfclashm2m: Accessor for m2m field 'm2m_4' clashes with m2m field 'SelfClashM2M.selfclashm2m_set'. Add a related_name argument to the definition for 'm2m_4'.
invalid_models.selfclashm2m: Accessor for m2m field 'm2m_4' clashes with related m2m field 'SelfClashM2M.selfclashm2m_set'. Add a related_name argument to the definition for 'm2m_4'.
invalid_models.selfclashm2m: Accessor for m2m field 'm2m_4' clashes with related m2m field 'SelfClashM2M.selfclashm2m_set'. Add a related_name argument to the definition for 'm2m_4'.
invalid_models.selfclashm2m: Reverse query name for m2m field 'm2m_3' clashes with field 'SelfClashM2M.selfclashm2m'. Add a related_name argument to the definition for 'm2m_3'.
invalid_models.selfclashm2m: Reverse query name for m2m field 'm2m_4' clashes with field 'SelfClashM2M.selfclashm2m'. Add a related_name argument to the definition for 'm2m_4'.
"""

View File

View File

@ -0,0 +1,10 @@
from django.dispatch import dispatcher
from django.db.models import signals
import models as test_client_app
from django.contrib.auth.models import User
def setup_test(app, created_models, verbosity):
# Create a user account for the login-based tests
User.objects.create_user('testclient','testclient@example.com', 'password')
dispatcher.connect(setup_test, sender=test_client_app, signal=signals.post_syncdb)

View File

@ -0,0 +1,101 @@
"""
39. Testing using the Test Client
The test client is a class that can act like a simple
browser for testing purposes.
It allows the user to compose GET and POST requests, and
obtain the response that the server gave to those requests.
The server Response objects are annotated with the details
of the contexts and templates that were rendered during the
process of serving the request.
Client objects are stateful - they will retain cookie (and
thus session) details for the lifetime of the Client instance.
This is not intended as a replacement for Twill,Selenium, or
other browser automation frameworks - it is here to allow
testing against the contexts and templates produced by a view,
rather than the HTML rendered to the end-user.
"""
from django.test.client import Client
import unittest
class ClientTest(unittest.TestCase):
def setUp(self):
"Set up test environment"
self.client = Client()
def test_get_view(self):
"GET a view"
response = self.client.get('/test_client/get_view/')
# Check some response details
self.assertEqual(response.status_code, 200)
self.assertEqual(response.context['var'], 42)
self.assertEqual(response.template.name, 'GET Template')
self.failUnless('This is a test.' in response.content)
def test_get_post_view(self):
"GET a view that normally expects POSTs"
response = self.client.get('/test_client/post_view/', {})
# Check some response details
self.assertEqual(response.status_code, 200)
self.assertEqual(response.template.name, 'Empty POST Template')
def test_empty_post(self):
"POST an empty dictionary to a view"
response = self.client.post('/test_client/post_view/', {})
# Check some response details
self.assertEqual(response.status_code, 200)
self.assertEqual(response.template.name, 'Empty POST Template')
def test_post_view(self):
"POST some data to a view"
post_data = {
'value': 37
}
response = self.client.post('/test_client/post_view/', post_data)
# Check some response details
self.assertEqual(response.status_code, 200)
self.assertEqual(response.context['data'], '37')
self.assertEqual(response.template.name, 'POST Template')
self.failUnless('Data received' in response.content)
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.assertEqual(response.status_code, 302)
def test_unknown_page(self):
"GET an invalid URL"
response = self.client.get('/test_client/unknown_view/')
# Check that the response was a 404
self.assertEqual(response.status_code, 404)
def test_view_with_login(self):
"Request a page that is protected with @login_required"
# Get the page without logging in. Should result in 302.
response = self.client.get('/test_client/login_protected_view/')
self.assertEqual(response.status_code, 302)
# Request a page that requires a login
response = self.client.login('/test_client/login_protected_view/', 'testclient', 'password')
self.assertTrue(response)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.context['user'].username, 'testclient')
self.assertEqual(response.template.name, 'Login Template')
def test_view_with_bad_login(self):
"Request a page that is protected with @login, but use bad credentials"
response = self.client.login('/test_client/login_protected_view/', 'otheruser', 'nopassword')
self.assertFalse(response)

View File

@ -0,0 +1,9 @@
from django.conf.urls.defaults import *
import views
urlpatterns = patterns('',
(r'^get_view/$', views.get_view),
(r'^post_view/$', views.post_view),
(r'^redirect_view/$', views.redirect_view),
(r'^login_protected_view/$', views.login_protected_view),
)

View File

@ -0,0 +1,35 @@
from django.template import Context, Template
from django.http import HttpResponse, HttpResponseRedirect
from django.contrib.auth.decorators import login_required
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': 42})
return HttpResponse(t.render(c))
def post_view(request):
"""A view that expects a POST, and returns a different template depending
on whether any POST data is available
"""
if request.POST:
t = Template('Data received: {{ data }} is the value.', name='POST Template')
c = Context({'data': request.POST['value']})
else:
t = Template('Viewing POST page.', name='Empty POST Template')
c = Context()
return HttpResponse(t.render(c))
def redirect_view(request):
"A view that redirects all requests to the GET view"
return HttpResponseRedirect('/test_client/get_view/')
@login_required
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))

View File

@ -84,7 +84,7 @@ class Templates(unittest.TestCase):
'basic-syntax03': ("{{ first }} --- {{ second }}", {"first" : 1, "second" : 2}, "1 --- 2"),
# Fail silently when a variable is not found in the current context
'basic-syntax04': ("as{{ missing }}df", {}, "asINVALIDdf"),
'basic-syntax04': ("as{{ missing }}df", {}, ("asdf","asINVALIDdf")),
# A variable may not contain more than one word
'basic-syntax06': ("{{ multi word variable }}", {}, template.TemplateSyntaxError),
@ -100,7 +100,7 @@ class Templates(unittest.TestCase):
'basic-syntax10': ("{{ var.otherclass.method }}", {"var": SomeClass()}, "OtherClass.method"),
# Fail silently when a variable's attribute isn't found
'basic-syntax11': ("{{ var.blech }}", {"var": SomeClass()}, "INVALID"),
'basic-syntax11': ("{{ var.blech }}", {"var": SomeClass()}, ("","INVALID")),
# Raise TemplateSyntaxError when trying to access a variable beginning with an underscore
'basic-syntax12': ("{{ var.__dict__ }}", {"var": SomeClass()}, template.TemplateSyntaxError),
@ -116,10 +116,10 @@ class Templates(unittest.TestCase):
'basic-syntax18': ("{{ foo.bar }}", {"foo" : {"bar" : "baz"}}, "baz"),
# Fail silently when a variable's dictionary key isn't found
'basic-syntax19': ("{{ foo.spam }}", {"foo" : {"bar" : "baz"}}, "INVALID"),
'basic-syntax19': ("{{ foo.spam }}", {"foo" : {"bar" : "baz"}}, ("","INVALID")),
# Fail silently when accessing a non-simple method
'basic-syntax20': ("{{ var.method2 }}", {"var": SomeClass()}, "INVALID"),
'basic-syntax20': ("{{ var.method2 }}", {"var": SomeClass()}, ("","INVALID")),
# Basic filter usage
'basic-syntax21': ("{{ var|upper }}", {"var": "Django is the greatest!"}, "DJANGO IS THE GREATEST!"),
@ -158,7 +158,7 @@ class Templates(unittest.TestCase):
'basic-syntax32': (r'{{ var|yesno:"yup,nup,mup" }} {{ var|yesno }}', {"var": True}, 'yup yes'),
# Fail silently for methods that raise an exception with a "silent_variable_failure" attribute
'basic-syntax33': (r'1{{ var.method3 }}2', {"var": SomeClass()}, "1INVALID2"),
'basic-syntax33': (r'1{{ var.method3 }}2', {"var": SomeClass()}, ("12", "1INVALID2")),
# In methods that raise an exception without a "silent_variable_attribute" set to True,
# the exception propogates
@ -464,6 +464,14 @@ class Templates(unittest.TestCase):
# translation of a constant string
'i18n13': ('{{ _("Page not found") }}', {'LANGUAGE_CODE': 'de'}, 'Seite nicht gefunden'),
### HANDLING OF TEMPLATE_TAG_IF_INVALID ###################################
'invalidstr01': ('{{ var|default:"Foo" }}', {}, ('Foo','INVALID')),
'invalidstr02': ('{{ var|default_if_none:"Foo" }}', {}, ('','INVALID')),
'invalidstr03': ('{% for v in var %}({{ v }}){% endfor %}', {}, ''),
'invalidstr04': ('{% if var %}Yes{% else %}No{% endif %}', {}, 'No'),
'invalidstr04': ('{% if var|default:"Foo" %}Yes{% else %}No{% endif %}', {}, 'Yes'),
### MULTILINE #############################################################
'multiline01': ("""
@ -507,7 +515,7 @@ class Templates(unittest.TestCase):
'{{ item.foo }}' + \
'{% endfor %},' + \
'{% endfor %}',
{}, 'INVALID:INVALIDINVALIDINVALIDINVALIDINVALIDINVALIDINVALID,'),
{}, ''),
### TEMPLATETAG TAG #######################################################
'templatetag01': ('{% templatetag openblock %}', {}, '{%'),
@ -592,30 +600,44 @@ class Templates(unittest.TestCase):
old_td, settings.TEMPLATE_DEBUG = settings.TEMPLATE_DEBUG, False
# Set TEMPLATE_STRING_IF_INVALID to a known string
old_invalid, settings.TEMPLATE_STRING_IF_INVALID = settings.TEMPLATE_STRING_IF_INVALID, 'INVALID'
old_invalid = settings.TEMPLATE_STRING_IF_INVALID
for name, vals in tests:
install()
if isinstance(vals[2], tuple):
normal_string_result = vals[2][0]
invalid_string_result = vals[2][1]
else:
normal_string_result = vals[2]
invalid_string_result = vals[2]
if 'LANGUAGE_CODE' in vals[1]:
activate(vals[1]['LANGUAGE_CODE'])
else:
activate('en-us')
try:
output = loader.get_template(name).render(template.Context(vals[1]))
except Exception, e:
if e.__class__ != vals[2]:
failures.append("Template test: %s -- FAILED. Got %s, exception: %s" % (name, e.__class__, e))
continue
for invalid_str, result in [('', normal_string_result),
('INVALID', invalid_string_result)]:
settings.TEMPLATE_STRING_IF_INVALID = invalid_str
try:
output = loader.get_template(name).render(template.Context(vals[1]))
except Exception, e:
if e.__class__ != result:
failures.append("Template test (TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Got %s, exception: %s" % (invalid_str, name, e.__class__, e))
continue
if output != result:
failures.append("Template test (TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Expected %r, got %r" % (invalid_str, name, result, output))
if 'LANGUAGE_CODE' in vals[1]:
deactivate()
if output != vals[2]:
failures.append("Template test: %s -- FAILED. Expected %r, got %r" % (name, vals[2], output))
loader.template_source_loaders = old_template_loaders
deactivate()
settings.TEMPLATE_DEBUG = old_td
settings.TEMPLATE_STRING_IF_INVALID = old_invalid
self.assertEqual(failures, [])
self.assertEqual(failures, [], '\n'.join(failures))
if __name__ == "__main__":
unittest.main()

View File

@ -34,8 +34,9 @@ except ImportError:
# Import copy of _thread_local.py from Python 2.4
from django.utils._threading_local import local
# helpers
EV = threading.Event()
class LocalSettings:
"""Settings holder that allows thread-local overrides of defaults.
"""
@ -69,7 +70,7 @@ def thread_two(func, *arg):
debug("t2 ODB: %s", settings.OTHER_DATABASES)
debug("t2 waiting")
ev.wait(2.0)
EV.wait(2.0)
func(*arg)
debug("t2 complete")
t2 = threading.Thread(target=start)
@ -94,7 +95,7 @@ def thread_three(func, *arg):
connection.settings.DATABASE_NAME)
debug("t3 waiting")
ev.wait(2.0)
EV.wait(2.0)
func(*arg)
debug("t3 complete")
t3 = threading.Thread(target=start)
@ -113,7 +114,6 @@ def start_response(code, headers):
class TestThreadIsolation(unittest.TestCase):
# event used to synchronize threads so we can be sure they are running
# together
ev = threading.Event()
lock = threading.RLock()
errors = []
@ -235,7 +235,7 @@ class TestThreadIsolation(unittest.TestCase):
t3 = thread_three(MockHandler(self.request_three), env, start_response)
try:
self.ev.set()
EV.set()
MockHandler(self.request_one)(env, start_response)
finally:
t2.join()

View File

@ -6,6 +6,7 @@ import unittest
MODEL_TESTS_DIR_NAME = 'modeltests'
REGRESSION_TESTS_DIR_NAME = 'regressiontests'
TEST_DATABASE_NAME = 'django_test_db'
TEST_TEMPLATE_DIR = 'templates'
TEST_DATABASES = ('_a', '_b')
@ -81,20 +82,25 @@ class InvalidModelTestCase(unittest.TestCase):
def django_tests(verbosity, tests_to_run):
from django.conf import settings
from django.db.models.loading import get_apps, load_app
old_installed_apps = settings.INSTALLED_APPS
old_test_database_name = settings.TEST_DATABASE_NAME
old_root_urlconf = settings.ROOT_URLCONF
old_template_dirs = settings.TEMPLATE_DIRS
# Redirect some settings for the duration of these tests
settings.TEST_DATABASE_NAME = TEST_DATABASE_NAME
settings.INSTALLED_APPS = ALWAYS_INSTALLED_APPS
settings.TEST_DATABASES = TEST_DATABASES
settings.TEST_DATABASE_MODELS = TEST_DATABASE_MODELS
settings.ROOT_URLCONF = 'urls'
settings.TEMPLATE_DIRS = (os.path.join(os.path.dirname(__file__), TEST_TEMPLATE_DIR),)
# load all the ALWAYS_INSTALLED_APPS
get_apps()
test_models = []
# Load all the test model apps
test_models = []
for model_dir, model_name in get_test_models():
model_label = '.'.join([model_dir, model_name])
try:
@ -125,6 +131,9 @@ def django_tests(verbosity, tests_to_run):
# 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
if __name__ == "__main__":
from optparse import OptionParser
usage = "%prog [options] [model model model ...]"

1
tests/templates/404.html Normal file
View File

@ -0,0 +1 @@
Django Internal Tests: 404 Error

1
tests/templates/500.html Normal file
View File

@ -0,0 +1 @@
Django Internal Tests: 500 Error

View File

@ -0,0 +1,19 @@
<html>
<head></head>
<body>
<h1>Django Internal Tests: Login</h1>
{% if form.has_errors %}
<p>Your username and password didn't match. Please try again.</p>
{% endif %}
<form method="post" action=".">
<table>
<tr><td><label for="id_username">Username:</label></td><td>{{ form.username }}</td></tr>
<tr><td><label for="id_password">Password:</label></td><td>{{ form.password }}</td></tr>
</table>
<input type="submit" value="login" />
<input type="hidden" name="next" value="{{ next }}" />
</form>
</body>
</html>

10
tests/urls.py Normal file
View File

@ -0,0 +1,10 @@
from django.conf.urls.defaults import *
urlpatterns = patterns('',
# test_client modeltest urls
(r'^test_client/', include('modeltests.test_client.urls')),
# Always provide the auth system login and logout views
(r'^accounts/login/$', 'django.contrib.auth.views.login', {'template_name': 'login.html'}),
(r'^accounts/logout/$', 'django.contrib.auth.views.login'),
)