summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristopher Long <indirecthit@gmail.com>2006-08-28 13:06:03 +0000
committerChristopher Long <indirecthit@gmail.com>2006-08-28 13:06:03 +0000
commit61d6f592c9c6240537bf4f4c3844f563f7fb68e6 (patch)
treefcba989e95f88cce22f8374ceea5c5201d318624
parentbd64483da63c3790446107b3f7151768dc1f6e65 (diff)
downloaddjango-61d6f592c9c6240537bf4f4c3844f563f7fb68e6.tar.gz
[per-object-permissions] Merged to trunk 3666
git-svn-id: http://code.djangoproject.com/svn/django/branches/per-object-permissions@3669 bcc190cf-cafb-0310-a4f2-bffc1f526a37
-rw-r--r--django/conf/global_settings.py6
-rw-r--r--django/contrib/admin/media/css/global.css2
-rw-r--r--django/contrib/admin/templates/admin_doc/model_detail.html2
-rw-r--r--django/contrib/admin/templatetags/admin_modify.py2
-rw-r--r--django/contrib/admin/views/main.py2
-rw-r--r--django/contrib/auth/management.py8
-rw-r--r--django/contrib/contenttypes/management.py5
-rw-r--r--django/contrib/flatpages/views.py5
-rw-r--r--django/contrib/sites/management.py5
-rw-r--r--django/core/management.py50
-rw-r--r--django/core/validators.py2
-rw-r--r--django/test/__init__.py (renamed from tests/othertests/__init__.py)0
-rw-r--r--django/test/client.py208
-rw-r--r--django/test/doctest.py (renamed from tests/doctest.py)11
-rw-r--r--django/test/simple.py67
-rw-r--r--django/test/testcases.py30
-rw-r--r--django/test/utils.py78
-rw-r--r--django/views/generic/date_based.py15
-rw-r--r--docs/faq.txt29
-rw-r--r--docs/model-api.txt19
-rw-r--r--docs/overview.txt2
-rw-r--r--docs/settings.txt11
-rw-r--r--docs/templates.txt8
-rw-r--r--tests/modeltests/basic/models.py11
-rw-r--r--tests/modeltests/choices/models.py4
-rw-r--r--tests/modeltests/custom_columns/models.py4
-rw-r--r--tests/modeltests/custom_managers/models.py4
-rw-r--r--tests/modeltests/custom_methods/models.py4
-rw-r--r--tests/modeltests/custom_pk/models.py4
-rw-r--r--tests/modeltests/empty/models.py4
-rw-r--r--tests/modeltests/field_defaults/models.py4
-rw-r--r--tests/modeltests/generic_relations/models.py4
-rw-r--r--tests/modeltests/get_latest/models.py4
-rw-r--r--tests/modeltests/get_or_create/models.py4
-rw-r--r--tests/modeltests/invalid_models/models.py2
-rw-r--r--tests/modeltests/lookup/models.py4
-rw-r--r--tests/modeltests/m2m_and_m2o/models.py4
-rw-r--r--tests/modeltests/m2m_intermediary/models.py4
-rw-r--r--tests/modeltests/m2m_multiple/models.py4
-rw-r--r--tests/modeltests/m2m_recursive/models.py4
-rw-r--r--tests/modeltests/m2o_recursive/models.py4
-rw-r--r--tests/modeltests/m2o_recursive2/models.py4
-rw-r--r--tests/modeltests/manipulators/models.py4
-rw-r--r--tests/modeltests/many_to_many/models.py4
-rw-r--r--tests/modeltests/many_to_one/models.py4
-rw-r--r--tests/modeltests/many_to_one_null/models.py4
-rw-r--r--tests/modeltests/model_inheritance/models.py4
-rw-r--r--tests/modeltests/mutually_referential/models.py4
-rw-r--r--tests/modeltests/one_to_one/models.py4
-rw-r--r--tests/modeltests/or_lookups/models.py4
-rw-r--r--tests/modeltests/ordering/models.py4
-rw-r--r--tests/modeltests/pagination/models.py4
-rw-r--r--tests/modeltests/properties/models.py4
-rw-r--r--tests/modeltests/reserved_names/models.py4
-rw-r--r--tests/modeltests/reverse_lookup/models.py4
-rw-r--r--tests/modeltests/save_delete_hooks/models.py4
-rw-r--r--tests/modeltests/serializers/models.py4
-rw-r--r--tests/modeltests/str/models.py4
-rw-r--r--tests/modeltests/transactions/models.py6
-rw-r--r--tests/modeltests/validation/models.py4
-rw-r--r--tests/othertests/cache.py60
-rw-r--r--tests/othertests/markup.py70
-rw-r--r--tests/othertests/templates.py634
-rw-r--r--tests/regressiontests/cache/__init__.py0
-rw-r--r--tests/regressiontests/cache/models.py0
-rw-r--r--tests/regressiontests/cache/tests.py71
-rw-r--r--tests/regressiontests/dateformat/__init__.py0
-rw-r--r--tests/regressiontests/dateformat/models.py0
-rw-r--r--tests/regressiontests/dateformat/tests.py (renamed from tests/othertests/dateformat.py)0
-rw-r--r--tests/regressiontests/db_typecasts/__init__.py0
-rw-r--r--tests/regressiontests/db_typecasts/models.py0
-rw-r--r--tests/regressiontests/db_typecasts/tests.py (renamed from tests/othertests/db_typecasts.py)15
-rw-r--r--tests/regressiontests/defaultfilters/__init__.py0
-rw-r--r--tests/regressiontests/defaultfilters/models.py0
-rw-r--r--tests/regressiontests/defaultfilters/tests.py (renamed from tests/othertests/defaultfilters.py)0
-rw-r--r--tests/regressiontests/httpwrappers/__init__.py0
-rw-r--r--tests/regressiontests/httpwrappers/models.py0
-rw-r--r--tests/regressiontests/httpwrappers/tests.py (renamed from tests/othertests/httpwrappers.py)0
-rw-r--r--tests/regressiontests/initial_sql_regress/models.py2
-rw-r--r--tests/regressiontests/many_to_one_regress/models.py2
-rw-r--r--tests/regressiontests/markup/__init__.py0
-rw-r--r--tests/regressiontests/markup/models.py0
-rw-r--r--tests/regressiontests/markup/tests.py69
-rw-r--r--tests/regressiontests/one_to_one_regress/models.py4
-rw-r--r--tests/regressiontests/string_lookup/models.py4
-rw-r--r--tests/regressiontests/templates/__init__.py0
-rw-r--r--tests/regressiontests/templates/models.py0
-rw-r--r--tests/regressiontests/templates/tests.py621
-rw-r--r--tests/regressiontests/urlpatterns_reverse/__init__.py0
-rw-r--r--tests/regressiontests/urlpatterns_reverse/models.py0
-rw-r--r--tests/regressiontests/urlpatterns_reverse/tests.py (renamed from tests/othertests/urlpatterns_reverse.py)28
-rwxr-xr-xtests/runtests.py340
92 files changed, 1471 insertions, 1171 deletions
diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py
index 4ea694d064..56a4ccb48a 100644
--- a/django/conf/global_settings.py
+++ b/django/conf/global_settings.py
@@ -296,3 +296,9 @@ BANNED_IPS = ()
##################
AUTHENTICATION_BACKENDS = ('django.contrib.auth.backends.ModelBackend',)
+
+###########
+# TESTING #
+###########
+
+TEST_RUNNER='django.test.simple.run_tests'
diff --git a/django/contrib/admin/media/css/global.css b/django/contrib/admin/media/css/global.css
index e08aa29992..639f3321eb 100644
--- a/django/contrib/admin/media/css/global.css
+++ b/django/contrib/admin/media/css/global.css
@@ -1,4 +1,4 @@
-body { margin:0; padding:0; font-size:12px; font-family:"Lucida Grande","Bitstream Vera Sans",Verdana,Arial,sans-serif; color:#333; background:#fff; }
+body { margin:0; padding:0; font-size:12px; font-family:"Lucida Grande","DejaVu Sans","Bitstream Vera Sans",Verdana,Arial,sans-serif; color:#333; background:#fff; }
/* LINKS */
a:link, a:visited { color: #5b80b2; text-decoration:none; }
diff --git a/django/contrib/admin/templates/admin_doc/model_detail.html b/django/contrib/admin/templates/admin_doc/model_detail.html
index 44fc43e704..70133e26dd 100644
--- a/django/contrib/admin/templates/admin_doc/model_detail.html
+++ b/django/contrib/admin/templates/admin_doc/model_detail.html
@@ -35,7 +35,7 @@
<tr>
<td>{{ field.name }}</td>
<td>{{ field.data_type }}</td>
- <td>{% if field.verbose %}{{ field.verbose|escape }}{% endif %}{% if field.help_text %} - {{ field.help_text|escape }}{% endif %}</td>
+ <td>{% if field.verbose %}{{ field.verbose }}{% endif %}{% if field.help_text %} - {{ field.help_text }}{% endif %}</td>
</tr>
{% endfor %}
</tbody>
diff --git a/django/contrib/admin/templatetags/admin_modify.py b/django/contrib/admin/templatetags/admin_modify.py
index add1d4abac..4af7bc1db8 100644
--- a/django/contrib/admin/templatetags/admin_modify.py
+++ b/django/contrib/admin/templatetags/admin_modify.py
@@ -195,7 +195,7 @@ def filter_interface_script_maybe(bound_field):
f = bound_field.field
if f.rel and isinstance(f.rel, models.ManyToManyRel) and f.rel.filter_interface:
return '<script type="text/javascript">addEvent(window, "load", function(e) {' \
- ' SelectFilter.init("id_%s", "%s", %s, "%s"); });</script>\n' % (
+ ' SelectFilter.init("id_%s", %r, %s, "%s"); });</script>\n' % (
f.name, f.verbose_name, f.rel.filter_interface-1, settings.ADMIN_MEDIA_PREFIX)
else:
return ''
diff --git a/django/contrib/admin/views/main.py b/django/contrib/admin/views/main.py
index 2a8b135bc7..9b091b28da 100644
--- a/django/contrib/admin/views/main.py
+++ b/django/contrib/admin/views/main.py
@@ -423,7 +423,7 @@ def _get_deleted_objects(deleted_objects, perms_needed, user, obj, opts, current
if current_depth > 16:
return # Avoid recursing too deep.
opts_seen = []
- for related in opts.related_objects():
+ for related in opts.get_all_related_objects():
if related.opts in opts_seen:
continue
opts_seen.append(related.opts)
diff --git a/django/contrib/auth/management.py b/django/contrib/auth/management.py
index 1a07417f1d..3f52681747 100644
--- a/django/contrib/auth/management.py
+++ b/django/contrib/auth/management.py
@@ -16,7 +16,7 @@ def _get_all_permissions(opts):
perms.append((_get_permission_codename(action, opts), 'Can %s %s' % (action, opts.verbose_name)))
return perms + list(opts.permissions)
-def create_permissions(app, created_models):
+def create_permissions(app, created_models, verbosity):
from django.contrib.contenttypes.models import ContentType
from django.contrib.auth.models import Permission
app_models = get_models(app)
@@ -27,13 +27,13 @@ def create_permissions(app, created_models):
for codename, name in _get_all_permissions(klass._meta):
p, created = Permission.objects.get_or_create(codename=codename, content_type__pk=ctype.id,
defaults={'name': name, 'content_type': ctype})
- if created:
+ if created and verbosity >= 2:
print "Adding permission '%s'" % p
-def create_superuser(app, created_models):
+def create_superuser(app, created_models, verbosity, **kwargs):
from django.contrib.auth.models import User
from django.contrib.auth.create_superuser import createsuperuser as do_create
- if User in created_models:
+ if User in created_models and kwargs.get('interactive', True):
msg = "\nYou just installed Django's auth system, which means you don't have " \
"any superusers defined.\nWould you like to create one now? (yes/no): "
confirm = raw_input(msg)
diff --git a/django/contrib/contenttypes/management.py b/django/contrib/contenttypes/management.py
index a9174584bc..de3a685477 100644
--- a/django/contrib/contenttypes/management.py
+++ b/django/contrib/contenttypes/management.py
@@ -5,7 +5,7 @@ Creates content types for all installed models.
from django.dispatch import dispatcher
from django.db.models import get_models, signals
-def create_contenttypes(app, created_models):
+def create_contenttypes(app, created_models, verbosity):
from django.contrib.contenttypes.models import ContentType
app_models = get_models(app)
if not app_models:
@@ -19,6 +19,7 @@ def create_contenttypes(app, created_models):
ct = ContentType(name=str(opts.verbose_name),
app_label=opts.app_label, model=opts.object_name.lower())
ct.save()
- print "Adding content type '%s | %s'" % (ct.app_label, ct.model)
+ if verbosity >= 2:
+ print "Adding content type '%s | %s'" % (ct.app_label, ct.model)
dispatcher.connect(create_contenttypes, signal=signals.post_syncdb)
diff --git a/django/contrib/flatpages/views.py b/django/contrib/flatpages/views.py
index 4ad24795f7..f386a52101 100644
--- a/django/contrib/flatpages/views.py
+++ b/django/contrib/flatpages/views.py
@@ -3,6 +3,7 @@ from django.template import loader, RequestContext
from django.shortcuts import get_object_or_404
from django.http import HttpResponse
from django.conf import settings
+from django.core.xheaders import populate_xheaders
DEFAULT_TEMPLATE = 'flatpages/default.html'
@@ -32,4 +33,6 @@ def flatpage(request, url):
c = RequestContext(request, {
'flatpage': f,
})
- return HttpResponse(t.render(c))
+ response = HttpResponse(t.render(c))
+ populate_xheaders(request, response, FlatPage, f.id)
+ return response
diff --git a/django/contrib/sites/management.py b/django/contrib/sites/management.py
index 0e1a50227a..6831cab96d 100644
--- a/django/contrib/sites/management.py
+++ b/django/contrib/sites/management.py
@@ -7,9 +7,10 @@ from django.db.models import signals
from django.contrib.sites.models import Site
from django.contrib.sites import models as site_app
-def create_default_site(app, created_models):
+def create_default_site(app, created_models, verbosity):
if Site in created_models:
- print "Creating example.com Site object"
+ if verbosity >= 2:
+ print "Creating example.com Site object"
s = Site(domain="example.com", name="example.com")
s.save()
diff --git a/django/core/management.py b/django/core/management.py
index a469c72901..51c8c760d5 100644
--- a/django/core/management.py
+++ b/django/core/management.py
@@ -423,7 +423,7 @@ def get_sql_all(app):
get_sql_all.help_doc = "Prints the CREATE TABLE, initial-data and CREATE INDEX SQL statements for the given model module name(s)."
get_sql_all.args = APP_ARGS
-def syncdb():
+def syncdb(verbosity=2, interactive=True):
"Creates the database tables for all apps in INSTALLED_APPS whose tables haven't already been created."
from django.db import connection, transaction, models, get_creation_module
from django.db.models import signals
@@ -471,7 +471,8 @@ def syncdb():
except KeyError:
pending_references[refto] = refs
sql.extend(_get_sql_for_pending_references(model, pending_references))
- print "Creating table %s" % model._meta.db_table
+ if verbosity >= 2:
+ print "Creating table %s" % model._meta.db_table
for statement in sql:
cursor.execute(statement)
table_list.append(model._meta.db_table)
@@ -480,7 +481,8 @@ def syncdb():
if model in created_models:
sql = _get_many_to_many_sql_for_model(model)
if sql:
- print "Creating many-to-many tables for %s model" % model.__name__
+ if verbosity >= 2:
+ print "Creating many-to-many tables for %s model" % model.__name__
for statement in sql:
cursor.execute(statement)
@@ -490,7 +492,8 @@ def syncdb():
# to do at this point.
for app in models.get_apps():
dispatcher.send(signal=signals.post_syncdb, sender=app,
- app=app, created_models=created_models)
+ app=app, created_models=created_models,
+ verbosity=verbosity, interactive=interactive)
# Install initial data for the app (but only if this is a model we've
# just created)
@@ -1154,6 +1157,29 @@ def runfcgi(args):
runfastcgi(args)
runfcgi.args = '[various KEY=val options, use `runfcgi help` for help]'
+def test(verbosity, app_labels):
+ "Runs the test suite for the specified applications"
+ from django.conf import settings
+ from django.db.models import get_app, get_apps
+
+ if len(app_labels) == 0:
+ app_list = get_apps()
+ else:
+ app_list = [get_app(app_label) for app_label in app_labels]
+
+ test_path = settings.TEST_RUNNER.split('.')
+ # Allow for Python 2.5 relative paths
+ if len(test_path) > 1:
+ test_module_name = '.'.join(test_path[:-1])
+ else:
+ test_module_name = '.'
+ test_module = __import__(test_module_name, [],[],test_path[-1])
+ test_runner = getattr(test_module, test_path[-1])
+
+ test_runner(app_list, verbosity)
+test.help_doc = 'Runs the test suite for the specified applications, or the entire site if no apps are specified'
+test.args = '[--verbosity] ' + APP_ARGS
+
# Utilities for command-line script
DEFAULT_ACTION_MAPPING = {
@@ -1178,6 +1204,7 @@ DEFAULT_ACTION_MAPPING = {
'startproject': startproject,
'syncdb': syncdb,
'validate': validate,
+ 'test':test,
}
NO_SQL_TRANSACTION = (
@@ -1228,8 +1255,14 @@ def execute_from_command_line(action_mapping=DEFAULT_ACTION_MAPPING, argv=None):
help='Lets you manually add a directory the Python path, e.g. "/home/djangoprojects/myproject".')
parser.add_option('--plain', action='store_true', dest='plain',
help='Tells Django to use plain Python, not IPython, for "shell" command.')
+ parser.add_option('--noinput', action='store_false', dest='interactive', default=True,
+ help='Tells Django to NOT prompt the user for input of any kind.')
parser.add_option('--noreload', action='store_false', dest='use_reloader', default=True,
help='Tells Django to NOT use the auto-reloader when running the development server.')
+ parser.add_option('--verbosity', action='store', dest='verbosity', default='2',
+ type='choice', choices=['0', '1', '2'],
+ help='Verbosity level; 0=minimal output, 1=normal output, 2=all output')
+
options, args = parser.parse_args(argv[1:])
# Take care of options.
@@ -1256,8 +1289,10 @@ def execute_from_command_line(action_mapping=DEFAULT_ACTION_MAPPING, argv=None):
if action == 'shell':
action_mapping[action](options.plain is True)
- elif action in ('syncdb', 'validate', 'diffsettings', 'dbshell'):
+ elif action in ('validate', 'diffsettings', 'dbshell'):
action_mapping[action]()
+ elif action == 'syncdb':
+ action_mapping[action](int(options.verbosity), options.interactive)
elif action == 'inspectdb':
try:
for line in action_mapping[action]():
@@ -1270,6 +1305,11 @@ def execute_from_command_line(action_mapping=DEFAULT_ACTION_MAPPING, argv=None):
action_mapping[action](args[1])
except IndexError:
parser.print_usage_and_exit()
+ elif action == 'test':
+ try:
+ action_mapping[action](int(options.verbosity), args[1:])
+ except IndexError:
+ parser.print_usage_and_exit()
elif action in ('startapp', 'startproject'):
try:
name = args[1]
diff --git a/django/core/validators.py b/django/core/validators.py
index 81bea23e36..8f40ceb51a 100644
--- a/django/core/validators.py
+++ b/django/core/validators.py
@@ -68,7 +68,7 @@ def isAlphaNumericURL(field_data, all_data):
def isSlug(field_data, all_data):
if not slug_re.search(field_data):
- raise ValidationError, "This value must contain only letters, numbers, underscores or hyphens."
+ raise ValidationError, gettext("This value must contain only letters, numbers, underscores or hyphens.")
def isLowerCase(field_data, all_data):
if field_data.lower() != field_data:
diff --git a/tests/othertests/__init__.py b/django/test/__init__.py
index e69de29bb2..e69de29bb2 100644
--- a/tests/othertests/__init__.py
+++ b/django/test/__init__.py
diff --git a/django/test/client.py b/django/test/client.py
new file mode 100644
index 0000000000..871f6cfb9b
--- /dev/null
+++ b/django/test/client.py
@@ -0,0 +1,208 @@
+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.utils.functional import curry
+
+class ClientHandler(BaseHandler):
+ """
+ A HTTP Handler that can be used for testing purposes.
+ Uses the WSGI interface to compose requests, but returns
+ the raw HttpResponse object
+ """
+ def __call__(self, environ):
+ from django.conf import settings
+ from django.core import signals
+
+ # Set up middleware if needed. We couldn't do this earlier, because
+ # settings weren't available.
+ if self._request_middleware is None:
+ self.load_middleware()
+
+ dispatcher.send(signal=signals.request_started)
+ try:
+ request = WSGIRequest(environ)
+ response = self.get_response(request.path, request)
+
+ # Apply response middleware
+ for middleware_method in self._response_middleware:
+ response = middleware_method(request, response)
+
+ finally:
+ dispatcher.send(signal=signals.request_finished)
+
+ return response
+
+def store_rendered_templates(store, signal, sender, template, context):
+ "A utility function for storing templates and contexts that are rendered"
+ store.setdefault('template',[]).append(template)
+ store.setdefault('context',[]).append(context)
+
+def encode_multipart(boundary, data):
+ """
+ A simple method for encoding multipart POST data from a dictionary of
+ form values.
+
+ The key will be used as the form data name; the value will be transmitted
+ as content. If the value is a file, the contents of the file will be sent
+ as an application/octet-stream; otherwise, str(value) will be sent.
+ """
+ lines = []
+ for (key, value) in data.items():
+ if isinstance(value, file):
+ lines.extend([
+ '--' + boundary,
+ 'Content-Disposition: form-data; name="%s"' % key,
+ '',
+ '--' + boundary,
+ 'Content-Disposition: form-data; name="%s_file"; filename="%s"' % (key, value.name),
+ 'Content-Type: application/octet-stream',
+ '',
+ value.read()
+ ])
+ else:
+ lines.extend([
+ '--' + boundary,
+ 'Content-Disposition: form-data; name="%s"' % key,
+ '',
+ str(value)
+ ])
+
+ lines.extend([
+ '--' + boundary + '--',
+ '',
+ ])
+ return '\r\n'.join(lines)
+
+class Client:
+ """
+ A class that can act as a client 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
+ the like - it is here to allow testing against the
+ contexts and templates produced by a view, rather than the
+ HTML rendered to the end-user.
+ """
+ def __init__(self, **defaults):
+ self.handler = TestHandler()
+ self.defaults = defaults
+ self.cookie = SimpleCookie()
+
+ def request(self, **request):
+ """
+ The master request method. Composes the environment dictionary
+ and passes to the handler, returning the result of the handler.
+ Assumes defaults for the query environment, which can be overridden
+ using the arguments to the request.
+ """
+
+ environ = {
+ 'HTTP_COOKIE': self.cookie,
+ 'PATH_INFO': '/',
+ 'QUERY_STRING': '',
+ 'REQUEST_METHOD': 'GET',
+ 'SCRIPT_NAME': None,
+ 'SERVER_NAME': 'testserver',
+ 'SERVER_PORT': 80,
+ 'SERVER_PROTOCOL': 'HTTP/1.1',
+ }
+ environ.update(self.defaults)
+ environ.update(request)
+
+ # Curry a data dictionary into an instance of
+ # the template renderer callback function
+ 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
+ # If there was only one template rendered (the most likely case),
+ # flatten the list to a single element
+ for detail in ('template', 'context'):
+ if data.get(detail):
+ if len(data[detail]) == 1:
+ setattr(response, detail, data[detail][0]);
+ else:
+ setattr(response, detail, data[detail])
+ else:
+ setattr(response, detail, None)
+
+ if response.cookies:
+ self.cookie.update(response.cookies)
+
+ return response
+
+ def get(self, path, data={}, **extra):
+ "Request a response from the server using GET."
+ r = {
+ 'CONTENT_LENGTH': None,
+ 'CONTENT_TYPE': 'text/html; charset=utf-8',
+ 'PATH_INFO': path,
+ 'QUERY_STRING': urlencode(data),
+ 'REQUEST_METHOD': 'GET',
+ }
+ r.update(extra)
+
+ return self.request(**r)
+
+ def post(self, path, data={}, **extra):
+ "Request a response from the server using POST."
+
+ BOUNDARY = 'BoUnDaRyStRiNg'
+
+ encoded = encode_multipart(BOUNDARY, data)
+ stream = StringIO(encoded)
+ r = {
+ 'CONTENT_LENGTH': len(encoded),
+ 'CONTENT_TYPE': 'multipart/form-data; boundary=%s' % BOUNDARY,
+ 'PATH_INFO': path,
+ 'REQUEST_METHOD': 'POST',
+ 'wsgi.input': stream,
+ }
+ r.update(extra)
+
+ return self.request(**r)
+
+ 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.
+
+ path should be the URL of the login page, or of any page that
+ is login protected.
+
+ Returns True if login was successful; False if otherwise.
+ """
+ # First, GET the login page.
+ # This is required to establish the session.
+ response = self.get(path)
+ if response.status_code != 200:
+ return False
+
+ # Set up the block of form data required by the login page.
+ form_data = {
+ 'username': username,
+ 'password': password,
+ 'this_is_the_login_form': 1,
+ 'post_data': _encode_post_data({LOGIN_FORM_KEY: 1})
+ }
+ 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)
diff --git a/tests/doctest.py b/django/test/doctest.py
index df7aa978d3..d600d15e52 100644
--- a/tests/doctest.py
+++ b/django/test/doctest.py
@@ -2104,7 +2104,7 @@ def set_unittest_reportflags(flags):
class DocTestCase(unittest.TestCase):
def __init__(self, test, optionflags=0, setUp=None, tearDown=None,
- checker=None):
+ checker=None, runner=DocTestRunner):
unittest.TestCase.__init__(self)
self._dt_optionflags = optionflags
@@ -2112,6 +2112,7 @@ class DocTestCase(unittest.TestCase):
self._dt_test = test
self._dt_setUp = setUp
self._dt_tearDown = tearDown
+ self._dt_runner = runner
def setUp(self):
test = self._dt_test
@@ -2138,8 +2139,8 @@ class DocTestCase(unittest.TestCase):
# so add the default reporting flags
optionflags |= _unittest_reportflags
- runner = DocTestRunner(optionflags=optionflags,
- checker=self._dt_checker, verbose=False)
+ runner = self._dt_runner(optionflags=optionflags,
+ checker=self._dt_checker, verbose=False)
try:
runner.DIVIDER = "-"*70
@@ -2248,7 +2249,7 @@ class DocTestCase(unittest.TestCase):
return "Doctest: " + self._dt_test.name
def DocTestSuite(module=None, globs=None, extraglobs=None, test_finder=None,
- **options):
+ test_class=DocTestCase, **options):
"""
Convert doctest tests for a module to a unittest test suite.
@@ -2306,7 +2307,7 @@ def DocTestSuite(module=None, globs=None, extraglobs=None, test_finder=None,
if filename[-4:] in (".pyc", ".pyo"):
filename = filename[:-1]
test.filename = filename
- suite.addTest(DocTestCase(test, **options))
+ suite.addTest(test_class(test, **options))
return suite
diff --git a/django/test/simple.py b/django/test/simple.py
new file mode 100644
index 0000000000..e72f693459
--- /dev/null
+++ b/django/test/simple.py
@@ -0,0 +1,67 @@
+import unittest, doctest
+from django.conf import settings
+from django.core import management
+from django.test.utils import create_test_db, destroy_test_db
+from django.test.testcases import OutputChecker, DocTestRunner
+
+# The module name for tests outside models.py
+TEST_MODULE = 'tests'
+
+doctestOutputChecker = OutputChecker()
+
+def build_suite(app_module):
+ "Create a complete Django test suite for the provided application module"
+ suite = unittest.TestSuite()
+
+ # Load unit and doctests in the models.py file
+ suite.addTest(unittest.defaultTestLoader.loadTestsFromModule(app_module))
+ try:
+ suite.addTest(doctest.DocTestSuite(app_module,
+ checker=doctestOutputChecker,
+ runner=DocTestRunner))
+ except ValueError:
+ # No doc tests in models.py
+ pass
+
+ # Check to see if a separate 'tests' module exists parallel to the
+ # models module
+ try:
+ app_path = app_module.__name__.split('.')[:-1]
+ test_module = __import__('.'.join(app_path + [TEST_MODULE]), [], [], TEST_MODULE)
+
+ suite.addTest(unittest.defaultTestLoader.loadTestsFromModule(test_module))
+ try:
+ suite.addTest(doctest.DocTestSuite(test_module,
+ checker=doctestOutputChecker,
+ runner=DocTestRunner))
+ except ValueError:
+ # No doc tests in tests.py
+ pass
+ except ImportError:
+ # No tests.py file for application
+ pass
+
+ return suite
+
+def run_tests(module_list, verbosity=1, extra_tests=[]):
+ """
+ Run the unit tests for all the modules in the provided list.
+ This testrunner will search each of the modules in the provided list,
+ looking for doctests and unittests in models.py or tests.py within
+ the module. A list of 'extra' tests may also be provided; these tests
+ will be added to the test suite.
+ """
+
+ settings.DEBUG = False
+ suite = unittest.TestSuite()
+
+ for module in module_list:
+ suite.addTest(build_suite(module))
+
+ for test in extra_tests:
+ suite.addTest(test)
+
+ old_name = create_test_db(verbosity)
+ management.syncdb(verbosity, interactive=False)
+ unittest.TextTestRunner(verbosity=verbosity).run(suite)
+ destroy_test_db(old_name, verbosity)
diff --git a/django/test/testcases.py b/django/test/testcases.py
new file mode 100644
index 0000000000..1cfef6f19e
--- /dev/null
+++ b/django/test/testcases.py
@@ -0,0 +1,30 @@
+import re, doctest, unittest
+from django.db import transaction
+
+normalize_long_ints = lambda s: re.sub(r'(?<![\w])(\d+)L(?![\w])', '\\1', s)
+
+class OutputChecker(doctest.OutputChecker):
+ def check_output(self, want, got, optionflags):
+ ok = doctest.OutputChecker.check_output(self, want, got, optionflags)
+
+ # Doctest does an exact string comparison of output, which means long
+ # integers aren't equal to normal integers ("22L" vs. "22"). The
+ # following code normalizes long integers so that they equal normal
+ # integers.
+ 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)
+
+ # Rollback, in case of database errors. Otherwise they'd have
+ # side effects on other tests.
+ from django.db import transaction
+ transaction.rollback_unless_managed()
+
diff --git a/django/test/utils.py b/django/test/utils.py
new file mode 100644
index 0000000000..28db46dafd
--- /dev/null
+++ b/django/test/utils.py
@@ -0,0 +1,78 @@
+import sys, time
+from django.conf import settings
+from django.db import connection, transaction
+
+# The prefix to put on the default database name when creating
+# the test database.
+TEST_DATABASE_PREFIX = 'test_'
+
+def _set_autocommit(connection):
+ "Make sure a connection is in autocommit mode."
+ if hasattr(connection.connection, "autocommit"):
+ connection.connection.autocommit(True)
+ elif hasattr(connection.connection, "set_isolation_level"):
+ connection.connection.set_isolation_level(0)
+
+def create_test_db(verbosity=1, autoclobber=False):
+ if verbosity >= 1:
+ print "Creating test database..."
+ # If we're using SQLite, it's more convenient to test against an
+ # in-memory database.
+ if settings.DATABASE_ENGINE == "sqlite3":
+ TEST_DATABASE_NAME = ":memory:"
+ else:
+ TEST_DATABASE_NAME = TEST_DATABASE_PREFIX + settings.DATABASE_NAME
+
+ # Create the test database and connect to it. We need to autocommit
+ # if the database supports it because PostgreSQL doesn't allow
+ # CREATE/DROP DATABASE statements within transactions.
+ cursor = connection.cursor()
+ _set_autocommit(connection)
+ try:
+ cursor.execute("CREATE DATABASE %s" % TEST_DATABASE_NAME)
+ except Exception, e:
+ sys.stderr.write("Got an error creating the test database: %s\n" % e)
+ if not autoclobber:
+ confirm = raw_input("It appears the test database, %s, already exists. Type 'yes' to delete it, or 'no' to cancel: " % TEST_DATABASE_NAME)
+ if autoclobber or confirm == 'yes':
+ try:
+ if verbosity >= 1:
+ print "Destroying old test database..."
+ cursor.execute("DROP DATABASE %s" % TEST_DATABASE_NAME)
+ if verbosity >= 1:
+ print "Creating test database..."
+ cursor.execute("CREATE DATABASE %s" % TEST_DATABASE_NAME)
+ except Exception, e:
+ sys.stderr.write("Got an error recreating the test database: %s\n" % e)
+ sys.exit(2)
+ else:
+ print "Tests cancelled."
+ sys.exit(1)
+
+ connection.close()
+ old_database_name = settings.DATABASE_NAME
+ 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()
+
+ return old_database_name
+
+def destroy_test_db(old_database_name, verbosity=1):
+ # Unless we're using SQLite, remove the test database to clean up after
+ # ourselves. Connect to the previous database (not the test database)
+ # to do so, because it's not allowed to delete a database while being
+ # 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
+ cursor = connection.cursor()
+ _set_autocommit(connection)
+ time.sleep(1) # To avoid "database is being accessed by other users" errors.
+ cursor.execute("DROP DATABASE %s" % TEST_DATABASE_NAME)
+ connection.close()
+
diff --git a/django/views/generic/date_based.py b/django/views/generic/date_based.py
index 860199c22e..d13c0293be 100644
--- a/django/views/generic/date_based.py
+++ b/django/views/generic/date_based.py
@@ -1,6 +1,7 @@
from django.template import loader, RequestContext
from django.core.exceptions import ObjectDoesNotExist
from django.core.xheaders import populate_xheaders
+from django.db.models.fields import DateTimeField
from django.http import Http404, HttpResponse
import datetime, time
@@ -235,9 +236,10 @@ def archive_day(request, year, month, day, queryset, date_field,
model = queryset.model
now = datetime.datetime.now()
- lookup_kwargs = {
- '%s__range' % date_field: (datetime.datetime.combine(date, datetime.time.min), datetime.datetime.combine(date, datetime.time.max)),
- }
+ if isinstance(model._meta.get_field(date_field), DateTimeField):
+ lookup_kwargs = {'%s__range' % date_field: (datetime.datetime.combine(date, datetime.time.min), datetime.datetime.combine(date, datetime.time.max))}
+ else:
+ lookup_kwargs = {date_field: date}
# Only bother to check current date if the date isn't in the past and future objects aren't requested.
if date >= now.date() and not allow_future:
@@ -304,9 +306,10 @@ def object_detail(request, year, month, day, queryset, date_field,
model = queryset.model
now = datetime.datetime.now()
- lookup_kwargs = {
- '%s__range' % date_field: (datetime.datetime.combine(date, datetime.time.min), datetime.datetime.combine(date, datetime.time.max)),
- }
+ if isinstance(model._meta.get_field(date_field), DateTimeField):
+ lookup_kwargs = {'%s__range' % date_field: (datetime.datetime.combine(date, datetime.time.min), datetime.datetime.combine(date, datetime.time.max))}
+ else:
+ lookup_kwargs = {date_field: date}
# Only bother to check current date if the date isn't in the past and future objects aren't requested.
if date >= now.date() and not allow_future:
diff --git a/docs/faq.txt b/docs/faq.txt
index 42d9ddea55..d39e203c76 100644
--- a/docs/faq.txt
+++ b/docs/faq.txt
@@ -16,12 +16,17 @@ hours to take a complicated Web application from concept to public launch.
At the same time, the World Online Web developers have consistently been
perfectionists when it comes to following best practices of Web development.
-Thus, Django was designed not only to allow fast Web development, but
-*best-practice* Web development.
-
-Django would not be possible without a whole host of open-source projects --
-`Apache`_, `Python`_, and `PostgreSQL`_ to name a few -- and we're thrilled to
-be able to give something back to the open-source community.
+In fall 2003, the World Online developers (Adrian Holovaty and Simon Willison)
+ditched PHP and began using Python to develop its Web sites. As they built
+intensive, richly interactive sites such as Lawrence.com, they began to extract
+a generic Web development framework that let them build Web applications more
+and more quickly. They tweaked this framework constantly, adding improvements
+over two years.
+
+In summer 2005, World Online decided to open-source the resulting software,
+Django. Django would not be possible without a whole host of open-source
+projects -- `Apache`_, `Python`_, and `PostgreSQL`_ to name a few -- and we're
+thrilled to be able to give something back to the open-source community.
.. _Apache: http://httpd.apache.org/
.. _Python: http://www.python.org/
@@ -42,8 +47,8 @@ Django is pronounced **JANG**-oh. Rhymes with FANG-oh. The "D" is silent.
Is Django stable?
-----------------
-Yes. World Online has been using Django for more than two years. Sites built on
-Django have weathered traffic spikes of over one million hits an hour and a
+Yes. World Online has been using Django for more than three years. Sites built
+on Django have weathered traffic spikes of over one million hits an hour and a
number of Slashdottings. Yes, it's quite stable.
Does Django scale?
@@ -630,6 +635,14 @@ You can also use the Python API. See `creating users`_ for full info.
Contributing code
=================
+How can I get started contributing code to Django?
+--------------------------------------------------
+
+Thanks for asking! We've written an entire document devoted to this question.
+It's titled `Contributing to Django`_.
+
+.. _Contributing do Django: http://www.djangoproject.com/documentation/contributing/
+
I submitted a bug fix in the ticket system several weeks ago. Why are you ignoring my patch?
--------------------------------------------------------------------------------------------
diff --git a/docs/model-api.txt b/docs/model-api.txt
index 0a3abe4e26..b46a11c463 100644
--- a/docs/model-api.txt
+++ b/docs/model-api.txt
@@ -1222,10 +1222,13 @@ A few special cases to note about ``list_display``:
of the related object.
* ``ManyToManyField`` fields aren't supported, because that would entail
- executing a separate SQL statement for each row in the table.
+ executing a separate SQL statement for each row in the table. If you
+ want to do this nonetheless, give your model a custom method, and add
+ that method's name to ``list_display``. (See below for more on custom
+ methods in ``list_display``.)
- * If the field is a ``BooleanField``, Django will display a pretty "on" or
- "off" icon instead of ``True`` or ``False``.
+ * If the field is a ``BooleanField`` or ``NullBooleanField``, Django will
+ display a pretty "on" or "off" icon instead of ``True`` or ``False``.
* If the string given is a method of the model, Django will call it and
display the output. This method should have a ``short_description``
@@ -1262,6 +1265,16 @@ A few special cases to note about ``list_display``:
return '<span style="color: #%s;">%s %s</span>' % (self.color_code, self.first_name, self.last_name)
colored_name.allow_tags = True
+ * The ``__str__()`` method is just as valid in ``list_display`` as any
+ other model method, so it's perfectly OK to do this::
+
+ list_display = ('__str__', 'some_other_field')
+
+ * For any element of ``list_display`` that is not a field on the model, the
+ change list page will not allow ordering by that column. This is because
+ ordering is done at the database level, and Django has no way of knowing
+ how to order the result of a custom method at the SQL level.
+
``list_display_links``
----------------------
diff --git a/docs/overview.txt b/docs/overview.txt
index 5a399582e8..8e6274dd9a 100644
--- a/docs/overview.txt
+++ b/docs/overview.txt
@@ -159,7 +159,7 @@ of contents for your app, it contains a simple mapping between URL patterns and
Python callback functions. URLconfs also serve to decouple URLs from Python
code.
-Here's what a URLconf might look like for the above ``Reporter``/``Article``
+Here's what a URLconf might look like for the ``Reporter``/``Article``
example above::
from django.conf.urls.defaults import *
diff --git a/docs/settings.txt b/docs/settings.txt
index 67e0498e1a..d9df111155 100644
--- a/docs/settings.txt
+++ b/docs/settings.txt
@@ -473,25 +473,36 @@ LANGUAGES
Default: A tuple of all available languages. Currently, this is::
LANGUAGES = (
+ ('ar', _('Arabic')),
('bn', _('Bengali')),
('cs', _('Czech')),
('cy', _('Welsh')),
('da', _('Danish')),
('de', _('German')),
+ ('el', _('Greek')),
('en', _('English')),
('es', _('Spanish')),
+ ('es_AR', _('Argentinean Spanish')),
('fr', _('French')),
('gl', _('Galician')),
+ ('hu', _('Hungarian')),
+ ('he', _('Hebrew')),
('is', _('Icelandic')),
('it', _('Italian')),
+ ('ja', _('Japanese')),
+ ('nl', _('Dutch')),
('no', _('Norwegian')),
('pt-br', _('Brazilian')),
('ro', _('Romanian')),
('ru', _('Russian')),
('sk', _('Slovak')),
+ ('sl', _('Slovenian')),
('sr', _('Serbian')),
('sv', _('Swedish')),
+ ('ta', _('Tamil')),
+ ('uk', _('Ukrainian')),
('zh-cn', _('Simplified Chinese')),
+ ('zh-tw', _('Traditional Chinese')),
)
A tuple of two-tuples in the format (language code, language name). This
diff --git a/docs/templates.txt b/docs/templates.txt
index 49d30018fe..c9e76d6c94 100644
--- a/docs/templates.txt
+++ b/docs/templates.txt
@@ -141,6 +141,7 @@ It's easiest to understand template inheritance by starting with an example::
{% block content %}{% endblock %}
</div>
</body>
+ </html>
This template, which we'll call ``base.html``, defines a simple HTML skeleton
document that you might use for a simple two-column page. It's the job of
@@ -196,6 +197,7 @@ like::
<p>This is my second entry.</p>
</div>
</body>
+ </html>
Note that since the child template didn't define the ``sidebar`` block, the
value from the parent template is used instead. Content within a ``{% block %}``
@@ -363,7 +365,7 @@ extends
Signal that this template extends a parent template.
-This tag can be used in two ways:
+This tag can be used in two ways:
* ``{% extends "base.html" %}`` (with quotes) uses the literal value
``"base.html"`` as the name of the parent template to extend.
@@ -961,13 +963,13 @@ any string.
pluralize
~~~~~~~~~
-Returns a plural suffix if the value is not 1. By default, this suffix is ``'s'``.
+Returns a plural suffix if the value is not 1. By default, this suffix is ``'s'``.
Example::
You have {{ num_messages }} message{{ num_messages|pluralize }}.
-For words that require a suffix other than ``'s'``, you can provide an alternate
+For words that require a suffix other than ``'s'``, you can provide an alternate
suffix as a parameter to the filter.
Example::
diff --git a/tests/modeltests/basic/models.py b/tests/modeltests/basic/models.py
index 78d943eb97..acbea0d1e0 100644
--- a/tests/modeltests/basic/models.py
+++ b/tests/modeltests/basic/models.py
@@ -13,8 +13,7 @@ class Article(models.Model):
def __str__(self):
return self.headline
-API_TESTS = """
-
+__test__ = {'API_TESTS': """
# No articles are in the system yet.
>>> Article.objects.all()
[]
@@ -314,14 +313,14 @@ AttributeError: Manager isn't accessible via Article instances
>>> Article.objects.all()
[<Article: Article 6>, <Article: Default headline>, <Article: Article 7>, <Article: Updated article 8>]
-"""
+"""}
from django.conf import settings
building_docs = getattr(settings, 'BUILDING_DOCS', False)
if building_docs or settings.DATABASE_ENGINE == 'postgresql':
- API_TESTS += """
+ __test__['API_TESTS'] += """
# In PostgreSQL, microsecond-level precision is available.
>>> a9 = Article(headline='Article 9', pub_date=datetime(2005, 7, 31, 12, 30, 45, 180))
>>> a9.save()
@@ -330,7 +329,7 @@ datetime.datetime(2005, 7, 31, 12, 30, 45, 180)
"""
if building_docs or settings.DATABASE_ENGINE == 'mysql':
- API_TESTS += """
+ __test__['API_TESTS'] += """
# In MySQL, microsecond-level precision isn't available. You'll lose
# microsecond-level precision once the data is saved.
>>> a9 = Article(headline='Article 9', pub_date=datetime(2005, 7, 31, 12, 30, 45, 180))
@@ -339,7 +338,7 @@ if building_docs or settings.DATABASE_ENGINE == 'mysql':
datetime.datetime(2005, 7, 31, 12, 30, 45)
"""
-API_TESTS += """
+__test__['API_TESTS'] += """
# You can manually specify the primary key when creating a new object.
>>> a101 = Article(id=101, headline='Article 101', pub_date=datetime(2005, 7, 31, 12, 30, 45))
diff --git a/tests/modeltests/choices/models.py b/tests/modeltests/choices/models.py
index 881fb29fd2..37d36fe1d8 100644
--- a/tests/modeltests/choices/models.py
+++ b/tests/modeltests/choices/models.py
@@ -23,7 +23,7 @@ class Person(models.Model):
def __str__(self):
return self.name
-API_TESTS = """
+__test__ = {'API_TESTS':"""
>>> a = Person(name='Adrian', gender='M')
>>> a.save()
>>> s = Person(name='Sara', gender='F')
@@ -36,4 +36,4 @@ API_TESTS = """
'Male'
>>> s.get_gender_display()
'Female'
-"""
+"""}
diff --git a/tests/modeltests/custom_columns/models.py b/tests/modeltests/custom_columns/models.py
index 7d8c52d137..e88fa80da2 100644
--- a/tests/modeltests/custom_columns/models.py
+++ b/tests/modeltests/custom_columns/models.py
@@ -15,7 +15,7 @@ class Person(models.Model):
def __str__(self):
return '%s %s' % (self.first_name, self.last_name)
-API_TESTS = """
+__test__ = {'API_TESTS':"""
# Create a Person.
>>> p = Person(first_name='John', last_name='Smith')
>>> p.save()
@@ -50,4 +50,4 @@ AttributeError: 'Person' object has no attribute 'firstname'
Traceback (most recent call last):
...
AttributeError: 'Person' object has no attribute 'last'
-"""
+"""}
diff --git a/tests/modeltests/custom_managers/models.py b/tests/modeltests/custom_managers/models.py
index 1c4e91b526..99df875275 100644
--- a/tests/modeltests/custom_managers/models.py
+++ b/tests/modeltests/custom_managers/models.py
@@ -58,7 +58,7 @@ class Car(models.Model):
def __str__(self):
return self.name
-API_TESTS = """
+__test__ = {'API_TESTS':"""
>>> p1 = Person(first_name='Bugs', last_name='Bunny', fun=True)
>>> p1.save()
>>> p2 = Person(first_name='Droopy', last_name='Dog', fun=False)
@@ -104,4 +104,4 @@ True
# to the first manager defined in the class. In this case, it's "cars".
>>> Car._default_manager.order_by('name')
[<Car: Corvette>, <Car: Neon>]
-"""
+"""}
diff --git a/tests/modeltests/custom_methods/models.py b/tests/modeltests/custom_methods/models.py
index e314d97264..e8fb751d54 100644
--- a/tests/modeltests/custom_methods/models.py
+++ b/tests/modeltests/custom_methods/models.py
@@ -36,7 +36,7 @@ class Article(models.Model):
# positional arguments to Article().
return [self.__class__(*row) for row in cursor.fetchall()]
-API_TESTS = """
+__test__ = {'API_TESTS':"""
# Create a couple of Articles.
>>> from datetime import date
>>> a = Article(id=None, headline='Area man programs in Python', pub_date=date(2005, 7, 27))
@@ -55,4 +55,4 @@ False
[<Article: Area man programs in Python>]
>>> b.articles_from_same_day_2()
[<Article: Area man programs in Python>]
-"""
+"""}
diff --git a/tests/modeltests/custom_pk/models.py b/tests/modeltests/custom_pk/models.py
index f7b790ca21..ca788f6aa5 100644
--- a/tests/modeltests/custom_pk/models.py
+++ b/tests/modeltests/custom_pk/models.py
@@ -27,7 +27,7 @@ class Business(models.Model):
def __str__(self):
return self.name
-API_TESTS = """
+__test__ = {'API_TESTS':"""
>>> dan = Employee(employee_code='ABC123', first_name='Dan', last_name='Jones')
>>> dan.save()
>>> Employee.objects.all()
@@ -88,4 +88,4 @@ DoesNotExist: Employee matching query does not exist.
>>> Business.objects.filter(employees__first_name__startswith='Fran')
[<Business: Sears>]
-"""
+"""}
diff --git a/tests/modeltests/empty/models.py b/tests/modeltests/empty/models.py
index 0f5a0b870f..0e5d572504 100644
--- a/tests/modeltests/empty/models.py
+++ b/tests/modeltests/empty/models.py
@@ -10,7 +10,7 @@ from django.db import models
class Empty(models.Model):
pass
-API_TESTS = """
+__test__ = {'API_TESTS':"""
>>> m = Empty()
>>> m.id
>>> m.save()
@@ -23,4 +23,4 @@ True
>>> existing = Empty(m.id)
>>> existing.save()
-"""
+"""}
diff --git a/tests/modeltests/field_defaults/models.py b/tests/modeltests/field_defaults/models.py
index 0d69ffd8be..da4cd38974 100644
--- a/tests/modeltests/field_defaults/models.py
+++ b/tests/modeltests/field_defaults/models.py
@@ -19,7 +19,7 @@ class Article(models.Model):
def __str__(self):
return self.headline
-API_TESTS = """
+__test__ = {'API_TESTS':"""
>>> from datetime import datetime
# No articles are in the system yet.
@@ -48,4 +48,4 @@ API_TESTS = """
>>> d = now - a.pub_date
>>> d.seconds < 5
True
-"""
+"""}
diff --git a/tests/modeltests/generic_relations/models.py b/tests/modeltests/generic_relations/models.py
index e9a81a19e8..eb64d7ec3d 100644
--- a/tests/modeltests/generic_relations/models.py
+++ b/tests/modeltests/generic_relations/models.py
@@ -53,7 +53,7 @@ class Mineral(models.Model):
def __str__(self):
return self.name
-API_TESTS = """
+__test__ = {'API_TESTS':"""
# Create the world in 7 lines of code...
>>> lion = Animal(common_name="Lion", latin_name="Panthera leo")
>>> platypus = Animal(common_name="Platypus", latin_name="Ornithorhynchus anatinus")
@@ -105,4 +105,4 @@ API_TESTS = """
[<TaggedItem: shiny>]
>>> TaggedItem.objects.filter(content_type__pk=ctype.id, object_id=quartz.id)
[<TaggedItem: clearish>]
-"""
+"""}
diff --git a/tests/modeltests/get_latest/models.py b/tests/modeltests/get_latest/models.py
index 42e7a14ec7..ff8d8f28eb 100644
--- a/tests/modeltests/get_latest/models.py
+++ b/tests/modeltests/get_latest/models.py
@@ -29,7 +29,7 @@ class Person(models.Model):
def __str__(self):
return self.name
-API_TESTS = """
+__test__ = {'API_TESTS':"""
# Because no Articles exist yet, get_latest() raises ArticleDoesNotExist.
>>> Article.objects.latest()
Traceback (most recent call last):
@@ -76,4 +76,4 @@ AssertionError: latest() requires either a field_name parameter or 'get_latest_b
>>> Person.objects.latest('birthday')
<Person: Stephanie>
-"""
+"""}
diff --git a/tests/modeltests/get_or_create/models.py b/tests/modeltests/get_or_create/models.py
index 10a8721afc..b4f39ceded 100644
--- a/tests/modeltests/get_or_create/models.py
+++ b/tests/modeltests/get_or_create/models.py
@@ -15,7 +15,7 @@ class Person(models.Model):
def __str__(self):
return '%s %s' % (self.first_name, self.last_name)
-API_TESTS = """
+__test__ = {'API_TESTS':"""
# Acting as a divine being, create an Person.
>>> from datetime import date
>>> p = Person(first_name='John', last_name='Lennon', birthday=date(1940, 10, 9))
@@ -49,4 +49,4 @@ True
False
>>> Person.objects.count()
2
-"""
+"""}
diff --git a/tests/modeltests/invalid_models/models.py b/tests/modeltests/invalid_models/models.py
index eb305b4e92..5540c1bd5f 100644
--- a/tests/modeltests/invalid_models/models.py
+++ b/tests/modeltests/invalid_models/models.py
@@ -78,7 +78,7 @@ class SelfClashM2M(models.Model):
-error_log = """invalid_models.fielderrors: "charfield": CharFields require a "maxlength" attribute.
+model_errors = """invalid_models.fielderrors: "charfield": CharFields require a "maxlength" attribute.
invalid_models.fielderrors: "floatfield": FloatFields require a "decimal_places" attribute.
invalid_models.fielderrors: "floatfield": FloatFields require a "max_digits" attribute.
invalid_models.fielderrors: "filefield": FileFields require an "upload_to" attribute.
diff --git a/tests/modeltests/lookup/models.py b/tests/modeltests/lookup/models.py
index 55bb373a4b..09c3aa7aa8 100644
--- a/tests/modeltests/lookup/models.py
+++ b/tests/modeltests/lookup/models.py
@@ -15,7 +15,7 @@ class Article(models.Model):
def __str__(self):
return self.headline
-API_TESTS = r"""
+__test__ = {'API_TESTS':r"""
# Create a couple of Articles.
>>> from datetime import datetime
>>> a1 = Article(headline='Article 1', pub_date=datetime(2005, 7, 26))
@@ -191,4 +191,4 @@ DoesNotExist: Article matching query does not exist.
>>> Article.objects.filter(headline__contains='\\')
[<Article: Article with \ backslash>]
-"""
+"""}
diff --git a/tests/modeltests/m2m_and_m2o/models.py b/tests/modeltests/m2m_and_m2o/models.py
index f43fb12d9e..7fc66ed5a0 100644
--- a/tests/modeltests/m2m_and_m2o/models.py
+++ b/tests/modeltests/m2m_and_m2o/models.py
@@ -21,7 +21,7 @@ class Issue(models.Model):
ordering = ('num',)
-API_TESTS = """
+__test__ = {'API_TESTS':"""
>>> Issue.objects.all()
[]
>>> r = User(username='russell')
@@ -62,4 +62,4 @@ API_TESTS = """
[<Issue: 1>, <Issue: 2>, <Issue: 3>]
>>> Issue.objects.filter(Q(client=r.id) | Q(cc__id__exact=r.id))
[<Issue: 1>, <Issue: 2>, <Issue: 3>]
-"""
+"""}
diff --git a/tests/modeltests/m2m_intermediary/models.py b/tests/modeltests/m2m_intermediary/models.py
index 848d035c39..b917db6189 100644
--- a/tests/modeltests/m2m_intermediary/models.py
+++ b/tests/modeltests/m2m_intermediary/models.py
@@ -34,7 +34,7 @@ class Writer(models.Model):
def __str__(self):
return '%s (%s)' % (self.reporter, self.position)
-API_TESTS = """
+__test__ = {'API_TESTS':"""
# Create a few Reporters.
>>> r1 = Reporter(first_name='John', last_name='Smith')
>>> r1.save()
@@ -65,4 +65,4 @@ API_TESTS = """
<Article: This is a test>
>>> r1.writer_set.all()
[<Writer: John Smith (Main writer)>]
-"""
+"""}
diff --git a/tests/modeltests/m2m_multiple/models.py b/tests/modeltests/m2m_multiple/models.py
index e4fef75f19..5a1aa122a9 100644
--- a/tests/modeltests/m2m_multiple/models.py
+++ b/tests/modeltests/m2m_multiple/models.py
@@ -28,7 +28,7 @@ class Article(models.Model):
def __str__(self):
return self.headline
-API_TESTS = """
+__test__ = {'API_TESTS':"""
>>> from datetime import datetime
>>> c1 = Category(name='Sports')
@@ -76,4 +76,4 @@ API_TESTS = """
[]
>>> c4.secondary_article_set.all()
[<Article: Area man steals>, <Article: Area man runs>]
-"""
+"""}
diff --git a/tests/modeltests/m2m_recursive/models.py b/tests/modeltests/m2m_recursive/models.py
index dace32d565..9f31cf92c0 100644
--- a/tests/modeltests/m2m_recursive/models.py
+++ b/tests/modeltests/m2m_recursive/models.py
@@ -22,7 +22,7 @@ class Person(models.Model):
def __str__(self):
return self.name
-API_TESTS = """
+__test__ = {'API_TESTS':"""
>>> a = Person(name='Anne')
>>> a.save()
>>> b = Person(name='Bill')
@@ -189,4 +189,4 @@ API_TESTS = """
>>> d.stalkers.all()
[<Person: Chuck>]
-"""
+"""}
diff --git a/tests/modeltests/m2o_recursive/models.py b/tests/modeltests/m2o_recursive/models.py
index 44881b5a2f..0b528faf9e 100644
--- a/tests/modeltests/m2o_recursive/models.py
+++ b/tests/modeltests/m2o_recursive/models.py
@@ -19,7 +19,7 @@ class Category(models.Model):
def __str__(self):
return self.name
-API_TESTS = """
+__test__ = {'API_TESTS':"""
# Create a few Category objects.
>>> r = Category(id=None, name='Root category', parent=None)
>>> r.save()
@@ -37,4 +37,4 @@ None
[]
>>> c.parent
<Category: Root category>
-"""
+"""}
diff --git a/tests/modeltests/m2o_recursive2/models.py b/tests/modeltests/m2o_recursive2/models.py
index 93185c6a4c..5b7c5447ad 100644
--- a/tests/modeltests/m2o_recursive2/models.py
+++ b/tests/modeltests/m2o_recursive2/models.py
@@ -17,7 +17,7 @@ class Person(models.Model):
def __str__(self):
return self.full_name
-API_TESTS = """
+__test__ = {'API_TESTS':"""
# Create two Person objects -- the mom and dad in our family.
>>> dad = Person(full_name='John Smith Senior', mother=None, father=None)
>>> dad.save()
@@ -40,4 +40,4 @@ API_TESTS = """
[]
>>> kid.fathers_child_set.all()
[]
-"""
+"""}
diff --git a/tests/modeltests/manipulators/models.py b/tests/modeltests/manipulators/models.py
index f7b20d52ac..e5b8be55b5 100644
--- a/tests/modeltests/manipulators/models.py
+++ b/tests/modeltests/manipulators/models.py
@@ -21,7 +21,7 @@ class Album(models.Model):
def __str__(self):
return self.name
-API_TESTS = """
+__test__ = {'API_TESTS':"""
>>> from django.utils.datastructures import MultiValueDict
# Create a Musician object via the default AddManipulator.
@@ -88,4 +88,4 @@ True
<Album: Ultimate Ella>
>>> a2.release_date
datetime.date(2005, 2, 13)
-"""
+"""}
diff --git a/tests/modeltests/many_to_many/models.py b/tests/modeltests/many_to_many/models.py
index 0e989a0fbe..357f3ca629 100644
--- a/tests/modeltests/many_to_many/models.py
+++ b/tests/modeltests/many_to_many/models.py
@@ -28,7 +28,7 @@ class Article(models.Model):
class Meta:
ordering = ('headline',)
-API_TESTS = """
+__test__ = {'API_TESTS':"""
# Create a couple of Publications.
>>> p1 = Publication(id=None, title='The Python Journal')
>>> p1.save()
@@ -231,4 +231,4 @@ API_TESTS = """
>>> p1.article_set.all()
[<Article: NASA uses Python>]
-"""
+"""}
diff --git a/tests/modeltests/many_to_one/models.py b/tests/modeltests/many_to_one/models.py
index d202975128..82eb3257d0 100644
--- a/tests/modeltests/many_to_one/models.py
+++ b/tests/modeltests/many_to_one/models.py
@@ -25,7 +25,7 @@ class Article(models.Model):
class Meta:
ordering = ('headline',)
-API_TESTS = """
+__test__ = {'API_TESTS':"""
# Create a few Reporters.
>>> r = Reporter(first_name='John', last_name='Smith', email='john@example.com')
>>> r.save()
@@ -263,4 +263,4 @@ TypeError: Cannot resolve keyword 'reporter_id' into field
>>> Article.objects.all()
[]
-"""
+"""}
diff --git a/tests/modeltests/many_to_one_null/models.py b/tests/modeltests/many_to_one_null/models.py
index b1936b9861..fb0f6ac3b7 100644
--- a/tests/modeltests/many_to_one_null/models.py
+++ b/tests/modeltests/many_to_one_null/models.py
@@ -23,7 +23,7 @@ class Article(models.Model):
def __str__(self):
return self.headline
-API_TESTS = """
+__test__ = {'API_TESTS':"""
# Create a Reporter.
>>> r = Reporter(name='John Smith')
>>> r.save()
@@ -121,4 +121,4 @@ DoesNotExist: <Article: Fourth> is not related to <Reporter: John Smith>.
>>> Article.objects.filter(reporter__isnull=True)
[<Article: First>, <Article: Fourth>]
-"""
+"""}
diff --git a/tests/modeltests/model_inheritance/models.py b/tests/modeltests/model_inheritance/models.py
index 473cf24a9f..babef73e0a 100644
--- a/tests/modeltests/model_inheritance/models.py
+++ b/tests/modeltests/model_inheritance/models.py
@@ -26,7 +26,7 @@ class ItalianRestaurant(Restaurant):
def __str__(self):
return "%s the italian restaurant" % self.name
-API_TESTS = """
+__test__ = {'API_TESTS':"""
# Make sure Restaurant has the right fields in the right order.
>>> [f.name for f in Restaurant._meta.fields]
['id', 'name', 'address', 'serves_hot_dogs', 'serves_pizza']
@@ -50,4 +50,4 @@ API_TESTS = """
>>> ir.save()
-"""
+"""}
diff --git a/tests/modeltests/mutually_referential/models.py b/tests/modeltests/mutually_referential/models.py
index 07b52effbc..5a3897d21a 100644
--- a/tests/modeltests/mutually_referential/models.py
+++ b/tests/modeltests/mutually_referential/models.py
@@ -14,7 +14,7 @@ class Child(Model):
name = CharField(maxlength=100)
parent = ForeignKey(Parent)
-API_TESTS = """
+__test__ = {'API_TESTS':"""
# Create a Parent
>>> q = Parent(name='Elizabeth')
>>> q.save()
@@ -29,4 +29,4 @@ API_TESTS = """
>>> q.delete()
-""" \ No newline at end of file
+"""} \ No newline at end of file
diff --git a/tests/modeltests/one_to_one/models.py b/tests/modeltests/one_to_one/models.py
index f95556c08d..8afa74454d 100644
--- a/tests/modeltests/one_to_one/models.py
+++ b/tests/modeltests/one_to_one/models.py
@@ -30,7 +30,7 @@ class Waiter(models.Model):
def __str__(self):
return "%s the waiter at %s" % (self.name, self.restaurant)
-API_TESTS = """
+__test__ = {'API_TESTS':"""
# Create a couple of Places.
>>> p1 = Place(name='Demon Dogs', address='944 W. Fullerton')
>>> p1.save()
@@ -151,4 +151,4 @@ DoesNotExist: Restaurant matching query does not exist.
# Delete the restaurant; the waiter should also be removed
>>> r = Restaurant.objects.get(pk=1)
>>> r.delete()
-"""
+"""}
diff --git a/tests/modeltests/or_lookups/models.py b/tests/modeltests/or_lookups/models.py
index 80dc35f2b1..2de18edc1f 100644
--- a/tests/modeltests/or_lookups/models.py
+++ b/tests/modeltests/or_lookups/models.py
@@ -23,7 +23,7 @@ class Article(models.Model):
def __str__(self):
return self.headline
-API_TESTS = """
+__test__ = {'API_TESTS':"""
>>> from datetime import datetime
>>> from django.db.models import Q
@@ -101,4 +101,4 @@ API_TESTS = """
[<Article: Hello>]
>>> Article.objects.complex_filter(Q(pk=1) | Q(pk=2))
[<Article: Hello>, <Article: Goodbye>]
-"""
+"""}
diff --git a/tests/modeltests/ordering/models.py b/tests/modeltests/ordering/models.py
index b22568c900..110ae3d7fc 100644
--- a/tests/modeltests/ordering/models.py
+++ b/tests/modeltests/ordering/models.py
@@ -24,7 +24,7 @@ class Article(models.Model):
def __str__(self):
return self.headline
-API_TESTS = """
+__test__ = {'API_TESTS':"""
# Create a couple of Articles.
>>> from datetime import datetime
>>> a1 = Article(headline='Article 1', pub_date=datetime(2005, 7, 26))
@@ -64,4 +64,4 @@ API_TESTS = """
# don't know what order the output will be in.
>>> Article.objects.order_by('?')
[...]
-"""
+"""}
diff --git a/tests/modeltests/pagination/models.py b/tests/modeltests/pagination/models.py
index 165b251d35..ea2385dc79 100644
--- a/tests/modeltests/pagination/models.py
+++ b/tests/modeltests/pagination/models.py
@@ -15,7 +15,7 @@ class Article(models.Model):
def __str__(self):
return self.headline
-API_TESTS = """
+__test__ = {'API_TESTS':"""
# prepare a list of objects for pagination
>>> from datetime import datetime
>>> for x in range(1, 10):
@@ -64,4 +64,4 @@ True
>>> paginator.last_on_page(1)
9
-"""
+"""}
diff --git a/tests/modeltests/properties/models.py b/tests/modeltests/properties/models.py
index 3b0133bf8a..4ba6b1a808 100644
--- a/tests/modeltests/properties/models.py
+++ b/tests/modeltests/properties/models.py
@@ -20,7 +20,7 @@ class Person(models.Model):
full_name_2 = property(_get_full_name, _set_full_name)
-API_TESTS = """
+__test__ = {'API_TESTS':"""
>>> a = Person(first_name='John', last_name='Lennon')
>>> a.save()
>>> a.full_name
@@ -37,4 +37,4 @@ AttributeError: can't set attribute
>>> a2.save()
>>> a2.first_name
'Paul'
-"""
+"""}
diff --git a/tests/modeltests/reserved_names/models.py b/tests/modeltests/reserved_names/models.py
index db9196bebe..affe3f649d 100644
--- a/tests/modeltests/reserved_names/models.py
+++ b/tests/modeltests/reserved_names/models.py
@@ -24,7 +24,7 @@ class Thing(models.Model):
def __str__(self):
return self.when
-API_TESTS = """
+__test__ = {'API_TESTS':"""
>>> import datetime
>>> day1 = datetime.date(2005, 1, 1)
>>> day2 = datetime.date(2006, 2, 2)
@@ -53,4 +53,4 @@ b
>>> Thing.objects.filter(where__month=1)
[<Thing: a>]
-"""
+"""}
diff --git a/tests/modeltests/reverse_lookup/models.py b/tests/modeltests/reverse_lookup/models.py
index b8c4466021..7e6712676f 100644
--- a/tests/modeltests/reverse_lookup/models.py
+++ b/tests/modeltests/reverse_lookup/models.py
@@ -27,7 +27,7 @@ class Choice(models.Model):
def __str(self):
return self.name
-API_TESTS = """
+__test__ = {'API_TESTS':"""
>>> john = User(name="John Doe")
>>> john.save()
>>> jim = User(name="Jim Bo")
@@ -56,4 +56,4 @@ API_TESTS = """
Traceback (most recent call last):
...
TypeError: Cannot resolve keyword 'choice' into field
-"""
+"""}
diff --git a/tests/modeltests/save_delete_hooks/models.py b/tests/modeltests/save_delete_hooks/models.py
index f01a2932d3..6e24c308ba 100644
--- a/tests/modeltests/save_delete_hooks/models.py
+++ b/tests/modeltests/save_delete_hooks/models.py
@@ -24,7 +24,7 @@ class Person(models.Model):
super(Person, self).delete() # Call the "real" delete() method
print "After deletion"
-API_TESTS = """
+__test__ = {'API_TESTS':"""
>>> p1 = Person(first_name='John', last_name='Smith')
>>> p1.save()
Before save
@@ -39,4 +39,4 @@ After deletion
>>> Person.objects.all()
[]
-"""
+"""}
diff --git a/tests/modeltests/serializers/models.py b/tests/modeltests/serializers/models.py
index ccf565c365..d1d10b43c0 100644
--- a/tests/modeltests/serializers/models.py
+++ b/tests/modeltests/serializers/models.py
@@ -37,7 +37,7 @@ class Article(models.Model):
def __str__(self):
return self.headline
-API_TESTS = """
+__test__ = {'API_TESTS':"""
# Create some data:
>>> from datetime import datetime
>>> sports = Category(name="Sports")
@@ -118,4 +118,4 @@ API_TESTS = """
>>> Article.objects.all()
[<Article: Just kidding; I love TV poker>, <Article: Time to reform copyright>]
-"""
+"""}
diff --git a/tests/modeltests/str/models.py b/tests/modeltests/str/models.py
index 4e4228ac89..81230d538c 100644
--- a/tests/modeltests/str/models.py
+++ b/tests/modeltests/str/models.py
@@ -17,7 +17,7 @@ class Article(models.Model):
def __str__(self):
return self.headline
-API_TESTS = """
+__test__ = {'API_TESTS':"""
# Create an Article.
>>> from datetime import datetime
>>> a = Article(headline='Area man programs in Python', pub_date=datetime(2005, 7, 28))
@@ -28,4 +28,4 @@ API_TESTS = """
>>> a
<Article: Area man programs in Python>
-"""
+"""}
diff --git a/tests/modeltests/transactions/models.py b/tests/modeltests/transactions/models.py
index 92e0f38f47..e1fad8063e 100644
--- a/tests/modeltests/transactions/models.py
+++ b/tests/modeltests/transactions/models.py
@@ -17,16 +17,16 @@ class Reporter(models.Model):
def __str__(self):
return "%s %s" % (self.first_name, self.last_name)
-API_TESTS = """
+__test__ = {'API_TESTS':"""
>>> from django.db import connection, transaction
-"""
+"""}
from django.conf import settings
building_docs = getattr(settings, 'BUILDING_DOCS', False)
if building_docs or settings.DATABASE_ENGINE != 'mysql':
- API_TESTS += """
+ __test__['API_TESTS'] += """
# the default behavior is to autocommit after each save() action
>>> def create_a_reporter_then_fail(first, last):
... a = Reporter(first_name=first, last_name=last)
diff --git a/tests/modeltests/validation/models.py b/tests/modeltests/validation/models.py
index 57811f25a5..a9a3d3f485 100644
--- a/tests/modeltests/validation/models.py
+++ b/tests/modeltests/validation/models.py
@@ -20,7 +20,7 @@ class Person(models.Model):
def __str__(self):
return self.name
-API_TESTS = """
+__test__ = {'API_TESTS':"""
>>> import datetime
>>> valid_params = {
@@ -146,4 +146,4 @@ u'john@example.com'
>>> p.validate()
{'email': ['Enter a valid e-mail address.']}
-"""
+"""}
diff --git a/tests/othertests/cache.py b/tests/othertests/cache.py
deleted file mode 100644
index 81f2c20328..0000000000
--- a/tests/othertests/cache.py
+++ /dev/null
@@ -1,60 +0,0 @@
-# Unit tests for cache framework
-# Uses whatever cache backend is set in the test settings file.
-
-from django.core.cache import cache
-import time
-
-# functions/classes for complex data type tests
-def f():
- return 42
-class C:
- def m(n):
- return 24
-
-# simple set/get
-cache.set("key", "value")
-assert cache.get("key") == "value"
-
-# get with non-existent keys
-assert cache.get("does not exist") is None
-assert cache.get("does not exist", "bang!") == "bang!"
-
-# get_many
-cache.set('a', 'a')
-cache.set('b', 'b')
-cache.set('c', 'c')
-cache.set('d', 'd')
-assert cache.get_many(['a', 'c', 'd']) == {'a' : 'a', 'c' : 'c', 'd' : 'd'}
-assert cache.get_many(['a', 'b', 'e']) == {'a' : 'a', 'b' : 'b'}
-
-# delete
-cache.set("key1", "spam")
-cache.set("key2", "eggs")
-assert cache.get("key1") == "spam"
-cache.delete("key1")
-assert cache.get("key1") is None
-assert cache.get("key2") == "eggs"
-
-# has_key
-cache.set("hello", "goodbye")
-assert cache.has_key("hello") == True
-assert cache.has_key("goodbye") == False
-
-# test data types
-stuff = {
- 'string' : 'this is a string',
- 'int' : 42,
- 'list' : [1, 2, 3, 4],
- 'tuple' : (1, 2, 3, 4),
- 'dict' : {'A': 1, 'B' : 2},
- 'function' : f,
- 'class' : C,
-}
-for (key, value) in stuff.items():
- cache.set(key, value)
- assert cache.get(key) == value
-
-# expiration
-cache.set('expire', 'very quickly', 1)
-time.sleep(2)
-assert cache.get("expire") == None
diff --git a/tests/othertests/markup.py b/tests/othertests/markup.py
deleted file mode 100644
index 2b00a8c7a5..0000000000
--- a/tests/othertests/markup.py
+++ /dev/null
@@ -1,70 +0,0 @@
-# Quick tests for the markup templatetags (django.contrib.markup)
-
-from django.template import Template, Context, add_to_builtins
-import re
-
-add_to_builtins('django.contrib.markup.templatetags.markup')
-
-# find out if markup modules are installed and tailor the test appropriately
-try:
- import textile
-except ImportError:
- textile = None
-
-try:
- import markdown
-except ImportError:
- markdown = None
-
-try:
- import docutils
-except ImportError:
- docutils = None
-
-# simple examples 'cause this isn't actually testing the markup, just
-# that the filters work as advertised
-
-### test textile
-
-textile_content = """Paragraph 1
-
-Paragraph 2 with "quotes" and @code@"""
-
-t = Template("{{ textile_content|textile }}")
-rendered = t.render(Context(locals())).strip()
-if textile:
- assert rendered == """<p>Paragraph 1</p>
-
-<p>Paragraph 2 with &#8220;quotes&#8221; and <code>code</code></p>"""
-else:
- assert rendered == textile_content
-
-### test markdown
-
-markdown_content = """Paragraph 1
-
-## An h2"""
-
-t = Template("{{ markdown_content|markdown }}")
-rendered = t.render(Context(locals())).strip()
-if markdown:
- pattern = re.compile("""<p>Paragraph 1\s*</p>\s*<h2>\s*An h2</h2>""")
- assert pattern.match(rendered)
-else:
- assert rendered == markdown_content
-
-### test rest
-
-rest_content = """Paragraph 1
-
-Paragraph 2 with a link_
-
-.. _link: http://www.example.com/"""
-
-t = Template("{{ rest_content|restructuredtext }}")
-rendered = t.render(Context(locals())).strip()
-if docutils:
- assert rendered =="""<p>Paragraph 1</p>
-<p>Paragraph 2 with a <a class="reference" href="http://www.example.com/">link</a></p>"""
-else:
- assert rendered == rest_content
diff --git a/tests/othertests/templates.py b/tests/othertests/templates.py
deleted file mode 100644
index 9975f3b05c..0000000000
--- a/tests/othertests/templates.py
+++ /dev/null
@@ -1,634 +0,0 @@
-from django.conf import settings
-
-if __name__ == '__main__':
- # When running this file in isolation, we need to set up the configuration
- # before importing 'template'.
- settings.configure()
-
-from django import template
-from django.template import loader
-from django.utils.translation import activate, deactivate, install
-from django.utils.tzinfo import LocalTimezone
-from datetime import datetime, timedelta
-import traceback
-
-#################################
-# Custom template tag for tests #
-#################################
-
-register = template.Library()
-
-class EchoNode(template.Node):
- def __init__(self, contents):
- self.contents = contents
-
- def render(self, context):
- return " ".join(self.contents)
-
-def do_echo(parser, token):
- return EchoNode(token.contents.split()[1:])
-
-register.tag("echo", do_echo)
-
-template.libraries['django.templatetags.testtags'] = register
-
-#####################################
-# Helper objects for template tests #
-#####################################
-
-class SomeException(Exception):
- silent_variable_failure = True
-
-class SomeOtherException(Exception):
- pass
-
-class SomeClass:
- def __init__(self):
- self.otherclass = OtherClass()
-
- def method(self):
- return "SomeClass.method"
-
- def method2(self, o):
- return o
-
- def method3(self):
- raise SomeException
-
- def method4(self):
- raise SomeOtherException
-
-class OtherClass:
- def method(self):
- return "OtherClass.method"
-
-# NOW and NOW_tz are used by timesince tag tests.
-NOW = datetime.now()
-NOW_tz = datetime.now(LocalTimezone(datetime.now()))
-
-# SYNTAX --
-# 'template_name': ('template contents', 'context dict', 'expected string output' or Exception class)
-TEMPLATE_TESTS = {
-
- ### BASIC SYNTAX ##########################################################
-
- # Plain text should go through the template parser untouched
- 'basic-syntax01': ("something cool", {}, "something cool"),
-
- # Variables should be replaced with their value in the current context
- 'basic-syntax02': ("{{ headline }}", {'headline':'Success'}, "Success"),
-
- # More than one replacement variable is allowed in a template
- '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"),
-
- # A variable may not contain more than one word
- 'basic-syntax06': ("{{ multi word variable }}", {}, template.TemplateSyntaxError),
-
- # Raise TemplateSyntaxError for empty variable tags
- 'basic-syntax07': ("{{ }}", {}, template.TemplateSyntaxError),
- 'basic-syntax08': ("{{ }}", {}, template.TemplateSyntaxError),
-
- # Attribute syntax allows a template to call an object's attribute
- 'basic-syntax09': ("{{ var.method }}", {"var": SomeClass()}, "SomeClass.method"),
-
- # Multiple levels of attribute access are allowed
- '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"),
-
- # Raise TemplateSyntaxError when trying to access a variable beginning with an underscore
- 'basic-syntax12': ("{{ var.__dict__ }}", {"var": SomeClass()}, template.TemplateSyntaxError),
-
- # Raise TemplateSyntaxError when trying to access a variable containing an illegal character
- 'basic-syntax13': ("{{ va>r }}", {}, template.TemplateSyntaxError),
- 'basic-syntax14': ("{{ (var.r) }}", {}, template.TemplateSyntaxError),
- 'basic-syntax15': ("{{ sp%am }}", {}, template.TemplateSyntaxError),
- 'basic-syntax16': ("{{ eggs! }}", {}, template.TemplateSyntaxError),
- 'basic-syntax17': ("{{ moo? }}", {}, template.TemplateSyntaxError),
-
- # Attribute syntax allows a template to call a dictionary key's value
- '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"),
-
- # Fail silently when accessing a non-simple method
- 'basic-syntax20': ("{{ var.method2 }}", {"var": SomeClass()}, "INVALID"),
-
- # Basic filter usage
- 'basic-syntax21': ("{{ var|upper }}", {"var": "Django is the greatest!"}, "DJANGO IS THE GREATEST!"),
-
- # Chained filters
- 'basic-syntax22': ("{{ var|upper|lower }}", {"var": "Django is the greatest!"}, "django is the greatest!"),
-
- # Raise TemplateSyntaxError for space between a variable and filter pipe
- 'basic-syntax23': ("{{ var |upper }}", {}, template.TemplateSyntaxError),
-
- # Raise TemplateSyntaxError for space after a filter pipe
- 'basic-syntax24': ("{{ var| upper }}", {}, template.TemplateSyntaxError),
-
- # Raise TemplateSyntaxError for a nonexistent filter
- 'basic-syntax25': ("{{ var|does_not_exist }}", {}, template.TemplateSyntaxError),
-
- # Raise TemplateSyntaxError when trying to access a filter containing an illegal character
- 'basic-syntax26': ("{{ var|fil(ter) }}", {}, template.TemplateSyntaxError),
-
- # Raise TemplateSyntaxError for invalid block tags
- 'basic-syntax27': ("{% nothing_to_see_here %}", {}, template.TemplateSyntaxError),
-
- # Raise TemplateSyntaxError for empty block tags
- 'basic-syntax28': ("{% %}", {}, template.TemplateSyntaxError),
-
- # Chained filters, with an argument to the first one
- 'basic-syntax29': ('{{ var|removetags:"b i"|upper|lower }}', {"var": "<b><i>Yes</i></b>"}, "yes"),
-
- # Escaped string as argument
- 'basic-syntax30': (r'{{ var|default_if_none:" endquote\" hah" }}', {"var": None}, ' endquote" hah'),
-
- # Variable as argument
- 'basic-syntax31': (r'{{ var|default_if_none:var2 }}', {"var": None, "var2": "happy"}, 'happy'),
-
- # Default argument testing
- '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"),
-
- # In methods that raise an exception without a "silent_variable_attribute" set to True,
- # the exception propogates
- 'basic-syntax34': (r'1{{ var.method4 }}2', {"var": SomeClass()}, SomeOtherException),
-
- # Escaped backslash in argument
- 'basic-syntax35': (r'{{ var|default_if_none:"foo\bar" }}', {"var": None}, r'foo\bar'),
-
- # Escaped backslash using known escape char
- 'basic-syntax35': (r'{{ var|default_if_none:"foo\now" }}', {"var": None}, r'foo\now'),
-
- ### COMMENT TAG ###########################################################
- 'comment-tag01': ("{% comment %}this is hidden{% endcomment %}hello", {}, "hello"),
- 'comment-tag02': ("{% comment %}this is hidden{% endcomment %}hello{% comment %}foo{% endcomment %}", {}, "hello"),
-
- # Comment tag can contain invalid stuff.
- 'comment-tag03': ("foo{% comment %} {% if %} {% endcomment %}", {}, "foo"),
- 'comment-tag04': ("foo{% comment %} {% endblock %} {% endcomment %}", {}, "foo"),
- 'comment-tag05': ("foo{% comment %} {% somerandomtag %} {% endcomment %}", {}, "foo"),
-
- ### CYCLE TAG #############################################################
- 'cycle01': ('{% cycle a %}', {}, template.TemplateSyntaxError),
- 'cycle02': ('{% cycle a,b,c as abc %}{% cycle abc %}', {}, 'ab'),
- 'cycle03': ('{% cycle a,b,c as abc %}{% cycle abc %}{% cycle abc %}', {}, 'abc'),
- 'cycle04': ('{% cycle a,b,c as abc %}{% cycle abc %}{% cycle abc %}{% cycle abc %}', {}, 'abca'),
- 'cycle05': ('{% cycle %}', {}, template.TemplateSyntaxError),
- 'cycle06': ('{% cycle a %}', {}, template.TemplateSyntaxError),
- 'cycle07': ('{% cycle a,b,c as foo %}{% cycle bar %}', {}, template.TemplateSyntaxError),
-
- ### EXCEPTIONS ############################################################
-
- # Raise exception for invalid template name
- 'exception01': ("{% extends 'nonexistent' %}", {}, template.TemplateSyntaxError),
-
- # Raise exception for invalid template name (in variable)
- 'exception02': ("{% extends nonexistent %}", {}, template.TemplateSyntaxError),
-
- # Raise exception for extra {% extends %} tags
- 'exception03': ("{% extends 'inheritance01' %}{% block first %}2{% endblock %}{% extends 'inheritance16' %}", {}, template.TemplateSyntaxError),
-
- # Raise exception for custom tags used in child with {% load %} tag in parent, not in child
- 'exception04': ("{% extends 'inheritance17' %}{% block first %}{% echo 400 %}5678{% endblock %}", {}, template.TemplateSyntaxError),
-
- ### FILTER TAG ############################################################
- 'filter01': ('{% filter upper %}{% endfilter %}', {}, ''),
- 'filter02': ('{% filter upper %}django{% endfilter %}', {}, 'DJANGO'),
- 'filter03': ('{% filter upper|lower %}django{% endfilter %}', {}, 'django'),
-
- ### FIRSTOF TAG ###########################################################
- 'firstof01': ('{% firstof a b c %}', {'a':0,'b':0,'c':0}, ''),
- 'firstof02': ('{% firstof a b c %}', {'a':1,'b':0,'c':0}, '1'),
- 'firstof03': ('{% firstof a b c %}', {'a':0,'b':2,'c':0}, '2'),
- 'firstof04': ('{% firstof a b c %}', {'a':0,'b':0,'c':3}, '3'),
- 'firstof05': ('{% firstof a b c %}', {'a':1,'b':2,'c':3}, '1'),
- 'firstof06': ('{% firstof %}', {}, template.TemplateSyntaxError),
-
- ### FOR TAG ###############################################################
- 'for-tag01': ("{% for val in values %}{{ val }}{% endfor %}", {"values": [1, 2, 3]}, "123"),
- 'for-tag02': ("{% for val in values reversed %}{{ val }}{% endfor %}", {"values": [1, 2, 3]}, "321"),
- 'for-tag-vars01': ("{% for val in values %}{{ forloop.counter }}{% endfor %}", {"values": [6, 6, 6]}, "123"),
- 'for-tag-vars02': ("{% for val in values %}{{ forloop.counter0 }}{% endfor %}", {"values": [6, 6, 6]}, "012"),
- 'for-tag-vars03': ("{% for val in values %}{{ forloop.revcounter }}{% endfor %}", {"values": [6, 6, 6]}, "321"),
- 'for-tag-vars04': ("{% for val in values %}{{ forloop.revcounter0 }}{% endfor %}", {"values": [6, 6, 6]}, "210"),
-
- ### IF TAG ################################################################
- 'if-tag01': ("{% if foo %}yes{% else %}no{% endif %}", {"foo": True}, "yes"),
- 'if-tag02': ("{% if foo %}yes{% else %}no{% endif %}", {"foo": False}, "no"),
- 'if-tag03': ("{% if foo %}yes{% else %}no{% endif %}", {}, "no"),
-
- # AND
- 'if-tag-and01': ("{% if foo and bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': True}, 'yes'),
- 'if-tag-and02': ("{% if foo and bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': False}, 'no'),
- 'if-tag-and03': ("{% if foo and bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': True}, 'no'),
- 'if-tag-and04': ("{% if foo and bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': False}, 'no'),
- 'if-tag-and05': ("{% if foo and bar %}yes{% else %}no{% endif %}", {'foo': False}, 'no'),
- 'if-tag-and06': ("{% if foo and bar %}yes{% else %}no{% endif %}", {'bar': False}, 'no'),
- 'if-tag-and07': ("{% if foo and bar %}yes{% else %}no{% endif %}", {'foo': True}, 'no'),
- 'if-tag-and08': ("{% if foo and bar %}yes{% else %}no{% endif %}", {'bar': True}, 'no'),
-
- # OR
- 'if-tag-or01': ("{% if foo or bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': True}, 'yes'),
- 'if-tag-or02': ("{% if foo or bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': False}, 'yes'),
- 'if-tag-or03': ("{% if foo or bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': True}, 'yes'),
- 'if-tag-or04': ("{% if foo or bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': False}, 'no'),
- 'if-tag-or05': ("{% if foo or bar %}yes{% else %}no{% endif %}", {'foo': False}, 'no'),
- 'if-tag-or06': ("{% if foo or bar %}yes{% else %}no{% endif %}", {'bar': False}, 'no'),
- 'if-tag-or07': ("{% if foo or bar %}yes{% else %}no{% endif %}", {'foo': True}, 'yes'),
- 'if-tag-or08': ("{% if foo or bar %}yes{% else %}no{% endif %}", {'bar': True}, 'yes'),
-
- # TODO: multiple ORs
-
- # NOT
- 'if-tag-not01': ("{% if not foo %}no{% else %}yes{% endif %}", {'foo': True}, 'yes'),
- 'if-tag-not02': ("{% if not %}yes{% else %}no{% endif %}", {'foo': True}, 'no'),
- 'if-tag-not03': ("{% if not %}yes{% else %}no{% endif %}", {'not': True}, 'yes'),
- 'if-tag-not04': ("{% if not not %}no{% else %}yes{% endif %}", {'not': True}, 'yes'),
- 'if-tag-not05': ("{% if not not %}no{% else %}yes{% endif %}", {}, 'no'),
-
- 'if-tag-not06': ("{% if foo and not bar %}yes{% else %}no{% endif %}", {}, 'no'),
- 'if-tag-not07': ("{% if foo and not bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': True}, 'no'),
- 'if-tag-not08': ("{% if foo and not bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': False}, 'yes'),
- 'if-tag-not09': ("{% if foo and not bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': True}, 'no'),
- 'if-tag-not10': ("{% if foo and not bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': False}, 'no'),
-
- 'if-tag-not11': ("{% if not foo and bar %}yes{% else %}no{% endif %}", {}, 'no'),
- 'if-tag-not12': ("{% if not foo and bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': True}, 'no'),
- 'if-tag-not13': ("{% if not foo and bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': False}, 'no'),
- 'if-tag-not14': ("{% if not foo and bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': True}, 'yes'),
- 'if-tag-not15': ("{% if not foo and bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': False}, 'no'),
-
- 'if-tag-not16': ("{% if foo or not bar %}yes{% else %}no{% endif %}", {}, 'yes'),
- 'if-tag-not17': ("{% if foo or not bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': True}, 'yes'),
- 'if-tag-not18': ("{% if foo or not bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': False}, 'yes'),
- 'if-tag-not19': ("{% if foo or not bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': True}, 'no'),
- 'if-tag-not20': ("{% if foo or not bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': False}, 'yes'),
-
- 'if-tag-not21': ("{% if not foo or bar %}yes{% else %}no{% endif %}", {}, 'yes'),
- 'if-tag-not22': ("{% if not foo or bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': True}, 'yes'),
- 'if-tag-not23': ("{% if not foo or bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': False}, 'no'),
- 'if-tag-not24': ("{% if not foo or bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': True}, 'yes'),
- 'if-tag-not25': ("{% if not foo or bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': False}, 'yes'),
-
- 'if-tag-not26': ("{% if not foo and not bar %}yes{% else %}no{% endif %}", {}, 'yes'),
- 'if-tag-not27': ("{% if not foo and not bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': True}, 'no'),
- 'if-tag-not28': ("{% if not foo and not bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': False}, 'no'),
- 'if-tag-not29': ("{% if not foo and not bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': True}, 'no'),
- 'if-tag-not30': ("{% if not foo and not bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': False}, 'yes'),
-
- 'if-tag-not31': ("{% if not foo or not bar %}yes{% else %}no{% endif %}", {}, 'yes'),
- 'if-tag-not32': ("{% if not foo or not bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': True}, 'no'),
- 'if-tag-not33': ("{% if not foo or not bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': False}, 'yes'),
- 'if-tag-not34': ("{% if not foo or not bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': True}, 'yes'),
- 'if-tag-not35': ("{% if not foo or not bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': False}, 'yes'),
-
- # AND and OR raises a TemplateSyntaxError
- 'if-tag-error01': ("{% if foo or bar and baz %}yes{% else %}no{% endif %}", {'foo': False, 'bar': False}, template.TemplateSyntaxError),
- 'if-tag-error02': ("{% if foo and %}yes{% else %}no{% endif %}", {'foo': True}, template.TemplateSyntaxError),
- 'if-tag-error03': ("{% if foo or %}yes{% else %}no{% endif %}", {'foo': True}, template.TemplateSyntaxError),
- 'if-tag-error04': ("{% if not foo and %}yes{% else %}no{% endif %}", {'foo': True}, template.TemplateSyntaxError),
- 'if-tag-error05': ("{% if not foo or %}yes{% else %}no{% endif %}", {'foo': True}, template.TemplateSyntaxError),
-
- ### IFCHANGED TAG #########################################################
- 'ifchanged01': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% endfor %}', { 'num': (1,2,3) }, '123'),
- 'ifchanged02': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% endfor %}', { 'num': (1,1,3) }, '13'),
- 'ifchanged03': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% endfor %}', { 'num': (1,1,1) }, '1'),
-
- ### IFEQUAL TAG ###########################################################
- 'ifequal01': ("{% ifequal a b %}yes{% endifequal %}", {"a": 1, "b": 2}, ""),
- 'ifequal02': ("{% ifequal a b %}yes{% endifequal %}", {"a": 1, "b": 1}, "yes"),
- 'ifequal03': ("{% ifequal a b %}yes{% else %}no{% endifequal %}", {"a": 1, "b": 2}, "no"),
- 'ifequal04': ("{% ifequal a b %}yes{% else %}no{% endifequal %}", {"a": 1, "b": 1}, "yes"),
- 'ifequal05': ("{% ifequal a 'test' %}yes{% else %}no{% endifequal %}", {"a": "test"}, "yes"),
- 'ifequal06': ("{% ifequal a 'test' %}yes{% else %}no{% endifequal %}", {"a": "no"}, "no"),
- 'ifequal07': ('{% ifequal a "test" %}yes{% else %}no{% endifequal %}', {"a": "test"}, "yes"),
- 'ifequal08': ('{% ifequal a "test" %}yes{% else %}no{% endifequal %}', {"a": "no"}, "no"),
- 'ifequal09': ('{% ifequal a "test" %}yes{% else %}no{% endifequal %}', {}, "no"),
- 'ifequal10': ('{% ifequal a b %}yes{% else %}no{% endifequal %}', {}, "yes"),
-
- # SMART SPLITTING
- 'ifequal-split01': ('{% ifequal a "test man" %}yes{% else %}no{% endifequal %}', {}, "no"),
- 'ifequal-split02': ('{% ifequal a "test man" %}yes{% else %}no{% endifequal %}', {'a': 'foo'}, "no"),
- 'ifequal-split03': ('{% ifequal a "test man" %}yes{% else %}no{% endifequal %}', {'a': 'test man'}, "yes"),
- 'ifequal-split04': ("{% ifequal a 'test man' %}yes{% else %}no{% endifequal %}", {'a': 'test man'}, "yes"),
- 'ifequal-split05': ("{% ifequal a 'i \"love\" you' %}yes{% else %}no{% endifequal %}", {'a': ''}, "no"),
- 'ifequal-split06': ("{% ifequal a 'i \"love\" you' %}yes{% else %}no{% endifequal %}", {'a': 'i "love" you'}, "yes"),
- 'ifequal-split07': ("{% ifequal a 'i \"love\" you' %}yes{% else %}no{% endifequal %}", {'a': 'i love you'}, "no"),
- 'ifequal-split08': (r"{% ifequal a 'I\'m happy' %}yes{% else %}no{% endifequal %}", {'a': "I'm happy"}, "yes"),
- 'ifequal-split09': (r"{% ifequal a 'slash\man' %}yes{% else %}no{% endifequal %}", {'a': r"slash\man"}, "yes"),
- 'ifequal-split10': (r"{% ifequal a 'slash\man' %}yes{% else %}no{% endifequal %}", {'a': r"slashman"}, "no"),
-
- ### IFNOTEQUAL TAG ########################################################
- 'ifnotequal01': ("{% ifnotequal a b %}yes{% endifnotequal %}", {"a": 1, "b": 2}, "yes"),
- 'ifnotequal02': ("{% ifnotequal a b %}yes{% endifnotequal %}", {"a": 1, "b": 1}, ""),
- 'ifnotequal03': ("{% ifnotequal a b %}yes{% else %}no{% endifnotequal %}", {"a": 1, "b": 2}, "yes"),
- 'ifnotequal04': ("{% ifnotequal a b %}yes{% else %}no{% endifnotequal %}", {"a": 1, "b": 1}, "no"),
-
- ### INCLUDE TAG ###########################################################
- 'include01': ('{% include "basic-syntax01" %}', {}, "something cool"),
- 'include02': ('{% include "basic-syntax02" %}', {'headline': 'Included'}, "Included"),
- 'include03': ('{% include template_name %}', {'template_name': 'basic-syntax02', 'headline': 'Included'}, "Included"),
- 'include04': ('a{% include "nonexistent" %}b', {}, "ab"),
-
- ### INHERITANCE ###########################################################
-
- # Standard template with no inheritance
- 'inheritance01': ("1{% block first %}_{% endblock %}3{% block second %}_{% endblock %}", {}, '1_3_'),
-
- # Standard two-level inheritance
- 'inheritance02': ("{% extends 'inheritance01' %}{% block first %}2{% endblock %}{% block second %}4{% endblock %}", {}, '1234'),
-
- # Three-level with no redefinitions on third level
- 'inheritance03': ("{% extends 'inheritance02' %}", {}, '1234'),
-
- # Two-level with no redefinitions on second level
- 'inheritance04': ("{% extends 'inheritance01' %}", {}, '1_3_'),
-
- # Two-level with double quotes instead of single quotes
- 'inheritance05': ('{% extends "inheritance02" %}', {}, '1234'),
-
- # Three-level with variable parent-template name
- 'inheritance06': ("{% extends foo %}", {'foo': 'inheritance02'}, '1234'),
-
- # Two-level with one block defined, one block not defined
- 'inheritance07': ("{% extends 'inheritance01' %}{% block second %}5{% endblock %}", {}, '1_35'),
-
- # Three-level with one block defined on this level, two blocks defined next level
- 'inheritance08': ("{% extends 'inheritance02' %}{% block second %}5{% endblock %}", {}, '1235'),
-
- # Three-level with second and third levels blank
- 'inheritance09': ("{% extends 'inheritance04' %}", {}, '1_3_'),
-
- # Three-level with space NOT in a block -- should be ignored
- 'inheritance10': ("{% extends 'inheritance04' %} ", {}, '1_3_'),
-
- # Three-level with both blocks defined on this level, but none on second level
- 'inheritance11': ("{% extends 'inheritance04' %}{% block first %}2{% endblock %}{% block second %}4{% endblock %}", {}, '1234'),
-
- # Three-level with this level providing one and second level providing the other
- 'inheritance12': ("{% extends 'inheritance07' %}{% block first %}2{% endblock %}", {}, '1235'),
-
- # Three-level with this level overriding second level
- 'inheritance13': ("{% extends 'inheritance02' %}{% block first %}a{% endblock %}{% block second %}b{% endblock %}", {}, '1a3b'),
-
- # A block defined only in a child template shouldn't be displayed
- 'inheritance14': ("{% extends 'inheritance01' %}{% block newblock %}NO DISPLAY{% endblock %}", {}, '1_3_'),
-
- # A block within another block
- 'inheritance15': ("{% extends 'inheritance01' %}{% block first %}2{% block inner %}inner{% endblock %}{% endblock %}", {}, '12inner3_'),
-
- # A block within another block (level 2)
- 'inheritance16': ("{% extends 'inheritance15' %}{% block inner %}out{% endblock %}", {}, '12out3_'),
-
- # {% load %} tag (parent -- setup for exception04)
- 'inheritance17': ("{% load testtags %}{% block first %}1234{% endblock %}", {}, '1234'),
-
- # {% load %} tag (standard usage, without inheritance)
- 'inheritance18': ("{% load testtags %}{% echo this that theother %}5678", {}, 'this that theother5678'),
-
- # {% load %} tag (within a child template)
- 'inheritance19': ("{% extends 'inheritance01' %}{% block first %}{% load testtags %}{% echo 400 %}5678{% endblock %}", {}, '140056783_'),
-
- # Two-level inheritance with {{ block.super }}
- 'inheritance20': ("{% extends 'inheritance01' %}{% block first %}{{ block.super }}a{% endblock %}", {}, '1_a3_'),
-
- # Three-level inheritance with {{ block.super }} from parent
- 'inheritance21': ("{% extends 'inheritance02' %}{% block first %}{{ block.super }}a{% endblock %}", {}, '12a34'),
-
- # Three-level inheritance with {{ block.super }} from grandparent
- 'inheritance22': ("{% extends 'inheritance04' %}{% block first %}{{ block.super }}a{% endblock %}", {}, '1_a3_'),
-
- # Three-level inheritance with {{ block.super }} from parent and grandparent
- 'inheritance23': ("{% extends 'inheritance20' %}{% block first %}{{ block.super }}b{% endblock %}", {}, '1_ab3_'),
-
- # Inheritance from local context without use of template loader
- 'inheritance24': ("{% extends context_template %}{% block first %}2{% endblock %}{% block second %}4{% endblock %}", {'context_template': template.Template("1{% block first %}_{% endblock %}3{% block second %}_{% endblock %}")}, '1234'),
-
- # Inheritance from local context with variable parent template
- 'inheritance25': ("{% extends context_template.1 %}{% block first %}2{% endblock %}{% block second %}4{% endblock %}", {'context_template': [template.Template("Wrong"), template.Template("1{% block first %}_{% endblock %}3{% block second %}_{% endblock %}")]}, '1234'),
-
- ### I18N ##################################################################
-
- # {% spaceless %} tag
- 'spaceless01': ("{% spaceless %} <b> <i> text </i> </b> {% endspaceless %}", {}, "<b> <i> text </i> </b>"),
- 'spaceless02': ("{% spaceless %} <b> \n <i> text </i> \n </b> {% endspaceless %}", {}, "<b> <i> text </i> </b>"),
- 'spaceless03': ("{% spaceless %}<b><i>text</i></b>{% endspaceless %}", {}, "<b><i>text</i></b>"),
-
- # simple translation of a string delimited by '
- 'i18n01': ("{% load i18n %}{% trans 'xxxyyyxxx' %}", {}, "xxxyyyxxx"),
-
- # simple translation of a string delimited by "
- 'i18n02': ('{% load i18n %}{% trans "xxxyyyxxx" %}', {}, "xxxyyyxxx"),
-
- # simple translation of a variable
- 'i18n03': ('{% load i18n %}{% blocktrans %}{{ anton }}{% endblocktrans %}', {'anton': 'xxxyyyxxx'}, "xxxyyyxxx"),
-
- # simple translation of a variable and filter
- 'i18n04': ('{% load i18n %}{% blocktrans with anton|lower as berta %}{{ berta }}{% endblocktrans %}', {'anton': 'XXXYYYXXX'}, "xxxyyyxxx"),
-
- # simple translation of a string with interpolation
- 'i18n05': ('{% load i18n %}{% blocktrans %}xxx{{ anton }}xxx{% endblocktrans %}', {'anton': 'yyy'}, "xxxyyyxxx"),
-
- # simple translation of a string to german
- 'i18n06': ('{% load i18n %}{% trans "Page not found" %}', {'LANGUAGE_CODE': 'de'}, "Seite nicht gefunden"),
-
- # translation of singular form
- 'i18n07': ('{% load i18n %}{% blocktrans count number as counter %}singular{% plural %}plural{% endblocktrans %}', {'number': 1}, "singular"),
-
- # translation of plural form
- 'i18n08': ('{% load i18n %}{% blocktrans count number as counter %}singular{% plural %}plural{% endblocktrans %}', {'number': 2}, "plural"),
-
- # simple non-translation (only marking) of a string to german
- 'i18n09': ('{% load i18n %}{% trans "Page not found" noop %}', {'LANGUAGE_CODE': 'de'}, "Page not found"),
-
- # translation of a variable with a translated filter
- 'i18n10': ('{{ bool|yesno:_("ja,nein") }}', {'bool': True}, 'ja'),
-
- # translation of a variable with a non-translated filter
- 'i18n11': ('{{ bool|yesno:"ja,nein" }}', {'bool': True}, 'ja'),
-
- # usage of the get_available_languages tag
- 'i18n12': ('{% load i18n %}{% get_available_languages as langs %}{% for lang in langs %}{% ifequal lang.0 "de" %}{{ lang.0 }}{% endifequal %}{% endfor %}', {}, 'de'),
-
- # translation of a constant string
- 'i18n13': ('{{ _("Page not found") }}', {'LANGUAGE_CODE': 'de'}, 'Seite nicht gefunden'),
-
- ### MULTILINE #############################################################
-
- 'multiline01': ("""
- Hello,
- boys.
- How
- are
- you
- gentlemen.
- """,
- {},
- """
- Hello,
- boys.
- How
- are
- you
- gentlemen.
- """),
-
- ### REGROUP TAG ###########################################################
- 'regroup01': ('{% regroup data by bar as grouped %}' + \
- '{% for group in grouped %}' + \
- '{{ group.grouper }}:' + \
- '{% for item in group.list %}' + \
- '{{ item.foo }}' + \
- '{% endfor %},' + \
- '{% endfor %}',
- {'data': [ {'foo':'c', 'bar':1},
- {'foo':'d', 'bar':1},
- {'foo':'a', 'bar':2},
- {'foo':'b', 'bar':2},
- {'foo':'x', 'bar':3} ]},
- '1:cd,2:ab,3:x,'),
-
- # Test for silent failure when target variable isn't found
- 'regroup02': ('{% regroup data by bar as grouped %}' + \
- '{% for group in grouped %}' + \
- '{{ group.grouper }}:' + \
- '{% for item in group.list %}' + \
- '{{ item.foo }}' + \
- '{% endfor %},' + \
- '{% endfor %}',
- {}, 'INVALID:INVALIDINVALIDINVALIDINVALIDINVALIDINVALIDINVALID,'),
-
- ### TEMPLATETAG TAG #######################################################
- 'templatetag01': ('{% templatetag openblock %}', {}, '{%'),
- 'templatetag02': ('{% templatetag closeblock %}', {}, '%}'),
- 'templatetag03': ('{% templatetag openvariable %}', {}, '{{'),
- 'templatetag04': ('{% templatetag closevariable %}', {}, '}}'),
- 'templatetag05': ('{% templatetag %}', {}, template.TemplateSyntaxError),
- 'templatetag06': ('{% templatetag foo %}', {}, template.TemplateSyntaxError),
- 'templatetag07': ('{% templatetag openbrace %}', {}, '{'),
- 'templatetag08': ('{% templatetag closebrace %}', {}, '}'),
- 'templatetag09': ('{% templatetag openbrace %}{% templatetag openbrace %}', {}, '{{'),
- 'templatetag10': ('{% templatetag closebrace %}{% templatetag closebrace %}', {}, '}}'),
-
- ### WIDTHRATIO TAG ########################################################
- 'widthratio01': ('{% widthratio a b 0 %}', {'a':50,'b':100}, '0'),
- 'widthratio02': ('{% widthratio a b 100 %}', {'a':0,'b':0}, ''),
- 'widthratio03': ('{% widthratio a b 100 %}', {'a':0,'b':100}, '0'),
- 'widthratio04': ('{% widthratio a b 100 %}', {'a':50,'b':100}, '50'),
- 'widthratio05': ('{% widthratio a b 100 %}', {'a':100,'b':100}, '100'),
-
- # 62.5 should round to 63
- 'widthratio06': ('{% widthratio a b 100 %}', {'a':50,'b':80}, '63'),
-
- # 71.4 should round to 71
- 'widthratio07': ('{% widthratio a b 100 %}', {'a':50,'b':70}, '71'),
-
- # Raise exception if we don't have 3 args, last one an integer
- 'widthratio08': ('{% widthratio %}', {}, template.TemplateSyntaxError),
- 'widthratio09': ('{% widthratio a b %}', {'a':50,'b':100}, template.TemplateSyntaxError),
- 'widthratio10': ('{% widthratio a b 100.0 %}', {'a':50,'b':100}, template.TemplateSyntaxError),
-
- ### NOW TAG ########################################################
- # Simple case
- 'now01' : ('{% now "j n Y"%}', {}, str(datetime.now().day) + ' ' + str(datetime.now().month) + ' ' + str(datetime.now().year)),
-
- # Check parsing of escaped and special characters
- 'now02' : ('{% now "j "n" Y"%}', {}, template.TemplateSyntaxError),
-# 'now03' : ('{% now "j \"n\" Y"%}', {}, str(datetime.now().day) + '"' + str(datetime.now().month) + '"' + str(datetime.now().year)),
-# 'now04' : ('{% now "j \nn\n Y"%}', {}, str(datetime.now().day) + '\n' + str(datetime.now().month) + '\n' + str(datetime.now().year))
-
- ### TIMESINCE TAG ##################################################
- # Default compare with datetime.now()
- 'timesince01' : ('{{ a|timesince }}', {'a':datetime.now() + timedelta(minutes=-1, seconds = -10)}, '1 minute'),
- 'timesince02' : ('{{ a|timesince }}', {'a':(datetime.now() - timedelta(days=1, minutes = 1))}, '1 day'),
- 'timesince03' : ('{{ a|timesince }}', {'a':(datetime.now() -
- timedelta(hours=1, minutes=25, seconds = 10))}, '1 hour, 25 minutes'),
-
- # Compare to a given parameter
- 'timesince04' : ('{{ a|timesince:b }}', {'a':NOW + timedelta(days=2), 'b':NOW + timedelta(days=1)}, '1 day'),
- 'timesince05' : ('{{ a|timesince:b }}', {'a':NOW + timedelta(days=2, minutes=1), 'b':NOW + timedelta(days=2)}, '1 minute'),
-
- # Check that timezone is respected
- 'timesince06' : ('{{ a|timesince:b }}', {'a':NOW_tz + timedelta(hours=8), 'b':NOW_tz}, '8 hours'),
-
- ### TIMEUNTIL TAG ##################################################
- # Default compare with datetime.now()
- 'timeuntil01' : ('{{ a|timeuntil }}', {'a':datetime.now() + timedelta(minutes=2, seconds = 10)}, '2 minutes'),
- 'timeuntil02' : ('{{ a|timeuntil }}', {'a':(datetime.now() + timedelta(days=1, seconds = 10))}, '1 day'),
- 'timeuntil03' : ('{{ a|timeuntil }}', {'a':(datetime.now() + timedelta(hours=8, minutes=10, seconds = 10))}, '8 hours, 10 minutes'),
-
- # Compare to a given parameter
- 'timeuntil04' : ('{{ a|timeuntil:b }}', {'a':NOW - timedelta(days=1), 'b':NOW - timedelta(days=2)}, '1 day'),
- 'timeuntil05' : ('{{ a|timeuntil:b }}', {'a':NOW - timedelta(days=2), 'b':NOW - timedelta(days=2, minutes=1)}, '1 minute'),
-}
-
-def test_template_loader(template_name, template_dirs=None):
- "A custom template loader that loads the unit-test templates."
- try:
- return (TEMPLATE_TESTS[template_name][0] , "test:%s" % template_name)
- except KeyError:
- raise template.TemplateDoesNotExist, template_name
-
-def run_tests(verbosity=0, standalone=False):
- # Register our custom template loader.
- old_template_loaders = loader.template_source_loaders
- loader.template_source_loaders = [test_template_loader]
-
- failed_tests = []
- tests = TEMPLATE_TESTS.items()
- tests.sort()
-
- # Turn TEMPLATE_DEBUG off, because tests assume that.
- 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'
-
- for name, vals in tests:
- install()
- 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]:
- if verbosity:
- print "Template test: %s -- Passed" % name
- else:
- if verbosity:
- traceback.print_exc()
- print "Template test: %s -- FAILED. Got %s, exception: %s" % (name, e.__class__, e)
- failed_tests.append(name)
- continue
- if 'LANGUAGE_CODE' in vals[1]:
- deactivate()
- if output == vals[2]:
- if verbosity:
- print "Template test: %s -- Passed" % name
- else:
- if verbosity:
- print "Template test: %s -- FAILED. Expected %r, got %r" % (name, vals[2], output)
- failed_tests.append(name)
- loader.template_source_loaders = old_template_loaders
- deactivate()
- settings.TEMPLATE_DEBUG = old_td
- settings.TEMPLATE_STRING_IF_INVALID = old_invalid
-
- if failed_tests and not standalone:
- msg = "Template tests %s failed." % failed_tests
- if not verbosity:
- msg += " Re-run tests with -v1 to see actual failures"
- raise Exception, msg
-
-if __name__ == "__main__":
- run_tests(1, True)
diff --git a/tests/regressiontests/cache/__init__.py b/tests/regressiontests/cache/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/regressiontests/cache/__init__.py
diff --git a/tests/regressiontests/cache/models.py b/tests/regressiontests/cache/models.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/regressiontests/cache/models.py
diff --git a/tests/regressiontests/cache/tests.py b/tests/regressiontests/cache/tests.py
new file mode 100644
index 0000000000..cf58ab321a
--- /dev/null
+++ b/tests/regressiontests/cache/tests.py
@@ -0,0 +1,71 @@
+# Unit tests for cache framework
+# Uses whatever cache backend is set in the test settings file.
+
+from django.core.cache import cache
+import time, unittest
+
+# functions/classes for complex data type tests
+def f():
+ return 42
+class C:
+ def m(n):
+ return 24
+
+class Cache(unittest.TestCase):
+ def test_simple(self):
+ # simple set/get
+ cache.set("key", "value")
+ self.assertEqual(cache.get("key"), "value")
+
+ def test_non_existent(self):
+ # get with non-existent keys
+ self.assertEqual(cache.get("does not exist"), None)
+ self.assertEqual(cache.get("does not exist", "bang!"), "bang!")
+
+ def test_get_many(self):
+ # get_many
+ cache.set('a', 'a')
+ cache.set('b', 'b')
+ cache.set('c', 'c')
+ cache.set('d', 'd')
+ self.assertEqual(cache.get_many(['a', 'c', 'd']), {'a' : 'a', 'c' : 'c', 'd' : 'd'})
+ self.assertEqual(cache.get_many(['a', 'b', 'e']), {'a' : 'a', 'b' : 'b'})
+
+ def test_delete(self):
+ # delete
+ cache.set("key1", "spam")
+ cache.set("key2", "eggs")
+ self.assertEqual(cache.get("key1"), "spam")
+ cache.delete("key1")
+ self.assertEqual(cache.get("key1"), None)
+ self.assertEqual(cache.get("key2"), "eggs")
+
+ def test_has_key(self):
+ # has_key
+ cache.set("hello", "goodbye")
+ self.assertEqual(cache.has_key("hello"), True)
+ self.assertEqual(cache.has_key("goodbye"), False)
+
+ def test_data_types(self):
+ # test data types
+ stuff = {
+ 'string' : 'this is a string',
+ 'int' : 42,
+ 'list' : [1, 2, 3, 4],
+ 'tuple' : (1, 2, 3, 4),
+ 'dict' : {'A': 1, 'B' : 2},
+ 'function' : f,
+ 'class' : C,
+ }
+ for (key, value) in stuff.items():
+ cache.set(key, value)
+ self.assertEqual(cache.get(key), value)
+
+ def test_expiration(self):
+ # expiration
+ cache.set('expire', 'very quickly', 1)
+ time.sleep(2)
+ self.assertEqual(cache.get("expire"), None)
+
+if __name__ == '__main__':
+ unittest.main() \ No newline at end of file
diff --git a/tests/regressiontests/dateformat/__init__.py b/tests/regressiontests/dateformat/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/regressiontests/dateformat/__init__.py
diff --git a/tests/regressiontests/dateformat/models.py b/tests/regressiontests/dateformat/models.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/regressiontests/dateformat/models.py
diff --git a/tests/othertests/dateformat.py b/tests/regressiontests/dateformat/tests.py
index 0287587b4a..0287587b4a 100644
--- a/tests/othertests/dateformat.py
+++ b/tests/regressiontests/dateformat/tests.py
diff --git a/tests/regressiontests/db_typecasts/__init__.py b/tests/regressiontests/db_typecasts/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/regressiontests/db_typecasts/__init__.py
diff --git a/tests/regressiontests/db_typecasts/models.py b/tests/regressiontests/db_typecasts/models.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/regressiontests/db_typecasts/models.py
diff --git a/tests/othertests/db_typecasts.py b/tests/regressiontests/db_typecasts/tests.py
index ffc9b34aec..f4b77fe3a6 100644
--- a/tests/othertests/db_typecasts.py
+++ b/tests/regressiontests/db_typecasts/tests.py
@@ -1,7 +1,7 @@
# Unit tests for typecast functions in django.db.backends.util
from django.db.backends import util as typecasts
-import datetime
+import datetime, unittest
TEST_CASES = {
'typecast_date': (
@@ -45,7 +45,12 @@ TEST_CASES = {
),
}
-for k, v in TEST_CASES.items():
- for inpt, expected in v:
- got = getattr(typecasts, k)(inpt)
- assert got == expected, "In %s: %r doesn't match %r. Got %r instead." % (k, inpt, expected, got)
+class DBTypeCasts(unittest.TestCase):
+ def test_typeCasts(self):
+ for k, v in TEST_CASES.items():
+ for inpt, expected in v:
+ got = getattr(typecasts, k)(inpt)
+ assert got == expected, "In %s: %r doesn't match %r. Got %r instead." % (k, inpt, expected, got)
+
+if __name__ == '__main__':
+ unittest.main() \ No newline at end of file
diff --git a/tests/regressiontests/defaultfilters/__init__.py b/tests/regressiontests/defaultfilters/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/regressiontests/defaultfilters/__init__.py
diff --git a/tests/regressiontests/defaultfilters/models.py b/tests/regressiontests/defaultfilters/models.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/regressiontests/defaultfilters/models.py
diff --git a/tests/othertests/defaultfilters.py b/tests/regressiontests/defaultfilters/tests.py
index 9b1cfda833..9b1cfda833 100644
--- a/tests/othertests/defaultfilters.py
+++ b/tests/regressiontests/defaultfilters/tests.py
diff --git a/tests/regressiontests/httpwrappers/__init__.py b/tests/regressiontests/httpwrappers/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/regressiontests/httpwrappers/__init__.py
diff --git a/tests/regressiontests/httpwrappers/models.py b/tests/regressiontests/httpwrappers/models.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/regressiontests/httpwrappers/models.py
diff --git a/tests/othertests/httpwrappers.py b/tests/regressiontests/httpwrappers/tests.py
index 385c3048d9..385c3048d9 100644
--- a/tests/othertests/httpwrappers.py
+++ b/tests/regressiontests/httpwrappers/tests.py
diff --git a/tests/regressiontests/initial_sql_regress/models.py b/tests/regressiontests/initial_sql_regress/models.py
index c4cf12bdf7..dedbba8e5c 100644
--- a/tests/regressiontests/initial_sql_regress/models.py
+++ b/tests/regressiontests/initial_sql_regress/models.py
@@ -7,7 +7,7 @@ from django.db import models
class Simple(models.Model):
name = models.CharField(maxlength = 50)
-API_TESTS = ""
+__test__ = {'API_TESTS':""}
# NOTE: The format of the included SQL file for this test suite is important.
# It must end with a trailing newline in order to test the fix for #2161.
diff --git a/tests/regressiontests/many_to_one_regress/models.py b/tests/regressiontests/many_to_one_regress/models.py
index 485e928777..6c067446b1 100644
--- a/tests/regressiontests/many_to_one_regress/models.py
+++ b/tests/regressiontests/many_to_one_regress/models.py
@@ -10,4 +10,4 @@ class Second(models.Model):
# created (the field names being lower-cased versions of their opposite
# classes is important here).
-API_TESTS = ""
+__test__ = {'API_TESTS':""}
diff --git a/tests/regressiontests/markup/__init__.py b/tests/regressiontests/markup/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/regressiontests/markup/__init__.py
diff --git a/tests/regressiontests/markup/models.py b/tests/regressiontests/markup/models.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/regressiontests/markup/models.py
diff --git a/tests/regressiontests/markup/tests.py b/tests/regressiontests/markup/tests.py
new file mode 100644
index 0000000000..bd3f52b9dd
--- /dev/null
+++ b/tests/regressiontests/markup/tests.py
@@ -0,0 +1,69 @@
+# Quick tests for the markup templatetags (django.contrib.markup)
+
+from django.template import Template, Context, add_to_builtins
+import re
+import unittest
+
+add_to_builtins('django.contrib.markup.templatetags.markup')
+
+class Templates(unittest.TestCase):
+ def test_textile(self):
+ try:
+ import textile
+ except ImportError:
+ textile = None
+
+ textile_content = """Paragraph 1
+
+Paragraph 2 with "quotes" and @code@"""
+
+ t = Template("{{ textile_content|textile }}")
+ rendered = t.render(Context(locals())).strip()
+ if textile:
+ self.assertEqual(rendered, """<p>Paragraph 1</p>
+
+<p>Paragraph 2 with &#8220;quotes&#8221; and <code>code</code></p>""")
+ else:
+ self.assertEqual(rendered, textile_content)
+
+ def test_markdown(self):
+ try:
+ import markdown
+ except ImportError:
+ markdown = None
+
+ markdown_content = """Paragraph 1
+
+## An h2"""
+
+ t = Template("{{ markdown_content|markdown }}")
+ rendered = t.render(Context(locals())).strip()
+ if markdown:
+ pattern = re.compile("""<p>Paragraph 1\s*</p>\s*<h2>\s*An h2</h2>""")
+ self.assert_(pattern.match(rendered))
+ else:
+ self.assertEqual(rendered, markdown_content)
+
+ def test_docutils(self):
+ try:
+ import docutils
+ except ImportError:
+ docutils = None
+
+ rest_content = """Paragraph 1
+
+Paragraph 2 with a link_
+
+.. _link: http://www.example.com/"""
+
+ t = Template("{{ rest_content|restructuredtext }}")
+ rendered = t.render(Context(locals())).strip()
+ if docutils:
+ self.assertEqual(rendered, """<p>Paragraph 1</p>
+<p>Paragraph 2 with a <a class="reference" href="http://www.example.com/">link</a></p>""")
+ else:
+ self.assertEqual(rendered, rest_content)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/regressiontests/one_to_one_regress/models.py b/tests/regressiontests/one_to_one_regress/models.py
index 6cc1df4e5c..b81f4266e1 100644
--- a/tests/regressiontests/one_to_one_regress/models.py
+++ b/tests/regressiontests/one_to_one_regress/models.py
@@ -22,7 +22,7 @@ class Favorites(models.Model):
def __str__(self):
return "Favorites for %s" % self.name
-API_TESTS = """
+__test__ = {'API_TESTS':"""
# Regression test for #1064 and #1506: Check that we create models via the m2m
# relation if the remote model has a OneToOneField.
>>> p1 = Place(name='Demon Dogs', address='944 W. Fullerton')
@@ -34,4 +34,4 @@ API_TESTS = """
>>> f.restaurants = [r]
>>> f.restaurants.all()
[<Restaurant: Demon Dogs the restaurant>]
-"""
+"""}
diff --git a/tests/regressiontests/string_lookup/models.py b/tests/regressiontests/string_lookup/models.py
index a4582ca4e9..441bb3f8a3 100644
--- a/tests/regressiontests/string_lookup/models.py
+++ b/tests/regressiontests/string_lookup/models.py
@@ -34,7 +34,7 @@ class Base(models.Model):
def __str__(self):
return "Base %s" % self.name
-API_TESTS = """
+__test__ = {'API_TESTS':"""
# Regression test for #1661 and #1662: Check that string form referencing of models works,
# both as pre and post reference, on all RelatedField types.
@@ -66,4 +66,4 @@ API_TESTS = """
>>> child1.parent
<Base: Base Base1>
-"""
+"""}
diff --git a/tests/regressiontests/templates/__init__.py b/tests/regressiontests/templates/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/regressiontests/templates/__init__.py
diff --git a/tests/regressiontests/templates/models.py b/tests/regressiontests/templates/models.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/regressiontests/templates/models.py
diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py
new file mode 100644
index 0000000000..2d1ce192ef
--- /dev/null
+++ b/tests/regressiontests/templates/tests.py
@@ -0,0 +1,621 @@
+from django.conf import settings
+
+if __name__ == '__main__':
+ # When running this file in isolation, we need to set up the configuration
+ # before importing 'template'.
+ settings.configure()
+
+from django import template
+from django.template import loader
+from django.utils.translation import activate, deactivate, install
+from django.utils.tzinfo import LocalTimezone
+from datetime import datetime, timedelta
+import unittest
+
+#################################
+# Custom template tag for tests #
+#################################
+
+register = template.Library()
+
+class EchoNode(template.Node):
+ def __init__(self, contents):
+ self.contents = contents
+
+ def render(self, context):
+ return " ".join(self.contents)
+
+def do_echo(parser, token):
+ return EchoNode(token.contents.split()[1:])
+
+register.tag("echo", do_echo)
+
+template.libraries['django.templatetags.testtags'] = register
+
+#####################################
+# Helper objects for template tests #
+#####################################
+
+class SomeException(Exception):
+ silent_variable_failure = True
+
+class SomeOtherException(Exception):
+ pass
+
+class SomeClass:
+ def __init__(self):
+ self.otherclass = OtherClass()
+
+ def method(self):
+ return "SomeClass.method"
+
+ def method2(self, o):
+ return o
+
+ def method3(self):
+ raise SomeException
+
+ def method4(self):
+ raise SomeOtherException
+
+class OtherClass:
+ def method(self):
+ return "OtherClass.method"
+
+class Templates(unittest.TestCase):
+ def test_templates(self):
+ # NOW and NOW_tz are used by timesince tag tests.
+ NOW = datetime.now()
+ NOW_tz = datetime.now(LocalTimezone(datetime.now()))
+
+ # SYNTAX --
+ # 'template_name': ('template contents', 'context dict', 'expected string output' or Exception class)
+ TEMPLATE_TESTS = {
+
+ ### BASIC SYNTAX ##########################################################
+
+ # Plain text should go through the template parser untouched
+ 'basic-syntax01': ("something cool", {}, "something cool"),
+
+ # Variables should be replaced with their value in the current context
+ 'basic-syntax02': ("{{ headline }}", {'headline':'Success'}, "Success"),
+
+ # More than one replacement variable is allowed in a template
+ '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"),
+
+ # A variable may not contain more than one word
+ 'basic-syntax06': ("{{ multi word variable }}", {}, template.TemplateSyntaxError),
+
+ # Raise TemplateSyntaxError for empty variable tags
+ 'basic-syntax07': ("{{ }}", {}, template.TemplateSyntaxError),
+ 'basic-syntax08': ("{{ }}", {}, template.TemplateSyntaxError),
+
+ # Attribute syntax allows a template to call an object's attribute
+ 'basic-syntax09': ("{{ var.method }}", {"var": SomeClass()}, "SomeClass.method"),
+
+ # Multiple levels of attribute access are allowed
+ '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"),
+
+ # Raise TemplateSyntaxError when trying to access a variable beginning with an underscore
+ 'basic-syntax12': ("{{ var.__dict__ }}", {"var": SomeClass()}, template.TemplateSyntaxError),
+
+ # Raise TemplateSyntaxError when trying to access a variable containing an illegal character
+ 'basic-syntax13': ("{{ va>r }}", {}, template.TemplateSyntaxError),
+ 'basic-syntax14': ("{{ (var.r) }}", {}, template.TemplateSyntaxError),
+ 'basic-syntax15': ("{{ sp%am }}", {}, template.TemplateSyntaxError),
+ 'basic-syntax16': ("{{ eggs! }}", {}, template.TemplateSyntaxError),
+ 'basic-syntax17': ("{{ moo? }}", {}, template.TemplateSyntaxError),
+
+ # Attribute syntax allows a template to call a dictionary key's value
+ '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"),
+
+ # Fail silently when accessing a non-simple method
+ 'basic-syntax20': ("{{ var.method2 }}", {"var": SomeClass()}, "INVALID"),
+
+ # Basic filter usage
+ 'basic-syntax21': ("{{ var|upper }}", {"var": "Django is the greatest!"}, "DJANGO IS THE GREATEST!"),
+
+ # Chained filters
+ 'basic-syntax22': ("{{ var|upper|lower }}", {"var": "Django is the greatest!"}, "django is the greatest!"),
+
+ # Raise TemplateSyntaxError for space between a variable and filter pipe
+ 'basic-syntax23': ("{{ var |upper }}", {}, template.TemplateSyntaxError),
+
+ # Raise TemplateSyntaxError for space after a filter pipe
+ 'basic-syntax24': ("{{ var| upper }}", {}, template.TemplateSyntaxError),
+
+ # Raise TemplateSyntaxError for a nonexistent filter
+ 'basic-syntax25': ("{{ var|does_not_exist }}", {}, template.TemplateSyntaxError),
+
+ # Raise TemplateSyntaxError when trying to access a filter containing an illegal character
+ 'basic-syntax26': ("{{ var|fil(ter) }}", {}, template.TemplateSyntaxError),
+
+ # Raise TemplateSyntaxError for invalid block tags
+ 'basic-syntax27': ("{% nothing_to_see_here %}", {}, template.TemplateSyntaxError),
+
+ # Raise TemplateSyntaxError for empty block tags
+ 'basic-syntax28': ("{% %}", {}, template.TemplateSyntaxError),
+
+ # Chained filters, with an argument to the first one
+ 'basic-syntax29': ('{{ var|removetags:"b i"|upper|lower }}', {"var": "<b><i>Yes</i></b>"}, "yes"),
+
+ # Escaped string as argument
+ 'basic-syntax30': (r'{{ var|default_if_none:" endquote\" hah" }}', {"var": None}, ' endquote" hah'),
+
+ # Variable as argument
+ 'basic-syntax31': (r'{{ var|default_if_none:var2 }}', {"var": None, "var2": "happy"}, 'happy'),
+
+ # Default argument testing
+ '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"),
+
+ # In methods that raise an exception without a "silent_variable_attribute" set to True,
+ # the exception propogates
+ 'basic-syntax34': (r'1{{ var.method4 }}2', {"var": SomeClass()}, SomeOtherException),
+
+ # Escaped backslash in argument
+ 'basic-syntax35': (r'{{ var|default_if_none:"foo\bar" }}', {"var": None}, r'foo\bar'),
+
+ # Escaped backslash using known escape char
+ 'basic-syntax35': (r'{{ var|default_if_none:"foo\now" }}', {"var": None}, r'foo\now'),
+
+ ### COMMENT TAG ###########################################################
+ 'comment-tag01': ("{% comment %}this is hidden{% endcomment %}hello", {}, "hello"),
+ 'comment-tag02': ("{% comment %}this is hidden{% endcomment %}hello{% comment %}foo{% endcomment %}", {}, "hello"),
+
+ # Comment tag can contain invalid stuff.
+ 'comment-tag03': ("foo{% comment %} {% if %} {% endcomment %}", {}, "foo"),
+ 'comment-tag04': ("foo{% comment %} {% endblock %} {% endcomment %}", {}, "foo"),
+ 'comment-tag05': ("foo{% comment %} {% somerandomtag %} {% endcomment %}", {}, "foo"),
+
+ ### CYCLE TAG #############################################################
+ 'cycle01': ('{% cycle a %}', {}, template.TemplateSyntaxError),
+ 'cycle02': ('{% cycle a,b,c as abc %}{% cycle abc %}', {}, 'ab'),
+ 'cycle03': ('{% cycle a,b,c as abc %}{% cycle abc %}{% cycle abc %}', {}, 'abc'),
+ 'cycle04': ('{% cycle a,b,c as abc %}{% cycle abc %}{% cycle abc %}{% cycle abc %}', {}, 'abca'),
+ 'cycle05': ('{% cycle %}', {}, template.TemplateSyntaxError),
+ 'cycle06': ('{% cycle a %}', {}, template.TemplateSyntaxError),
+ 'cycle07': ('{% cycle a,b,c as foo %}{% cycle bar %}', {}, template.TemplateSyntaxError),
+
+ ### EXCEPTIONS ############################################################
+
+ # Raise exception for invalid template name
+ 'exception01': ("{% extends 'nonexistent' %}", {}, template.TemplateSyntaxError),
+
+ # Raise exception for invalid template name (in variable)
+ 'exception02': ("{% extends nonexistent %}", {}, template.TemplateSyntaxError),
+
+ # Raise exception for extra {% extends %} tags
+ 'exception03': ("{% extends 'inheritance01' %}{% block first %}2{% endblock %}{% extends 'inheritance16' %}", {}, template.TemplateSyntaxError),
+
+ # Raise exception for custom tags used in child with {% load %} tag in parent, not in child
+ 'exception04': ("{% extends 'inheritance17' %}{% block first %}{% echo 400 %}5678{% endblock %}", {}, template.TemplateSyntaxError),
+
+ ### FILTER TAG ############################################################
+ 'filter01': ('{% filter upper %}{% endfilter %}', {}, ''),
+ 'filter02': ('{% filter upper %}django{% endfilter %}', {}, 'DJANGO'),
+ 'filter03': ('{% filter upper|lower %}django{% endfilter %}', {}, 'django'),
+
+ ### FIRSTOF TAG ###########################################################
+ 'firstof01': ('{% firstof a b c %}', {'a':0,'b':0,'c':0}, ''),
+ 'firstof02': ('{% firstof a b c %}', {'a':1,'b':0,'c':0}, '1'),
+ 'firstof03': ('{% firstof a b c %}', {'a':0,'b':2,'c':0}, '2'),
+ 'firstof04': ('{% firstof a b c %}', {'a':0,'b':0,'c':3}, '3'),
+ 'firstof05': ('{% firstof a b c %}', {'a':1,'b':2,'c':3}, '1'),
+ 'firstof06': ('{% firstof %}', {}, template.TemplateSyntaxError),
+
+ ### FOR TAG ###############################################################
+ 'for-tag01': ("{% for val in values %}{{ val }}{% endfor %}", {"values": [1, 2, 3]}, "123"),
+ 'for-tag02': ("{% for val in values reversed %}{{ val }}{% endfor %}", {"values": [1, 2, 3]}, "321"),
+ 'for-tag-vars01': ("{% for val in values %}{{ forloop.counter }}{% endfor %}", {"values": [6, 6, 6]}, "123"),
+ 'for-tag-vars02': ("{% for val in values %}{{ forloop.counter0 }}{% endfor %}", {"values": [6, 6, 6]}, "012"),
+ 'for-tag-vars03': ("{% for val in values %}{{ forloop.revcounter }}{% endfor %}", {"values": [6, 6, 6]}, "321"),
+ 'for-tag-vars04': ("{% for val in values %}{{ forloop.revcounter0 }}{% endfor %}", {"values": [6, 6, 6]}, "210"),
+
+ ### IF TAG ################################################################
+ 'if-tag01': ("{% if foo %}yes{% else %}no{% endif %}", {"foo": True}, "yes"),
+ 'if-tag02': ("{% if foo %}yes{% else %}no{% endif %}", {"foo": False}, "no"),
+ 'if-tag03': ("{% if foo %}yes{% else %}no{% endif %}", {}, "no"),
+
+ # AND
+ 'if-tag-and01': ("{% if foo and bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': True}, 'yes'),
+ 'if-tag-and02': ("{% if foo and bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': False}, 'no'),
+ 'if-tag-and03': ("{% if foo and bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': True}, 'no'),
+ 'if-tag-and04': ("{% if foo and bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': False}, 'no'),
+ 'if-tag-and05': ("{% if foo and bar %}yes{% else %}no{% endif %}", {'foo': False}, 'no'),
+ 'if-tag-and06': ("{% if foo and bar %}yes{% else %}no{% endif %}", {'bar': False}, 'no'),
+ 'if-tag-and07': ("{% if foo and bar %}yes{% else %}no{% endif %}", {'foo': True}, 'no'),
+ 'if-tag-and08': ("{% if foo and bar %}yes{% else %}no{% endif %}", {'bar': True}, 'no'),
+
+ # OR
+ 'if-tag-or01': ("{% if foo or bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': True}, 'yes'),
+ 'if-tag-or02': ("{% if foo or bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': False}, 'yes'),
+ 'if-tag-or03': ("{% if foo or bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': True}, 'yes'),
+ 'if-tag-or04': ("{% if foo or bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': False}, 'no'),
+ 'if-tag-or05': ("{% if foo or bar %}yes{% else %}no{% endif %}", {'foo': False}, 'no'),
+ 'if-tag-or06': ("{% if foo or bar %}yes{% else %}no{% endif %}", {'bar': False}, 'no'),
+ 'if-tag-or07': ("{% if foo or bar %}yes{% else %}no{% endif %}", {'foo': True}, 'yes'),
+ 'if-tag-or08': ("{% if foo or bar %}yes{% else %}no{% endif %}", {'bar': True}, 'yes'),
+
+ # TODO: multiple ORs
+
+ # NOT
+ 'if-tag-not01': ("{% if not foo %}no{% else %}yes{% endif %}", {'foo': True}, 'yes'),
+ 'if-tag-not02': ("{% if not %}yes{% else %}no{% endif %}", {'foo': True}, 'no'),
+ 'if-tag-not03': ("{% if not %}yes{% else %}no{% endif %}", {'not': True}, 'yes'),
+ 'if-tag-not04': ("{% if not not %}no{% else %}yes{% endif %}", {'not': True}, 'yes'),
+ 'if-tag-not05': ("{% if not not %}no{% else %}yes{% endif %}", {}, 'no'),
+
+ 'if-tag-not06': ("{% if foo and not bar %}yes{% else %}no{% endif %}", {}, 'no'),
+ 'if-tag-not07': ("{% if foo and not bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': True}, 'no'),
+ 'if-tag-not08': ("{% if foo and not bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': False}, 'yes'),
+ 'if-tag-not09': ("{% if foo and not bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': True}, 'no'),
+ 'if-tag-not10': ("{% if foo and not bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': False}, 'no'),
+
+ 'if-tag-not11': ("{% if not foo and bar %}yes{% else %}no{% endif %}", {}, 'no'),
+ 'if-tag-not12': ("{% if not foo and bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': True}, 'no'),
+ 'if-tag-not13': ("{% if not foo and bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': False}, 'no'),
+ 'if-tag-not14': ("{% if not foo and bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': True}, 'yes'),
+ 'if-tag-not15': ("{% if not foo and bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': False}, 'no'),
+
+ 'if-tag-not16': ("{% if foo or not bar %}yes{% else %}no{% endif %}", {}, 'yes'),
+ 'if-tag-not17': ("{% if foo or not bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': True}, 'yes'),
+ 'if-tag-not18': ("{% if foo or not bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': False}, 'yes'),
+ 'if-tag-not19': ("{% if foo or not bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': True}, 'no'),
+ 'if-tag-not20': ("{% if foo or not bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': False}, 'yes'),
+
+ 'if-tag-not21': ("{% if not foo or bar %}yes{% else %}no{% endif %}", {}, 'yes'),
+ 'if-tag-not22': ("{% if not foo or bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': True}, 'yes'),
+ 'if-tag-not23': ("{% if not foo or bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': False}, 'no'),
+ 'if-tag-not24': ("{% if not foo or bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': True}, 'yes'),
+ 'if-tag-not25': ("{% if not foo or bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': False}, 'yes'),
+
+ 'if-tag-not26': ("{% if not foo and not bar %}yes{% else %}no{% endif %}", {}, 'yes'),
+ 'if-tag-not27': ("{% if not foo and not bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': True}, 'no'),
+ 'if-tag-not28': ("{% if not foo and not bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': False}, 'no'),
+ 'if-tag-not29': ("{% if not foo and not bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': True}, 'no'),
+ 'if-tag-not30': ("{% if not foo and not bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': False}, 'yes'),
+
+ 'if-tag-not31': ("{% if not foo or not bar %}yes{% else %}no{% endif %}", {}, 'yes'),
+ 'if-tag-not32': ("{% if not foo or not bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': True}, 'no'),
+ 'if-tag-not33': ("{% if not foo or not bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': False}, 'yes'),
+ 'if-tag-not34': ("{% if not foo or not bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': True}, 'yes'),
+ 'if-tag-not35': ("{% if not foo or not bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': False}, 'yes'),
+
+ # AND and OR raises a TemplateSyntaxError
+ 'if-tag-error01': ("{% if foo or bar and baz %}yes{% else %}no{% endif %}", {'foo': False, 'bar': False}, template.TemplateSyntaxError),
+ 'if-tag-error02': ("{% if foo and %}yes{% else %}no{% endif %}", {'foo': True}, template.TemplateSyntaxError),
+ 'if-tag-error03': ("{% if foo or %}yes{% else %}no{% endif %}", {'foo': True}, template.TemplateSyntaxError),
+ 'if-tag-error04': ("{% if not foo and %}yes{% else %}no{% endif %}", {'foo': True}, template.TemplateSyntaxError),
+ 'if-tag-error05': ("{% if not foo or %}yes{% else %}no{% endif %}", {'foo': True}, template.TemplateSyntaxError),
+
+ ### IFCHANGED TAG #########################################################
+ 'ifchanged01': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% endfor %}', { 'num': (1,2,3) }, '123'),
+ 'ifchanged02': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% endfor %}', { 'num': (1,1,3) }, '13'),
+ 'ifchanged03': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% endfor %}', { 'num': (1,1,1) }, '1'),
+
+ ### IFEQUAL TAG ###########################################################
+ 'ifequal01': ("{% ifequal a b %}yes{% endifequal %}", {"a": 1, "b": 2}, ""),
+ 'ifequal02': ("{% ifequal a b %}yes{% endifequal %}", {"a": 1, "b": 1}, "yes"),
+ 'ifequal03': ("{% ifequal a b %}yes{% else %}no{% endifequal %}", {"a": 1, "b": 2}, "no"),
+ 'ifequal04': ("{% ifequal a b %}yes{% else %}no{% endifequal %}", {"a": 1, "b": 1}, "yes"),
+ 'ifequal05': ("{% ifequal a 'test' %}yes{% else %}no{% endifequal %}", {"a": "test"}, "yes"),
+ 'ifequal06': ("{% ifequal a 'test' %}yes{% else %}no{% endifequal %}", {"a": "no"}, "no"),
+ 'ifequal07': ('{% ifequal a "test" %}yes{% else %}no{% endifequal %}', {"a": "test"}, "yes"),
+ 'ifequal08': ('{% ifequal a "test" %}yes{% else %}no{% endifequal %}', {"a": "no"}, "no"),
+ 'ifequal09': ('{% ifequal a "test" %}yes{% else %}no{% endifequal %}', {}, "no"),
+ 'ifequal10': ('{% ifequal a b %}yes{% else %}no{% endifequal %}', {}, "yes"),
+
+ # SMART SPLITTING
+ 'ifequal-split01': ('{% ifequal a "test man" %}yes{% else %}no{% endifequal %}', {}, "no"),
+ 'ifequal-split02': ('{% ifequal a "test man" %}yes{% else %}no{% endifequal %}', {'a': 'foo'}, "no"),
+ 'ifequal-split03': ('{% ifequal a "test man" %}yes{% else %}no{% endifequal %}', {'a': 'test man'}, "yes"),
+ 'ifequal-split04': ("{% ifequal a 'test man' %}yes{% else %}no{% endifequal %}", {'a': 'test man'}, "yes"),
+ 'ifequal-split05': ("{% ifequal a 'i \"love\" you' %}yes{% else %}no{% endifequal %}", {'a': ''}, "no"),
+ 'ifequal-split06': ("{% ifequal a 'i \"love\" you' %}yes{% else %}no{% endifequal %}", {'a': 'i "love" you'}, "yes"),
+ 'ifequal-split07': ("{% ifequal a 'i \"love\" you' %}yes{% else %}no{% endifequal %}", {'a': 'i love you'}, "no"),
+ 'ifequal-split08': (r"{% ifequal a 'I\'m happy' %}yes{% else %}no{% endifequal %}", {'a': "I'm happy"}, "yes"),
+ 'ifequal-split09': (r"{% ifequal a 'slash\man' %}yes{% else %}no{% endifequal %}", {'a': r"slash\man"}, "yes"),
+ 'ifequal-split10': (r"{% ifequal a 'slash\man' %}yes{% else %}no{% endifequal %}", {'a': r"slashman"}, "no"),
+
+ ### IFNOTEQUAL TAG ########################################################
+ 'ifnotequal01': ("{% ifnotequal a b %}yes{% endifnotequal %}", {"a": 1, "b": 2}, "yes"),
+ 'ifnotequal02': ("{% ifnotequal a b %}yes{% endifnotequal %}", {"a": 1, "b": 1}, ""),
+ 'ifnotequal03': ("{% ifnotequal a b %}yes{% else %}no{% endifnotequal %}", {"a": 1, "b": 2}, "yes"),
+ 'ifnotequal04': ("{% ifnotequal a b %}yes{% else %}no{% endifnotequal %}", {"a": 1, "b": 1}, "no"),
+
+ ### INCLUDE TAG ###########################################################
+ 'include01': ('{% include "basic-syntax01" %}', {}, "something cool"),
+ 'include02': ('{% include "basic-syntax02" %}', {'headline': 'Included'}, "Included"),
+ 'include03': ('{% include template_name %}', {'template_name': 'basic-syntax02', 'headline': 'Included'}, "Included"),
+ 'include04': ('a{% include "nonexistent" %}b', {}, "ab"),
+
+ ### INHERITANCE ###########################################################
+
+ # Standard template with no inheritance
+ 'inheritance01': ("1{% block first %}_{% endblock %}3{% block second %}_{% endblock %}", {}, '1_3_'),
+
+ # Standard two-level inheritance
+ 'inheritance02': ("{% extends 'inheritance01' %}{% block first %}2{% endblock %}{% block second %}4{% endblock %}", {}, '1234'),
+
+ # Three-level with no redefinitions on third level
+ 'inheritance03': ("{% extends 'inheritance02' %}", {}, '1234'),
+
+ # Two-level with no redefinitions on second level
+ 'inheritance04': ("{% extends 'inheritance01' %}", {}, '1_3_'),
+
+ # Two-level with double quotes instead of single quotes
+ 'inheritance05': ('{% extends "inheritance02" %}', {}, '1234'),
+
+ # Three-level with variable parent-template name
+ 'inheritance06': ("{% extends foo %}", {'foo': 'inheritance02'}, '1234'),
+
+ # Two-level with one block defined, one block not defined
+ 'inheritance07': ("{% extends 'inheritance01' %}{% block second %}5{% endblock %}", {}, '1_35'),
+
+ # Three-level with one block defined on this level, two blocks defined next level
+ 'inheritance08': ("{% extends 'inheritance02' %}{% block second %}5{% endblock %}", {}, '1235'),
+
+ # Three-level with second and third levels blank
+ 'inheritance09': ("{% extends 'inheritance04' %}", {}, '1_3_'),
+
+ # Three-level with space NOT in a block -- should be ignored
+ 'inheritance10': ("{% extends 'inheritance04' %} ", {}, '1_3_'),
+
+ # Three-level with both blocks defined on this level, but none on second level
+ 'inheritance11': ("{% extends 'inheritance04' %}{% block first %}2{% endblock %}{% block second %}4{% endblock %}", {}, '1234'),
+
+ # Three-level with this level providing one and second level providing the other
+ 'inheritance12': ("{% extends 'inheritance07' %}{% block first %}2{% endblock %}", {}, '1235'),
+
+ # Three-level with this level overriding second level
+ 'inheritance13': ("{% extends 'inheritance02' %}{% block first %}a{% endblock %}{% block second %}b{% endblock %}", {}, '1a3b'),
+
+ # A block defined only in a child template shouldn't be displayed
+ 'inheritance14': ("{% extends 'inheritance01' %}{% block newblock %}NO DISPLAY{% endblock %}", {}, '1_3_'),
+
+ # A block within another block
+ 'inheritance15': ("{% extends 'inheritance01' %}{% block first %}2{% block inner %}inner{% endblock %}{% endblock %}", {}, '12inner3_'),
+
+ # A block within another block (level 2)
+ 'inheritance16': ("{% extends 'inheritance15' %}{% block inner %}out{% endblock %}", {}, '12out3_'),
+
+ # {% load %} tag (parent -- setup for exception04)
+ 'inheritance17': ("{% load testtags %}{% block first %}1234{% endblock %}", {}, '1234'),
+
+ # {% load %} tag (standard usage, without inheritance)
+ 'inheritance18': ("{% load testtags %}{% echo this that theother %}5678", {}, 'this that theother5678'),
+
+ # {% load %} tag (within a child template)
+ 'inheritance19': ("{% extends 'inheritance01' %}{% block first %}{% load testtags %}{% echo 400 %}5678{% endblock %}", {}, '140056783_'),
+
+ # Two-level inheritance with {{ block.super }}
+ 'inheritance20': ("{% extends 'inheritance01' %}{% block first %}{{ block.super }}a{% endblock %}", {}, '1_a3_'),
+
+ # Three-level inheritance with {{ block.super }} from parent
+ 'inheritance21': ("{% extends 'inheritance02' %}{% block first %}{{ block.super }}a{% endblock %}", {}, '12a34'),
+
+ # Three-level inheritance with {{ block.super }} from grandparent
+ 'inheritance22': ("{% extends 'inheritance04' %}{% block first %}{{ block.super }}a{% endblock %}", {}, '1_a3_'),
+
+ # Three-level inheritance with {{ block.super }} from parent and grandparent
+ 'inheritance23': ("{% extends 'inheritance20' %}{% block first %}{{ block.super }}b{% endblock %}", {}, '1_ab3_'),
+
+ # Inheritance from local context without use of template loader
+ 'inheritance24': ("{% extends context_template %}{% block first %}2{% endblock %}{% block second %}4{% endblock %}", {'context_template': template.Template("1{% block first %}_{% endblock %}3{% block second %}_{% endblock %}")}, '1234'),
+
+ # Inheritance from local context with variable parent template
+ 'inheritance25': ("{% extends context_template.1 %}{% block first %}2{% endblock %}{% block second %}4{% endblock %}", {'context_template': [template.Template("Wrong"), template.Template("1{% block first %}_{% endblock %}3{% block second %}_{% endblock %}")]}, '1234'),
+
+ ### I18N ##################################################################
+
+ # {% spaceless %} tag
+ 'spaceless01': ("{% spaceless %} <b> <i> text </i> </b> {% endspaceless %}", {}, "<b> <i> text </i> </b>"),
+ 'spaceless02': ("{% spaceless %} <b> \n <i> text </i> \n </b> {% endspaceless %}", {}, "<b> <i> text </i> </b>"),
+ 'spaceless03': ("{% spaceless %}<b><i>text</i></b>{% endspaceless %}", {}, "<b><i>text</i></b>"),
+
+ # simple translation of a string delimited by '
+ 'i18n01': ("{% load i18n %}{% trans 'xxxyyyxxx' %}", {}, "xxxyyyxxx"),
+
+ # simple translation of a string delimited by "
+ 'i18n02': ('{% load i18n %}{% trans "xxxyyyxxx" %}', {}, "xxxyyyxxx"),
+
+ # simple translation of a variable
+ 'i18n03': ('{% load i18n %}{% blocktrans %}{{ anton }}{% endblocktrans %}', {'anton': 'xxxyyyxxx'}, "xxxyyyxxx"),
+
+ # simple translation of a variable and filter
+ 'i18n04': ('{% load i18n %}{% blocktrans with anton|lower as berta %}{{ berta }}{% endblocktrans %}', {'anton': 'XXXYYYXXX'}, "xxxyyyxxx"),
+
+ # simple translation of a string with interpolation
+ 'i18n05': ('{% load i18n %}{% blocktrans %}xxx{{ anton }}xxx{% endblocktrans %}', {'anton': 'yyy'}, "xxxyyyxxx"),
+
+ # simple translation of a string to german
+ 'i18n06': ('{% load i18n %}{% trans "Page not found" %}', {'LANGUAGE_CODE': 'de'}, "Seite nicht gefunden"),
+
+ # translation of singular form
+ 'i18n07': ('{% load i18n %}{% blocktrans count number as counter %}singular{% plural %}plural{% endblocktrans %}', {'number': 1}, "singular"),
+
+ # translation of plural form
+ 'i18n08': ('{% load i18n %}{% blocktrans count number as counter %}singular{% plural %}plural{% endblocktrans %}', {'number': 2}, "plural"),
+
+ # simple non-translation (only marking) of a string to german
+ 'i18n09': ('{% load i18n %}{% trans "Page not found" noop %}', {'LANGUAGE_CODE': 'de'}, "Page not found"),
+
+ # translation of a variable with a translated filter
+ 'i18n10': ('{{ bool|yesno:_("ja,nein") }}', {'bool': True}, 'ja'),
+
+ # translation of a variable with a non-translated filter
+ 'i18n11': ('{{ bool|yesno:"ja,nein" }}', {'bool': True}, 'ja'),
+
+ # usage of the get_available_languages tag
+ 'i18n12': ('{% load i18n %}{% get_available_languages as langs %}{% for lang in langs %}{% ifequal lang.0 "de" %}{{ lang.0 }}{% endifequal %}{% endfor %}', {}, 'de'),
+
+ # translation of a constant string
+ 'i18n13': ('{{ _("Page not found") }}', {'LANGUAGE_CODE': 'de'}, 'Seite nicht gefunden'),
+
+ ### MULTILINE #############################################################
+
+ 'multiline01': ("""
+ Hello,
+ boys.
+ How
+ are
+ you
+ gentlemen.
+ """,
+ {},
+ """
+ Hello,
+ boys.
+ How
+ are
+ you
+ gentlemen.
+ """),
+
+ ### REGROUP TAG ###########################################################
+ 'regroup01': ('{% regroup data by bar as grouped %}' + \
+ '{% for group in grouped %}' + \
+ '{{ group.grouper }}:' + \
+ '{% for item in group.list %}' + \
+ '{{ item.foo }}' + \
+ '{% endfor %},' + \
+ '{% endfor %}',
+ {'data': [ {'foo':'c', 'bar':1},
+ {'foo':'d', 'bar':1},
+ {'foo':'a', 'bar':2},
+ {'foo':'b', 'bar':2},
+ {'foo':'x', 'bar':3} ]},
+ '1:cd,2:ab,3:x,'),
+
+ # Test for silent failure when target variable isn't found
+ 'regroup02': ('{% regroup data by bar as grouped %}' + \
+ '{% for group in grouped %}' + \
+ '{{ group.grouper }}:' + \
+ '{% for item in group.list %}' + \
+ '{{ item.foo }}' + \
+ '{% endfor %},' + \
+ '{% endfor %}',
+ {}, 'INVALID:INVALIDINVALIDINVALIDINVALIDINVALIDINVALIDINVALID,'),
+
+ ### TEMPLATETAG TAG #######################################################
+ 'templatetag01': ('{% templatetag openblock %}', {}, '{%'),
+ 'templatetag02': ('{% templatetag closeblock %}', {}, '%}'),
+ 'templatetag03': ('{% templatetag openvariable %}', {}, '{{'),
+ 'templatetag04': ('{% templatetag closevariable %}', {}, '}}'),
+ 'templatetag05': ('{% templatetag %}', {}, template.TemplateSyntaxError),
+ 'templatetag06': ('{% templatetag foo %}', {}, template.TemplateSyntaxError),
+ 'templatetag07': ('{% templatetag openbrace %}', {}, '{'),
+ 'templatetag08': ('{% templatetag closebrace %}', {}, '}'),
+ 'templatetag09': ('{% templatetag openbrace %}{% templatetag openbrace %}', {}, '{{'),
+ 'templatetag10': ('{% templatetag closebrace %}{% templatetag closebrace %}', {}, '}}'),
+
+ ### WIDTHRATIO TAG ########################################################
+ 'widthratio01': ('{% widthratio a b 0 %}', {'a':50,'b':100}, '0'),
+ 'widthratio02': ('{% widthratio a b 100 %}', {'a':0,'b':0}, ''),
+ 'widthratio03': ('{% widthratio a b 100 %}', {'a':0,'b':100}, '0'),
+ 'widthratio04': ('{% widthratio a b 100 %}', {'a':50,'b':100}, '50'),
+ 'widthratio05': ('{% widthratio a b 100 %}', {'a':100,'b':100}, '100'),
+
+ # 62.5 should round to 63
+ 'widthratio06': ('{% widthratio a b 100 %}', {'a':50,'b':80}, '63'),
+
+ # 71.4 should round to 71
+ 'widthratio07': ('{% widthratio a b 100 %}', {'a':50,'b':70}, '71'),
+
+ # Raise exception if we don't have 3 args, last one an integer
+ 'widthratio08': ('{% widthratio %}', {}, template.TemplateSyntaxError),
+ 'widthratio09': ('{% widthratio a b %}', {'a':50,'b':100}, template.TemplateSyntaxError),
+ 'widthratio10': ('{% widthratio a b 100.0 %}', {'a':50,'b':100}, template.TemplateSyntaxError),
+
+ ### NOW TAG ########################################################
+ # Simple case
+ 'now01' : ('{% now "j n Y"%}', {}, str(datetime.now().day) + ' ' + str(datetime.now().month) + ' ' + str(datetime.now().year)),
+
+ # Check parsing of escaped and special characters
+ 'now02' : ('{% now "j "n" Y"%}', {}, template.TemplateSyntaxError),
+ # 'now03' : ('{% now "j \"n\" Y"%}', {}, str(datetime.now().day) + '"' + str(datetime.now().month) + '"' + str(datetime.now().year)),
+ # 'now04' : ('{% now "j \nn\n Y"%}', {}, str(datetime.now().day) + '\n' + str(datetime.now().month) + '\n' + str(datetime.now().year))
+
+ ### TIMESINCE TAG ##################################################
+ # Default compare with datetime.now()
+ 'timesince01' : ('{{ a|timesince }}', {'a':datetime.now() + timedelta(minutes=-1, seconds = -10)}, '1 minute'),
+ 'timesince02' : ('{{ a|timesince }}', {'a':(datetime.now() - timedelta(days=1, minutes = 1))}, '1 day'),
+ 'timesince03' : ('{{ a|timesince }}', {'a':(datetime.now() -
+ timedelta(hours=1, minutes=25, seconds = 10))}, '1 hour, 25 minutes'),
+
+ # Compare to a given parameter
+ 'timesince04' : ('{{ a|timesince:b }}', {'a':NOW + timedelta(days=2), 'b':NOW + timedelta(days=1)}, '1 day'),
+ 'timesince05' : ('{{ a|timesince:b }}', {'a':NOW + timedelta(days=2, minutes=1), 'b':NOW + timedelta(days=2)}, '1 minute'),
+
+ # Check that timezone is respected
+ 'timesince06' : ('{{ a|timesince:b }}', {'a':NOW_tz + timedelta(hours=8), 'b':NOW_tz}, '8 hours'),
+
+ ### TIMEUNTIL TAG ##################################################
+ # Default compare with datetime.now()
+ 'timeuntil01' : ('{{ a|timeuntil }}', {'a':datetime.now() + timedelta(minutes=2, seconds = 10)}, '2 minutes'),
+ 'timeuntil02' : ('{{ a|timeuntil }}', {'a':(datetime.now() + timedelta(days=1, seconds = 10))}, '1 day'),
+ 'timeuntil03' : ('{{ a|timeuntil }}', {'a':(datetime.now() + timedelta(hours=8, minutes=10, seconds = 10))}, '8 hours, 10 minutes'),
+
+ # Compare to a given parameter
+ 'timeuntil04' : ('{{ a|timeuntil:b }}', {'a':NOW - timedelta(days=1), 'b':NOW - timedelta(days=2)}, '1 day'),
+ 'timeuntil05' : ('{{ a|timeuntil:b }}', {'a':NOW - timedelta(days=2), 'b':NOW - timedelta(days=2, minutes=1)}, '1 minute'),
+ }
+
+ # Register our custom template loader.
+ def test_template_loader(template_name, template_dirs=None):
+ "A custom template loader that loads the unit-test templates."
+ try:
+ return (TEMPLATE_TESTS[template_name][0] , "test:%s" % template_name)
+ except KeyError:
+ raise template.TemplateDoesNotExist, template_name
+
+ old_template_loaders = loader.template_source_loaders
+ loader.template_source_loaders = [test_template_loader]
+
+ failures = []
+ tests = TEMPLATE_TESTS.items()
+ tests.sort()
+
+ # Turn TEMPLATE_DEBUG off, because tests assume that.
+ 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'
+
+ for name, vals in tests:
+ install()
+ 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
+ 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, [])
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/regressiontests/urlpatterns_reverse/__init__.py b/tests/regressiontests/urlpatterns_reverse/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/regressiontests/urlpatterns_reverse/__init__.py
diff --git a/tests/regressiontests/urlpatterns_reverse/models.py b/tests/regressiontests/urlpatterns_reverse/models.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/regressiontests/urlpatterns_reverse/models.py
diff --git a/tests/othertests/urlpatterns_reverse.py b/tests/regressiontests/urlpatterns_reverse/tests.py
index 236944d49f..8f571ac66c 100644
--- a/tests/othertests/urlpatterns_reverse.py
+++ b/tests/regressiontests/urlpatterns_reverse/tests.py
@@ -1,7 +1,7 @@
"Unit tests for reverse URL lookup"
from django.core.urlresolvers import reverse_helper, NoReverseMatch
-import re
+import re, unittest
test_data = (
('^places/(\d+)/$', 'places/3/', [3], {}),
@@ -25,23 +25,15 @@ test_data = (
('^people/(?P<state>\w\w)/(\w+)/$', 'people/il/adrian/', ['adrian'], {'state': 'il'}),
)
-def run_tests(verbosity=0):
- for regex, expected, args, kwargs in test_data:
- passed = True
- try:
- got = reverse_helper(re.compile(regex), *args, **kwargs)
- except NoReverseMatch, e:
- if expected != NoReverseMatch:
- passed, got = False, str(e)
- else:
- if got != expected:
- passed, got = False, got
- if passed and verbosity:
- print "Passed: %s" % regex
- elif not passed:
- print "REVERSE LOOKUP FAILED: %s" % regex
- print " Got: %s" % got
- print " Expected: %r" % expected
+class URLPatternReverse(unittest.TestCase):
+ def test_urlpattern_reverse(self):
+ for regex, expected, args, kwargs in test_data:
+ try:
+ got = reverse_helper(re.compile(regex), *args, **kwargs)
+ except NoReverseMatch, e:
+ self.assertEqual(expected, NoReverseMatch)
+ else:
+ self.assertEquals(got, expected)
if __name__ == "__main__":
run_tests(1)
diff --git a/tests/runtests.py b/tests/runtests.py
index b98a739249..57087ef3a7 100755
--- a/tests/runtests.py
+++ b/tests/runtests.py
@@ -1,23 +1,10 @@
#!/usr/bin/env python
-import os, re, sys, time, traceback
-
-# doctest is included in the same package as this module, because this testing
-# framework uses features only available in the Python 2.4 version of doctest,
-# and Django aims to work with Python 2.3+.
-import doctest
+import os, sys, traceback
+import unittest
MODEL_TESTS_DIR_NAME = 'modeltests'
-OTHER_TESTS_DIR = "othertests"
REGRESSION_TESTS_DIR_NAME = 'regressiontests'
-TEST_DATABASE_NAME = 'django_test_db'
-
-error_list = []
-def log_error(model_name, title, description):
- error_list.append({
- 'title': "%r module: %s" % (model_name, title),
- 'description': description,
- })
MODEL_TEST_DIR = os.path.join(os.path.dirname(__file__), MODEL_TESTS_DIR_NAME)
REGRESSION_TEST_DIR = os.path.join(os.path.dirname(__file__), REGRESSION_TESTS_DIR_NAME)
@@ -37,258 +24,101 @@ def get_test_models():
models = []
for loc, dirpath in (MODEL_TESTS_DIR_NAME, MODEL_TEST_DIR), (REGRESSION_TESTS_DIR_NAME, REGRESSION_TEST_DIR):
for f in os.listdir(dirpath):
- if f.startswith('__init__') or f.startswith('.') or f.startswith('sql'):
+ if f.startswith('__init__') or f.startswith('.') or f.startswith('sql') or f.startswith('invalid'):
continue
models.append((loc, f))
return models
-class DjangoDoctestRunner(doctest.DocTestRunner):
- def __init__(self, verbosity_level, *args, **kwargs):
- self.verbosity_level = verbosity_level
- doctest.DocTestRunner.__init__(self, *args, **kwargs)
- self._checker = DjangoDoctestOutputChecker()
- self.optionflags = doctest.ELLIPSIS
-
- def report_start(self, out, test, example):
- if self.verbosity_level > 1:
- out(" >>> %s\n" % example.source.strip())
-
- def report_failure(self, out, test, example, got):
- log_error(test.name, "API test failed",
- "Code: %r\nLine: %s\nExpected: %r\nGot: %r" % (example.source.strip(), example.lineno, example.want, got))
-
- def report_unexpected_exception(self, out, test, example, exc_info):
- from django.db import transaction
- tb = ''.join(traceback.format_exception(*exc_info)[1:])
- log_error(test.name, "API test raised an exception",
- "Code: %r\nLine: %s\nException: %s" % (example.source.strip(), example.lineno, tb))
- # Rollback, in case of database errors. Otherwise they'd have
- # side effects on other tests.
- transaction.rollback_unless_managed()
-
-normalize_long_ints = lambda s: re.sub(r'(?<![\w])(\d+)L(?![\w])', '\\1', s)
-
-class DjangoDoctestOutputChecker(doctest.OutputChecker):
- def check_output(self, want, got, optionflags):
- ok = doctest.OutputChecker.check_output(self, want, got, optionflags)
-
- # Doctest does an exact string comparison of output, which means long
- # integers aren't equal to normal integers ("22L" vs. "22"). The
- # following code normalizes long integers so that they equal normal
- # integers.
- if not ok:
- return normalize_long_ints(want) == normalize_long_ints(got)
- return ok
-
-class TestRunner:
- def __init__(self, verbosity_level=0, which_tests=None):
- self.verbosity_level = verbosity_level
- self.which_tests = which_tests
-
- def output(self, required_level, message):
- if self.verbosity_level > required_level - 1:
- print message
-
- def run_tests(self):
- from django.conf import settings
-
- # An empty access of the settings to force the default options to be
- # installed prior to assigning to them.
- settings.INSTALLED_APPS
-
- # Manually set INSTALLED_APPS to point to the test models.
- settings.INSTALLED_APPS = ALWAYS_INSTALLED_APPS + ['.'.join(a) for a in get_test_models()]
-
- # Manually set DEBUG and USE_I18N.
- settings.DEBUG = False
- settings.USE_I18N = True
-
- from django.db import connection
+def get_invalid_models():
+ models = []
+ for loc, dirpath in (MODEL_TESTS_DIR_NAME, MODEL_TEST_DIR), (REGRESSION_TESTS_DIR_NAME, REGRESSION_TEST_DIR):
+ for f in os.listdir(dirpath):
+ if f.startswith('__init__') or f.startswith('.') or f.startswith('sql'):
+ continue
+ if f.startswith('invalid'):
+ models.append((loc, f))
+ return models
+
+class InvalidModelTestCase(unittest.TestCase):
+ def __init__(self, model_label):
+ unittest.TestCase.__init__(self)
+ self.model_label = model_label
+
+ def runTest(self):
from django.core import management
- import django.db.models
-
- # Determine which models we're going to test.
- test_models = get_test_models()
- if 'othertests' in self.which_tests:
- self.which_tests.remove('othertests')
- run_othertests = True
- if not self.which_tests:
- test_models = []
- else:
- run_othertests = not self.which_tests
-
- if self.which_tests:
- # Only run the specified tests.
- bad_models = [m for m in self.which_tests if (MODEL_TESTS_DIR_NAME, m) not in test_models and (REGRESSION_TESTS_DIR_NAME, m) not in test_models]
- if bad_models:
- sys.stderr.write("Models not found: %s\n" % bad_models)
- sys.exit(1)
- else:
- all_tests = []
- for test in self.which_tests:
- for loc in MODEL_TESTS_DIR_NAME, REGRESSION_TESTS_DIR_NAME:
- if (loc, test) in test_models:
- all_tests.append((loc, test))
- test_models = all_tests
-
- self.output(0, "Running tests with database %r" % settings.DATABASE_ENGINE)
-
- # If we're using SQLite, it's more convenient to test against an
- # in-memory database.
- if settings.DATABASE_ENGINE == "sqlite3":
- global TEST_DATABASE_NAME
- TEST_DATABASE_NAME = ":memory:"
- else:
- # Create the test database and connect to it. We need to autocommit
- # if the database supports it because PostgreSQL doesn't allow
- # CREATE/DROP DATABASE statements within transactions.
- cursor = connection.cursor()
- self._set_autocommit(connection)
- self.output(1, "Creating test database")
- try:
- cursor.execute("CREATE DATABASE %s" % TEST_DATABASE_NAME)
- except Exception, e:
- sys.stderr.write("Got an error creating the test database: %s\n" % e)
- confirm = raw_input("It appears the test database, %s, already exists. Type 'yes' to delete it, or 'no' to cancel: " % TEST_DATABASE_NAME)
- if confirm == 'yes':
- cursor.execute("DROP DATABASE %s" % TEST_DATABASE_NAME)
- cursor.execute("CREATE DATABASE %s" % TEST_DATABASE_NAME)
- else:
- print "Tests cancelled."
- return
- connection.close()
- old_database_name = settings.DATABASE_NAME
- settings.DATABASE_NAME = TEST_DATABASE_NAME
-
- # Initialize the test database.
- cursor = connection.cursor()
-
from django.db.models.loading import load_app
- # Install the core always installed apps
- for app in ALWAYS_INSTALLED_APPS:
- self.output(1, "Installing contrib app %s" % app)
- mod = load_app(app)
- management.install(mod)
-
- # Run the tests for each test model.
- self.output(1, "Running app tests")
- for model_dir, model_name in test_models:
- self.output(1, "%s model: Importing" % model_name)
- try:
- mod = load_app(model_dir + '.' + model_name)
- except Exception, e:
- log_error(model_name, "Error while importing", ''.join(traceback.format_exception(*sys.exc_info())[1:]))
- continue
-
- if not getattr(mod, 'error_log', None):
- # Model is not marked as an invalid model
- self.output(1, "%s.%s model: Installing" % (model_dir, model_name))
- management.install(mod)
-
- # Run the API tests.
- p = doctest.DocTestParser()
- test_namespace = dict([(m._meta.object_name, m) \
- for m in django.db.models.get_models(mod)])
- dtest = p.get_doctest(mod.API_TESTS, test_namespace, model_name, None, None)
- # Manually set verbose=False, because "-v" command-line parameter
- # has side effects on doctest TestRunner class.
- runner = DjangoDoctestRunner(verbosity_level=verbosity_level, verbose=False)
- self.output(1, "%s.%s model: Running tests" % (model_dir, model_name))
- runner.run(dtest, clear_globs=True, out=sys.stdout.write)
- else:
- # Check that model known to be invalid is invalid for the right reasons.
- self.output(1, "%s.%s model: Validating" % (model_dir, model_name))
-
- from cStringIO import StringIO
- s = StringIO()
- count = management.get_validation_errors(s, mod)
- s.seek(0)
- error_log = s.read()
- actual = error_log.split('\n')
- expected = mod.error_log.split('\n')
-
- unexpected = [err for err in actual if err not in expected]
- missing = [err for err in expected if err not in actual]
-
- if unexpected or missing:
- unexpected_log = '\n'.join(unexpected)
- missing_log = '\n'.join(missing)
- log_error(model_name,
- "Validator found %d validation errors, %d expected" % (count, len(expected) - 1),
- "Missing errors:\n%s\n\nUnexpected errors:\n%s" % (missing_log, unexpected_log))
-
- if run_othertests:
- # Run the non-model tests in the other tests dir
- self.output(1, "Running other tests")
- other_tests_dir = os.path.join(os.path.dirname(__file__), OTHER_TESTS_DIR)
- test_modules = [f[:-3] for f in os.listdir(other_tests_dir) if f.endswith('.py') and not f.startswith('__init__')]
- for module in test_modules:
- self.output(1, "%s module: Importing" % module)
- try:
- mod = __import__("othertests." + module, '', '', [''])
- except Exception, e:
- log_error(module, "Error while importing", ''.join(traceback.format_exception(*sys.exc_info())[1:]))
- continue
- if mod.__doc__:
- p = doctest.DocTestParser()
- dtest = p.get_doctest(mod.__doc__, mod.__dict__, module, None, None)
- runner = DjangoDoctestRunner(verbosity_level=verbosity_level, verbose=False)
- self.output(1, "%s module: running tests" % module)
- runner.run(dtest, clear_globs=True, out=sys.stdout.write)
- if hasattr(mod, "run_tests") and callable(mod.run_tests):
- self.output(1, "%s module: running tests" % module)
- try:
- mod.run_tests(verbosity_level)
- except Exception, e:
- log_error(module, "Exception running tests", ''.join(traceback.format_exception(*sys.exc_info())[1:]))
- continue
-
- # Unless we're using SQLite, remove the test database to clean up after
- # ourselves. Connect to the previous database (not the test database)
- # to do so, because it's not allowed to delete a database while being
- # connected to it.
- if settings.DATABASE_ENGINE != "sqlite3":
- connection.close()
- settings.DATABASE_NAME = old_database_name
- cursor = connection.cursor()
- self.output(1, "Deleting test database")
- self._set_autocommit(connection)
- time.sleep(1) # To avoid "database is being accessed by other users" errors.
- cursor.execute("DROP DATABASE %s" % TEST_DATABASE_NAME)
-
- # Display output.
- if error_list:
- for d in error_list:
- print
- print d['title']
- print "=" * len(d['title'])
- print d['description']
- print "%s error%s:" % (len(error_list), len(error_list) != 1 and 's' or '')
- else:
- print "All tests passed."
-
- def _set_autocommit(self, connection):
- """
- Make sure a connection is in autocommit mode.
- """
- if hasattr(connection.connection, "autocommit"):
- connection.connection.autocommit(True)
- elif hasattr(connection.connection, "set_isolation_level"):
- connection.connection.set_isolation_level(0)
-
+ from cStringIO import StringIO
+
+ try:
+ module = load_app(self.model_label)
+ except Exception, e:
+ self.fail('Unable to load invalid model module')
+
+ s = StringIO()
+ count = management.get_validation_errors(s, module)
+ s.seek(0)
+ error_log = s.read()
+ actual = error_log.split('\n')
+ expected = module.model_errors.split('\n')
+
+ unexpected = [err for err in actual if err not in expected]
+ missing = [err for err in expected if err not in actual]
+
+ self.assert_(not unexpected, "Unexpected Errors: " + '\n'.join(unexpected))
+ self.assert_(not missing, "Missing Errors: " + '\n'.join(missing))
+
+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
+
+ # load all the ALWAYS_INSTALLED_APPS
+ settings.INSTALLED_APPS = ALWAYS_INSTALLED_APPS
+ get_apps()
+
+ test_models = []
+ # Load all the test model apps
+ for model_dir, model_name in get_test_models():
+ model_label = '.'.join([model_dir, model_name])
+ try:
+ # if the model was named on the command line, or
+ # no models were named (i.e., run all), import
+ # this model and add it to the list to test.
+ if not tests_to_run or model_name in tests_to_run:
+ if verbosity >= 1:
+ print "Importing model %s" % model_name
+ mod = load_app(model_label)
+ settings.INSTALLED_APPS.append(model_label)
+ test_models.append(mod)
+ except Exception, e:
+ sys.stderr.write("Error while importing %s:" % model_name + ''.join(traceback.format_exception(*sys.exc_info())[1:]))
+ continue
+
+ # Add tests for invalid models
+ extra_tests = []
+ for model_dir, model_name in get_invalid_models():
+ model_label = '.'.join([model_dir, model_name])
+ if not tests_to_run or model_name in tests_to_run:
+ extra_tests.append(InvalidModelTestCase(model_label))
+
+ # Run the test suite, including the extra validation tests.
+ from django.test.simple import run_tests
+ run_tests(test_models, verbosity, extra_tests=extra_tests)
+
+ # Restore the old INSTALLED_APPS setting
+ settings.INSTALLED_APPS = old_installed_apps
+
if __name__ == "__main__":
from optparse import OptionParser
usage = "%prog [options] [model model model ...]"
parser = OptionParser(usage=usage)
- parser.add_option('-v', help='How verbose should the output be? Choices are 0, 1 and 2, where 2 is most verbose. Default is 0.',
- type='choice', choices=['0', '1', '2'])
+ parser.add_option('-v','--verbosity', action='store', dest='verbosity', default='0',
+ type='choice', choices=['0', '1', '2'],
+ help='Verbosity level; 0=minimal output, 1=normal output, 2=all output')
parser.add_option('--settings',
help='Python path to settings module, e.g. "myproject.settings". If this isn\'t provided, the DJANGO_SETTINGS_MODULE environment variable will be used.')
options, args = parser.parse_args()
- verbosity_level = 0
- if options.v:
- verbosity_level = int(options.v)
if options.settings:
os.environ['DJANGO_SETTINGS_MODULE'] = options.settings
- t = TestRunner(verbosity_level, args)
- t.run_tests()
+
+ django_tests(int(options.verbosity), args)