diff options
| author | Honza Král <honza.kral@gmail.com> | 2009-12-07 01:41:26 +0000 |
|---|---|---|
| committer | Honza Král <honza.kral@gmail.com> | 2009-12-07 01:41:26 +0000 |
| commit | 3b895d4a9a68a841ec517b84451da496bb258b3f (patch) | |
| tree | 1913ed9144ff29b5cb359822c7b6a4eecd3ba0fb /tests | |
| parent | 30ea350dabad28b0e524feabce434d446e013d6f (diff) | |
[soc2009/model-validation] Merged to trunk at r11791
git-svn-id: http://code.djangoproject.com/svn/django/branches/soc2009/model-validation@11798 bcc190cf-cafb-0310-a4f2-bffc1f526a37
Diffstat (limited to 'tests')
17 files changed, 342 insertions, 4 deletions
diff --git a/tests/modeltests/model_package/tests.py b/tests/modeltests/model_package/tests.py index 6e8c158a68..7fd4b6f679 100644 --- a/tests/modeltests/model_package/tests.py +++ b/tests/modeltests/model_package/tests.py @@ -1,4 +1,13 @@ -""" +from django.db import models + +class Advertisment(models.Model): + customer = models.CharField(max_length=100) + publications = models.ManyToManyField("model_package.Publication", null=True, blank=True) + + class Meta: + app_label = 'model_package' + +__test__ = {'API_TESTS': """ >>> from models.publication import Publication >>> from models.article import Article >>> from django.contrib.auth.views import Site @@ -19,7 +28,6 @@ >>> a.save() >>> a.publications.add(p) >>> a.sites.add(current_site) ->>> a.save() >>> a = Article.objects.get(id=1) >>> a @@ -29,6 +37,19 @@ >>> a.sites.count() 1 -""" +# Regression for #12248 - Models can exist in the test package, too + +>>> ad = Advertisment(customer="Lawrence Journal-World") +>>> ad.save() +>>> ad.publications.add(p) + +>>> ad = Advertisment.objects.get(id=1) +>>> ad +<Advertisment: Advertisment object> + +>>> ad.publications.count() +1 + +"""} diff --git a/tests/regressiontests/admin_validation/models.py b/tests/regressiontests/admin_validation/models.py index 5506114841..eb53a9dd6e 100644 --- a/tests/regressiontests/admin_validation/models.py +++ b/tests/regressiontests/admin_validation/models.py @@ -26,6 +26,21 @@ class TwoAlbumFKAndAnE(models.Model): e = models.CharField(max_length=1) +class Author(models.Model): + name = models.CharField(max_length=100) + + +class Book(models.Model): + name = models.CharField(max_length=100) + subtitle = models.CharField(max_length=100) + price = models.FloatField() + authors = models.ManyToManyField(Author, through='AuthorsBooks') + + +class AuthorsBooks(models.Model): + author = models.ForeignKey(Author) + book = models.ForeignKey(Book) + __test__ = {'API_TESTS':""" @@ -95,4 +110,48 @@ Exception: <class 'regressiontests.admin_validation.models.TwoAlbumFKAndAnE'> ha >>> validate_inline(TwoAlbumFKAndAnEInline, None, Album) +# Regression test for #12203/#12237 - Fail more gracefully when a M2M field that +# specifies the 'through' option is included in the 'fields' or the 'fieldsets' +# ModelAdmin options. + +>>> class BookAdmin(admin.ModelAdmin): +... fields = ['authors'] + +>>> validate(BookAdmin, Book) +Traceback (most recent call last): + ... +ImproperlyConfigured: 'BookAdmin.fields' can't include the ManyToManyField field 'authors' because 'authors' manually specifies a 'through' model. + +>>> class FieldsetBookAdmin(admin.ModelAdmin): +... fieldsets = ( +... ('Header 1', {'fields': ('name',)}), +... ('Header 2', {'fields': ('authors',)}), +... ) + +>>> validate(FieldsetBookAdmin, Book) +Traceback (most recent call last): + ... +ImproperlyConfigured: 'FieldsetBookAdmin.fieldsets[1][1]['fields']' can't include the ManyToManyField field 'authors' because 'authors' manually specifies a 'through' model. + +>>> class NestedFieldsetAdmin(admin.ModelAdmin): +... fieldsets = ( +... ('Main', {'fields': ('price', ('name', 'subtitle'))}), +... ) + +>>> validate(NestedFieldsetAdmin, Book) + +# Regression test for #12209 -- If the explicitly provided through model +# is specified as a string, the admin should still be able use +# Model.m2m_field.through + +>>> class AuthorsInline(admin.TabularInline): +... model = Book.authors.through + +>>> class BookAdmin(admin.ModelAdmin): +... inlines = [AuthorsInline] + +# If the through model is still a string (and hasn't been resolved to a model) +# the validation will fail. +>>> validate(BookAdmin, Book) + """} diff --git a/tests/regressiontests/bash_completion/__init__.py b/tests/regressiontests/bash_completion/__init__.py new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/tests/regressiontests/bash_completion/__init__.py @@ -0,0 +1 @@ + diff --git a/tests/regressiontests/bash_completion/management/__init__.py b/tests/regressiontests/bash_completion/management/__init__.py new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/tests/regressiontests/bash_completion/management/__init__.py @@ -0,0 +1 @@ + diff --git a/tests/regressiontests/bash_completion/management/commands/__init__.py b/tests/regressiontests/bash_completion/management/commands/__init__.py new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/tests/regressiontests/bash_completion/management/commands/__init__.py @@ -0,0 +1 @@ + diff --git a/tests/regressiontests/bash_completion/management/commands/test_command.py b/tests/regressiontests/bash_completion/management/commands/test_command.py new file mode 100644 index 0000000000..5cb8820a8f --- /dev/null +++ b/tests/regressiontests/bash_completion/management/commands/test_command.py @@ -0,0 +1,14 @@ +import sys, os +from optparse import OptionParser, make_option + +from django.core.management.base import BaseCommand + + +class Command(BaseCommand): + option_list = BaseCommand.option_list + ( + make_option("--list", action="store_true", dest="list", + help="Print all options"), + ) + + def handle(self, *args, **options): + pass diff --git a/tests/regressiontests/bash_completion/models.py b/tests/regressiontests/bash_completion/models.py new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/tests/regressiontests/bash_completion/models.py @@ -0,0 +1 @@ + diff --git a/tests/regressiontests/bash_completion/tests.py b/tests/regressiontests/bash_completion/tests.py new file mode 100644 index 0000000000..24c8b1d3f3 --- /dev/null +++ b/tests/regressiontests/bash_completion/tests.py @@ -0,0 +1,87 @@ +""" +A series of tests to establish that the command-line bash completion works. +""" +import os +import unittest +import sys +import StringIO + +from django.conf import settings +from django.core.management import ManagementUtility + +class BashCompletionTests(unittest.TestCase): + """ + Testing the Python level bash completion code. + This requires setting up the environment as if we got passed data + from bash. + """ + + def setUp(self): + self.old_DJANGO_AUTO_COMPLETE = os.environ.get('DJANGO_AUTO_COMPLETE') + os.environ['DJANGO_AUTO_COMPLETE'] = '1' + self.output = StringIO.StringIO() + self.old_stdout = sys.stdout + sys.stdout = self.output + + def tearDown(self): + sys.stdout = self.old_stdout + if self.old_DJANGO_AUTO_COMPLETE: + os.environ['DJANGO_AUTO_COMPLETE'] = self.old_DJANGO_AUTO_COMPLETE + else: + del os.environ['DJANGO_AUTO_COMPLETE'] + + def _user_input(self, input_str): + os.environ['COMP_WORDS'] = input_str + os.environ['COMP_CWORD'] = str(len(input_str.split()) - 1) + sys.argv = input_str.split(' ') + + def _run_autocomplete(self): + util = ManagementUtility(argv=sys.argv) + try: + util.autocomplete() + except SystemExit: + pass + return self.output.getvalue().strip().split('\n') + + def test_django_admin_py(self): + "django_admin.py will autocomplete option flags" + self._user_input('django-admin.py sqlall --v') + output = self._run_autocomplete() + self.assertEqual(output, ['--verbosity=']) + + def test_manage_py(self): + "manage.py will autocomplete option flags" + self._user_input('manage.py sqlall --v') + output = self._run_autocomplete() + self.assertEqual(output, ['--verbosity=']) + + def test_custom_command(self): + "A custom command can autocomplete option flags" + self._user_input('django-admin.py test_command --l') + output = self._run_autocomplete() + self.assertEqual(output, ['--list']) + + def test_subcommands(self): + "Subcommands can be autocompleted" + self._user_input('django-admin.py sql') + output = self._run_autocomplete() + self.assertEqual(output, ['sql sqlall sqlclear sqlcustom sqlflush sqlindexes sqlinitialdata sqlreset sqlsequencereset']) + + def test_help(self): + "No errors, just an empty list if there are no autocomplete options" + self._user_input('django-admin.py help --') + output = self._run_autocomplete() + self.assertEqual(output, ['']) + + def test_runfcgi(self): + "Command arguments will be autocompleted" + self._user_input('django-admin.py runfcgi h') + output = self._run_autocomplete() + self.assertEqual(output, ['host=']) + + def test_app_completion(self): + "Application names will be autocompleted for an AppCommand" + self._user_input('django-admin.py sqlall a') + output = self._run_autocomplete() + app_labels = [name.split('.')[-1] for name in settings.INSTALLED_APPS] + self.assertEqual(output, sorted(label for label in app_labels if label.startswith('a'))) diff --git a/tests/regressiontests/defer_regress/models.py b/tests/regressiontests/defer_regress/models.py index da9822ab88..a1cd19788d 100644 --- a/tests/regressiontests/defer_regress/models.py +++ b/tests/regressiontests/defer_regress/models.py @@ -115,6 +115,23 @@ u'c1' >>> results[0].second_child.name u'c2' +# Test for #12163 - Pickling error saving session with unsaved model instances. +>>> from django.contrib.sessions.backends.db import SessionStore +>>> SESSION_KEY = '2b1189a188b44ad18c35e1baac6ceead' +>>> item = Item() +>>> item._deferred +False +>>> s = SessionStore(SESSION_KEY) +>>> s.clear() +>>> s['item'] = item +>>> s.save() +>>> s = SessionStore(SESSION_KEY) +>>> s.modified = True +>>> s.save() +>>> i2 = s['item'] +>>> i2._deferred # Item must still be non-deferred +False + # Finally, we need to flush the app cache for the defer module. # Using only/defer creates some artifical entries in the app cache # that messes up later tests. Purge all entries, just to be sure. diff --git a/tests/regressiontests/forms/fields.py b/tests/regressiontests/forms/fields.py index b02b4612a2..eeff56761d 100644 --- a/tests/regressiontests/forms/fields.py +++ b/tests/regressiontests/forms/fields.py @@ -1,4 +1,29 @@ # -*- coding: utf-8 -*- +""" +########## +# Fields # +########## + +Each Field class does some sort of validation. Each Field has a clean() method, +which either raises django.forms.ValidationError or returns the "clean" +data -- usually a Unicode object, but, in some rare cases, a list. + +Each Field's __init__() takes at least these parameters: + required -- Boolean that specifies whether the field is required. + True by default. + widget -- A Widget class, or instance of a Widget class, that should be + used for this Field when displaying it. Each Field has a default + Widget that it'll use if you don't specify this. In most cases, + the default widget is TextInput. + label -- A verbose name for this field, for use in displaying this field in + a form. By default, Django will use a "pretty" version of the form + field name, if the Field is part of a Form. + initial -- A value to use in this Field's initial display. This value is + *not* used as a fallback if data isn't given. + +Other than that, the Field subclasses have class-specific options for +__init__(). For example, CharField has a max_length option. +""" import datetime import time import re diff --git a/tests/regressiontests/forms/regressions.py b/tests/regressiontests/forms/regressions.py index 51aa41d2fb..9471932057 100644 --- a/tests/regressiontests/forms/regressions.py +++ b/tests/regressiontests/forms/regressions.py @@ -102,4 +102,34 @@ u'<ul class="errorlist"><li>(Hidden field data) This field is required.</li></ul >>> f.as_table() u'<tr><td colspan="2"><ul class="errorlist"><li>(Hidden field data) This field is required.</li></ul><input type="hidden" name="data" id="id_data" /></td></tr>' +################################################### +# Tests for XSS vulnerabilities in error messages # +################################################### + +# The forms layer doesn't escape input values directly because error messages +# might be presented in non-HTML contexts. Instead, the message is just marked +# for escaping by the template engine. So we'll need to construct a little +# silly template to trigger the escaping. + +>>> from django.template import Template, Context +>>> t = Template('{{ form.errors }}') + +>>> class SomeForm(Form): +... field = ChoiceField(choices=[('one', 'One')]) +>>> f = SomeForm({'field': '<script>'}) +>>> t.render(Context({'form': f})) +u'<ul class="errorlist"><li>field<ul class="errorlist"><li>Select a valid choice. <script> is not one of the available choices.</li></ul></li></ul>' + +>>> class SomeForm(Form): +... field = MultipleChoiceField(choices=[('one', 'One')]) +>>> f = SomeForm({'field': ['<script>']}) +>>> t.render(Context({'form': f})) +u'<ul class="errorlist"><li>field<ul class="errorlist"><li>Select a valid choice. <script> is not one of the available choices.</li></ul></li></ul>' + +>>> from regressiontests.forms.models import ChoiceModel +>>> class SomeForm(Form): +... field = ModelMultipleChoiceField(ChoiceModel.objects.all()) +>>> f = SomeForm({'field': ['<script>']}) +>>> t.render(Context({'form': f})) +u'<ul class="errorlist"><li>field<ul class="errorlist"><li>"<script>" is not a valid value for a primary key.</li></ul></li></ul>' """ diff --git a/tests/regressiontests/many_to_one_regress/models.py b/tests/regressiontests/many_to_one_regress/models.py index 3f35bef62c..d491f002c2 100644 --- a/tests/regressiontests/many_to_one_regress/models.py +++ b/tests/regressiontests/many_to_one_regress/models.py @@ -167,4 +167,10 @@ Traceback (most recent call last): ... ValueError: Cannot assign "<Child: Child object>": "Child.parent" must be a "Parent" instance. +# Regression for #12190 -- Should be able to instantiate a FK +# outside of a model, and interrogate its related field. +>>> cat = models.ForeignKey(Category) +>>> cat.rel.get_related_field().name +'id' + """} diff --git a/tests/regressiontests/queries/models.py b/tests/regressiontests/queries/models.py index cf21d5210b..ddac509ede 100644 --- a/tests/regressiontests/queries/models.py +++ b/tests/regressiontests/queries/models.py @@ -755,10 +755,20 @@ Bug #6180, #6203 -- dates with limits and/or counts >>> Item.objects.dates('created', 'day')[0] datetime.datetime(2007, 12, 19, 0, 0) -Bug #7087 -- dates with extra select columns +Bug #7087/#12242 -- dates with extra select columns >>> Item.objects.dates('created', 'day').extra(select={'a': 1}) [datetime.datetime(2007, 12, 19, 0, 0), datetime.datetime(2007, 12, 20, 0, 0)] +>>> Item.objects.extra(select={'a': 1}).dates('created', 'day') +[datetime.datetime(2007, 12, 19, 0, 0), datetime.datetime(2007, 12, 20, 0, 0)] + +>>> name="one" +>>> Item.objects.dates('created', 'day').extra(where=['name=%s'], params=[name]) +[datetime.datetime(2007, 12, 19, 0, 0)] + +>>> Item.objects.extra(where=['name=%s'], params=[name]).dates('created', 'day') +[datetime.datetime(2007, 12, 19, 0, 0)] + Bug #7155 -- nullable dates >>> Item.objects.dates('modified', 'day') [datetime.datetime(2007, 12, 19, 0, 0)] diff --git a/tests/regressiontests/urlpatterns_reverse/middleware.py b/tests/regressiontests/urlpatterns_reverse/middleware.py new file mode 100644 index 0000000000..7bb9aa924f --- /dev/null +++ b/tests/regressiontests/urlpatterns_reverse/middleware.py @@ -0,0 +1,7 @@ +from django.core.urlresolvers import set_urlconf + +import urlconf_inner + +class ChangeURLconfMiddleware(object): + def process_request(self, request): + request.urlconf = urlconf_inner.__name__ diff --git a/tests/regressiontests/urlpatterns_reverse/tests.py b/tests/regressiontests/urlpatterns_reverse/tests.py index d4f281ba81..34c9db25d3 100644 --- a/tests/regressiontests/urlpatterns_reverse/tests.py +++ b/tests/regressiontests/urlpatterns_reverse/tests.py @@ -16,11 +16,16 @@ ImproperlyConfigured: The included urlconf regressiontests.urlpatterns_reverse.n import unittest +from django.conf import settings from django.core.urlresolvers import reverse, resolve, NoReverseMatch, Resolver404 from django.http import HttpResponseRedirect, HttpResponsePermanentRedirect from django.shortcuts import redirect from django.test import TestCase +import urlconf_outer +import urlconf_inner +import middleware + test_data = ( ('places', '/places/3/', [3], {}), ('places', '/places/3/', ['3'], {}), @@ -239,3 +244,35 @@ class NamespaceTests(TestCase): self.assertEquals('/other1/inner/37/42/', reverse('nodefault:urlobject-view', args=[37,42], current_app='other-ns1')) self.assertEquals('/other1/inner/42/37/', reverse('nodefault:urlobject-view', kwargs={'arg1':42, 'arg2':37}, current_app='other-ns1')) + +class RequestURLconfTests(TestCase): + def setUp(self): + self.root_urlconf = settings.ROOT_URLCONF + self.middleware_classes = settings.MIDDLEWARE_CLASSES + settings.ROOT_URLCONF = urlconf_outer.__name__ + + def tearDown(self): + settings.ROOT_URLCONF = self.root_urlconf + settings.MIDDLEWARE_CLASSES = self.middleware_classes + + def test_urlconf(self): + response = self.client.get('/test/me/') + self.assertEqual(response.status_code, 200) + self.assertEqual(response.content, 'outer:/test/me/,' + 'inner:/inner_urlconf/second_test/') + response = self.client.get('/inner_urlconf/second_test/') + self.assertEqual(response.status_code, 200) + response = self.client.get('/second_test/') + self.assertEqual(response.status_code, 404) + + def test_urlconf_overridden(self): + settings.MIDDLEWARE_CLASSES += ( + '%s.ChangeURLconfMiddleware' % middleware.__name__, + ) + response = self.client.get('/test/me/') + self.assertEqual(response.status_code, 404) + response = self.client.get('/inner_urlconf/second_test/') + self.assertEqual(response.status_code, 404) + response = self.client.get('/second_test/') + self.assertEqual(response.status_code, 200) + self.assertEqual(response.content, 'outer:,inner:/second_test/') diff --git a/tests/regressiontests/urlpatterns_reverse/urlconf_inner.py b/tests/regressiontests/urlpatterns_reverse/urlconf_inner.py new file mode 100644 index 0000000000..d188e06421 --- /dev/null +++ b/tests/regressiontests/urlpatterns_reverse/urlconf_inner.py @@ -0,0 +1,12 @@ +from django.conf.urls.defaults import * +from django.template import Template, Context +from django.http import HttpResponse + +def inner_view(request): + content = Template('{% url outer as outer_url %}outer:{{ outer_url }},' + '{% url inner as inner_url %}inner:{{ inner_url }}').render(Context()) + return HttpResponse(content) + +urlpatterns = patterns('', + url(r'^second_test/$', inner_view, name='inner'), +)
\ No newline at end of file diff --git a/tests/regressiontests/urlpatterns_reverse/urlconf_outer.py b/tests/regressiontests/urlpatterns_reverse/urlconf_outer.py new file mode 100644 index 0000000000..506e03666e --- /dev/null +++ b/tests/regressiontests/urlpatterns_reverse/urlconf_outer.py @@ -0,0 +1,9 @@ +from django.conf.urls.defaults import * + +import urlconf_inner + + +urlpatterns = patterns('', + url(r'^test/me/$', urlconf_inner.inner_view, name='outer'), + url(r'^inner_urlconf/', include(urlconf_inner.__name__)) +)
\ No newline at end of file |
