1
0
mirror of https://github.com/django/django.git synced 2025-07-04 17:59:13 +00:00

boulder-oracle-sprint: Merged to [5156]

git-svn-id: http://code.djangoproject.com/svn/django/branches/boulder-oracle-sprint@5157 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Boulder Sprinters 2007-05-07 15:50:55 +00:00
parent 0f22c6a7c8
commit a275d3da8e
16 changed files with 445 additions and 162 deletions

View File

@ -998,7 +998,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_many_to_many_objects(), True) + \
field_choices(current_opts.get_all_related_objects(), True) + \ field_choices(current_opts.get_all_related_objects(), True) + \
field_choices(current_opts.fields, False) 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 # Check whether an intermediate join is required between current_table
# and new_table. # and new_table.

View File

@ -1,12 +1,16 @@
import datetime
import sys import sys
from cStringIO import StringIO from cStringIO import StringIO
from urlparse import urlparse from urlparse import urlparse
from django.conf import settings 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.base import BaseHandler
from django.core.handlers.wsgi import WSGIRequest from django.core.handlers.wsgi import WSGIRequest
from django.core.signals import got_request_exception from django.core.signals import got_request_exception
from django.dispatch import dispatcher 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.test import signals
from django.utils.functional import curry from django.utils.functional import curry
@ -113,7 +117,6 @@ class Client:
self.handler = ClientHandler() self.handler = ClientHandler()
self.defaults = defaults self.defaults = defaults
self.cookies = SimpleCookie() self.cookies = SimpleCookie()
self.session = {}
self.exc_info = None self.exc_info = None
def store_exc_info(self, *args, **kwargs): def store_exc_info(self, *args, **kwargs):
@ -123,6 +126,15 @@ class Client:
""" """
self.exc_info = sys.exc_info() 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): def request(self, **request):
""" """
The master request method. Composes the environment dictionary The master request method. Composes the environment dictionary
@ -171,16 +183,10 @@ class Client:
if self.exc_info: if self.exc_info:
raise self.exc_info[1], None, self.exc_info[2] raise self.exc_info[1], None, self.exc_info[2]
# Update persistent cookie and session data # Update persistent cookie data
if response.cookies: if response.cookies:
self.cookies.update(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 return response
def get(self, path, data={}, **extra): def get(self, path, data={}, **extra):
@ -215,42 +221,34 @@ class Client:
return self.request(**r) 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 user = authenticate(**credentials)
is protected by a @login_required access decorator. 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 # Set the cookie to represent the session
login is complete. Returns False if login process failed. self.cookies[settings.SESSION_COOKIE_NAME] = obj.session_key
""" self.cookies[settings.SESSION_COOKIE_NAME]['max-age'] = None
# First, GET the page that is login protected. self.cookies[settings.SESSION_COOKIE_NAME]['path'] = '/'
# This page will redirect to the login page. self.cookies[settings.SESSION_COOKIE_NAME]['domain'] = settings.SESSION_COOKIE_DOMAIN
response = self.get(path) self.cookies[settings.SESSION_COOKIE_NAME]['secure'] = settings.SESSION_COOKIE_SECURE or None
if response.status_code != 302: 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 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

@ -1,7 +1,9 @@
import re, doctest, unittest import re, doctest, unittest
from urlparse import urlparse
from django.db import transaction from django.db import transaction
from django.core import management from django.core import management
from django.db.models import get_apps from django.db.models import get_apps
from django.test.client import Client
normalize_long_ints = lambda s: re.sub(r'(?<![\w])(\d+)L(?![\w])', '\\1', s) normalize_long_ints = lambda s: re.sub(r'(?<![\w])(\d+)L(?![\w])', '\\1', s)
@ -46,5 +48,91 @@ class TestCase(unittest.TestCase):
super(). super().
""" """
self.client = Client()
self.install_fixtures() self.install_fixtures()
super(TestCase, self).run(result) super(TestCase, self).run(result)
def assertRedirects(self, response, expected_path):
"""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)
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)
def assertContains(self, 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``
times in the content of the response.
"""
self.assertEqual(response.status_code, 200,
"Couldn't retrieve page'")
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))
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
try:
for err in errors:
if field:
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])))
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())))
except KeyError:
self.fail("The form '%s' in context %d does not contain the field '%s'" % (form, i, field))
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,
"Template '%s' was not one of the templates used to render the response. Templates used: %s" %
(template_name, template_names))
elif response.template:
self.assertEqual(template_name, response.template.name,
"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],
"Template '%s' was used unexpectedly in rendering the response" % template_name)
elif response.template:
self.assertNotEqual(template_name, response.template.name,
"Template '%s' was used unexpectedly in rendering the response" % template_name)

View File

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

View File

@ -2,19 +2,29 @@
Testing Django applications Testing Django applications
=========================== ===========================
Automated testing is an extremely useful weapon in the bug-killing arsenal Automated testing is an extremely useful bug-killing tool for the modern
of the modern developer. When initially writing code, a test suite can be Web developer. You can use a collection of tests -- a **test suite** -- to
used to validate that code behaves as expected. When refactoring or to solve, or avoid, a number of problems:
modifying code, tests serve as a guide to ensure that behavior hasn't
changed unexpectedly as a result of the refactor.
Testing a web application is a complex task, as there are many * When you're writing new code, you can use tests to validate your code
components of a web application that must be validated and tested. To works as expected.
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.
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. slightly before the next official Django release.
(That's *no* excuse not to write tests, though!) (That's *no* excuse not to write tests, though!)
@ -166,7 +176,7 @@ To assist in testing various features of your application, Django provides
tools that can be used to establish tests and test conditions. tools that can be used to establish tests and test conditions.
* `Test Client`_ * `Test Client`_
* Fixtures_ * `TestCase`_
Test Client Test Client
----------- -----------
@ -246,22 +256,35 @@ can be invoked on the ``Client`` instance.
file name), and `attachment_file` (containing the file data). Note that you 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. need to manually close the file after it has been provided to the POST.
``login(path, username, password)`` ``login(**credentials)``
In a production site, it is likely that some views will be protected with ** New in Django development version **
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.
If login is possible, the final return value of ``login()`` is the response On a production site, it is likely that some views will be protected from
that is generated by issuing a GET request on the protected URL. If login anonymous access through the use of the @login_required decorator, or some
is not possible, ``login()`` returns False. 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, 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 which contains no users by default. As a result, logins that are valid
site will not work. You will need to create users as part of the test suite on your production site will not work under test conditions. You will
to be able to test logins to your application. need to create users as part of the test suite (either manually, or
using a test fixture).
Testing Responses Testing Responses
~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~
@ -357,9 +380,31 @@ The following is a simple unit test using the Test Client::
# Check that the rendered context contains 5 customers # Check that the rendered context contains 5 customers
self.failUnlessEqual(len(response.context['customers']), 5) self.failUnlessEqual(len(response.context['customers']), 5)
Fixtures TestCase
-------- --------
Normal python unit tests extend a base class of ``unittest.testCase``.
Django provides an extension of this base class - ``django.test.TestCase``
- that provides some additional capabilities that can be useful for
testing web sites.
Moving from a normal unittest TestCase to a Django TestCase is easy - just
change the base class of your test from ``unittest.TestCase`` to
``django.test.TestCase``. All of the standard Python unit test facilities
will continue to be available, but they will be augmented with some useful
extra facilities.
Default Test Client
~~~~~~~~~~~~~~~~~~~
** 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
``self.client``. This client is recreated for each test.
Fixture loading
~~~~~~~~~~~~~~~
A test case for a database-backed website isn't much use if there isn't any A test case for a database-backed website isn't much use if there isn't any
data in the database. To make it easy to put test data into the database, data in the database. To make it easy to put test data into the database,
Django provides a fixtures framework. Django provides a fixtures framework.
@ -377,15 +422,13 @@ multiple applications.
data (such as a default set of categories). Fixtures with other names data (such as a default set of categories). Fixtures with other names
can be installed manually using ``django-admin.py loaddata``. can be installed manually using ``django-admin.py loaddata``.
However, for the purposes of unit testing, each test must be able to However, for the purposes of unit testing, each test must be able to
guarantee the contents of the database at the start of each and every guarantee the contents of the database at the start of each and every
test. To do this, Django provides a TestCase baseclass that can integrate test.
with fixtures.
Moving from a normal unittest TestCase to a Django TestCase is easy - just To define a fixture for a test, all you need to do is add a class
change the base class of your test, and define a list of fixtures attribute to your test describing the fixtures you want the test to use.
to be used. For example, the test case from `Writing unittests`_ would For example, the test case from `Writing unittests`_ would
look like:: look like::
from django.test import TestCase from django.test import TestCase
@ -410,6 +453,45 @@ 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 can be certain that the outcome of a test will not be affected by
another test, or the order of test execution. another test, or the order of test execution.
Assertions
~~~~~~~~~~
** 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.
``assertContains(response, text, count=1)``
Assert that a response indicates that a page was retrieved successfully,
(i.e., the HTTP status code was 200), 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)``
Assert that the response received redirects the browser to the provided
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.
Running tests Running tests
============= =============

View File

@ -71,7 +71,7 @@ __test__ = {'API_TESTS':"""
>>> Author.objects.filter(firstname__exact='John') >>> Author.objects.filter(firstname__exact='John')
Traceback (most recent call last): 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 = Author.objects.get(last_name__exact='Smith')
>>> a.first_name >>> a.first_name

View File

@ -223,11 +223,11 @@ DoesNotExist: Article matching query does not exist.
>>> Article.objects.filter(pub_date_year='2005').count() >>> Article.objects.filter(pub_date_year='2005').count()
Traceback (most recent call last): 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') >>> Article.objects.filter(headline__starts='Article')
Traceback (most recent call last): 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) >>> Article.objects.filter(reporter_id__exact=1)
Traceback (most recent call last): 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 # You need to specify a comparison clause
>>> Article.objects.filter(reporter_id=1) >>> Article.objects.filter(reporter_id=1)
Traceback (most recent call last): 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 # You can also instantiate an Article by passing
# the Reporter's ID instead of a Reporter object. # 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") >>> Poll.objects.get(choice__name__exact="This is the answer")
Traceback (most recent call last): 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

@ -24,19 +24,21 @@ from django.test import Client, TestCase
class ClientTest(TestCase): class ClientTest(TestCase):
fixtures = ['testdata.json'] fixtures = ['testdata.json']
def setUp(self):
"Set up test environment"
self.client = Client()
def test_get_view(self): def test_get_view(self):
"GET a view" "GET a view"
response = self.client.get('/test_client/get_view/') response = self.client.get('/test_client/get_view/')
# Check some response details # Check some response details
self.assertEqual(response.status_code, 200) self.assertContains(response, 'This is a test')
self.assertEqual(response.context['var'], 42) self.assertEqual(response.context['var'], 42)
self.assertEqual(response.template.name, 'GET Template') self.assertEqual(response.template.name, 'GET Template')
self.failUnless('This is a test.' in response.content)
def test_no_template_view(self):
"Check that template usage assersions work then templates aren't in use"
response = self.client.get('/test_client/no_template_view/')
# Check that the no template case doesn't mess with the template assertions
self.assertTemplateNotUsed(response, 'GET Template')
def test_get_post_view(self): def test_get_post_view(self):
"GET a view that normally expects POSTs" "GET a view that normally expects POSTs"
@ -45,6 +47,8 @@ class ClientTest(TestCase):
# Check some response details # Check some response details
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual(response.template.name, 'Empty GET Template') 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): def test_empty_post(self):
"POST an empty dictionary to a view" "POST an empty dictionary to a view"
@ -53,6 +57,8 @@ class ClientTest(TestCase):
# Check some response details # Check some response details
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual(response.template.name, 'Empty POST Template') self.assertEqual(response.template.name, 'Empty POST Template')
self.assertTemplateNotUsed(response, 'Empty GET Template')
self.assertTemplateUsed(response, 'Empty POST Template')
def test_post(self): def test_post(self):
"POST some data to a view" "POST some data to a view"
@ -80,7 +86,7 @@ class ClientTest(TestCase):
response = self.client.get('/test_client/redirect_view/') response = self.client.get('/test_client/redirect_view/')
# Check that the response was a 302 (redirect) # Check that the response was a 302 (redirect)
self.assertEqual(response.status_code, 302) self.assertRedirects(response, '/test_client/get_view/')
def test_valid_form(self): def test_valid_form(self):
"POST valid data to a form" "POST valid data to a form"
@ -93,7 +99,7 @@ class ClientTest(TestCase):
} }
response = self.client.post('/test_client/form_view/', post_data) response = self.client.post('/test_client/form_view/', post_data)
self.assertEqual(response.status_code, 200) 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): def test_incomplete_data_form(self):
"POST incomplete data to a form" "POST incomplete data to a form"
@ -102,8 +108,13 @@ class ClientTest(TestCase):
'value': 37 'value': 37
} }
response = self.client.post('/test_client/form_view/', post_data) response = self.client.post('/test_client/form_view/', post_data)
self.assertContains(response, 'This field is required.', 3)
self.assertEqual(response.status_code, 200) 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', '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): def test_form_error(self):
"POST erroneous data to a form" "POST erroneous data to a form"
@ -116,7 +127,57 @@ class ClientTest(TestCase):
} }
response = self.client.post('/test_client/form_view/', post_data) response = self.client.post('/test_client/form_view/', post_data)
self.assertEqual(response.status_code, 200) 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): def test_unknown_page(self):
"GET an invalid URL" "GET an invalid URL"
@ -130,20 +191,21 @@ class ClientTest(TestCase):
# Get the page without logging in. Should result in 302. # Get the page without logging in. Should result in 302.
response = self.client.get('/test_client/login_protected_view/') response = self.client.get('/test_client/login_protected_view/')
self.assertEqual(response.status_code, 302) self.assertRedirects(response, '/accounts/login/')
# Log in
self.client.login(username='testclient', password='password')
# Request a page that requires a login # Request a page that requires a login
response = self.client.login('/test_client/login_protected_view/', 'testclient', 'password') response = self.client.get('/test_client/login_protected_view/')
self.failUnless(response)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual(response.context['user'].username, 'testclient') self.assertEqual(response.context['user'].username, 'testclient')
self.assertEqual(response.template.name, 'Login Template')
def test_view_with_bad_login(self): def test_view_with_bad_login(self):
"Request a page that is protected with @login, but use bad credentials" "Request a page that is protected with @login, but use bad credentials"
response = self.client.login('/test_client/login_protected_view/', 'otheruser', 'nopassword') login = self.client.login(username='otheruser', password='nopassword')
self.failIf(response) self.failIf(login)
def test_session_modifying_view(self): def test_session_modifying_view(self):
"Request a page that modifies the session" "Request a page that modifies the session"

View File

@ -2,11 +2,13 @@ from django.conf.urls.defaults import *
import views import views
urlpatterns = patterns('', urlpatterns = patterns('',
(r'^no_template_view/$', views.no_template_view),
(r'^get_view/$', views.get_view), (r'^get_view/$', views.get_view),
(r'^post_view/$', views.post_view), (r'^post_view/$', views.post_view),
(r'^raw_post_view/$', views.raw_post_view), (r'^raw_post_view/$', views.raw_post_view),
(r'^redirect_view/$', views.redirect_view), (r'^redirect_view/$', views.redirect_view),
(r'^form_view/$', views.form_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'^login_protected_view/$', views.login_protected_view),
(r'^session_view/$', views.session_view), (r'^session_view/$', views.session_view),
(r'^broken_view/$', views.broken_view) (r'^broken_view/$', views.broken_view)

View File

@ -4,6 +4,11 @@ from django.http import HttpResponse, HttpResponseRedirect
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.newforms.forms import Form from django.newforms.forms import Form
from django.newforms import fields from django.newforms import fields
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")
def get_view(request): def get_view(request):
"A simple view that expects a GET request, and returns a rendered template" "A simple view that expects a GET request, and returns a rendered template"
@ -80,6 +85,25 @@ def form_view(request):
return HttpResponse(t.render(c)) 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): def login_protected_view(request):
"A simple view that is login protected." "A simple view that is login protected."
t = Template('This is a login protected test. Username is {{ user.username }}.', name='Login Template') t = Template('This is a login protected test. Username is {{ user.username }}.', name='Login Template')

View File

@ -32,7 +32,7 @@ __test__ = {'API_TESTS':"""
>>> Choice.objects.filter(foo__exact=None) >>> Choice.objects.filter(foo__exact=None)
Traceback (most recent call last): 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 # Can't use None on anything other than __exact
>>> Choice.objects.filter(id__gt=None) >>> Choice.objects.filter(id__gt=None)

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