1
0
mirror of https://github.com/django/django.git synced 2025-07-04 09:49:12 +00:00

unicode: Merged changes from trunk up to [5182].

git-svn-id: http://code.djangoproject.com/svn/django/branches/unicode@5185 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Malcolm Tredinnick 2007-05-11 07:28:33 +00:00
parent ce4722a52a
commit a7a756e27e
53 changed files with 6926 additions and 3900 deletions

View File

@ -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,8 @@ 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>
Michal Chruszcz <troll@pld-linux.org>
Ian Clelland <clelland@gmail.com>
crankycoder@gmail.com
Matt Croydon <http://www.postneo.com/>
@ -145,7 +149,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

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -8,26 +8,14 @@ msgid ""
msgstr ""
"Project-Id-Version: djangojs 1.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2006-03-30 13:28+0200\n"
"PO-Revision-Date: 2006-03-30 13:35+0200\n"
"POT-Creation-Date: 2007-05-06 13:08+0300\n"
"PO-Revision-Date: 2007-05-06 13:08+0300\n"
"Last-Translator: Meir Kriheli <meir@mksoft.co.il>\n"
"Language-Team: Hebrew\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit"
#: contrib/admin/media/js/dateparse.js:32
#: contrib/admin/media/js/calendar.js:24
msgid ""
"January February March April May June July August September October November "
"December"
msgstr ""
"ינואר פברואר מרץ אפריל מאי יוני יולי אוגוסט ספטמבר אוקטובר נובמבר דצמבר"
#: contrib/admin/media/js/dateparse.js:33
msgid "Sunday Monday Tuesday Wednesday Thursday Friday Saturday"
msgstr "ראשון שני שלישי רביעי חמישי שישי שבת"
#: contrib/admin/media/js/SelectFilter2.js:33
#, perl-format
msgid "Available %s"
@ -58,54 +46,75 @@ msgstr "יש לסמן את ההרשאות המבוקשות וללחוץ על "
msgid "Clear all"
msgstr "איפוס הכל"
#: contrib/admin/media/js/calendar.js:24
#: contrib/admin/media/js/dateparse.js:32
msgid ""
"January February March April May June July August September October November "
"December"
msgstr ""
"ינואר פברואר מרץ אפריל מאי יוני יולי אוגוסט ספטמבר אוקטובר נובמבר דצמבר"
#: contrib/admin/media/js/calendar.js:25
msgid "S M T W T F S"
msgstr "ר ש ש ר ח ש ש"
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:45
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:80
#: contrib/admin/media/js/dateparse.js:33
msgid "Sunday Monday Tuesday Wednesday Thursday Friday Saturday"
msgstr "ראשון שני שלישי רביעי חמישי שישי שבת"
#: contrib/admin/media/js/admin/CollapsedFieldsets.js:34
#: contrib/admin/media/js/admin/CollapsedFieldsets.js:72
msgid "Show"
msgstr "הצג"
#: contrib/admin/media/js/admin/CollapsedFieldsets.js:63
msgid "Hide"
msgstr "הסתר"
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:47
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:81
msgid "Now"
msgstr "כעת"
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:48
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:51
msgid "Clock"
msgstr "שעון"
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:77
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:78
msgid "Choose a time"
msgstr "בחירת שעה"
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:81
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:82
msgid "Midnight"
msgstr "חצות"
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:82
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:83
msgid "6 a.m."
msgstr "6 בבוקר"
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:83
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:84
msgid "Noon"
msgstr "צהריים"
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:87
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:168
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:88
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:183
msgid "Cancel"
msgstr "ביטול"
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:111
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:162
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:128
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:177
msgid "Today"
msgstr "היום"
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:114
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:132
msgid "Calendar"
msgstr "לוח שנה"
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:160
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:175
msgid "Yesterday"
msgstr "אתמול"
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:164
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:179
msgid "Tomorrow"
msgstr "מחר"

File diff suppressed because it is too large Load Diff

View File

@ -10,7 +10,7 @@ msgstr ""
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2007-02-15 10:46+1100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Last-Translator: Gatis Tomsons <gatis.tomsons@gmail.com>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
@ -19,64 +19,65 @@ msgstr ""
#: contrib/admin/media/js/SelectFilter2.js:33
#, perl-format
msgid "Available %s"
msgstr ""
msgstr "Pieejams %s"
#: contrib/admin/media/js/SelectFilter2.js:41
msgid "Choose all"
msgstr ""
msgstr "Izvēlēties visu"
#: contrib/admin/media/js/SelectFilter2.js:46
msgid "Add"
msgstr ""
msgstr "Pievienot"
#: contrib/admin/media/js/SelectFilter2.js:48
msgid "Remove"
msgstr ""
msgstr "Izņemt"
#: contrib/admin/media/js/SelectFilter2.js:53
#, perl-format
msgid "Chosen %s"
msgstr ""
msgstr "Izvēlies %s"
#: contrib/admin/media/js/SelectFilter2.js:54
msgid "Select your choice(s) and click "
msgstr ""
msgstr "Izvēlies un klikšķini"
#: contrib/admin/media/js/SelectFilter2.js:59
msgid "Clear all"
msgstr ""
msgstr "Attīrīt visu"
#: contrib/admin/media/js/dateparse.js:32
#: contrib/admin/media/js/calendar.js:24
msgid ""
"January February March April May June July August September October November "
"December"
msgstr ""
msgstr "Janvāris Februāris Marts Aprīlis Maijs Jūnijs Jūlijs Augusts Septembris Oktobris Novembris"
"Decembris"
#: contrib/admin/media/js/dateparse.js:33
msgid "Sunday Monday Tuesday Wednesday Thursday Friday Saturday"
msgstr ""
msgstr "Svētdiena Pirmdiena Otrdiena Trešdiena Ceturtdiena Piektdiena Sestdiena"
#: contrib/admin/media/js/calendar.js:25
msgid "S M T W T F S"
msgstr ""
msgstr "S M T W T F S"
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:47
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:81
msgid "Now"
msgstr ""
msgstr "Tagad"
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:51
msgid "Clock"
msgstr ""
msgstr "Pulkstens"
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:78
msgid "Choose a time"
msgstr ""
msgstr "Izvēlieties laiku"
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:82
msgid "Midnight"
msgstr ""
msgstr "Pusnakts"
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:83
msgid "6 a.m."
@ -84,35 +85,35 @@ msgstr ""
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:84
msgid "Noon"
msgstr ""
msgstr "Pusdienas laiks"
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:88
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:183
msgid "Cancel"
msgstr ""
msgstr "Atcelt"
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:128
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:177
msgid "Today"
msgstr ""
msgstr "Šodien"
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:132
msgid "Calendar"
msgstr ""
msgstr "Kalendārs"
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:175
msgid "Yesterday"
msgstr ""
msgstr "Vakar"
#: contrib/admin/media/js/admin/DateTimeShortcuts.js:179
msgid "Tomorrow"
msgstr ""
msgstr "Rīt"
#: contrib/admin/media/js/admin/CollapsedFieldsets.js:34
#: contrib/admin/media/js/admin/CollapsedFieldsets.js:72
msgid "Show"
msgstr ""
msgstr "Parādīt"
#: contrib/admin/media/js/admin/CollapsedFieldsets.js:63
msgid "Hide"
msgstr ""
msgstr "Slēpt"

File diff suppressed because it is too large Load Diff

View File

@ -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:

View File

@ -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

View File

@ -241,14 +241,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):
table_output = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + \
style.SQL_TABLE(backend.quote_name(f.m2m_db_table())) + ' (']
table_output.append(' %s %s %s,' % \

View File

@ -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

View File

@ -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.

View File

@ -1,9 +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.contrib.contenttypes import generic
import operator
import re
@ -948,7 +948,7 @@ def lookup_inner(path, lookup_type, value, opts, table, column):
field_choices(current_opts.get_all_related_many_to_many_objects(), True) + \
field_choices(current_opts.get_all_related_objects(), True) + \
field_choices(current_opts.fields, False)
raise TypeError, "Cannot resolve keyword '%s' into field, choices are: %s" % (name, ", ".join(choices))
raise TypeError, "Cannot resolve keyword '%s' into field. Choices are: %s" % (name, ", ".join(choices))
# Check whether an intermediate join is required between current_table
# and new_table.
@ -1041,7 +1041,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()),
@ -1049,7 +1049,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]

View File

@ -100,6 +100,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:
@ -583,6 +587,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

View File

@ -1,12 +1,16 @@
import datetime
import sys
from cStringIO import StringIO
from urlparse import urlparse
from django.conf import settings
from django.contrib.auth import authenticate, login
from django.contrib.sessions.models import Session
from django.contrib.sessions.middleware import SessionWrapper
from django.core.handlers.base import BaseHandler
from django.core.handlers.wsgi import WSGIRequest
from django.core.signals import got_request_exception
from django.dispatch import dispatcher
from django.http import urlencode, SimpleCookie
from django.http import urlencode, SimpleCookie, HttpRequest
from django.test import signals
from django.utils.functional import curry
@ -113,7 +117,6 @@ class Client:
self.handler = ClientHandler()
self.defaults = defaults
self.cookies = SimpleCookie()
self.session = {}
self.exc_info = None
def store_exc_info(self, *args, **kwargs):
@ -123,6 +126,15 @@ class Client:
"""
self.exc_info = sys.exc_info()
def _session(self):
"Obtain the current session variables"
if 'django.contrib.sessions' in settings.INSTALLED_APPS:
cookie = self.cookies.get(settings.SESSION_COOKIE_NAME, None)
if cookie:
return SessionWrapper(cookie.value)
return {}
session = property(_session)
def request(self, **request):
"""
The master request method. Composes the environment dictionary
@ -171,16 +183,10 @@ class Client:
if self.exc_info:
raise self.exc_info[1], None, self.exc_info[2]
# Update persistent cookie and session data
# Update persistent cookie data
if response.cookies:
self.cookies.update(response.cookies)
if 'django.contrib.sessions' in settings.INSTALLED_APPS:
from django.contrib.sessions.middleware import SessionWrapper
cookie = self.cookies.get(settings.SESSION_COOKIE_NAME, None)
if cookie:
self.session = SessionWrapper(cookie.value)
return response
def get(self, path, data={}, **extra):
@ -215,42 +221,34 @@ class Client:
return self.request(**r)
def login(self, path, username, password, **extra):
def login(self, **credentials):
"""Set the Client to appear as if it has sucessfully logged into a site.
Returns True if login is possible; False if the provided credentials
are incorrect, or if the Sessions framework is not available.
"""
A specialized sequence of GET and POST to log into a view that
is protected by a @login_required access decorator.
user = authenticate(**credentials)
if user and 'django.contrib.sessions' in settings.INSTALLED_APPS:
obj = Session.objects.get_new_session_object()
path should be the URL of the page that is login protected.
# Create a fake request to store login details
request = HttpRequest()
request.session = SessionWrapper(obj.session_key)
login(request, user)
Returns the response from GETting the requested URL after
login is complete. Returns False if login process failed.
"""
# First, GET the page that is login protected.
# This page will redirect to the login page.
response = self.get(path)
if response.status_code != 302:
# Set the cookie to represent the session
self.cookies[settings.SESSION_COOKIE_NAME] = obj.session_key
self.cookies[settings.SESSION_COOKIE_NAME]['max-age'] = None
self.cookies[settings.SESSION_COOKIE_NAME]['path'] = '/'
self.cookies[settings.SESSION_COOKIE_NAME]['domain'] = settings.SESSION_COOKIE_DOMAIN
self.cookies[settings.SESSION_COOKIE_NAME]['secure'] = settings.SESSION_COOKIE_SECURE or None
self.cookies[settings.SESSION_COOKIE_NAME]['expires'] = None
# Set the session values
Session.objects.save(obj.session_key, request.session._session,
datetime.datetime.now() + datetime.timedelta(seconds=settings.SESSION_COOKIE_AGE))
return True
else:
return False
_, _, login_path, _, data, _= urlparse(response['Location'])
next = data.split('=')[1]
# Second, GET the login page; required to set up cookies
response = self.get(login_path, **extra)
if response.status_code != 200:
return False
# Last, POST the login data.
form_data = {
'username': username,
'password': password,
'next' : next,
}
response = self.post(login_path, data=form_data, **extra)
# Login page should 302 redirect to the originally requested page
if (response.status_code != 302 or
urlparse(response['Location'])[2] != path):
return False
# Since we are logged in, request the actual page again
return self.get(path)

View File

@ -49,9 +49,12 @@ def build_suite(app_module):
pass
else:
# The module exists, so there must be an import error in the
# test module itself. We don't need the module; close the file
# handle returned by find_module.
mod[0].close()
# test module itself. We don't need the module; so if the
# module was a single file module (i.e., tests.py), close the file
# handle returned by find_module. Otherwise, the test module
# is a directory, and there is nothing to close.
if mod[0]:
mod[0].close()
raise
return suite

View File

@ -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,48 +33,116 @@ 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):
def assertRedirects(self, response, expected_path, status_code=302, target_status_code=200):
"""Assert that a response redirected to a specific URL, and that the
redirect URL can be loaded.
"""
self.assertEqual(response.status_code, 302,
"Response didn't redirect: Reponse code was %d" % response.status_code)
self.assertEqual(response.status_code, status_code,
"Response didn't redirect: Reponse code was %d (expected %d)" %
(response.status_code, status_code))
scheme, netloc, path, params, query, fragment = urlparse(response['Location'])
self.assertEqual(path, expected_path,
"Response redirected to '%s', expected '%s'" % (path, expected_path))
redirect_response = self.client.get(path)
self.assertEqual(redirect_response.status_code, 200,
"Couldn't retrieve redirection page '%s'" % path)
self.assertEqual(redirect_response.status_code, target_status_code,
"Couldn't retrieve redirection page '%s': response code was %d (expected %d)" %
(path, response.status_code, status_code))
def assertContains(self, response, text, count=1):
def assertContains(self, response, text, count=1, status_code=200):
"""Assert that a response indicates that a page was retreived successfully,
(i.e., the HTTP status code was 200), and that ``text`` occurs ``count``
(i.e., the HTTP status code was as expected), and that ``text`` occurs ``count``
times in the content of the response.
"""
self.assertEqual(response.status_code, 200,
"Couldn't retrieve page'")
self.assertEqual(response.status_code, status_code,
"Couldn't retrieve page: Response code was %d (expected %d)'" %
(response.status_code, status_code))
real_count = response.content.count(text)
self.assertEqual(real_count, count,
"Could only find %d of %d instances of '%s' in response" % (real_count, count, text))
"Found %d instances of '%s' in response (expected %d)" % (real_count, text, count))
def assertFormError(self, response, form, field, errors):
"Assert that a form used to render the response has a specific field error"
if not response.context:
self.fail('Response did not use any contexts to render the response')
# If there is a single context, put it into a list to simplify processing
if not isinstance(response.context, list):
contexts = [response.context]
else:
contexts = response.context
# If a single error string is provided, make it a list to simplify processing
if not isinstance(errors, list):
errors = [errors]
# Search all contexts for the error.
found_form = False
for i,context in enumerate(contexts):
if form in context:
found_form = True
for err in errors:
if field:
if field in context[form].errors:
self.assertTrue(err in context[form].errors[field],
"The field '%s' on form '%s' in context %d does not contain the error '%s' (actual errors: %s)" %
(field, form, i, err, list(context[form].errors[field])))
elif field in context[form].fields:
self.fail("The field '%s' on form '%s' in context %d contains no errors" %
(field, form, i))
else:
self.fail("The form '%s' in context %d does not contain the field '%s'" % (form, i, field))
else:
self.assertTrue(err in context[form].non_field_errors(),
"The form '%s' in context %d does not contain the non-field error '%s' (actual errors: %s)" %
(form, i, err, list(context[form].non_field_errors())))
if not found_form:
self.fail("The form '%s' was not used to render the response" % form)
def assertTemplateUsed(self, response, template_name):
"Assert that the template with the provided name was used in rendering the response"
if isinstance(response.template, list):
template_names = [t.name for t in response.template]
self.assertTrue(template_name in template_names,
u"Template '%s' was not one of the templates used to render the response. Templates used: %s" %
(template_name, u', '.join(template_names)))
elif response.template:
self.assertEqual(template_name, response.template.name,
u"Template '%s' was not used to render the response. Actual template was '%s'" %
(template_name, response.template.name))
else:
self.fail('No templates used to render the response')
def assertTemplateNotUsed(self, response, template_name):
"Assert that the template with the provided name was NOT used in rendering the response"
if isinstance(response.template, list):
self.assertFalse(template_name in [t.name for t in response.template],
u"Template '%s' was used unexpectedly in rendering the response" % template_name)
elif response.template:
self.assertNotEqual(template_name, response.template.name,
u"Template '%s' was used unexpectedly in rendering the response" % template_name)

View File

@ -1,7 +1,7 @@
import sys, time
from django.conf import settings
from django.db import connection, transaction, backend
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"):

View File

@ -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

View File

@ -20,14 +20,14 @@ In two lines::
send_mail('Subject here', 'Here is the message.', 'from@example.com',
['to@example.com'], fail_silently=False)
Mail will be sent using the SMTP host and port specified in the `EMAIL_HOST`_
and `EMAIL_PORT`_ settings. The `EMAIL_HOST_USER`_ and `EMAIL_HOST_PASSWORD`_
settings, if set, will be used to authenticate to the SMTP server and the
`EMAIL_USE_TLS`_ settings will control whether a secure connection is used.
Mail is sent using the SMTP host and port specified in the `EMAIL_HOST`_ and
`EMAIL_PORT`_ settings. The `EMAIL_HOST_USER`_ and `EMAIL_HOST_PASSWORD`_
settings, if set, are used to authenticate to the SMTP server, and the
`EMAIL_USE_TLS`_ setting controls whether a secure connection is used.
.. note::
The character set of email sent with ``django.core.mail`` will be set to
The character set of e-mail sent with ``django.core.mail`` will be set to
the value of your `DEFAULT_CHARSET setting`_.
.. _DEFAULT_CHARSET setting: ../settings/#default-charset
@ -37,7 +37,6 @@ settings, if set, will be used to authenticate to the SMTP server and the
.. _EMAIL_HOST_PASSWORD: ../settings/#email-host-password
.. _EMAIL_USE_TLS: ../settings/#email-use-tls
send_mail()
===========
@ -193,57 +192,64 @@ The EmailMessage and SMTPConnection classes
Django's ``send_mail()`` and ``send_mass_mail()`` functions are actually thin
wrappers that make use of the ``EmailMessage`` and ``SMTPConnection`` classes
in ``django.mail``. If you ever need to customize the way Django sends email,
you can subclass these two classes to suit your needs.
in ``django.core.mail``. If you ever need to customize the way Django sends
e-mail, you can subclass these two classes to suit your needs.
.. note::
Not all features of the ``EmailMessage`` class are available through the
``send_mail()`` and related wrapper functions. If you wish to use advanced
features such as including BCC recipients or multi-part email, you will
need to create ``EmailMessage`` instances directly.
features, such as BCC'ed recipients or multi-part e-mail, you'll need to
create ``EmailMessage`` instances directly.
In general, ``EmailMessage`` is responsible for creating the email message
In general, ``EmailMessage`` is responsible for creating the e-mail message
itself. ``SMTPConnection`` is responsible for the network connection side of
the operation. This means you can reuse the same connection (an
``SMTPConnection`` instance) for multiple messages.
The ``EmailMessage`` class is initialised as follows::
The ``EmailMessage`` class is initialized as follows::
email = EmailMessage(subject, body, from_email, to, bcc, connection)
All of these parameters are optional. If ``from_email`` is omitted, the value
from ``settings.DEFAULT_FROM_EMAIL`` is used. Both the ``to`` and ``bcc``
parameters are lists of addresses.
parameters are lists of addresses, as strings.
The class has the following methods that you can use:
For example::
* ``send()`` sends the message, using either the connection that is specified
in the ``connection`` attribute, or creating a new connection if none already
exists.
* ``message()`` constructs a ``django.core.mail.SafeMIMEText`` object (a
sub-class of Python's ``email.MIMEText.MIMEText`` class) holding the
message to be sent. If you ever need to extend the `EmailMessage` class,
you will probably want to override this method to put the content you wish
into the MIME object.
* ``recipients()`` returns a lists of all the recipients of the message,
whether they are recorded in the ``to`` or ``bcc`` attributes. This is
another method you need to possibly override when sub-classing, since the
SMTP server needs to be told the full list of recipients when the message
is sent. If you add another way to specify recipients in your class, they
need to be returned from this method as well.
email = EmailMessage('Hello', 'Body goes here', 'from@example.com',
['to1@example.com', 'to2@example.com'],
['bcc@example.com'])
The class has the following methods:
* ``send()`` sends the message, using either the connection that is
specified in the ``connection`` attribute, or creating a new connection
if none already exists.
* ``message()`` constructs a ``django.core.mail.SafeMIMEText`` object (a
sub-class of Python's ``email.MIMEText.MIMEText`` class) holding the
message to be sent. If you ever need to extend the `EmailMessage` class,
you'll probably want to override this method to put the content you wish
into the MIME object.
* ``recipients()`` returns a list of all the recipients of the message,
whether they're recorded in the ``to`` or ``bcc`` attributes. This is
another method you might need to override when sub-classing, because the
SMTP server needs to be told the full list of recipients when the message
is sent. If you add another way to specify recipients in your class, they
need to be returned from this method as well.
The ``SMTPConnection`` class is initialized with the host, port, username and
password for the SMTP server. If you don't specify one or more of those
options, they are read from your settings file.
If you are sending lots of messages at once, the ``send_messages()`` method of
the ``SMTPConnection`` class will be useful. It takes a list of ``EmailMessage``
instances (or sub-classes) and sends them over a single connection. For
example, if you have a function called ``get_notification_email()`` that returns a
list of ``EmailMessage`` objects representing some periodic email you wish to
If you're sending lots of messages at once, the ``send_messages()`` method of
the ``SMTPConnection`` class is useful. It takes a list of ``EmailMessage``
instances (or subclasses) and sends them over a single connection. For example,
if you have a function called ``get_notification_email()`` that returns a
list of ``EmailMessage`` objects representing some periodic e-mail you wish to
send out, you could send this with::
connection = SMTPConnection() # Use default settings for connection
messages = get_notification_email()
connection.send_messages(messages)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -212,6 +212,9 @@ 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,
@ -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

View File

@ -2,19 +2,29 @@
Testing Django applications
===========================
Automated testing is an extremely useful weapon in the bug-killing arsenal
of the modern developer. When initially writing code, a test suite can be
used to validate that code behaves as expected. When refactoring or
modifying code, tests serve as a guide to ensure that behavior hasn't
changed unexpectedly as a result of the refactor.
Automated testing is an extremely useful bug-killing tool for the modern
Web developer. You can use a collection of tests -- a **test suite** -- to
to solve, or avoid, a number of problems:
Testing a web application is a complex task, as there are many
components of a web application that must be validated and tested. To
help you test your application, Django provides a test execution
framework, and range of utilities that can be used to simulate and
inspect various facets of a web application.
* When you're writing new code, you can use tests to validate your code
works as expected.
This testing framework is currently under development, and may change
* When you're refactoring or modifying old code, you can use tests to
ensure your changes haven't affected your application's behavior
unexpectedly.
Testing a Web application is a complex task, because a Web application is made
of several layers of logic -- from HTTP-level request handling, to form
validation and processing, to template rendering. With Django's test-execution
framework and assorted utilities, you can simulate requests, insert test data,
inspect your application's output and generally verify your code is doing what
it should be doing.
The best part is, it's really easy.
.. admonition:: Note
This testing framework is currently under development. It may change
slightly before the next official Django release.
(That's *no* excuse not to write tests, though!)
@ -167,6 +177,7 @@ tools that can be used to establish tests and test conditions.
* `Test Client`_
* `TestCase`_
* `Email services`_
Test Client
-----------
@ -246,29 +257,42 @@ can be invoked on the ``Client`` instance.
file name), and `attachment_file` (containing the file data). Note that you
need to manually close the file after it has been provided to the POST.
``login(path, username, password)``
In a production site, it is likely that some views will be protected with
the @login_required decorator provided by ``django.contrib.auth``. Interacting
with a URL that has been login protected is a slightly complex operation,
so the Test Client provides a simple method to automate the login process. A
call to ``login()`` stimulates the series of GET and POST calls required
to log a user into a @login_required protected view.
``login(**credentials)``
**New in Django development version**
If login is possible, the final return value of ``login()`` is the response
that is generated by issuing a GET request on the protected URL. If login
is not possible, ``login()`` returns False.
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
other login checking mechanism. The ``login()`` method can be used to
simulate the effect of a user logging into the site. As a result of calling
this method, the Client will have all the cookies and session data required
to pass any login-based tests that may form part of a view.
In most cases, the ``credentials`` required by this method are the username
and password of the user that wants to log in, provided as keyword
arguments::
c = Client()
c.login(username='fred', password='secret')
# Now you can access a login protected view
If you are using a different authentication backend, this method may
require different credentials.
``login()`` returns ``True`` if it the credentials were accepted and login
was successful.
Note that since the test suite will be executed using the test database,
which contains no users by default. As a result, logins for your production
site will not work. You will need to create users as part of the test suite
to be able to test logins to your application.
which contains no users by default. As a result, logins that are valid
on your production site will not work under test conditions. You will
need to create users as part of the test suite (either manually, or
using a test fixture).
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
@ -373,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
@ -430,24 +454,98 @@ 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.
``django.TestCase`` adds to these, providing some assertions
that can be useful in testing the behavior of web sites.
``assertRedirects(response, expected_path)``
Assert that the response received redirects the browser to the provided
path, and that the expected_path can be retrieved.
``assertContains(response, text, count=1)``
Assert that a response indicates that a page was retreived successfully,
(i.e., the HTTP status code was 200), and that ``text`` occurs ``count``
``assertContains(response, text, count=1, status_code=200)``
Assert that a response indicates that a page could be retrieved and
produced the nominated status code, and that ``text`` occurs ``count``
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``
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.
``assertTemplateNotUsed(response, template_name)``
Assert that the template with the given name was *not* used in rendering
the response.
``assertRedirects(response, expected_path, status_code=302, target_status_code=200)``
Assert that the response received produced the nominated status code,
redirects the browser to the provided path, and that retrieving the provided
path yields a response with the target status code.
``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
=============
@ -472,6 +570,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::
@ -562,11 +664,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.

View File

@ -71,7 +71,7 @@ __test__ = {'API_TESTS':"""
>>> Author.objects.filter(firstname__exact='John')
Traceback (most recent call last):
...
TypeError: Cannot resolve keyword 'firstname' into field, choices are: article, id, first_name, last_name
TypeError: Cannot resolve keyword 'firstname' into field. Choices are: article, id, first_name, last_name
>>> a = Author.objects.get(last_name__exact='Smith')
>>> a.first_name

View File

@ -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

View File

@ -223,11 +223,11 @@ DoesNotExist: Article matching query does not exist.
>>> Article.objects.filter(pub_date_year='2005').count()
Traceback (most recent call last):
...
TypeError: Cannot resolve keyword 'pub_date_year' into field, choices are: id, headline, pub_date
TypeError: Cannot resolve keyword 'pub_date_year' into field. Choices are: id, headline, pub_date
>>> Article.objects.filter(headline__starts='Article')
Traceback (most recent call last):
...
TypeError: Cannot resolve keyword 'headline__starts' into field, choices are: id, headline, pub_date
TypeError: Cannot resolve keyword 'headline__starts' into field. Choices are: id, headline, pub_date
"""}

View File

@ -174,13 +174,13 @@ False
>>> Article.objects.filter(reporter_id__exact=1)
Traceback (most recent call last):
...
TypeError: Cannot resolve keyword 'reporter_id' into field, choices are: id, headline, pub_date, reporter
TypeError: Cannot resolve keyword 'reporter_id' into field. Choices are: id, headline, pub_date, reporter
# You need to specify a comparison clause
>>> Article.objects.filter(reporter_id=1)
Traceback (most recent call last):
...
TypeError: Cannot resolve keyword 'reporter_id' into field, choices are: id, headline, pub_date, reporter
TypeError: Cannot resolve keyword 'reporter_id' into field. Choices are: id, headline, pub_date, reporter
# You can also instantiate an Article by passing
# the Reporter's ID instead of a Reporter object.

View File

@ -55,5 +55,5 @@ __test__ = {'API_TESTS':"""
>>> Poll.objects.get(choice__name__exact="This is the answer")
Traceback (most recent call last):
...
TypeError: Cannot resolve keyword 'choice' into field, choices are: poll_choice, related_choice, id, question, creator
TypeError: Cannot resolve keyword 'choice' into field. Choices are: poll_choice, related_choice, id, question, creator
"""}

View File

@ -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']
@ -40,6 +41,8 @@ class ClientTest(TestCase):
# Check some response details
self.assertEqual(response.status_code, 200)
self.assertEqual(response.template.name, 'Empty GET Template')
self.assertTemplateUsed(response, 'Empty GET Template')
self.assertTemplateNotUsed(response, 'Empty POST Template')
def test_empty_post(self):
"POST an empty dictionary to a view"
@ -48,6 +51,8 @@ class ClientTest(TestCase):
# Check some response details
self.assertEqual(response.status_code, 200)
self.assertEqual(response.template.name, 'Empty POST Template')
self.assertTemplateNotUsed(response, 'Empty GET Template')
self.assertTemplateUsed(response, 'Empty POST Template')
def test_post(self):
"POST some data to a view"
@ -63,6 +68,7 @@ class ClientTest(TestCase):
self.failUnless('Data received' in response.content)
def test_raw_post(self):
"POST raw data (with a content type) to a view"
test_doc = """<?xml version="1.0" encoding="utf-8"?><library><book><title>Blink</title><author>Malcolm Gladwell</author></book></library>"""
response = self.client.post("/test_client/raw_post_view/", test_doc,
content_type="text/xml")
@ -77,6 +83,28 @@ class ClientTest(TestCase):
# Check that the response was a 302 (redirect)
self.assertRedirects(response, '/test_client/get_view/')
def test_permanent_redirect(self):
"GET a URL that redirects permanently elsewhere"
response = self.client.get('/test_client/permanent_redirect_view/')
# Check that the response was a 301 (permanent redirect)
self.assertRedirects(response, '/test_client/get_view/', status_code=301)
def test_redirect_to_strange_location(self):
"GET a URL that redirects to a non-200 page"
response = self.client.get('/test_client/double_redirect_view/')
# Check that the response was a 302, and that
# the attempt to get the redirection location returned 301 when retrieved
self.assertRedirects(response, '/test_client/permanent_redirect_view/', target_status_code=301)
def test_notfound_response(self):
"GET a URL that responds as '404:Not Found'"
response = self.client.get('/test_client/bad_view/')
# Check that the response was a 404, and that the content contains MAGIC
self.assertContains(response, 'MAGIC', status_code=404)
def test_valid_form(self):
"POST valid data to a form"
post_data = {
@ -88,7 +116,7 @@ class ClientTest(TestCase):
}
response = self.client.post('/test_client/form_view/', post_data)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.template.name, "Valid POST Template")
self.assertTemplateUsed(response, "Valid POST Template")
def test_incomplete_data_form(self):
"POST incomplete data to a form"
@ -97,8 +125,13 @@ class ClientTest(TestCase):
'value': 37
}
response = self.client.post('/test_client/form_view/', post_data)
self.assertContains(response, 'This field is required', 3)
self.assertEqual(response.template.name, "Invalid POST Template")
self.assertContains(response, 'This field is required.', 3)
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, "Invalid POST Template")
self.assertFormError(response, 'form', 'email', 'This field is required.')
self.assertFormError(response, 'form', 'single', 'This field is required.')
self.assertFormError(response, 'form', 'multi', 'This field is required.')
def test_form_error(self):
"POST erroneous data to a form"
@ -111,7 +144,57 @@ class ClientTest(TestCase):
}
response = self.client.post('/test_client/form_view/', post_data)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.template.name, "Invalid POST Template")
self.assertTemplateUsed(response, "Invalid POST Template")
self.assertFormError(response, 'form', 'email', 'Enter a valid e-mail address.')
def test_valid_form_with_template(self):
"POST valid data to a form using multiple templates"
post_data = {
'text': 'Hello World',
'email': 'foo@example.com',
'value': 37,
'single': 'b',
'multi': ('b','c','e')
}
response = self.client.post('/test_client/form_view_with_template/', post_data)
self.assertContains(response, 'POST data OK')
self.assertTemplateUsed(response, "form_view.html")
self.assertTemplateUsed(response, 'base.html')
self.assertTemplateNotUsed(response, "Valid POST Template")
def test_incomplete_data_form_with_template(self):
"POST incomplete data to a form using multiple templates"
post_data = {
'text': 'Hello World',
'value': 37
}
response = self.client.post('/test_client/form_view_with_template/', post_data)
self.assertContains(response, 'POST data has errors')
self.assertTemplateUsed(response, 'form_view.html')
self.assertTemplateUsed(response, 'base.html')
self.assertTemplateNotUsed(response, "Invalid POST Template")
self.assertFormError(response, 'form', 'email', 'This field is required.')
self.assertFormError(response, 'form', 'single', 'This field is required.')
self.assertFormError(response, 'form', 'multi', 'This field is required.')
def test_form_error_with_template(self):
"POST erroneous data to a form using multiple templates"
post_data = {
'text': 'Hello World',
'email': 'not an email address',
'value': 37,
'single': 'b',
'multi': ('b','c','e')
}
response = self.client.post('/test_client/form_view_with_template/', post_data)
self.assertContains(response, 'POST data has errors')
self.assertTemplateUsed(response, "form_view.html")
self.assertTemplateUsed(response, 'base.html')
self.assertTemplateNotUsed(response, "Invalid POST Template")
self.assertFormError(response, 'form', 'email', 'Enter a valid e-mail address.')
def test_unknown_page(self):
"GET an invalid URL"
@ -127,18 +210,19 @@ class ClientTest(TestCase):
response = self.client.get('/test_client/login_protected_view/')
self.assertRedirects(response, '/accounts/login/')
# Log in
self.client.login(username='testclient', password='password')
# Request a page that requires a login
response = self.client.login('/test_client/login_protected_view/', 'testclient', 'password')
self.failUnless(response)
response = self.client.get('/test_client/login_protected_view/')
self.assertEqual(response.status_code, 200)
self.assertEqual(response.context['user'].username, 'testclient')
self.assertEqual(response.template.name, 'Login Template')
def test_view_with_bad_login(self):
"Request a page that is protected with @login, but use bad credentials"
response = self.client.login('/test_client/login_protected_view/', 'otheruser', 'nopassword')
self.failIf(response)
login = self.client.login(username='otheruser', password='nopassword')
self.failIf(login)
def test_session_modifying_view(self):
"Request a page that modifies the session"
@ -165,3 +249,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')

View File

@ -1,4 +1,5 @@
from django.conf.urls.defaults import *
from django.views.generic.simple import redirect_to
import views
urlpatterns = patterns('',
@ -6,8 +7,14 @@ urlpatterns = patterns('',
(r'^post_view/$', views.post_view),
(r'^raw_post_view/$', views.raw_post_view),
(r'^redirect_view/$', views.redirect_view),
(r'^permanent_redirect_view/$', redirect_to, { 'url': '/test_client/get_view/' }),
(r'^double_redirect_view/$', views.double_redirect_view),
(r'^bad_view/$', views.bad_view),
(r'^form_view/$', views.form_view),
(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)
)

View File

@ -1,9 +1,11 @@
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.http import HttpResponse, HttpResponseRedirect, HttpResponseNotFound
from django.contrib.auth.decorators import login_required
from django.newforms.forms import Form
from django.newforms import fields
from django.shortcuts import render_to_response
def get_view(request):
"A simple view that expects a GET request, and returns a rendered template"
@ -48,6 +50,14 @@ def redirect_view(request):
"A view that redirects all requests to the GET view"
return HttpResponseRedirect('/test_client/get_view/')
def double_redirect_view(request):
"A view that redirects all requests to a redirection view"
return HttpResponseRedirect('/test_client/permanent_redirect_view/')
def bad_view(request):
"A view that returns a 404 with some error content"
return HttpResponseNotFound('Not found!. This page contains some MAGIC content')
TestChoices = (
('a', 'First Choice'),
('b', 'Second Choice'),
@ -80,6 +90,25 @@ def form_view(request):
return HttpResponse(t.render(c))
def form_view_with_template(request):
"A view that tests a simple form"
if request.method == 'POST':
form = TestForm(request.POST)
if form.is_valid():
message = 'POST data OK'
else:
message = 'POST data has errors'
else:
form = TestForm()
message = 'GET form page'
return render_to_response('form_view.html',
{
'form': form,
'message': message
}
)
def login_protected_view(request):
"A simple view that is login protected."
t = Template('This is a login protected test. Username is {{ user.username }}.', name='Login Template')
@ -100,3 +129,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")

View File

@ -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 = {

View File

@ -32,7 +32,7 @@ __test__ = {'API_TESTS':"""
>>> Choice.objects.filter(foo__exact=None)
Traceback (most recent call last):
...
TypeError: Cannot resolve keyword 'foo' into field, choices are: id, poll, choice
TypeError: Cannot resolve keyword 'foo' into field. Choices are: id, poll, choice
# Can't use None on anything other than __exact
>>> Choice.objects.filter(id__gt=None)

View File

@ -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,

View File

@ -592,6 +592,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 #############################################################
@ -743,6 +745,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()
@ -750,6 +753,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]
@ -760,7 +767,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]))
@ -774,6 +781,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

View File

@ -0,0 +1,136 @@
"""
Regression tests for the Test Client, especially the customized assertions.
"""
from django.test import Client, TestCase
from django.core import mail
class AssertTemplateUsedTests(TestCase):
fixtures = ['testdata.json']
def test_no_context(self):
"Template usage assertions work then templates aren't in use"
response = self.client.get('/test_client_regress/no_template_view/')
# Check that the no template case doesn't mess with the template assertions
self.assertTemplateNotUsed(response, 'GET Template')
try:
self.assertTemplateUsed(response, 'GET Template')
except AssertionError, e:
self.assertEquals(str(e), "No templates used to render the response")
def test_single_context(self):
"Template assertions work when there is a single context"
response = self.client.get('/test_client/post_view/', {})
#
try:
self.assertTemplateNotUsed(response, 'Empty GET Template')
except AssertionError, e:
self.assertEquals(str(e), "Template 'Empty GET Template' was used unexpectedly in rendering the response")
try:
self.assertTemplateUsed(response, 'Empty POST Template')
except AssertionError, e:
self.assertEquals(str(e), "Template 'Empty POST Template' was not used to render the response. Actual template was 'Empty GET Template'")
def test_multiple_context(self):
"Template assertions work when there are multiple contexts"
post_data = {
'text': 'Hello World',
'email': 'foo@example.com',
'value': 37,
'single': 'b',
'multi': ('b','c','e')
}
response = self.client.post('/test_client/form_view_with_template/', post_data)
self.assertContains(response, 'POST data OK')
try:
self.assertTemplateNotUsed(response, "form_view.html")
except AssertionError, e:
self.assertEquals(str(e), "Template 'form_view.html' was used unexpectedly in rendering the response")
try:
self.assertTemplateNotUsed(response, 'base.html')
except AssertionError, e:
self.assertEquals(str(e), "Template 'base.html' was used unexpectedly in rendering the response")
try:
self.assertTemplateUsed(response, "Valid POST Template")
except AssertionError, e:
self.assertEquals(str(e), "Template 'Valid POST Template' was not one of the templates used to render the response. Templates used: form_view.html, base.html")
class AssertFormErrorTests(TestCase):
def test_unknown_form(self):
"An assertion is raised if the form name is unknown"
post_data = {
'text': 'Hello World',
'email': 'not an email address',
'value': 37,
'single': 'b',
'multi': ('b','c','e')
}
response = self.client.post('/test_client/form_view/', post_data)
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, "Invalid POST Template")
try:
self.assertFormError(response, 'wrong_form', 'some_field', 'Some error.')
except AssertionError, e:
self.assertEqual(str(e), "The form 'wrong_form' was not used to render the response")
def test_unknown_field(self):
"An assertion is raised if the field name is unknown"
post_data = {
'text': 'Hello World',
'email': 'not an email address',
'value': 37,
'single': 'b',
'multi': ('b','c','e')
}
response = self.client.post('/test_client/form_view/', post_data)
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, "Invalid POST Template")
try:
self.assertFormError(response, 'form', 'some_field', 'Some error.')
except AssertionError, e:
self.assertEqual(str(e), "The form 'form' in context 0 does not contain the field 'some_field'")
def test_noerror_field(self):
"An assertion is raised if the field doesn't have any errors"
post_data = {
'text': 'Hello World',
'email': 'not an email address',
'value': 37,
'single': 'b',
'multi': ('b','c','e')
}
response = self.client.post('/test_client/form_view/', post_data)
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, "Invalid POST Template")
try:
self.assertFormError(response, 'form', 'value', 'Some error.')
except AssertionError, e:
self.assertEqual(str(e), "The field 'value' on form 'form' in context 0 contains no errors")
def test_unknown_error(self):
"An assertion is raised if the field doesn't contain the provided error"
post_data = {
'text': 'Hello World',
'email': 'not an email address',
'value': 37,
'single': 'b',
'multi': ('b','c','e')
}
response = self.client.post('/test_client/form_view/', post_data)
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, "Invalid POST Template")
try:
self.assertFormError(response, 'form', 'email', 'Some error.')
except AssertionError, e:
self.assertEqual(str(e), "The field 'email' on form 'form' in context 0 does not contain the error 'Some error.' (actual errors: [u'Enter a valid e-mail address.'])")

View File

@ -0,0 +1,7 @@
from django.conf.urls.defaults import *
from django.views.generic.simple import redirect_to
import views
urlpatterns = patterns('',
(r'^no_template_view/$', views.no_template_view),
)

View File

@ -0,0 +1,8 @@
from django.core.mail import EmailMessage, SMTPConnection
from django.http import HttpResponse
from django.shortcuts import render_to_response
def no_template_view(request):
"A simple view that expects a GET request, and returns a rendered template"
return HttpResponse("No template used")

View File

@ -0,0 +1,8 @@
<html>
<head></head>
<body>
<h1>Django Internal Tests: {% block title %}{% endblock %}</h1>
{% block content %}
{% endblock %}
</body>
</html>

View File

@ -0,0 +1,15 @@
{% extends "base.html" %}
{% block title %}Submit data{% endblock %}
{% block content %}
<h1>{{ message }}</h1>
<form method='post' action='.'>
{% if form.errors %}
<p class='warning'>Please correct the errors below:</p>
{% endif %}
<ul class='form'>
{{ form }}
<li><input type='submit' value='Submit'></li>
</ul>
</form>
{% endblock %}

View File

@ -1,7 +1,6 @@
<html>
<head></head>
<body>
<h1>Django Internal Tests: Login</h1>
{% extends "base.html" %}
{% block title %}Login{% endblock %}
{% block content %}
{% if form.has_errors %}
<p>Your username and password didn't match. Please try again.</p>
{% endif %}
@ -15,5 +14,4 @@
<input type="submit" value="login" />
<input type="hidden" name="next" value="{{ next }}" />
</form>
</body>
</html>
{% endblock %}

View File

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