summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoulder Sprinters <boulder-sprinters@djangoproject.com>2007-05-08 17:46:05 +0000
committerBoulder Sprinters <boulder-sprinters@djangoproject.com>2007-05-08 17:46:05 +0000
commit7f13278f8619b1155fa51276bb63afa9997610da (patch)
tree2df768ea9c6c866926ee7c3c6831a4fe91dfa097
parenta275d3da8ed8cea8c2c92fc15151f43fb56b42ce (diff)
downloaddjango-7f13278f8619b1155fa51276bb63afa9997610da.tar.gz
boulder-oracle-sprint: Merged to [5173]
git-svn-id: http://code.djangoproject.com/svn/django/branches/boulder-oracle-sprint@5174 bcc190cf-cafb-0310-a4f2-bffc1f526a37
-rw-r--r--AUTHORS5
-rw-r--r--django/conf/locale/de/LC_MESSAGES/django.mobin44060 -> 44063 bytes
-rw-r--r--django/conf/locale/de/LC_MESSAGES/django.po16
-rw-r--r--django/contrib/admin/templatetags/log.py9
-rw-r--r--django/contrib/contenttypes/generic.py (renamed from django/db/models/fields/generic.py)0
-rw-r--r--django/core/cache/backends/base.py3
-rw-r--r--django/core/management.py4
-rw-r--r--django/db/models/__init__.py1
-rw-r--r--django/db/models/base.py2
-rw-r--r--django/db/models/query.py9
-rw-r--r--django/template/__init__.py9
-rw-r--r--django/test/testcases.py22
-rw-r--r--django/test/utils.py32
-rw-r--r--docs/contributing.txt9
-rw-r--r--docs/i18n.txt6
-rw-r--r--docs/model-api.txt2
-rw-r--r--docs/serialization.txt2
-rw-r--r--docs/sitemaps.txt2
-rw-r--r--docs/templates_python.txt21
-rw-r--r--docs/testing.txt104
-rw-r--r--tests/modeltests/generic_relations/models.py7
-rw-r--r--tests/modeltests/test_client/models.py34
-rw-r--r--tests/modeltests/test_client/urls.py4
-rw-r--r--tests/modeltests/test_client/views.py26
-rw-r--r--tests/regressiontests/cache/tests.py5
-rw-r--r--tests/regressiontests/serializers_regress/models.py5
-rw-r--r--tests/regressiontests/templates/tests.py13
27 files changed, 271 insertions, 81 deletions
diff --git a/AUTHORS b/AUTHORS
index 027dbc39ba..fb2d5f7112 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -43,12 +43,14 @@ answer newbie questions, and generally made Django that much better:
adurdin@gmail.com
alang@bright-green.com
+ Marty Alchin <gulopine@gamemusic.org>
Daniel Alves Barbosa de Oliveira Vaz <danielvaz@gmail.com>
Andreas
andy@jadedplanet.net
Fabrice Aneche <akh@nobugware.com>
ant9000@netwise.it
David Ascher <http://ascher.ca/>
+ david@kazserve.org
Arthur <avandorp@gmail.com>
axiak@mit.edu
Jiri Barton
@@ -68,6 +70,7 @@ answer newbie questions, and generally made Django that much better:
Amit Chakradeo <http://amit.chakradeo.net/>
ChaosKCW
ivan.chelubeev@gmail.com
+ Bryan Chow <bryan at verdjn dot com>
Ian Clelland <clelland@gmail.com>
crankycoder@gmail.com
Matt Croydon <http://www.postneo.com/>
@@ -145,7 +148,7 @@ answer newbie questions, and generally made Django that much better:
lerouxb@gmail.com
Waylan Limberg <waylan@gmail.com>
limodou
- mattmcc
+ Matt McClanahan <http://mmcc.cx/>
Martin Maney <http://www.chipy.org/Martin_Maney>
masonsimon+django@gmail.com
Manuzhai
diff --git a/django/conf/locale/de/LC_MESSAGES/django.mo b/django/conf/locale/de/LC_MESSAGES/django.mo
index 396fe5a432..5f2eee4f33 100644
--- a/django/conf/locale/de/LC_MESSAGES/django.mo
+++ b/django/conf/locale/de/LC_MESSAGES/django.mo
Binary files differ
diff --git a/django/conf/locale/de/LC_MESSAGES/django.po b/django/conf/locale/de/LC_MESSAGES/django.po
index 9237e18b2a..52b70bda00 100644
--- a/django/conf/locale/de/LC_MESSAGES/django.po
+++ b/django/conf/locale/de/LC_MESSAGES/django.po
@@ -367,11 +367,11 @@ msgstr "Abmelden"
#: contrib/admin/templates/admin/base_site.html:4
msgid "Django site admin"
-msgstr "Django Systemverwaltung"
+msgstr "Django-Systemverwaltung"
#: contrib/admin/templates/admin/base_site.html:7
msgid "Django administration"
-msgstr "Django Verwaltung"
+msgstr "Django-Verwaltung"
#: contrib/admin/templates/admin/change_form.html:15
#: contrib/admin/templates/admin/index.html:28
@@ -385,7 +385,7 @@ msgstr "Geschichte"
#: contrib/admin/templates/admin/change_form.html:22
msgid "View on site"
-msgstr "Im Web Anzeigen"
+msgstr "Im Web anzeigen"
#: contrib/admin/templates/admin/change_form.html:32
#: contrib/admin/templates/admin/auth/user/change_password.html:24
@@ -614,7 +614,7 @@ msgid ""
"your computer is \"internal\").</p>\n"
msgstr ""
"\n"
-"<p class=\"help\">Um Bookmarklets zu installieren müssen diese Links in die\n"
+"<p class=\"help\">Um Bookmarklets zu installieren, müssen diese Links in die\n"
"Browser-Werkzeugleiste gezogen werden, oder mittels rechter Maustaste in "
"die\n"
"Bookmarks gespeichert werden. Danach können die Bookmarklets von jeder "
@@ -998,7 +998,7 @@ msgstr "%s ist scheinbar kein urlpattern Objekt"
#: contrib/admin/views/main.py:223
msgid "Site administration"
-msgstr "Website Verwaltung"
+msgstr "Website-Verwaltung"
#: contrib/admin/views/main.py:271 contrib/admin/views/main.py:356
#, python-format
@@ -1023,7 +1023,7 @@ msgstr "und"
#: contrib/admin/views/main.py:337
#, python-format
msgid "Changed %s."
-msgstr "%s geändert"
+msgstr "%s geändert."
#: contrib/admin/views/main.py:339
#, python-format
@@ -1490,8 +1490,8 @@ msgstr "Ihr Name:"
msgid ""
"This rating is required because you've entered at least one other rating."
msgstr ""
-"Diese Abstimmung ist zwingend erforderlich, da Du an mindestens einer "
-"weiteren Abstimmung teilnimmst."
+"Diese Abstimmung ist zwingend erforderlich, da Sie an mindestens einer "
+"weiteren Abstimmung teilnehmen."
#: contrib/comments/views/comments.py:111
#, python-format
diff --git a/django/contrib/admin/templatetags/log.py b/django/contrib/admin/templatetags/log.py
index 5caba2b795..8d52d2e944 100644
--- a/django/contrib/admin/templatetags/log.py
+++ b/django/contrib/admin/templatetags/log.py
@@ -11,9 +11,12 @@ class AdminLogNode(template.Node):
return "<GetAdminLog Node>"
def render(self, context):
- if self.user is not None and not self.user.isdigit():
- self.user = context[self.user].id
- context[self.varname] = LogEntry.objects.filter(user__id__exact=self.user).select_related()[:self.limit]
+ if self.user is None:
+ context[self.varname] = LogEntry.objects.all().select_related()[:self.limit]
+ else:
+ if not self.user.isdigit():
+ self.user = context[self.user].id
+ context[self.varname] = LogEntry.objects.filter(user__id__exact=self.user).select_related()[:self.limit]
return ''
class DoGetAdminLog:
diff --git a/django/db/models/fields/generic.py b/django/contrib/contenttypes/generic.py
index f995ab2044..f995ab2044 100644
--- a/django/db/models/fields/generic.py
+++ b/django/contrib/contenttypes/generic.py
diff --git a/django/core/cache/backends/base.py b/django/core/cache/backends/base.py
index ef5f6a6b3e..bb67399f3b 100644
--- a/django/core/cache/backends/base.py
+++ b/django/core/cache/backends/base.py
@@ -54,3 +54,6 @@ class BaseCache(object):
Returns True if the key is in the cache and has not expired.
"""
return self.get(key) is not None
+
+ __contains__ = has_key
+
diff --git a/django/core/management.py b/django/core/management.py
index 47f44f8c8d..91de11f3b3 100644
--- a/django/core/management.py
+++ b/django/core/management.py
@@ -260,14 +260,14 @@ def _get_sql_for_pending_references(model, pending_references):
def _get_many_to_many_sql_for_model(model):
from django.db import backend, get_creation_module
- from django.db.models import GenericRel
+ from django.contrib.contenttypes import generic
data_types = get_creation_module().DATA_TYPES
opts = model._meta
final_output = []
for f in opts.many_to_many:
- if not isinstance(f.rel, GenericRel):
+ if not isinstance(f.rel, generic.GenericRel):
tablespace = f.db_tablespace or opts.db_tablespace
if tablespace and backend.supports_tablespaces and backend.autoindexes_primary_keys:
tablespace_sql = ' ' + backend.get_tablespace_sql(tablespace, inline=True)
diff --git a/django/db/models/__init__.py b/django/db/models/__init__.py
index ccd60023f9..6c3abb6b59 100644
--- a/django/db/models/__init__.py
+++ b/django/db/models/__init__.py
@@ -8,7 +8,6 @@ from django.db.models.manager import Manager
from django.db.models.base import Model, AdminOptions
from django.db.models.fields import *
from django.db.models.fields.related import ForeignKey, OneToOneField, ManyToManyField, ManyToOneRel, ManyToManyRel, OneToOneRel, TABULAR, STACKED
-from django.db.models.fields.generic import GenericRelation, GenericRel, GenericForeignKey
from django.db.models import signals
from django.utils.functional import curry
from django.utils.text import capfirst
diff --git a/django/db/models/base.py b/django/db/models/base.py
index eb95aae4f2..a567f0ed37 100644
--- a/django/db/models/base.py
+++ b/django/db/models/base.py
@@ -42,11 +42,11 @@ class ModelBase(type):
new_class._meta.parents.append(base)
new_class._meta.parents.extend(base._meta.parents)
- model_module = sys.modules[new_class.__module__]
if getattr(new_class._meta, 'app_label', None) is None:
# Figure out the app_label by looking one level up.
# For 'django.contrib.sites.models', this would be 'sites'.
+ model_module = sys.modules[new_class.__module__]
new_class._meta.app_label = model_module.__name__.split('.')[-2]
# Bail out early if we have already created this class.
diff --git a/django/db/models/query.py b/django/db/models/query.py
index d31ccf003e..e3b9c794f8 100644
--- a/django/db/models/query.py
+++ b/django/db/models/query.py
@@ -1,10 +1,9 @@
from django.db import backend, connection, transaction
from django.db.models.fields import DateField, FieldDoesNotExist
-from django.db.models.fields.generic import GenericRelation
-from django.db.models import signals
+from django.db.models import signals, loading
from django.dispatch import dispatcher
from django.utils.datastructures import SortedDict
-from django.conf import settings
+from django.contrib.contenttypes import generic
import datetime
import operator
import re
@@ -1091,7 +1090,7 @@ def delete_objects(seen_objs):
pk_list = [pk for pk,instance in seen_objs[cls]]
for related in cls._meta.get_all_related_many_to_many_objects():
- if not isinstance(related.field, GenericRelation):
+ if not isinstance(related.field, generic.GenericRelation):
for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE):
cursor.execute("DELETE FROM %s WHERE %s IN (%s)" % \
(qn(related.field.m2m_db_table()),
@@ -1099,7 +1098,7 @@ def delete_objects(seen_objs):
','.join(['%s' for pk in pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE]])),
pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE])
for f in cls._meta.many_to_many:
- if isinstance(f, GenericRelation):
+ if isinstance(f, generic.GenericRelation):
from django.contrib.contenttypes.models import ContentType
query_extra = 'AND %s=%%s' % f.rel.to._meta.get_field(f.content_type_field_name).column
args_extra = [ContentType.objects.get_for_model(cls).id]
diff --git a/django/template/__init__.py b/django/template/__init__.py
index 4cb4f21156..9811a5649d 100644
--- a/django/template/__init__.py
+++ b/django/template/__init__.py
@@ -99,6 +99,10 @@ libraries = {}
# global list of libraries to load by default for a new parser
builtins = []
+# True if TEMPLATE_STRING_IF_INVALID contains a format string (%s). None means
+# uninitialised.
+invalid_var_format_string = None
+
class TemplateSyntaxError(Exception):
def __str__(self):
try:
@@ -575,6 +579,11 @@ class FilterExpression(object):
obj = None
else:
if settings.TEMPLATE_STRING_IF_INVALID:
+ global invalid_var_format_string
+ if invalid_var_format_string is None:
+ invalid_var_format_string = '%s' in settings.TEMPLATE_STRING_IF_INVALID
+ if invalid_var_format_string:
+ return settings.TEMPLATE_STRING_IF_INVALID % self.var
return settings.TEMPLATE_STRING_IF_INVALID
else:
obj = settings.TEMPLATE_STRING_IF_INVALID
diff --git a/django/test/testcases.py b/django/test/testcases.py
index 80f55b20d3..153931f42a 100644
--- a/django/test/testcases.py
+++ b/django/test/testcases.py
@@ -1,7 +1,7 @@
import re, doctest, unittest
from urlparse import urlparse
from django.db import transaction
-from django.core import management
+from django.core import management, mail
from django.db.models import get_apps
from django.test.client import Client
@@ -33,23 +33,27 @@ class DocTestRunner(doctest.DocTestRunner):
transaction.rollback_unless_managed()
class TestCase(unittest.TestCase):
- def install_fixtures(self):
- """If the Test Case class has a 'fixtures' member, clear the database and
- install the named fixtures at the start of each test.
+ def _pre_setup(self):
+ """Perform any pre-test setup. This includes:
+ * If the Test Case class has a 'fixtures' member, clearing the
+ database and installing the named fixtures at the start of each test.
+ * Clearing the mail test outbox.
+
"""
management.flush(verbosity=0, interactive=False)
if hasattr(self, 'fixtures'):
management.load_data(self.fixtures, verbosity=0)
-
+ mail.outbox = []
+
def run(self, result=None):
- """Wrapper around default run method so that user-defined Test Cases
- automatically call install_fixtures without having to include a call to
- super().
+ """Wrapper around default run method to perform common Django test set up.
+ This means that user-defined Test Cases aren't required to include a call
+ to super().setUp().
"""
self.client = Client()
- self.install_fixtures()
+ self._pre_setup()
super(TestCase, self).run(result)
def assertRedirects(self, response, expected_path):
diff --git a/django/test/utils.py b/django/test/utils.py
index 70b7f3cdbe..da339bb808 100644
--- a/django/test/utils.py
+++ b/django/test/utils.py
@@ -1,7 +1,7 @@
import sys, time
from django.conf import settings
from django.db import connection, backend, get_creation_module
-from django.core import management
+from django.core import management, mail
from django.dispatch import dispatcher
from django.test import signals
from django.template import Template
@@ -18,24 +18,54 @@ def instrumented_test_render(self, context):
dispatcher.send(signal=signals.template_rendered, sender=self, template=self, context=context)
return self.nodelist.render(context)
+class TestSMTPConnection(object):
+ """A substitute SMTP connection for use during test sessions.
+ The test connection stores email messages in a dummy outbox,
+ rather than sending them out on the wire.
+
+ """
+ def __init__(*args, **kwargs):
+ pass
+ def open(self):
+ "Mock the SMTPConnection open() interface"
+ pass
+ def close(self):
+ "Mock the SMTPConnection close() interface"
+ pass
+ def send_messages(self, messages):
+ "Redirect messages to the dummy outbox"
+ mail.outbox.extend(messages)
+
def setup_test_environment():
"""Perform any global pre-test setup. This involves:
- Installing the instrumented test renderer
+ - Diverting the email sending functions to a test buffer
"""
Template.original_render = Template.render
Template.render = instrumented_test_render
+ mail.original_SMTPConnection = mail.SMTPConnection
+ mail.SMTPConnection = TestSMTPConnection
+
+ mail.outbox = []
+
def teardown_test_environment():
"""Perform any global post-test teardown. This involves:
- Restoring the original test renderer
+ - Restoring the email sending functions
"""
Template.render = Template.original_render
del Template.original_render
+ mail.SMTPConnection = mail.original_SMTPConnection
+ del mail.original_SMTPConnection
+
+ del mail.outbox
+
def _set_autocommit(connection):
"Make sure a connection is in autocommit mode."
if hasattr(connection.connection, "autocommit"):
diff --git a/docs/contributing.txt b/docs/contributing.txt
index 1d2b635b76..d05c166b37 100644
--- a/docs/contributing.txt
+++ b/docs/contributing.txt
@@ -396,10 +396,11 @@ To run the tests, ``cd`` to the ``tests/`` directory and type::
./runtests.py --settings=path.to.django.settings
Yes, the unit tests need a settings module, but only for database connection
-info -- the ``DATABASE_ENGINE``, ``DATABASE_USER`` and ``DATABASE_PASSWORD``.
-You will also need a ``ROOT_URLCONF`` setting (its value is ignored; it just
-needs to be present) and a ``SITE_ID`` setting (any integer value will do) in
-order for all the tests to pass.
+info -- the ``DATABASE_NAME`` (required, but will be ignored),
+``DATABASE_ENGINE``, ``DATABASE_USER`` and ``DATABASE_PASSWORD`` settings. You
+will also need a ``ROOT_URLCONF`` setting (its value is ignored; it just needs
+to be present) and a ``SITE_ID`` setting (any integer value will do) in order
+for all the tests to pass.
The unit tests will not touch your existing databases; they create a new
database, called ``django_test_db``, which is deleted when the tests are
diff --git a/docs/i18n.txt b/docs/i18n.txt
index 56e6f7e02c..1d7a0063b2 100644
--- a/docs/i18n.txt
+++ b/docs/i18n.txt
@@ -310,7 +310,7 @@ To create or update a message file, run this command::
...where ``de`` is the language code for the message file you want to create.
The language code, in this case, is in locale format. For example, it's
-``pt_BR`` for Brazilian and ``de_AT`` for Austrian German.
+``pt_BR`` for Brazilian Portugese and ``de_AT`` for Austrian German.
The script should be run from one of three places:
@@ -463,8 +463,8 @@ following this algorithm:
Notes:
* In each of these places, the language preference is expected to be in the
- standard language format, as a string. For example, Brazilian is
- ``pt-br``.
+ standard language format, as a string. For example, Brazilian Portugese
+ is ``pt-br``.
* If a base language is available but the sublanguage specified is not,
Django uses the base language. For example, if a user specifies ``de-at``
(Austrian German) but Django only has ``de`` available, Django uses
diff --git a/docs/model-api.txt b/docs/model-api.txt
index a14c469661..961269aebd 100644
--- a/docs/model-api.txt
+++ b/docs/model-api.txt
@@ -459,7 +459,7 @@ string, not ``NULL``.
``blank``
~~~~~~~~~
-If ``True``, the field is allowed to be blank.
+If ``True``, the field is allowed to be blank. Default is ``False``.
Note that this is different than ``null``. ``null`` is purely
database-related, whereas ``blank`` is validation-related. If a field has
diff --git a/docs/serialization.txt b/docs/serialization.txt
index 8af4da26a8..3216cb061e 100644
--- a/docs/serialization.txt
+++ b/docs/serialization.txt
@@ -109,7 +109,7 @@ serializer, you must pass ``ensure_ascii=False`` as a parameter to the
For example::
- json_serializer = serializers.get_serializer("json")
+ json_serializer = serializers.get_serializer("json")()
json_serializer.serialize(queryset, ensure_ascii=False, stream=response)
Writing custom serializers
diff --git a/docs/sitemaps.txt b/docs/sitemaps.txt
index dafc009859..550f448de1 100644
--- a/docs/sitemaps.txt
+++ b/docs/sitemaps.txt
@@ -2,8 +2,6 @@
The sitemap framework
=====================
-**New in Django development version**.
-
Django comes with a high-level sitemap-generating framework that makes
creating sitemap_ XML files easy.
diff --git a/docs/templates_python.txt b/docs/templates_python.txt
index 1eeede1fe8..853707f58c 100644
--- a/docs/templates_python.txt
+++ b/docs/templates_python.txt
@@ -212,21 +212,24 @@ template tags. If an invalid variable is provided to one of these template
tags, the variable will be interpreted as ``None``. Filters are always
applied to invalid variables within these template tags.
+If ``TEMPLATE_STRING_IF_INVALID`` contains a ``'%s'``, the format marker will
+be replaced with the name of the invalid variable.
+
.. admonition:: For debug purposes only!
- While ``TEMPLATE_STRING_IF_INVALID`` can be a useful debugging tool,
- it is a bad idea to turn it on as a 'development default'.
+ While ``TEMPLATE_STRING_IF_INVALID`` can be a useful debugging tool,
+ it is a bad idea to turn it on as a 'development default'.
- Many templates, including those in the Admin site, rely upon the
- silence of the template system when a non-existent variable is
+ Many templates, including those in the Admin site, rely upon the
+ silence of the template system when a non-existent variable is
encountered. If you assign a value other than ``''`` to
- ``TEMPLATE_STRING_IF_INVALID``, you will experience rendering
+ ``TEMPLATE_STRING_IF_INVALID``, you will experience rendering
problems with these templates and sites.
- Generally, ``TEMPLATE_STRING_IF_INVALID`` should only be enabled
- in order to debug a specific template problem, then cleared
+ Generally, ``TEMPLATE_STRING_IF_INVALID`` should only be enabled
+ in order to debug a specific template problem, then cleared
once debugging is complete.
-
+
Playing with Context objects
----------------------------
@@ -866,7 +869,7 @@ current context, available in the ``render`` method::
try:
actual_date = resolve_variable(self.date_to_be_formatted, context)
return actual_date.strftime(self.format_string)
- except VariableDoesNotExist:
+ except template.VariableDoesNotExist:
return ''
``resolve_variable`` will try to resolve ``blog_entry.date_updated`` and then
diff --git a/docs/testing.txt b/docs/testing.txt
index b3b33e9678..ba13dab67e 100644
--- a/docs/testing.txt
+++ b/docs/testing.txt
@@ -177,6 +177,7 @@ tools that can be used to establish tests and test conditions.
* `Test Client`_
* `TestCase`_
+* `Email services`_
Test Client
-----------
@@ -257,7 +258,7 @@ can be invoked on the ``Client`` instance.
need to manually close the file after it has been provided to the POST.
``login(**credentials)``
- ** New in Django development version **
+ **New in Django development version**
On a production site, it is likely that some views will be protected from
anonymous access through the use of the @login_required decorator, or some
@@ -289,9 +290,9 @@ can be invoked on the ``Client`` instance.
Testing Responses
~~~~~~~~~~~~~~~~~
-The ``get()``, ``post()`` and ``login()`` methods all return a Response
-object. This Response object has the following properties that can be used
-for testing purposes:
+The ``get()`` and ``post()`` methods both return a Response object. This
+Response object has the following properties that can be used for testing
+purposes:
=============== ==========================================================
Property Description
@@ -396,7 +397,7 @@ extra facilities.
Default Test Client
~~~~~~~~~~~~~~~~~~~
-** New in Django development version **
+**New in Django development version**
Every test case in a ``django.test.TestCase`` instance has access to an
instance of a Django `Test Client`_. This Client can be accessed as
@@ -453,9 +454,18 @@ This flush/load procedure is repeated for each test in the test case, so you
can be certain that the outcome of a test will not be affected by
another test, or the order of test execution.
+Emptying the test outbox
+~~~~~~~~~~~~~~~~~~~~~~~~
+**New in Django development version**
+
+At the start of each test case, in addition to installing fixtures,
+Django clears the contents of the test email outbox.
+
+For more detail on email services during tests, see `Email services`_.
+
Assertions
~~~~~~~~~~
-** New in Django development version **
+**New in Django development version**
Normal Python unit tests have a wide range of assertions, such as
``assertTrue`` and ``assertEquals`` that can be used to validate behavior.
@@ -468,30 +478,73 @@ that can be useful in testing the behavior of web sites.
times in the content of the response.
``assertFormError(response, form, field, errors)``
- Assert that a field on a form raised the provided list of errors when
- rendered on the form.
-
- ``form`` is the name the form object was given in the template context.
-
- ``field`` is the name of the field on the form to check. If ``field``
+ Assert that a field on a form raised the provided list of errors when
+ rendered on the form.
+
+ ``form`` is the name the form object was given in the template context.
+
+ ``field`` is the name of the field on the form to check. If ``field``
has a value of ``None``, non-field errors will be checked.
-
- ``errors`` is an error string, or a list of error strings, that are
- expected as a result of form validation.
-
+
+ ``errors`` is an error string, or a list of error strings, that are
+ expected as a result of form validation.
+
``assertTemplateNotUsed(response, template_name)``
- Assert that the template with the given name was *not* used in rendering
+ Assert that the template with the given name was *not* used in rendering
the response.
-
+
``assertRedirects(response, expected_path)``
Assert that the response received redirects the browser to the provided
- path, and that the expected_path can be retrieved.
+ path, and that the expected_path can be retrieved.
``assertTemplateUsed(response, template_name)``
Assert that the template with the given name was used in rendering the
response.
-
-
+
+Email services
+--------------
+**New in Django development version**
+
+If your view makes use of the `Django email services`_, you don't really
+want email to be sent every time you run a test using that view.
+
+When the Django test framework is initialized, it transparently replaces the
+normal `SMTPConnection`_ class with a dummy implementation that redirects all
+email to a dummy outbox. This outbox, stored as ``django.core.mail.outbox``,
+is a simple list of all `EmailMessage`_ instances that have been sent.
+For example, during test conditions, it would be possible to run the following
+code::
+
+ from django.core import mail
+
+ # Send message
+ mail.send_mail('Subject here', 'Here is the message.', 'from@example.com',
+ ['to@example.com'], fail_silently=False)
+
+ # One message has been sent
+ self.assertEqual(len(mail.outbox), 1)
+ # Subject of first message is correct
+ self.assertEqual(mail.outbox[0].subject, 'Subject here')
+
+The ``mail.outbox`` object does not exist under normal execution conditions.
+The outbox is created during test setup, along with the dummy `SMTPConnection`_.
+When the test framework is torn down, the standard `SMTPConnection`_ class
+is restored, and the test outbox is destroyed.
+
+As noted `previously`_, the test outbox is emptied at the start of every
+test in a Django TestCase. To empty the outbox manually, assign the empty list
+to mail.outbox::
+
+ from django.core import mail
+
+ # Empty the test outbox
+ mail.outbox = []
+
+.. _`Django email services`: ../email/
+.. _`SMTPConnection`: ../email/#the-emailmessage-and-smtpconnection-classes
+.. _`EmailMessage`: ../email/#the-emailmessage-and-smtpconnection-classes
+.. _`previously`: #emptying-the-test-outbox
+
Running tests
=============
@@ -516,6 +569,10 @@ database settings will the same as they would be for the project normally.
If you wish to use a name other than the default for the test database,
you can use the ``TEST_DATABASE_NAME`` setting to provide a name.
+The test database is created by the user in the ``DATABASE_USER`` setting.
+This user needs to have sufficient privileges to create a new database on the
+system.
+
Once the test database has been established, Django will run your tests.
If everything goes well, at the end you'll see::
@@ -606,11 +663,12 @@ a number of utility methods in the ``django.test.utils`` module.
``setup_test_environment()``
Performs any global pre-test setup, such as the installing the
- instrumentation of the template rendering system.
+ instrumentation of the template rendering system and setting up
+ the dummy SMTPConnection.
``teardown_test_environment()``
Performs any global post-test teardown, such as removing the instrumentation
- of the template rendering system.
+ of the template rendering system and restoring normal email services.
``create_test_db(verbosity=1, autoclobber=False)``
Creates a new test database, and run ``syncdb`` against it.
diff --git a/tests/modeltests/generic_relations/models.py b/tests/modeltests/generic_relations/models.py
index 2b2f64165f..195f67db8f 100644
--- a/tests/modeltests/generic_relations/models.py
+++ b/tests/modeltests/generic_relations/models.py
@@ -11,6 +11,7 @@ from complete).
from django.db import models
from django.contrib.contenttypes.models import ContentType
+from django.contrib.contenttypes import generic
class TaggedItem(models.Model):
"""A tag on an item."""
@@ -18,7 +19,7 @@ class TaggedItem(models.Model):
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
- content_object = models.GenericForeignKey()
+ content_object = generic.GenericForeignKey()
class Meta:
ordering = ["tag"]
@@ -30,7 +31,7 @@ class Animal(models.Model):
common_name = models.CharField(maxlength=150)
latin_name = models.CharField(maxlength=150)
- tags = models.GenericRelation(TaggedItem)
+ tags = generic.GenericRelation(TaggedItem)
def __str__(self):
return self.common_name
@@ -39,7 +40,7 @@ class Vegetable(models.Model):
name = models.CharField(maxlength=150)
is_yucky = models.BooleanField(default=True)
- tags = models.GenericRelation(TaggedItem)
+ tags = generic.GenericRelation(TaggedItem)
def __str__(self):
return self.name
diff --git a/tests/modeltests/test_client/models.py b/tests/modeltests/test_client/models.py
index cd8dbe37d2..34242ee0d8 100644
--- a/tests/modeltests/test_client/models.py
+++ b/tests/modeltests/test_client/models.py
@@ -20,6 +20,7 @@ rather than the HTML rendered to the end-user.
"""
from django.test import Client, TestCase
+from django.core import mail
class ClientTest(TestCase):
fixtures = ['testdata.json']
@@ -232,3 +233,36 @@ class ClientTest(TestCase):
self.fail('Should raise an error')
except KeyError:
pass
+
+ def test_mail_sending(self):
+ "Test that mail is redirected to a dummy outbox during test setup"
+
+ response = self.client.get('/test_client/mail_sending_view/')
+ self.assertEqual(response.status_code, 200)
+
+ self.assertEqual(len(mail.outbox), 1)
+ self.assertEqual(mail.outbox[0].subject, 'Test message')
+ self.assertEqual(mail.outbox[0].body, 'This is a test email')
+ self.assertEqual(mail.outbox[0].from_email, 'from@example.com')
+ self.assertEqual(mail.outbox[0].to[0], 'first@example.com')
+ self.assertEqual(mail.outbox[0].to[1], 'second@example.com')
+
+ def test_mass_mail_sending(self):
+ "Test that mass mail is redirected to a dummy outbox during test setup"
+
+ response = self.client.get('/test_client/mass_mail_sending_view/')
+ self.assertEqual(response.status_code, 200)
+
+ self.assertEqual(len(mail.outbox), 2)
+ self.assertEqual(mail.outbox[0].subject, 'First Test message')
+ self.assertEqual(mail.outbox[0].body, 'This is the first test email')
+ self.assertEqual(mail.outbox[0].from_email, 'from@example.com')
+ self.assertEqual(mail.outbox[0].to[0], 'first@example.com')
+ self.assertEqual(mail.outbox[0].to[1], 'second@example.com')
+
+ self.assertEqual(mail.outbox[1].subject, 'Second Test message')
+ self.assertEqual(mail.outbox[1].body, 'This is the second test email')
+ self.assertEqual(mail.outbox[1].from_email, 'from@example.com')
+ self.assertEqual(mail.outbox[1].to[0], 'second@example.com')
+ self.assertEqual(mail.outbox[1].to[1], 'third@example.com')
+ \ No newline at end of file
diff --git a/tests/modeltests/test_client/urls.py b/tests/modeltests/test_client/urls.py
index f63c486d01..52fc8fe692 100644
--- a/tests/modeltests/test_client/urls.py
+++ b/tests/modeltests/test_client/urls.py
@@ -11,5 +11,7 @@ urlpatterns = patterns('',
(r'^form_view_with_template/$', views.form_view_with_template),
(r'^login_protected_view/$', views.login_protected_view),
(r'^session_view/$', views.session_view),
- (r'^broken_view/$', views.broken_view)
+ (r'^broken_view/$', views.broken_view),
+ (r'^mail_sending_view/$', views.mail_sending_view),
+ (r'^mass_mail_sending_view/$', views.mass_mail_sending_view)
)
diff --git a/tests/modeltests/test_client/views.py b/tests/modeltests/test_client/views.py
index 3b7a57f4d0..18d6a2dcd9 100644
--- a/tests/modeltests/test_client/views.py
+++ b/tests/modeltests/test_client/views.py
@@ -1,4 +1,5 @@
from xml.dom.minidom import parseString
+from django.core.mail import EmailMessage, SMTPConnection
from django.template import Context, Template
from django.http import HttpResponse, HttpResponseRedirect
from django.contrib.auth.decorators import login_required
@@ -124,3 +125,28 @@ def session_view(request):
def broken_view(request):
"""A view which just raises an exception, simulating a broken view."""
raise KeyError("Oops! Looks like you wrote some bad code.")
+
+def mail_sending_view(request):
+ EmailMessage(
+ "Test message",
+ "This is a test email",
+ "from@example.com",
+ ['first@example.com', 'second@example.com']).send()
+ return HttpResponse("Mail sent")
+
+def mass_mail_sending_view(request):
+ m1 = EmailMessage(
+ 'First Test message',
+ 'This is the first test email',
+ 'from@example.com',
+ ['first@example.com', 'second@example.com'])
+ m2 = EmailMessage(
+ 'Second Test message',
+ 'This is the second test email',
+ 'from@example.com',
+ ['second@example.com', 'third@example.com'])
+
+ c = SMTPConnection()
+ c.send_messages([m1,m2])
+
+ return HttpResponse("Mail sent")
diff --git a/tests/regressiontests/cache/tests.py b/tests/regressiontests/cache/tests.py
index cf58ab321a..9dc7161c03 100644
--- a/tests/regressiontests/cache/tests.py
+++ b/tests/regressiontests/cache/tests.py
@@ -46,6 +46,11 @@ class Cache(unittest.TestCase):
self.assertEqual(cache.has_key("hello"), True)
self.assertEqual(cache.has_key("goodbye"), False)
+ def test_in(self):
+ cache.set("hello", "goodbye")
+ self.assertEqual("hello" in cache, True)
+ self.assertEqual("goodbye" in cache, False)
+
def test_data_types(self):
# test data types
stuff = {
diff --git a/tests/regressiontests/serializers_regress/models.py b/tests/regressiontests/serializers_regress/models.py
index d3415ac1b9..c287b6e0d6 100644
--- a/tests/regressiontests/serializers_regress/models.py
+++ b/tests/regressiontests/serializers_regress/models.py
@@ -6,6 +6,7 @@ This class sets up a model for each model field type
"""
from django.db import models
+from django.contrib.contenttypes import generic
from django.contrib.contenttypes.models import ContentType
# The following classes are for testing basic data
@@ -80,7 +81,7 @@ class Tag(models.Model):
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
- content_object = models.GenericForeignKey()
+ content_object = generic.GenericForeignKey()
class Meta:
ordering = ["data"]
@@ -88,7 +89,7 @@ class Tag(models.Model):
class GenericData(models.Model):
data = models.CharField(maxlength=30)
- tags = models.GenericRelation(Tag)
+ tags = generic.GenericRelation(Tag)
# The following test classes are all for validation
# of related objects; in particular, forward, backward,
diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py
index 9be8f022f6..a5ed2dbf56 100644
--- a/tests/regressiontests/templates/tests.py
+++ b/tests/regressiontests/templates/tests.py
@@ -586,6 +586,8 @@ class Templates(unittest.TestCase):
'invalidstr03': ('{% for v in var %}({{ v }}){% endfor %}', {}, ''),
'invalidstr04': ('{% if var %}Yes{% else %}No{% endif %}', {}, 'No'),
'invalidstr04': ('{% if var|default:"Foo" %}Yes{% else %}No{% endif %}', {}, 'Yes'),
+ 'invalidstr05': ('{{ var }}', {}, ('', 'INVALID %s', 'var')),
+ 'invalidstr06': ('{{ var.prop }}', {'var': {}}, ('', 'INVALID %s', 'var.prop')),
### MULTILINE #############################################################
@@ -737,6 +739,7 @@ class Templates(unittest.TestCase):
# Set TEMPLATE_STRING_IF_INVALID to a known string
old_invalid = settings.TEMPLATE_STRING_IF_INVALID
+ expected_invalid_str = 'INVALID'
for name, vals in tests:
install()
@@ -744,6 +747,10 @@ class Templates(unittest.TestCase):
if isinstance(vals[2], tuple):
normal_string_result = vals[2][0]
invalid_string_result = vals[2][1]
+ if '%s' in invalid_string_result:
+ expected_invalid_str = 'INVALID %s'
+ invalid_string_result = invalid_string_result % vals[2][2]
+ template.invalid_var_format_string = True
else:
normal_string_result = vals[2]
invalid_string_result = vals[2]
@@ -754,7 +761,7 @@ class Templates(unittest.TestCase):
activate('en-us')
for invalid_str, result in [('', normal_string_result),
- ('INVALID', invalid_string_result)]:
+ (expected_invalid_str, invalid_string_result)]:
settings.TEMPLATE_STRING_IF_INVALID = invalid_str
try:
output = loader.get_template(name).render(template.Context(vals[1]))
@@ -768,6 +775,10 @@ class Templates(unittest.TestCase):
if 'LANGUAGE_CODE' in vals[1]:
deactivate()
+ if template.invalid_var_format_string:
+ expected_invalid_str = 'INVALID'
+ template.invalid_var_format_string = False
+
loader.template_source_loaders = old_template_loaders
deactivate()
settings.TEMPLATE_DEBUG = old_td