diff --git a/django/db/models/query.py b/django/db/models/query.py index 93dcdd1776..d31ccf003e 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -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_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. diff --git a/django/test/client.py b/django/test/client.py index 95d3b85922..c3110f02ec 100644 --- a/django/test/client.py +++ b/django/test/client.py @@ -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) + \ No newline at end of file diff --git a/django/test/testcases.py b/django/test/testcases.py index 2bfb9a733a..80f55b20d3 100644 --- a/django/test/testcases.py +++ b/django/test/testcases.py @@ -1,8 +1,10 @@ import re, doctest, unittest +from urlparse import urlparse from django.db import transaction from django.core import management from django.db.models import get_apps - +from django.test.client import Client + normalize_long_ints = lambda s: re.sub(r'(?>> 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 diff --git a/tests/modeltests/lookup/models.py b/tests/modeltests/lookup/models.py index c28f0e015f..c634aef8a1 100644 --- a/tests/modeltests/lookup/models.py +++ b/tests/modeltests/lookup/models.py @@ -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 """} diff --git a/tests/modeltests/many_to_one/models.py b/tests/modeltests/many_to_one/models.py index 3ed449d598..02f7bf1066 100644 --- a/tests/modeltests/many_to_one/models.py +++ b/tests/modeltests/many_to_one/models.py @@ -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. diff --git a/tests/modeltests/reverse_lookup/models.py b/tests/modeltests/reverse_lookup/models.py index d30269c5c6..4d6591551a 100644 --- a/tests/modeltests/reverse_lookup/models.py +++ b/tests/modeltests/reverse_lookup/models.py @@ -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 """} diff --git a/tests/modeltests/test_client/models.py b/tests/modeltests/test_client/models.py index 44ddffb55f..cd8dbe37d2 100644 --- a/tests/modeltests/test_client/models.py +++ b/tests/modeltests/test_client/models.py @@ -24,20 +24,22 @@ from django.test import Client, TestCase class ClientTest(TestCase): fixtures = ['testdata.json'] - def setUp(self): - "Set up test environment" - self.client = Client() - def test_get_view(self): "GET a view" response = self.client.get('/test_client/get_view/') # 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.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): "GET a view that normally expects POSTs" response = self.client.get('/test_client/post_view/', {}) @@ -45,6 +47,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" @@ -53,6 +57,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" @@ -80,7 +86,7 @@ class ClientTest(TestCase): response = self.client.get('/test_client/redirect_view/') # 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): "POST valid data to a form" @@ -93,7 +99,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" @@ -102,8 +108,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.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): "POST erroneous data to a form" @@ -116,7 +127,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" @@ -130,20 +191,21 @@ class ClientTest(TestCase): # Get the page without logging in. Should result in 302. 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 - 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" diff --git a/tests/modeltests/test_client/urls.py b/tests/modeltests/test_client/urls.py index 707ecc186d..f63c486d01 100644 --- a/tests/modeltests/test_client/urls.py +++ b/tests/modeltests/test_client/urls.py @@ -2,11 +2,13 @@ from django.conf.urls.defaults import * import views urlpatterns = patterns('', + (r'^no_template_view/$', views.no_template_view), (r'^get_view/$', views.get_view), (r'^post_view/$', views.post_view), (r'^raw_post_view/$', views.raw_post_view), (r'^redirect_view/$', views.redirect_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) diff --git a/tests/modeltests/test_client/views.py b/tests/modeltests/test_client/views.py index 36ec144cf6..3b7a57f4d0 100644 --- a/tests/modeltests/test_client/views.py +++ b/tests/modeltests/test_client/views.py @@ -4,6 +4,11 @@ from django.http import HttpResponse, HttpResponseRedirect 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 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): "A simple view that expects a GET request, and returns a rendered template" @@ -79,6 +84,25 @@ def form_view(request): c = Context({'form': form}) 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." diff --git a/tests/regressiontests/null_queries/models.py b/tests/regressiontests/null_queries/models.py index 4396ab4005..21944d9e7a 100644 --- a/tests/regressiontests/null_queries/models.py +++ b/tests/regressiontests/null_queries/models.py @@ -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) diff --git a/tests/templates/base.html b/tests/templates/base.html new file mode 100644 index 0000000000..611bc094a9 --- /dev/null +++ b/tests/templates/base.html @@ -0,0 +1,8 @@ + + + +

Django Internal Tests: {% block title %}{% endblock %}

+{% block content %} +{% endblock %} + + \ No newline at end of file diff --git a/tests/templates/form_view.html b/tests/templates/form_view.html new file mode 100644 index 0000000000..1487217547 --- /dev/null +++ b/tests/templates/form_view.html @@ -0,0 +1,15 @@ +{% extends "base.html" %} +{% block title %}Submit data{% endblock %} +{% block content %} +

{{ message }}

+
+{% if form.errors %} +

Please correct the errors below:

+{% endif %} + +
+ +{% endblock %} \ No newline at end of file diff --git a/tests/templates/login.html b/tests/templates/login.html index 8a0974c9a1..d55e9ddc75 100644 --- a/tests/templates/login.html +++ b/tests/templates/login.html @@ -1,7 +1,6 @@ - - - -

Django Internal Tests: Login

+{% extends "base.html" %} +{% block title %}Login{% endblock %} +{% block content %} {% if form.has_errors %}

Your username and password didn't match. Please try again.

{% endif %} @@ -15,5 +14,4 @@ - - \ No newline at end of file +{% endblock %} \ No newline at end of file