Refs #2333 - Re-added the template rendering signal for testing purposes; however, the signal is not available during normal operation. It is only added as part of an instrumentation step that occurs during test framework setup. Previous attempt (r3659) was reverted (r3666) due to performance concerns.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@3707 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
d78e2ae355
commit
d043200077
|
@ -60,6 +60,8 @@ from django.conf import settings
|
||||||
from django.template.context import Context, RequestContext, ContextPopException
|
from django.template.context import Context, RequestContext, ContextPopException
|
||||||
from django.utils.functional import curry
|
from django.utils.functional import curry
|
||||||
from django.utils.text import smart_split
|
from django.utils.text import smart_split
|
||||||
|
from django.dispatch import dispatcher
|
||||||
|
from django.template import signals
|
||||||
|
|
||||||
__all__ = ('Template', 'Context', 'RequestContext', 'compile_string')
|
__all__ = ('Template', 'Context', 'RequestContext', 'compile_string')
|
||||||
|
|
||||||
|
@ -137,13 +139,14 @@ class StringOrigin(Origin):
|
||||||
return self.source
|
return self.source
|
||||||
|
|
||||||
class Template(object):
|
class Template(object):
|
||||||
def __init__(self, template_string, origin=None):
|
def __init__(self, template_string, origin=None, name='<Unknown Template>'):
|
||||||
"Compilation stage"
|
"Compilation stage"
|
||||||
if settings.TEMPLATE_DEBUG and origin == None:
|
if settings.TEMPLATE_DEBUG and origin == None:
|
||||||
origin = StringOrigin(template_string)
|
origin = StringOrigin(template_string)
|
||||||
# Could do some crazy stack-frame stuff to record where this string
|
# Could do some crazy stack-frame stuff to record where this string
|
||||||
# came from...
|
# came from...
|
||||||
self.nodelist = compile_string(template_string, origin)
|
self.nodelist = compile_string(template_string, origin)
|
||||||
|
self.name = name
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
for node in self.nodelist:
|
for node in self.nodelist:
|
||||||
|
@ -152,6 +155,7 @@ class Template(object):
|
||||||
|
|
||||||
def render(self, context):
|
def render(self, context):
|
||||||
"Display stage -- can be called many times"
|
"Display stage -- can be called many times"
|
||||||
|
dispatcher.send(signal=signals.template_rendered, sender=self, template=self, context=context)
|
||||||
return self.nodelist.render(context)
|
return self.nodelist.render(context)
|
||||||
|
|
||||||
def compile_string(template_string, origin):
|
def compile_string(template_string, origin):
|
||||||
|
|
|
@ -251,7 +251,7 @@ class SsiNode(Node):
|
||||||
output = ''
|
output = ''
|
||||||
if self.parsed:
|
if self.parsed:
|
||||||
try:
|
try:
|
||||||
t = Template(output)
|
t = Template(output, name=self.filepath)
|
||||||
return t.render(context)
|
return t.render(context)
|
||||||
except TemplateSyntaxError, e:
|
except TemplateSyntaxError, e:
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
|
|
|
@ -76,14 +76,16 @@ def get_template(template_name):
|
||||||
Returns a compiled Template object for the given template name,
|
Returns a compiled Template object for the given template name,
|
||||||
handling template inheritance recursively.
|
handling template inheritance recursively.
|
||||||
"""
|
"""
|
||||||
return get_template_from_string(*find_template_source(template_name))
|
source, origin = find_template_source(template_name)
|
||||||
|
template = get_template_from_string(source, origin, template_name)
|
||||||
|
return template
|
||||||
|
|
||||||
def get_template_from_string(source, origin=None):
|
def get_template_from_string(source, origin=None, name=None):
|
||||||
"""
|
"""
|
||||||
Returns a compiled Template object for the given template code,
|
Returns a compiled Template object for the given template code,
|
||||||
handling template inheritance recursively.
|
handling template inheritance recursively.
|
||||||
"""
|
"""
|
||||||
return Template(source, origin)
|
return Template(source, origin, name)
|
||||||
|
|
||||||
def render_to_string(template_name, dictionary=None, context_instance=None):
|
def render_to_string(template_name, dictionary=None, context_instance=None):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -57,7 +57,7 @@ class ExtendsNode(Node):
|
||||||
except TemplateDoesNotExist:
|
except TemplateDoesNotExist:
|
||||||
raise TemplateSyntaxError, "Template %r cannot be extended, because it doesn't exist" % parent
|
raise TemplateSyntaxError, "Template %r cannot be extended, because it doesn't exist" % parent
|
||||||
else:
|
else:
|
||||||
return get_template_from_string(source, origin)
|
return get_template_from_string(source, origin, parent)
|
||||||
|
|
||||||
def render(self, context):
|
def render(self, context):
|
||||||
compiled_parent = self.get_parent(context)
|
compiled_parent = self.get_parent(context)
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
from cStringIO import StringIO
|
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.base import BaseHandler
|
||||||
from django.core.handlers.wsgi import WSGIRequest
|
from django.core.handlers.wsgi import WSGIRequest
|
||||||
from django.dispatch import dispatcher
|
from django.dispatch import dispatcher
|
||||||
from django.http import urlencode, SimpleCookie
|
from django.http import urlencode, SimpleCookie
|
||||||
from django.template import signals
|
from django.test import signals
|
||||||
from django.utils.functional import curry
|
from django.utils.functional import curry
|
||||||
|
|
||||||
class ClientHandler(BaseHandler):
|
class ClientHandler(BaseHandler):
|
||||||
|
@ -96,7 +95,7 @@ class Client:
|
||||||
HTML rendered to the end-user.
|
HTML rendered to the end-user.
|
||||||
"""
|
"""
|
||||||
def __init__(self, **defaults):
|
def __init__(self, **defaults):
|
||||||
self.handler = TestHandler()
|
self.handler = ClientHandler()
|
||||||
self.defaults = defaults
|
self.defaults = defaults
|
||||||
self.cookie = SimpleCookie()
|
self.cookie = SimpleCookie()
|
||||||
|
|
||||||
|
@ -180,29 +179,38 @@ class Client:
|
||||||
def login(self, path, username, password, **extra):
|
def login(self, path, username, password, **extra):
|
||||||
"""
|
"""
|
||||||
A specialized sequence of GET and POST to log into a view that
|
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
|
path should be the URL of the page that is login protected.
|
||||||
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.
|
# First, GET the page that is login protected.
|
||||||
# This is required to establish the session.
|
# This page will redirect to the login page.
|
||||||
response = self.get(path)
|
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:
|
if response.status_code != 200:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Set up the block of form data required by the login page.
|
# Last, POST the login data.
|
||||||
form_data = {
|
form_data = {
|
||||||
'username': username,
|
'username': username,
|
||||||
'password': password,
|
'password': password,
|
||||||
'this_is_the_login_form': 1,
|
'next' : next,
|
||||||
'post_data': _encode_post_data({LOGIN_FORM_KEY: 1})
|
|
||||||
}
|
}
|
||||||
response = self.post(path, data=form_data, **extra)
|
response = self.post(login_path, data=form_data, **extra)
|
||||||
|
|
||||||
# login page should give response 200 (if you requested the login
|
# Login page should 302 redirect to the originally requested page
|
||||||
# page specifically), or 302 (if you requested a login
|
if response.status_code != 302 or response['Location'] != path:
|
||||||
# protected page, to which the login can redirect).
|
return False
|
||||||
return response.status_code in (200,302)
|
|
||||||
|
# Since we are logged in, request the actual page again
|
||||||
|
return self.get(path)
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
template_rendered = object()
|
|
@ -1,6 +1,7 @@
|
||||||
import unittest, doctest
|
import unittest, doctest
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core import management
|
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.utils import create_test_db, destroy_test_db
|
||||||
from django.test.testcases import OutputChecker, DocTestRunner
|
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
|
the module. A list of 'extra' tests may also be provided; these tests
|
||||||
will be added to the test suite.
|
will be added to the test suite.
|
||||||
"""
|
"""
|
||||||
|
setup_test_environment()
|
||||||
|
|
||||||
settings.DEBUG = False
|
settings.DEBUG = False
|
||||||
suite = unittest.TestSuite()
|
suite = unittest.TestSuite()
|
||||||
|
@ -66,3 +68,5 @@ def run_tests(module_list, verbosity=1, extra_tests=[]):
|
||||||
management.syncdb(verbosity, interactive=False)
|
management.syncdb(verbosity, interactive=False)
|
||||||
unittest.TextTestRunner(verbosity=verbosity).run(suite)
|
unittest.TextTestRunner(verbosity=verbosity).run(suite)
|
||||||
destroy_test_db(old_name, verbosity)
|
destroy_test_db(old_name, verbosity)
|
||||||
|
|
||||||
|
teardown_test_environment()
|
||||||
|
|
|
@ -1,11 +1,40 @@
|
||||||
import sys, time
|
import sys, time
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import connection, transaction, backend
|
from django.db import connection, transaction, backend
|
||||||
|
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 prefix to put on the default database name when creating
|
||||||
# the test database.
|
# the test database.
|
||||||
TEST_DATABASE_PREFIX = 'test_'
|
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):
|
def _set_autocommit(connection):
|
||||||
"Make sure a connection is in autocommit mode."
|
"Make sure a connection is in autocommit mode."
|
||||||
if hasattr(connection.connection, "autocommit"):
|
if hasattr(connection.connection, "autocommit"):
|
||||||
|
@ -75,4 +104,3 @@ def destroy_test_db(old_database_name, verbosity=1):
|
||||||
time.sleep(1) # To avoid "database is being accessed by other users" errors.
|
time.sleep(1) # To avoid "database is being accessed by other users" errors.
|
||||||
cursor.execute("DROP DATABASE %s" % backend.quote_name(TEST_DATABASE_NAME))
|
cursor.execute("DROP DATABASE %s" % backend.quote_name(TEST_DATABASE_NAME))
|
||||||
connection.close()
|
connection.close()
|
||||||
|
|
||||||
|
|
|
@ -115,7 +115,7 @@ def technical_500_response(request, exc_type, exc_value, tb):
|
||||||
'function': '?',
|
'function': '?',
|
||||||
'lineno': '?',
|
'lineno': '?',
|
||||||
}]
|
}]
|
||||||
t = Template(TECHNICAL_500_TEMPLATE)
|
t = Template(TECHNICAL_500_TEMPLATE, name='Technical 500 Template')
|
||||||
c = Context({
|
c = Context({
|
||||||
'exception_type': exc_type.__name__,
|
'exception_type': exc_type.__name__,
|
||||||
'exception_value': exc_value,
|
'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.
|
# tried exists but is an empty list. The URLconf must've been empty.
|
||||||
return empty_urlconf(request)
|
return empty_urlconf(request)
|
||||||
|
|
||||||
t = Template(TECHNICAL_404_TEMPLATE)
|
t = Template(TECHNICAL_404_TEMPLATE, name='Technical 404 Template')
|
||||||
c = Context({
|
c = Context({
|
||||||
'root_urlconf': settings.ROOT_URLCONF,
|
'root_urlconf': settings.ROOT_URLCONF,
|
||||||
'urlpatterns': tried,
|
'urlpatterns': tried,
|
||||||
|
@ -154,7 +154,7 @@ def technical_404_response(request, exception):
|
||||||
|
|
||||||
def empty_urlconf(request):
|
def empty_urlconf(request):
|
||||||
"Create an empty URLconf 404 error response."
|
"Create an empty URLconf 404 error response."
|
||||||
t = Template(EMPTY_URLCONF_TEMPLATE)
|
t = Template(EMPTY_URLCONF_TEMPLATE, name='Empty URLConf Template')
|
||||||
c = Context({
|
c = Context({
|
||||||
'project_name': settings.SETTINGS_MODULE.split('.')[0]
|
'project_name': settings.SETTINGS_MODULE.split('.')[0]
|
||||||
})
|
})
|
||||||
|
|
|
@ -81,7 +81,7 @@ def directory_index(path, fullpath):
|
||||||
try:
|
try:
|
||||||
t = loader.get_template('static/directory_index')
|
t = loader.get_template('static/directory_index')
|
||||||
except TemplateDoesNotExist:
|
except TemplateDoesNotExist:
|
||||||
t = Template(DEFAULT_DIRECTORY_INDEX_TEMPLATE)
|
t = Template(DEFAULT_DIRECTORY_INDEX_TEMPLATE, name='Default Directory Index Template')
|
||||||
files = []
|
files = []
|
||||||
for f in os.listdir(fullpath):
|
for f in os.listdir(fullpath):
|
||||||
if not f.startswith('.'):
|
if not f.startswith('.'):
|
||||||
|
|
Loading…
Reference in New Issue