diff --git a/AUTHORS b/AUTHORS index 3b8a583e4af65..7b20bebc92a9a 100644 --- a/AUTHORS +++ b/AUTHORS @@ -260,6 +260,7 @@ answer newbie questions, and generally made Django that much better: Vasiliy Stavenko Thomas Steinacher nowell strite + Thomas Stromberg Sundance SuperJared Radek Švarz diff --git a/django/contrib/csrf/middleware.py b/django/contrib/csrf/middleware.py index 93a9484ca655e..1ef0e6cefd27f 100644 --- a/django/contrib/csrf/middleware.py +++ b/django/contrib/csrf/middleware.py @@ -40,7 +40,7 @@ class CsrfMiddleware(object): """ def process_request(self, request): - if request.POST: + if request.method == 'POST': try: session_id = request.COOKIES[settings.SESSION_COOKIE_NAME] except KeyError: diff --git a/django/contrib/localflavor/it/it_province.py b/django/contrib/localflavor/it/it_province.py index 669ecd7f95553..db041d719e3ea 100644 --- a/django/contrib/localflavor/it/it_province.py +++ b/django/contrib/localflavor/it/it_province.py @@ -45,7 +45,7 @@ ('IM', 'Imperia'), ('IS', 'Isernia'), ('SP', 'La Spezia'), - ('AQ', u'L’Acquila'), + ('AQ', u'L’Aquila'), ('LT', 'Latina'), ('LE', 'Lecce'), ('LC', 'Lecco'), diff --git a/django/core/management/base.py b/django/core/management/base.py index 866dec4407217..978aa7902a46e 100644 --- a/django/core/management/base.py +++ b/django/core/management/base.py @@ -1,6 +1,7 @@ from django.core.exceptions import ImproperlyConfigured from django.core.management.color import color_style import sys +import os class CommandError(Exception): pass @@ -44,7 +45,7 @@ def execute(self, *args, **options): sys.stderr.write(self.style.ERROR(str('Error: %s\n' % e))) sys.exit(1) - def validate(self, app=None): + def validate(self, app=None, display_num_errors=False): """ Validates the given app, raising CommandError for any errors. @@ -61,6 +62,8 @@ def validate(self, app=None): s.seek(0) error_text = s.read() raise CommandError("One or more models did not validate:\n%s" % error_text) + if display_num_errors: + print "%s error%s found" % (num_errors, num_errors != 1 and 's' or '') def handle(self, *args, **options): raise NotImplementedError() @@ -119,7 +122,6 @@ def handle_noargs(self, **options): def copy_helper(style, app_or_project, name, directory, other_name=''): import django - import os import re import shutil other = {'project': 'app', 'app': 'project'}[app_or_project] @@ -155,5 +157,15 @@ def copy_helper(style, app_or_project, name, directory, other_name=''): fp_new.close() try: shutil.copymode(path_old, path_new) + _make_writeable(path_new) except OSError: sys.stderr.write(style.NOTICE("Notice: Couldn't set permission bits on %s. You're probably using an uncommon filesystem setup. No problem.\n" % path_new)) + +def _make_writeable(filename): + "Makes sure that the file is writeable. Useful if our source is read-only." + import stat + if not os.access(filename, os.W_OK): + st = os.stat(filename) + new_permissions = stat.S_IMODE(st.st_mode) | stat.S_IWUSR + os.chmod(filename, new_permissions) + diff --git a/django/core/management/color.py b/django/core/management/color.py index 06edb6066b893..ce95401491c16 100644 --- a/django/core/management/color.py +++ b/django/core/management/color.py @@ -7,7 +7,7 @@ def color_style(): "Returns a Style object with the Django color scheme." - if sys.platform == 'win32' or sys.platform == 'Pocket PC' or not sys.stdout.isatty(): + if sys.platform == 'win32' or sys.platform == 'Pocket PC' or sys.platform.startswith('java') or not sys.stdout.isatty(): return no_style() class dummy: pass style = dummy() diff --git a/django/core/management/commands/runserver.py b/django/core/management/commands/runserver.py index d06744e9fa46d..272ca18c07abf 100644 --- a/django/core/management/commands/runserver.py +++ b/django/core/management/commands/runserver.py @@ -37,7 +37,7 @@ def handle(self, addrport='', *args, **options): def inner_run(): from django.conf import settings print "Validating models..." - self.validate() + self.validate(display_num_errors=True) print "\nDjango version %s, using settings %r" % (django.get_version(), settings.SETTINGS_MODULE) print "Development server is running at http://%s:%s/" % (addr, port) print "Quit the server with %s." % quit_command diff --git a/django/core/management/commands/startproject.py b/django/core/management/commands/startproject.py index 0d9e4bd381829..ab4f409f15074 100644 --- a/django/core/management/commands/startproject.py +++ b/django/core/management/commands/startproject.py @@ -28,11 +28,6 @@ def handle_label(self, project_name, **options): # Create a random SECRET_KEY hash, and put it in the main settings. main_settings_file = os.path.join(directory, project_name, 'settings.py') settings_contents = open(main_settings_file, 'r').read() - - # If settings.py was copied from a read-only source, make it writeable. - if not os.access(main_settings_file, os.W_OK): - os.chmod(main_settings_file, 0600) - fp = open(main_settings_file, 'w') secret_key = ''.join([choice('abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)') for i in range(50)]) settings_contents = re.sub(r"(?<=SECRET_KEY = ')'", secret_key + "'", settings_contents) diff --git a/django/core/management/commands/validate.py b/django/core/management/commands/validate.py index 2751f41abdd24..760d41c5bfe33 100644 --- a/django/core/management/commands/validate.py +++ b/django/core/management/commands/validate.py @@ -6,4 +6,4 @@ class Command(NoArgsCommand): requires_model_validation = False def handle_noargs(self, **options): - self.validate() + self.validate(display_num_errors=True) diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py index 7919e1cc50c76..a482a240cfb01 100644 --- a/django/db/backends/sqlite3/base.py +++ b/django/db/backends/sqlite3/base.py @@ -1,5 +1,9 @@ """ -SQLite3 backend for django. Requires pysqlite2 (http://pysqlite.org/). +SQLite3 backend for django. + +Python 2.3 and 2.4 require pysqlite2 (http://pysqlite.org/). + +Python 2.5 and later use the sqlite3 module in the standard library. """ from django.db.backends import BaseDatabaseWrapper, BaseDatabaseFeatures, BaseDatabaseOperations, util diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py index 6779b9b5f2b16..22babfd6c0d05 100644 --- a/django/template/defaultfilters.py +++ b/django/template/defaultfilters.py @@ -355,8 +355,8 @@ def unordered_list(value): Recursively takes a self-nested list and returns an HTML unordered list -- WITHOUT opening and closing
    tags. - The list is assumed to be in the proper format. For example, if ``var`` contains - ``['States', [['Kansas', [['Lawrence', []], ['Topeka', []]]], ['Illinois', []]]]``, + The list is assumed to be in the proper format. For example, if ``var`` + contains: ``['States', ['Kansas', ['Lawrence', 'Topeka'], 'Illinois']]``, then ``{{ var|unordered_list }}`` would return::
  • States @@ -371,14 +371,61 @@ def unordered_list(value):
""" - def _helper(value, tabs): + def convert_old_style_list(list_): + """ + Converts old style lists to the new easier to understand format. + + The old list format looked like: + ['Item 1', [['Item 1.1', []], ['Item 1.2', []]] + + And it is converted to: + ['Item 1', ['Item 1.1', 'Item 1.2]] + """ + if not isinstance(list_, (tuple, list)) or len(list_) != 2: + return list_, False + first_item, second_item = list_ + if second_item == []: + return [first_item], True + old_style_list = True + new_second_item = [] + for sublist in second_item: + item, old_style_list = convert_old_style_list(sublist) + if not old_style_list: + break + new_second_item.extend(item) + if old_style_list: + second_item = new_second_item + return [first_item, second_item], old_style_list + def _helper(list_, tabs=1): indent = u'\t' * tabs - if value[1]: - return u'%s
  • %s\n%s
      \n%s\n%s
    \n%s
  • ' % (indent, force_unicode(value[0]), indent, - u'\n'.join([_helper(v, tabs+1) for v in value[1]]), indent, indent) - else: - return u'%s
  • %s
  • ' % (indent, force_unicode(value[0])) - return _helper(value, 1) + output = [] + + list_length = len(list_) + i = 0 + while i < list_length: + title = list_[i] + sublist = '' + sublist_item = None + if isinstance(title, (list, tuple)): + sublist_item = title + title = '' + elif i < list_length - 1: + next_item = list_[i+1] + if next_item and isinstance(next_item, (list, tuple)): + # The next item is a sub-list. + sublist_item = next_item + # We've processed the next item now too. + i += 1 + if sublist_item: + sublist = _helper(sublist_item, tabs+1) + sublist = '\n%s
      \n%s\n%s
    \n%s' % (indent, sublist, + indent, indent) + output.append('%s
  • %s%s
  • ' % (indent, force_unicode(title), + sublist)) + i += 1 + return '\n'.join(output) + value, converted = convert_old_style_list(value) + return _helper(value) ################### # INTEGERS # diff --git a/django/test/client.py b/django/test/client.py index 9ec36159733b7..c3e221554f566 100644 --- a/django/test/client.py +++ b/django/test/client.py @@ -11,6 +11,7 @@ from django.core.signals import got_request_exception from django.dispatch import dispatcher from django.http import SimpleCookie, HttpRequest +from django.template import TemplateDoesNotExist from django.test import signals from django.utils.functional import curry from django.utils.encoding import smart_str @@ -165,7 +166,25 @@ def request(self, **request): # Capture exceptions created by the handler dispatcher.connect(self.store_exc_info, signal=got_request_exception) - response = self.handler(environ) + try: + response = self.handler(environ) + except TemplateDoesNotExist, e: + # If the view raises an exception, Django will attempt to show + # the 500.html template. If that template is not available, + # we should ignore the error in favor of re-raising the + # underlying exception that caused the 500 error. Any other + # template found to be missing during view error handling + # should be reported as-is. + if e.args != ('500.html',): + raise + + # Look for a signalled exception and reraise it + if self.exc_info: + raise self.exc_info[1], None, self.exc_info[2] + + # Save the client and request that stimulated the response + response.client = self + response.request = request # Add any rendered template detail to the response # If there was only one template rendered (the most likely case), @@ -179,10 +198,6 @@ def request(self, **request): else: setattr(response, detail, None) - # Look for a signalled exception and reraise it - if self.exc_info: - raise self.exc_info[1], None, self.exc_info[2] - # Update persistent cookie data if response.cookies: self.cookies.update(response.cookies) diff --git a/django/test/testcases.py b/django/test/testcases.py index 21c429271ca0c..baa6e7bb194a0 100644 --- a/django/test/testcases.py +++ b/django/test/testcases.py @@ -1,14 +1,28 @@ -import re, unittest -from urlparse import urlparse +import re +import unittest +from urlparse import urlsplit + +from django.http import QueryDict from django.db import transaction from django.core import mail from django.core.management import call_command -from django.db.models import get_apps from django.test import _doctest as doctest from django.test.client import Client normalize_long_ints = lambda s: re.sub(r'(?>> from django.contrib.auth.models import User >>> user = User.objects.create_user('john', 'lennon@thebeatles.com', 'johnpassword') - # At this point, user is a User object ready to be saved + # At this point, user is a User object that has already been saved # to the database. You can continue to change its attributes # if you want to change other fields. >>> user.is_staff = True diff --git a/docs/cache.txt b/docs/cache.txt index d13352b0255ca..92b5c1b43d23f 100644 --- a/docs/cache.txt +++ b/docs/cache.txt @@ -176,9 +176,11 @@ just implements the cache interface without doing anything. This is useful if you have a production site that uses heavy-duty caching in various places but a development/test environment on which you don't want to -cache. In that case, set ``CACHE_BACKEND`` to ``"dummy:///"`` in the settings -file for your development environment. As a result, your development -environment won't use caching and your production environment still will. +cache. As a result, your development environment won't use caching and your +production environment still will. To activate dummy caching, set +``CACHE_BACKEND`` like so:: + + CACHE_BACKEND = 'dummy:///' CACHE_BACKEND arguments ----------------------- diff --git a/docs/db-api.txt b/docs/db-api.txt index 3198f335c43f5..2a90b2d171f28 100644 --- a/docs/db-api.txt +++ b/docs/db-api.txt @@ -160,7 +160,7 @@ When you save an object, Django performs the following steps: is used to provide notification that an object has been successfully saved. (These signals are not yet documented.) -Raw Saves +Raw saves ~~~~~~~~~ **New in Django development version** diff --git a/docs/distributions.txt b/docs/distributions.txt index f9b9cbe9f88cf..d65e047276f6b 100644 --- a/docs/distributions.txt +++ b/docs/distributions.txt @@ -15,6 +15,18 @@ repository. .. _installing the development version: ../install/#installing-the-development-version +FreeBSD +======= + +The `FreeBSD`_ ports system offers both Django 0.96 (`py-django`_) and a more +recent, but not current, version based on Django's trunk (`py-django-devel`_). +These are installed in the normal FreeBSD way; for Django 0.96, for example, type: +``cd /usr/ports/www/py-django && sudo make install clean``. + +.. _FreeBSD: http://www.freebsd.org/ +.. _py-django: http://www.freebsd.org/cgi/cvsweb.cgi/ports/www/py-django/ +.. _py-django-devel: http://www.freebsd.org/cgi/cvsweb.cgi/ports/www/py-django-devel/ + Linux distributions =================== @@ -33,17 +45,6 @@ plan to use with Django. .. _Debian GNU/Linux: http://www.debian.org/ .. _packaged version of Django: http://packages.debian.org/stable/python/python-django -Ubuntu ------- - -The Debian ``python-django`` package is also available for `Ubuntu Linux`_, in -the "universe" repository for Ubuntu 7.04 ("Feisty Fawn"). The `current Ubuntu -package`_ is also based on Django 0.95.1 and can be installed in the same -fashion as for Debian. - -.. _Ubuntu Linux: http://www.ubuntu.com/ -.. _current Ubuntu package: http://packages.ubuntu.com/feisty/python/python-django - Fedora ------ @@ -65,6 +66,18 @@ The `current Gentoo build`_ can be installed by typing ``emerge django``. .. _Gentoo Linux: http://www.gentoo.org/ .. _current Gentoo build: http://packages.gentoo.org/packages/?category=dev-python;name=django +Ubuntu +------ + +The Debian ``python-django`` package is also available for `Ubuntu Linux`_, in +the "universe" repository for Ubuntu 7.04 ("Feisty Fawn"). The `current Ubuntu +package`_ is also based on Django 0.95.1 and can be installed in the same +fashion as for Debian. + +.. _Ubuntu Linux: http://www.ubuntu.com/ +.. _current Ubuntu package: http://packages.ubuntu.com/feisty/python/python-django + + Mac OS X ======== diff --git a/docs/django-admin.txt b/docs/django-admin.txt index 366483a47a75d..e3d1067dd3f5e 100644 --- a/docs/django-admin.txt +++ b/docs/django-admin.txt @@ -479,6 +479,9 @@ This is useful in a number of ways: Note that this server can only run on the default port on localhost; it does not yet accept a ``host`` or ``port`` parameter. +Also note that it does *not* automatically detect changes to your Python source +code (as ``runserver`` does). It does, however, detect changes to templates. + .. _unit tests: ../testing/ validate diff --git a/docs/modpython.txt b/docs/modpython.txt index c90296bd9a8f9..5a1a3f050609d 100644 --- a/docs/modpython.txt +++ b/docs/modpython.txt @@ -262,7 +262,8 @@ else. This is done using the PythonImport_ directive to mod_python. You need to ensure that you have specified the ``PythonInterpreter`` directive to mod_python as described above__ (you need to do this even if you aren't serving multiple installations in this case). Then add the ``PythonImport`` -line inside the ``Location`` or ``VirtualHost`` section. For example:: +line in the main server configuration (i.e., outside the ``Location`` or +``VirtualHost`` sections). For example:: PythonInterpreter my_django PythonImport /path/to/my/project/file.py my_django diff --git a/docs/templates.txt b/docs/templates.txt index 6cebd3b7bd33a..8bfa40dc5f338 100644 --- a/docs/templates.txt +++ b/docs/templates.txt @@ -1301,9 +1301,14 @@ unordered_list Recursively takes a self-nested list and returns an HTML unordered list -- WITHOUT opening and closing
      tags. +**Changed in Django development version** + +The format accepted by ``unordered_list`` has changed to an easier to +understand format. + The list is assumed to be in the proper format. For example, if ``var`` contains -``['States', [['Kansas', [['Lawrence', []], ['Topeka', []]]], ['Illinois', []]]]``, -then ``{{ var|unordered_list }}`` would return:: +``['States', ['Kansas', ['Lawrence', 'Topeka'], 'Illinois']]``, then +``{{ var|unordered_list }}`` would return::
    • States
        @@ -1317,6 +1322,9 @@ then ``{{ var|unordered_list }}`` would return::
    • +Note: the previous more restrictive and verbose format is still supported: +``['States', [['Kansas', [['Lawrence', []], ['Topeka', []]]], ['Illinois', []]]]``, + upper ~~~~~ diff --git a/docs/testing.txt b/docs/testing.txt index e27479cc34f6f..22a7e48a7aa87 100644 --- a/docs/testing.txt +++ b/docs/testing.txt @@ -577,13 +577,25 @@ Specifically, a ``Response`` object has the following attributes: =============== ========================================================== Attribute Description =============== ========================================================== - ``status_code`` The HTTP status of the response, as an integer. See - RFC2616_ for a full list of HTTP status codes. + ``client`` The test client that was used to make the request that + resulted in the response. ``content`` The body of the response, as a string. This is the final page content as rendered by the view, or any error message (such as the URL for a 302 redirect). + ``context`` The template ``Context`` instance that was used to render + the template that produced the response content. + + If the rendered page used multiple templates, then + ``context`` will be a list of ``Context`` + objects, in the order in which they were rendered. + + ``request`` The request data that stimulated the response. + + ``status_code`` The HTTP status of the response, as an integer. See + RFC2616_ for a full list of HTTP status codes. + ``template`` The ``Template`` instance that was used to render the final content. Use ``template.name`` to get the template's file name, if the template was loaded from a @@ -594,13 +606,6 @@ Specifically, a ``Response`` object has the following attributes: using `template inheritance`_ -- then ``template`` will be a list of ``Template`` instances, in the order in which they were rendered. - - ``context`` The template ``Context`` instance that was used to render - the template that produced the response content. - - As with ``template``, if the rendered page used multiple - templates, then ``context`` will be a list of ``Context`` - objects, in the order in which they were rendered. =============== ========================================================== .. _RFC2616: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html @@ -826,10 +831,10 @@ useful for testing Web applications: Asserts 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)`` +``assertRedirects(response, expected_url, status_code=302, target_status_code=200)`` Asserts that the response return a ``status_code`` redirect status, - it redirected to ``expected_path`` and the subsequent page was received with - ``target_status_code``. + it redirected to ``expected_url`` (including any GET data), and the subsequent + page was received with ``target_status_code``. ``assertTemplateUsed(response, template_name)`` Asserts that the template with the given name was used in rendering the diff --git a/docs/tutorial01.txt b/docs/tutorial01.txt index 65ea7503d7899..77b5b11103bef 100644 --- a/docs/tutorial01.txt +++ b/docs/tutorial01.txt @@ -259,7 +259,7 @@ These concepts are represented by simple Python classes. Edit the choice = models.CharField(max_length=200) votes = models.IntegerField() -.. adminition:: Errors about ``max_length`` +.. admonition:: Errors about ``max_length`` If Django gives you an error message saying that ``max_length`` is not a valid argument, you're most likely using an old version of @@ -383,7 +383,7 @@ Note the following: the SQL to the database. If you're interested, also run the following commands: - * ``python manage.py validate polls`` -- Checks for any errors in the + * ``python manage.py validate`` -- Checks for any errors in the construction of your models. * ``python manage.py sqlcustom polls`` -- Outputs any custom SQL statements diff --git a/tests/modeltests/test_client/models.py b/tests/modeltests/test_client/models.py index e1f3987847ac3..2bc1dfe02bf6a 100644 --- a/tests/modeltests/test_client/models.py +++ b/tests/modeltests/test_client/models.py @@ -86,6 +86,13 @@ def test_redirect(self): # Check that the response was a 302 (redirect) self.assertRedirects(response, '/test_client/get_view/') + + def test_redirect_with_query(self): + "GET a URL that redirects with given GET parameters" + response = self.client.get('/test_client/redirect_view/', {'var': 'value'}) + + # Check if parameters are intact + self.assertRedirects(response, '/test_client/get_view/?var=value') def test_permanent_redirect(self): "GET a URL that redirects permanently elsewhere" @@ -224,10 +231,11 @@ def test_view_with_login(self): # Get the page without logging in. Should result in 302. response = self.client.get('/test_client/login_protected_view/') - self.assertRedirects(response, '/accounts/login/') + self.assertRedirects(response, '/accounts/login/?next=/test_client/login_protected_view/') # Log in - self.client.login(username='testclient', password='password') + login = self.client.login(username='testclient', password='password') + self.assertTrue(login, 'Could not log in') # Request a page that requires a login response = self.client.get('/test_client/login_protected_view/') @@ -261,7 +269,7 @@ def test_logout(self): # Request a page that requires a login response = self.client.get('/test_client/login_protected_view/') - self.assertRedirects(response, '/accounts/login/') + self.assertRedirects(response, '/accounts/login/?next=/test_client/login_protected_view/') def test_session_modifying_view(self): "Request a page that modifies the session" diff --git a/tests/modeltests/test_client/views.py b/tests/modeltests/test_client/views.py index 81b4a2f283030..e2a9081fb2b30 100644 --- a/tests/modeltests/test_client/views.py +++ b/tests/modeltests/test_client/views.py @@ -1,4 +1,5 @@ from xml.dom.minidom import parseString + from django.core.mail import EmailMessage, SMTPConnection from django.template import Context, Template from django.http import HttpResponse, HttpResponseRedirect, HttpResponseNotFound @@ -11,7 +12,7 @@ def get_view(request): "A simple view that expects a GET request, and returns a rendered template" t = Template('This is a test. {{ var }} is the value.', name='GET Template') c = Context({'var': request.GET.get('var', 42)}) - + return HttpResponse(t.render(c)) def post_view(request): @@ -28,9 +29,9 @@ def post_view(request): else: t = Template('Viewing GET page.', name='Empty GET Template') c = Context() - + return HttpResponse(t.render(c)) - + def raw_post_view(request): """A view which expects raw XML to be posted and returns content extracted from the XML""" @@ -48,7 +49,12 @@ def raw_post_view(request): def redirect_view(request): "A view that redirects all requests to the GET view" - return HttpResponseRedirect('/test_client/get_view/') + if request.GET: + from urllib import urlencode + query = '?' + urlencode(request.GET, True) + else: + query = '' + return HttpResponseRedirect('/test_client/get_view/' + query) def double_redirect_view(request): "A view that redirects all requests to a redirection view" @@ -72,7 +78,7 @@ class TestForm(Form): value = fields.IntegerField() single = fields.ChoiceField(choices=TestChoices) multi = fields.MultipleChoiceField(choices=TestChoices) - + def form_view(request): "A view that tests a simple form" if request.method == 'POST': @@ -87,7 +93,7 @@ def form_view(request): form = TestForm(request.GET) t = Template('Viewing base form. {{ form }}.', name='Form GET Template') c = Context({'form': form}) - + return HttpResponse(t.render(c)) def form_view_with_template(request): @@ -101,26 +107,26 @@ def form_view_with_template(request): else: form = TestForm() message = 'GET form page' - return render_to_response('form_view.html', - { + 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') c = Context({'user': request.user}) - + return HttpResponse(t.render(c)) login_protected_view = login_required(login_protected_view) def session_view(request): "A view that modifies the session" request.session['tobacconist'] = 'hovercraft' - - t = Template('This is a view that modifies the session.', + + t = Template('This is a view that modifies the session.', name='Session Modifying View Template') c = Context() return HttpResponse(t.render(c)) @@ -131,25 +137,25 @@ def broken_view(request): def mail_sending_view(request): EmailMessage( - "Test message", - "This is a test email", - "from@example.com", + "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 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 Test message', + 'This is the second test email', + 'from@example.com', ['second@example.com', 'third@example.com']) - + c = SMTPConnection() c.send_messages([m1,m2]) - + return HttpResponse("Mail sent") diff --git a/tests/regressiontests/defaultfilters/tests.py b/tests/regressiontests/defaultfilters/tests.py index a1efae66f66a0..9482f1cc9fda6 100644 --- a/tests/regressiontests/defaultfilters/tests.py +++ b/tests/regressiontests/defaultfilters/tests.py @@ -266,6 +266,22 @@ >>> slice_(u'abcdefg', u'0::2') u'aceg' +>>> unordered_list([u'item 1', u'item 2']) +u'\t
    • item 1
    • \n\t
    • item 2
    • ' + +>>> unordered_list([u'item 1', [u'item 1.1']]) +u'\t
    • item 1\n\t
        \n\t\t
      • item 1.1
      • \n\t
      \n\t
    • ' + +>>> unordered_list([u'item 1', [u'item 1.1', u'item1.2'], u'item 2']) +u'\t
    • item 1\n\t
        \n\t\t
      • item 1.1
      • \n\t\t
      • item1.2
      • \n\t
      \n\t
    • \n\t
    • item 2
    • ' + +>>> unordered_list([u'item 1', [u'item 1.1', [u'item 1.1.1', [u'item 1.1.1.1']]]]) +u'\t
    • item 1\n\t
        \n\t\t
      • item 1.1\n\t\t
          \n\t\t\t
        • item 1.1.1\n\t\t\t
            \n\t\t\t\t
          • item 1.1.1.1
          • \n\t\t\t
          \n\t\t\t
        • \n\t\t
        \n\t\t
      • \n\t
      \n\t
    • ' + +>>> unordered_list(['States', ['Kansas', ['Lawrence', 'Topeka'], 'Illinois']]) +u'\t
    • States\n\t
        \n\t\t
      • Kansas\n\t\t
          \n\t\t\t
        • Lawrence
        • \n\t\t\t
        • Topeka
        • \n\t\t
        \n\t\t
      • \n\t\t
      • Illinois
      • \n\t
      \n\t
    • ' + +# Old format for unordered lists should still work >>> unordered_list([u'item 1', []]) u'\t
    • item 1
    • ' @@ -275,6 +291,9 @@ >>> unordered_list([u'item 1', [[u'item 1.1', []], [u'item 1.2', []]]]) u'\t
    • item 1\n\t
        \n\t\t
      • item 1.1
      • \n\t\t
      • item 1.2
      • \n\t
      \n\t
    • ' +>>> unordered_list(['States', [['Kansas', [['Lawrence', []], ['Topeka', []]]], ['Illinois', []]]]) +u'\t
    • States\n\t
        \n\t\t
      • Kansas\n\t\t
          \n\t\t\t
        • Lawrence
        • \n\t\t\t
        • Topeka
        • \n\t\t
        \n\t\t
      • \n\t\t
      • Illinois
      • \n\t
      \n\t
    • ' + >>> add(u'1', u'2') 3 diff --git a/tests/regressiontests/test_client_regress/fixtures/testdata.json b/tests/regressiontests/test_client_regress/fixtures/testdata.json new file mode 100644 index 0000000000000..5c9e415240833 --- /dev/null +++ b/tests/regressiontests/test_client_regress/fixtures/testdata.json @@ -0,0 +1,20 @@ +[ + { + "pk": "1", + "model": "auth.user", + "fields": { + "username": "testclient", + "first_name": "Test", + "last_name": "Client", + "is_active": true, + "is_superuser": false, + "is_staff": false, + "last_login": "2006-12-17 07:03:31", + "groups": [], + "user_permissions": [], + "password": "sha1$6efc0$f93efe9fd7542f25a7be94871ea45aa95de57161", + "email": "testclient@example.com", + "date_joined": "2006-12-17 07:03:31" + } + } +] \ No newline at end of file diff --git a/tests/regressiontests/test_client_regress/models.py b/tests/regressiontests/test_client_regress/models.py index e17770cd03964..a15e4f15ecbce 100644 --- a/tests/regressiontests/test_client_regress/models.py +++ b/tests/regressiontests/test_client_regress/models.py @@ -75,7 +75,7 @@ def test_single_context(self): 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'") + self.assertEquals(str(e), "Template 'Empty POST Template' was not a template used to render the response. Actual template(s) used: Empty GET Template") def test_multiple_context(self): "Template assertions work when there are multiple contexts" @@ -101,7 +101,7 @@ def test_multiple_context(self): 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") + self.assertEquals(str(e), "Template 'Valid POST Template' was not a template used to render the response. Actual template(s) used: form_view.html, base.html") class AssertRedirectsTests(TestCase): def test_redirect_page(self): @@ -112,6 +112,14 @@ def test_redirect_page(self): self.assertRedirects(response, '/test_client/get_view/') except AssertionError, e: self.assertEquals(str(e), "Response didn't redirect as expected: Response code was 301 (expected 302)") + + def test_lost_query(self): + "An assertion is raised if the redirect location doesn't preserve GET parameters" + response = self.client.get('/test_client/redirect_view/', {'var': 'value'}) + try: + self.assertRedirects(response, '/test_client/get_view/') + except AssertionError, e: + self.assertEquals(str(e), "Response redirected to '/test_client/get_view/?var=value', expected '/test_client/get_view/'") def test_incorrect_target(self): "An assertion is raised if the response redirects to another target" @@ -203,8 +211,29 @@ def test_unknown_error(self): 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.'])") + + def test_unknown_nonfield_error(self): + """ + Checks that an assertion is raised if the form's non field errors + 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', None, 'Some error.') + except AssertionError, e: + self.assertEqual(str(e), "The form 'form' in context 0 does not contain the non-field error 'Some error.' (actual errors: )") -class AssertFileUploadTests(TestCase): +class FileUploadTests(TestCase): def test_simple_upload(self): fd = open(os.path.join(os.path.dirname(__file__), "views.py")) post_data = { @@ -213,3 +242,22 @@ def test_simple_upload(self): } response = self.client.post('/test_client_regress/file_upload/', post_data) self.assertEqual(response.status_code, 200) + +class LoginTests(TestCase): + fixtures = ['testdata'] + + def test_login_different_client(self): + "Check that using a different test client doesn't violate authentication" + + # Create a second client, and log in. + c = Client() + login = c.login(username='testclient', password='password') + self.assertTrue(login, 'Could not log in') + + # Get a redirection page with the second client. + response = c.get("/test_client_regress/login_protected_redirect_view/") + + # At this points, the self.client isn't logged in. + # Check that assertRedirects uses the original client, not the + # default client. + self.assertRedirects(response, "/test_client_regress/get_view/") diff --git a/tests/regressiontests/test_client_regress/urls.py b/tests/regressiontests/test_client_regress/urls.py index 160f9f992d8ab..e771707f61db6 100644 --- a/tests/regressiontests/test_client_regress/urls.py +++ b/tests/regressiontests/test_client_regress/urls.py @@ -4,4 +4,6 @@ urlpatterns = patterns('', (r'^no_template_view/$', views.no_template_view), (r'^file_upload/$', views.file_upload_view), + (r'^get_view/$', views.get_view), + (r'^login_protected_redirect_view/$', views.login_protected_redirect_view) ) diff --git a/tests/regressiontests/test_client_regress/views.py b/tests/regressiontests/test_client_regress/views.py index d3fbcf844819e..75efd212e12b3 100644 --- a/tests/regressiontests/test_client_regress/views.py +++ b/tests/regressiontests/test_client_regress/views.py @@ -1,5 +1,6 @@ +from django.contrib.auth.decorators import login_required from django.core.mail import EmailMessage, SMTPConnection -from django.http import HttpResponse, HttpResponseServerError +from django.http import HttpResponse, HttpResponseRedirect, HttpResponseServerError from django.shortcuts import render_to_response def no_template_view(request): @@ -18,3 +19,12 @@ def file_upload_view(request): else: return HttpResponseServerError() +def get_view(request): + "A simple login protected view" + return HttpResponse("Hello world") +get_view = login_required(get_view) + +def login_protected_redirect_view(request): + "A view that redirects all requests to the GET view" + return HttpResponseRedirect('/test_client_regress/get_view/') +login_protected_redirect_view = login_required(login_protected_redirect_view) \ No newline at end of file