diff options
| author | Honza Král <honza.kral@gmail.com> | 2009-12-28 16:35:23 +0000 |
|---|---|---|
| committer | Honza Král <honza.kral@gmail.com> | 2009-12-28 16:35:23 +0000 |
| commit | f911df19a455246198b0c8c81ab96bf2abec04f8 (patch) | |
| tree | 0c0bfb72d622994419492db943d9d37857224f97 /tests/regressiontests | |
| parent | 695de8cc9145e139c2b22e05aa44f5ac04da6f85 (diff) | |
[soc2009/model-validation] Merget to trunk at r12009
git-svn-id: http://code.djangoproject.com/svn/django/branches/soc2009/model-validation@12014 bcc190cf-cafb-0310-a4f2-bffc1f526a37
Diffstat (limited to 'tests/regressiontests')
67 files changed, 2961 insertions, 431 deletions
diff --git a/tests/regressiontests/admin_scripts/tests.py b/tests/regressiontests/admin_scripts/tests.py index e67126e9a7..cdc9556afe 100644 --- a/tests/regressiontests/admin_scripts/tests.py +++ b/tests/regressiontests/admin_scripts/tests.py @@ -23,17 +23,15 @@ class AdminScriptTestCase(unittest.TestCase): settings_file = open(os.path.join(test_dir, filename), 'w') settings_file.write('# Settings file automatically generated by regressiontests.admin_scripts test case\n') exports = [ - 'DATABASE_ENGINE', - 'DATABASE_NAME', - 'DATABASE_USER', - 'DATABASE_PASSWORD', - 'DATABASE_HOST', - 'DATABASE_PORT', + 'DATABASES', 'ROOT_URLCONF' ] for s in exports: - if hasattr(settings,s): - settings_file.write("%s = '%s'\n" % (s, str(getattr(settings,s)))) + if hasattr(settings, s): + o = getattr(settings, s) + if not isinstance(o, dict): + o = "'%s'" % o + settings_file.write("%s = %s\n" % (s, o)) if apps is None: apps = ['django.contrib.auth', 'django.contrib.contenttypes', 'admin_scripts'] @@ -63,23 +61,25 @@ class AdminScriptTestCase(unittest.TestCase): except OSError: pass - def _ext_backend_path(self): + def _ext_backend_paths(self): """ - Returns the path for the external backend package, or None if no - external backend is detected. + Returns the paths for any external backend packages. """ + paths = [] first_package_re = re.compile(r'(^[^\.]+)\.') - result = first_package_re.findall(settings.DATABASE_ENGINE) - if result: - backend_pkg = __import__(result[0]) - backend_dir = os.path.dirname(backend_pkg.__file__) - return os.path.dirname(backend_dir) + for backend in settings.DATABASES.values(): + result = first_package_re.findall(backend['ENGINE']) + if result and result != 'django': + backend_pkg = __import__(result[0]) + backend_dir = os.path.dirname(backend_pkg.__file__) + paths.append(os.path.dirname(backend_dir)) + return paths def run_test(self, script, args, settings_file=None, apps=None): test_dir = os.path.dirname(os.path.dirname(__file__)) project_dir = os.path.dirname(test_dir) base_dir = os.path.dirname(project_dir) - ext_backend_base_dir = self._ext_backend_path() + ext_backend_base_dirs = self._ext_backend_paths() # Remember the old environment old_django_settings_module = os.environ.get('DJANGO_SETTINGS_MODULE', None) @@ -97,8 +97,7 @@ class AdminScriptTestCase(unittest.TestCase): elif 'DJANGO_SETTINGS_MODULE' in os.environ: del os.environ['DJANGO_SETTINGS_MODULE'] python_path = [test_dir, base_dir] - if ext_backend_base_dir: - python_path.append(ext_backend_base_dir) + python_path.extend(ext_backend_base_dirs) os.environ[python_path_var_name] = os.pathsep.join(python_path) # Build the command line @@ -523,7 +522,7 @@ class DjangoAdminSettingsDirectory(AdminScriptTestCase): A series of tests for django-admin.py when the settings file is in a directory. (see #9751). """ - + def setUp(self): self.write_settings('settings', is_dir=True) diff --git a/tests/regressiontests/admin_validation/models.py b/tests/regressiontests/admin_validation/models.py index eb53a9dd6e..682fca66de 100644 --- a/tests/regressiontests/admin_validation/models.py +++ b/tests/regressiontests/admin_validation/models.py @@ -19,6 +19,10 @@ class Song(models.Model): def __unicode__(self): return self.title + def readonly_method_on_model(self): + # does nothing + pass + class TwoAlbumFKAndAnE(models.Model): album1 = models.ForeignKey(Album, related_name="album1_set") @@ -110,6 +114,63 @@ Exception: <class 'regressiontests.admin_validation.models.TwoAlbumFKAndAnE'> ha >>> validate_inline(TwoAlbumFKAndAnEInline, None, Album) +>>> class SongAdmin(admin.ModelAdmin): +... readonly_fields = ("title",) + +>>> validate(SongAdmin, Song) + +>>> def my_function(obj): +... # does nothing +... pass +>>> class SongAdmin(admin.ModelAdmin): +... readonly_fields = (my_function,) + +>>> validate(SongAdmin, Song) + +>>> class SongAdmin(admin.ModelAdmin): +... readonly_fields = ("readonly_method_on_modeladmin",) +... +... def readonly_method_on_modeladmin(self, obj): +... # does nothing +... pass + +>>> validate(SongAdmin, Song) + +>>> class SongAdmin(admin.ModelAdmin): +... readonly_fields = ("readonly_method_on_model",) + +>>> validate(SongAdmin, Song) + +>>> class SongAdmin(admin.ModelAdmin): +... readonly_fields = ("title", "nonexistant") + +>>> validate(SongAdmin, Song) +Traceback (most recent call last): + ... +ImproperlyConfigured: SongAdmin.readonly_fields[1], 'nonexistant' is not a callable or an attribute of 'SongAdmin' or found in the model 'Song'. + +>>> class SongAdmin(admin.ModelAdmin): +... readonly_fields = ("title", "awesome_song") +... fields = ("album", "title", "awesome_song") + +>>> validate(SongAdmin, Song) +Traceback (most recent call last): + ... +ImproperlyConfigured: SongAdmin.readonly_fields[1], 'awesome_song' is not a callable or an attribute of 'SongAdmin' or found in the model 'Song'. + +>>> class SongAdmin(SongAdmin): +... def awesome_song(self, instance): +... if instance.title == "Born to Run": +... return "Best Ever!" +... return "Status unknown." + +>>> validate(SongAdmin, Song) + +>>> class SongAdmin(admin.ModelAdmin): +... readonly_fields = (lambda obj: "test",) + +>>> validate(SongAdmin, Song) + # 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. diff --git a/tests/regressiontests/admin_views/models.py b/tests/regressiontests/admin_views/models.py index 50bc05eef0..ffdc72620e 100644 --- a/tests/regressiontests/admin_views/models.py +++ b/tests/regressiontests/admin_views/models.py @@ -1,10 +1,14 @@ # -*- coding: utf-8 -*- +import datetime import tempfile import os -from django.core.files.storage import FileSystemStorage -from django.db import models + from django.contrib import admin +from django.core.files.storage import FileSystemStorage +from django.contrib.admin.views.main import ChangeList from django.core.mail import EmailMessage +from django.db import models + class Section(models.Model): """ @@ -418,7 +422,61 @@ class CategoryInline(admin.StackedInline): model = Category class CollectorAdmin(admin.ModelAdmin): - inlines = [WidgetInline, DooHickeyInline, GrommetInline, WhatsitInline, FancyDoodadInline, CategoryInline] + inlines = [ + WidgetInline, DooHickeyInline, GrommetInline, WhatsitInline, + FancyDoodadInline, CategoryInline + ] + +class Link(models.Model): + posted = models.DateField( + default=lambda: datetime.date.today() - datetime.timedelta(days=7) + ) + url = models.URLField() + post = models.ForeignKey("Post") + + +class LinkInline(admin.TabularInline): + model = Link + extra = 1 + + readonly_fields = ("posted",) + + +class Post(models.Model): + title = models.CharField(max_length=100) + content = models.TextField() + posted = models.DateField(default=datetime.date.today) + + def awesomeness_level(self): + return "Very awesome." + +class PostAdmin(admin.ModelAdmin): + readonly_fields = ('posted', 'awesomeness_level', 'coolness', lambda obj: "foo") + + inlines = [ + LinkInline + ] + + def coolness(self, instance): + if instance.pk: + return "%d amount of cool." % instance.pk + else: + return "Unkown coolness." + + +class Gadget(models.Model): + name = models.CharField(max_length=100) + + def __unicode__(self): + return self.name + +class CustomChangeList(ChangeList): + def get_query_set(self): + return self.root_query_set.filter(pk=9999) # Does not exist + +class GadgetAdmin(admin.ModelAdmin): + def get_changelist(self, request, **kwargs): + return CustomChangeList admin.site.register(Article, ArticleAdmin) admin.site.register(CustomArticle, CustomArticleAdmin) @@ -443,6 +501,8 @@ admin.site.register(Recommendation, RecommendationAdmin) admin.site.register(Recommender) admin.site.register(Collector, CollectorAdmin) admin.site.register(Category, CategoryAdmin) +admin.site.register(Post, PostAdmin) +admin.site.register(Gadget, GadgetAdmin) # We intentionally register Promo and ChapterXtra1 but not Chapter nor ChapterXtra2. # That way we cover all four cases: diff --git a/tests/regressiontests/admin_views/tests.py b/tests/regressiontests/admin_views/tests.py index 8607589289..320b632537 100644 --- a/tests/regressiontests/admin_views/tests.py +++ b/tests/regressiontests/admin_views/tests.py @@ -10,20 +10,18 @@ from django.contrib.admin.models import LogEntry, DELETION from django.contrib.admin.sites import LOGIN_FORM_KEY from django.contrib.admin.util import quote from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME +from django.utils import formats from django.utils.cache import get_max_age from django.utils.html import escape +from django.utils.translation import get_date_formats # local test models from models import Article, BarAccount, CustomArticle, EmptyModel, \ ExternalSubscriber, FooAccount, Gallery, ModelWithStringPrimaryKey, \ Person, Persona, Picture, Podcast, Section, Subscriber, Vodcast, \ Language, Collector, Widget, Grommet, DooHickey, FancyDoodad, Whatsit, \ - Category + Category, Post -try: - set -except NameError: - from sets import Set as set class AdminViewBasicTest(TestCase): fixtures = ['admin-views-users.xml', 'admin-views-colors.xml', 'admin-views-fabrics.xml'] @@ -610,6 +608,12 @@ class AdminViewStringPrimaryKeyTest(TestCase): def tearDown(self): self.client.logout() + def test_get_history_view(self): + "Retrieving the history for the object using urlencoded form of primary key should work" + response = self.client.get('/test_admin/admin/admin_views/modelwithstringprimarykey/%s/history/' % quote(self.pk)) + self.assertContains(response, escape(self.pk)) + self.failUnlessEqual(response.status_code, 200) + def test_get_change_view(self): "Retrieving the object using urlencoded form of primary key should work" response = self.client.get('/test_admin/admin/admin_views/modelwithstringprimarykey/%s/' % quote(self.pk)) @@ -1151,7 +1155,6 @@ class AdminActionsTest(TestCase): self.assert_('action-checkbox-column' in response.content, "Expected an action-checkbox-column in response") - def test_multiple_actions_form(self): """ Test that actions come from the form whose submit button was pressed (#10618). @@ -1169,6 +1172,62 @@ class AdminActionsTest(TestCase): self.assertEquals(len(mail.outbox), 1) self.assertEquals(mail.outbox[0].subject, 'Greetings from a function action') + def test_user_message_on_none_selected(self): + """ + User should see a warning when 'Go' is pressed and no items are selected. + """ + action_data = { + ACTION_CHECKBOX_NAME: [], + 'action' : 'delete_selected', + 'index': 0, + } + response = self.client.post('/test_admin/admin/admin_views/subscriber/', action_data) + msg = """Items must be selected in order to perform actions on them. No items have been changed.""" + self.assertContains(response, msg) + self.failUnlessEqual(Subscriber.objects.count(), 2) + + def test_user_message_on_no_action(self): + """ + User should see a warning when 'Go' is pressed and no action is selected. + """ + action_data = { + ACTION_CHECKBOX_NAME: [1, 2], + 'action' : '', + 'index': 0, + } + response = self.client.post('/test_admin/admin/admin_views/subscriber/', action_data) + msg = """No action selected.""" + self.assertContains(response, msg) + self.failUnlessEqual(Subscriber.objects.count(), 2) + + +class TestCustomChangeList(TestCase): + fixtures = ['admin-views-users.xml'] + urlbit = 'admin' + + def setUp(self): + result = self.client.login(username='super', password='secret') + self.failUnlessEqual(result, True) + + def tearDown(self): + self.client.logout() + + def test_custom_changelist(self): + """ + Validate that a custom ChangeList class can be used (#9749) + """ + # Insert some data + post_data = {"name": u"First Gadget"} + response = self.client.post('/test_admin/%s/admin_views/gadget/add/' % self.urlbit, post_data) + self.failUnlessEqual(response.status_code, 302) # redirect somewhere + # Hit the page once to get messages out of the queue message list + response = self.client.get('/test_admin/%s/admin_views/gadget/' % self.urlbit) + # Ensure that that data is still not visible on the page + response = self.client.get('/test_admin/%s/admin_views/gadget/' % self.urlbit) + self.failUnlessEqual(response.status_code, 200) + self.assertNotContains(response, 'First Gadget') + + class TestInlineNotEditable(TestCase): fixtures = ['admin-views-users.xml'] @@ -1627,3 +1686,54 @@ class NeverCacheTests(TestCase): "Check the never-cache status of the Javascript i18n view" response = self.client.get('/test_admin/jsi18n/') self.failUnlessEqual(get_max_age(response), None) + + +class ReadonlyTest(TestCase): + fixtures = ['admin-views-users.xml'] + + def setUp(self): + self.client.login(username='super', password='secret') + + def tearDown(self): + self.client.logout() + + def test_readonly_get(self): + response = self.client.get('/test_admin/admin/admin_views/post/add/') + self.assertEqual(response.status_code, 200) + self.assertNotContains(response, 'name="posted"') + # 3 fields + 2 submit buttons + 2 inline management form fields, + 2 + # hidden fields for inlines + 1 field for the inline + self.assertEqual(response.content.count("input"), 10) + self.assertContains(response, formats.localize(datetime.date.today())) + self.assertContains(response, + "<label>Awesomeness level:</label>") + self.assertContains(response, "Very awesome.") + self.assertContains(response, "Unkown coolness.") + self.assertContains(response, "foo") + self.assertContains(response, + formats.localize(datetime.date.today() - datetime.timedelta(days=7)) + ) + + p = Post.objects.create(title="I worked on readonly_fields", content="Its good stuff") + response = self.client.get('/test_admin/admin/admin_views/post/%d/' % p.pk) + self.assertContains(response, "%d amount of cool" % p.pk) + + def test_readonly_post(self): + data = { + "title": "Django Got Readonly Fields", + "content": "This is an incredible development.", + "link_set-TOTAL_FORMS": "1", + "link_set-INITIAL_FORMS": "0", + } + response = self.client.post('/test_admin/admin/admin_views/post/add/', data) + self.assertEqual(response.status_code, 302) + self.assertEqual(Post.objects.count(), 1) + p = Post.objects.get() + self.assertEqual(p.posted, datetime.date.today()) + + data["posted"] = "10-8-1990" # some date that's not today + response = self.client.post('/test_admin/admin/admin_views/post/add/', data) + self.assertEqual(response.status_code, 302) + self.assertEqual(Post.objects.count(), 2) + p = Post.objects.order_by('-id')[0] + self.assertEqual(p.posted, datetime.date.today()) diff --git a/tests/regressiontests/admin_widgets/urls2.py b/tests/regressiontests/admin_widgets/urls2.py index 1ad060cd09..e38ff66b49 100644 --- a/tests/regressiontests/admin_widgets/urls2.py +++ b/tests/regressiontests/admin_widgets/urls2.py @@ -3,5 +3,5 @@ from django.conf.urls.defaults import * import widgetadmin urlpatterns = patterns('', - (r'^deep/down/admin/(.*)', widgetadmin.site.root), + (r'^deep/down/admin/', include(widgetadmin.site.urls)), ) diff --git a/tests/regressiontests/aggregation_regress/models.py b/tests/regressiontests/aggregation_regress/models.py index 4476b86d64..7c51cd17a7 100644 --- a/tests/regressiontests/aggregation_regress/models.py +++ b/tests/regressiontests/aggregation_regress/models.py @@ -1,7 +1,7 @@ # coding: utf-8 import pickle -from django.db import connection, models +from django.db import connection, models, DEFAULT_DB_ALIAS from django.conf import settings try: @@ -97,19 +97,19 @@ __test__ = {'API_TESTS': """ {'pages__sum': 3703} # Annotations get combined with extra select clauses ->>> sorted(Book.objects.all().annotate(mean_auth_age=Avg('authors__age')).extra(select={'manufacture_cost' : 'price * .5'}).get(pk=2).__dict__.items()) +>>> sorted((k,v) for k,v in Book.objects.all().annotate(mean_auth_age=Avg('authors__age')).extra(select={'manufacture_cost' : 'price * .5'}).get(pk=2).__dict__.items() if k != '_state') [('contact_id', 3), ('id', 2), ('isbn', u'067232959'), ('manufacture_cost', ...11.545...), ('mean_auth_age', 45.0), ('name', u'Sams Teach Yourself Django in 24 Hours'), ('pages', 528), ('price', Decimal("23.09")), ('pubdate', datetime.date(2008, 3, 3)), ('publisher_id', 2), ('rating', 3.0)] # Order of the annotate/extra in the query doesn't matter ->>> sorted(Book.objects.all().extra(select={'manufacture_cost' : 'price * .5'}).annotate(mean_auth_age=Avg('authors__age')).get(pk=2).__dict__.items()) +>>> sorted((k,v) for k,v in Book.objects.all().extra(select={'manufacture_cost' : 'price * .5'}).annotate(mean_auth_age=Avg('authors__age')).get(pk=2).__dict__.items()if k != '_state') [('contact_id', 3), ('id', 2), ('isbn', u'067232959'), ('manufacture_cost', ...11.545...), ('mean_auth_age', 45.0), ('name', u'Sams Teach Yourself Django in 24 Hours'), ('pages', 528), ('price', Decimal("23.09")), ('pubdate', datetime.date(2008, 3, 3)), ('publisher_id', 2), ('rating', 3.0)] # Values queries can be combined with annotate and extra ->>> sorted(Book.objects.all().annotate(mean_auth_age=Avg('authors__age')).extra(select={'manufacture_cost' : 'price * .5'}).values().get(pk=2).items()) +>>> sorted((k,v) for k,v in Book.objects.all().annotate(mean_auth_age=Avg('authors__age')).extra(select={'manufacture_cost' : 'price * .5'}).values().get(pk=2).items()if k != '_state') [('contact_id', 3), ('id', 2), ('isbn', u'067232959'), ('manufacture_cost', ...11.545...), ('mean_auth_age', 45.0), ('name', u'Sams Teach Yourself Django in 24 Hours'), ('pages', 528), ('price', Decimal("23.09")), ('pubdate', datetime.date(2008, 3, 3)), ('publisher_id', 2), ('rating', 3.0)] # The order of the (empty) values, annotate and extra clauses doesn't matter ->>> sorted(Book.objects.all().values().annotate(mean_auth_age=Avg('authors__age')).extra(select={'manufacture_cost' : 'price * .5'}).get(pk=2).items()) +>>> sorted((k,v) for k,v in Book.objects.all().values().annotate(mean_auth_age=Avg('authors__age')).extra(select={'manufacture_cost' : 'price * .5'}).get(pk=2).items()if k != '_state') [('contact_id', 3), ('id', 2), ('isbn', u'067232959'), ('manufacture_cost', ...11.545...), ('mean_auth_age', 45.0), ('name', u'Sams Teach Yourself Django in 24 Hours'), ('pages', 528), ('price', Decimal("23.09")), ('pubdate', datetime.date(2008, 3, 3)), ('publisher_id', 2), ('rating', 3.0)] # If the annotation precedes the values clause, it won't be included @@ -250,10 +250,10 @@ FieldError: Cannot resolve keyword 'foo' into field. Choices are: authors, conta >>> out = pickle.dumps(qs) # Then check that the round trip works. ->>> query = qs.query.as_sql()[0] +>>> query = qs.query.get_compiler(qs.db).as_sql()[0] >>> select_fields = qs.query.select_fields >>> query2 = pickle.loads(pickle.dumps(qs)) ->>> query2.query.as_sql()[0] == query +>>> query2.query.get_compiler(query2.db).as_sql()[0] == query True >>> query2.query.select_fields = select_fields @@ -327,7 +327,7 @@ def run_stddev_tests(): Stddev and Variance are not guaranteed to be available for SQLite, and are not available for PostgreSQL before 8.2. """ - if settings.DATABASE_ENGINE == 'sqlite3': + if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] == 'django.db.backends.sqlite3': return False class StdDevPop(object): @@ -380,5 +380,4 @@ if run_stddev_tests(): >>> Book.objects.aggregate(Variance('price', sample=True)) {'price__variance': 700.53...} - """ diff --git a/tests/regressiontests/auth_backends/tests.py b/tests/regressiontests/auth_backends/tests.py deleted file mode 100644 index d22f0bf939..0000000000 --- a/tests/regressiontests/auth_backends/tests.py +++ /dev/null @@ -1,78 +0,0 @@ -try: - set -except NameError: - from sets import Set as set # Python 2.3 fallback - -__test__ = {'API_TESTS': """ ->>> from django.contrib.auth.models import User, Group, Permission, AnonymousUser ->>> from django.contrib.contenttypes.models import ContentType - -# No Permissions assigned yet, should return False except for superuser - ->>> user = User.objects.create_user('test', 'test@example.com', 'test') ->>> user.has_perm("auth.test") -False ->>> user.is_staff=True ->>> user.save() ->>> user.has_perm("auth.test") -False ->>> user.is_superuser=True ->>> user.save() ->>> user.has_perm("auth.test") -True ->>> user.is_staff = False ->>> user.is_superuser = False ->>> user.save() ->>> user.has_perm("auth.test") -False ->>> content_type=ContentType.objects.get_for_model(Group) ->>> perm = Permission.objects.create(name="test", content_type=content_type, codename="test") ->>> user.user_permissions.add(perm) ->>> user.save() - -# reloading user to purge the _perm_cache - ->>> user = User.objects.get(username="test") ->>> user.get_all_permissions() == set([u'auth.test']) -True ->>> user.get_group_permissions() == set([]) -True ->>> user.has_module_perms("Group") -False ->>> user.has_module_perms("auth") -True ->>> perm = Permission.objects.create(name="test2", content_type=content_type, codename="test2") ->>> user.user_permissions.add(perm) ->>> user.save() ->>> perm = Permission.objects.create(name="test3", content_type=content_type, codename="test3") ->>> user.user_permissions.add(perm) ->>> user.save() ->>> user = User.objects.get(username="test") ->>> user.get_all_permissions() == set([u'auth.test2', u'auth.test', u'auth.test3']) -True ->>> user.has_perm('test') -False ->>> user.has_perm('auth.test') -True ->>> user.has_perms(['auth.test2', 'auth.test3']) -True ->>> perm = Permission.objects.create(name="test_group", content_type=content_type, codename="test_group") ->>> group = Group.objects.create(name='test_group') ->>> group.permissions.add(perm) ->>> group.save() ->>> user.groups.add(group) ->>> user = User.objects.get(username="test") ->>> exp = set([u'auth.test2', u'auth.test', u'auth.test3', u'auth.test_group']) ->>> user.get_all_permissions() == exp -True ->>> user.get_group_permissions() == set([u'auth.test_group']) -True ->>> user.has_perms(['auth.test3', 'auth.test_group']) -True - ->>> user = AnonymousUser() ->>> user.has_perm('test') -False ->>> user.has_perms(['auth.test2', 'auth.test3']) -False -"""} diff --git a/tests/regressiontests/backends/tests.py b/tests/regressiontests/backends/tests.py index 628fabf04a..e4fa82403a 100644 --- a/tests/regressiontests/backends/tests.py +++ b/tests/regressiontests/backends/tests.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # Unit and doctests for specific database backends. import unittest -from django.db import backend, connection +from django.db import backend, connection, DEFAULT_DB_ALIAS from django.db.backends.signals import connection_created from django.conf import settings @@ -10,7 +10,7 @@ class Callproc(unittest.TestCase): def test_dbms_session(self): # If the backend is Oracle, test that we can call a standard # stored procedure through our cursor wrapper. - if settings.DATABASE_ENGINE == 'oracle': + if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] == 'django.db.backends.oracle': convert_unicode = backend.convert_unicode cursor = connection.cursor() cursor.callproc(convert_unicode('DBMS_SESSION.SET_IDENTIFIER'), @@ -18,13 +18,13 @@ class Callproc(unittest.TestCase): return True else: return True - + class LongString(unittest.TestCase): def test_long_string(self): # If the backend is Oracle, test that we can save a text longer # than 4000 chars and read it properly - if settings.DATABASE_ENGINE == 'oracle': + if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] == 'django.db.backends.oracle': c = connection.cursor() c.execute('CREATE TABLE ltext ("TEXT" NCLOB)') long_str = ''.join([unicode(x) for x in xrange(4000)]) @@ -64,7 +64,7 @@ __test__ = {'API_TESTS': """ # Unfortunately with sqlite3 the in-memory test database cannot be # closed, and so it cannot be re-opened during testing, and so we # sadly disable this test for now. -if settings.DATABASE_ENGINE != 'sqlite3': +if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] != 'django.db.backends.sqlite3': __test__['API_TESTS'] += """ >>> connection_created.connect(connection_created_test) >>> connection.close() # Ensure the connection is closed diff --git a/tests/regressiontests/datatypes/models.py b/tests/regressiontests/datatypes/models.py index 0ad809a45d..f6cbc9f0f6 100644 --- a/tests/regressiontests/datatypes/models.py +++ b/tests/regressiontests/datatypes/models.py @@ -3,7 +3,7 @@ This is a basic model to test saving and loading boolean and date-related types, which in the past were problematic for some database backends. """ -from django.db import models +from django.db import models, DEFAULT_DB_ALIAS from django.conf import settings class Donut(models.Model): @@ -93,11 +93,11 @@ u'Outstanding' # Regression test for #8354: the MySQL backend should raise an error if given # a timezone-aware datetime object. -if settings.DATABASE_ENGINE == 'mysql': +if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] == 'django.db.backends.mysql': __test__['API_TESTS'] += """ ->>> from django.utils import tzinfo ->>> dt = datetime.datetime(2008, 8, 31, 16, 20, tzinfo=tzinfo.FixedOffset(0)) ->>> d = Donut(name='Bear claw', consumed_at=dt) +>>> from django.utils import tzinfo +>>> dt = datetime.datetime(2008, 8, 31, 16, 20, tzinfo=tzinfo.FixedOffset(0)) +>>> d = Donut(name='Bear claw', consumed_at=dt) >>> d.save() Traceback (most recent call last): .... diff --git a/tests/regressiontests/defer_regress/models.py b/tests/regressiontests/defer_regress/models.py index a1cd19788d..c32f5bb493 100644 --- a/tests/regressiontests/defer_regress/models.py +++ b/tests/regressiontests/defer_regress/models.py @@ -31,6 +31,10 @@ class Leaf(models.Model): def __unicode__(self): return self.name +class ResolveThis(models.Model): + num = models.FloatField() + name = models.CharField(max_length=16) + __test__ = {"regression_tests": """ Deferred fields should really be deferred and not accidentally use the field's default value just because they aren't passed to __init__. @@ -132,11 +136,14 @@ False >>> 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. ->>> from django.db.models.loading import cache ->>> cache.app_models['defer_regress'] = {} +# Regression for #11936 - loading.get_models should not return deferred models by default. +>>> from django.db.models.loading import get_models +>>> sorted(get_models(models.get_app('defer_regress')), key=lambda obj: obj._meta.object_name) +[<class 'regressiontests.defer_regress.models.Child'>, <class 'regressiontests.defer_regress.models.Item'>, <class 'regressiontests.defer_regress.models.Leaf'>, <class 'regressiontests.defer_regress.models.RelatedItem'>, <class 'regressiontests.defer_regress.models.ResolveThis'>] + +>>> sorted(get_models(models.get_app('defer_regress'), include_deferred=True), key=lambda obj: obj._meta.object_name) +[<class 'regressiontests.defer_regress.models.Child'>, <class 'regressiontests.defer_regress.models.Item'>, <class 'regressiontests.defer_regress.models.Item_Deferred_name'>, <class 'regressiontests.defer_regress.models.Item_Deferred_name_other_value_text'>, <class 'regressiontests.defer_regress.models.Item_Deferred_other_value_text_value'>, <class 'regressiontests.defer_regress.models.Item_Deferred_text_value'>, <class 'regressiontests.defer_regress.models.Leaf'>, <class 'regressiontests.defer_regress.models.Leaf_Deferred_name_value'>, <class 'regressiontests.defer_regress.models.Leaf_Deferred_second_child_value'>, <class 'regressiontests.defer_regress.models.Leaf_Deferred_value'>, <class 'regressiontests.defer_regress.models.RelatedItem'>, <class 'regressiontests.defer_regress.models.RelatedItem_Deferred_item_id'>, <class 'regressiontests.defer_regress.models.ResolveThis'>, <class 'regressiontests.defer_regress.models.ResolveThis_Deferred_num'>] """ } + diff --git a/tests/regressiontests/defer_regress/tests.py b/tests/regressiontests/defer_regress/tests.py new file mode 100644 index 0000000000..860c7f8e31 --- /dev/null +++ b/tests/regressiontests/defer_regress/tests.py @@ -0,0 +1,9 @@ +from django.test import TestCase +from models import ResolveThis + +class DeferRegressionTest(TestCase): + def test_resolve_columns(self): + rt = ResolveThis.objects.create(num=5.0, name='Foobar') + qs = ResolveThis.objects.defer('num') + self.assertEqual(1, qs.count()) + self.assertEqual('Foobar', qs[0].name) diff --git a/tests/regressiontests/delete_regress/models.py b/tests/regressiontests/delete_regress/models.py index 93cadc58fa..ff561c9d20 100644 --- a/tests/regressiontests/delete_regress/models.py +++ b/tests/regressiontests/delete_regress/models.py @@ -1,5 +1,5 @@ from django.conf import settings -from django.db import models, backend, connection, transaction +from django.db import models, backend, connection, transaction, DEFAULT_DB_ALIAS from django.db.models import sql, query from django.test import TransactionTestCase @@ -8,17 +8,18 @@ class Book(models.Model): # Can't run this test under SQLite, because you can't # get two connections to an in-memory database. -if settings.DATABASE_ENGINE != 'sqlite3': +if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] != 'django.db.backends.sqlite3': class DeleteLockingTest(TransactionTestCase): def setUp(self): # Create a second connection to the database + conn_settings = settings.DATABASES[DEFAULT_DB_ALIAS] self.conn2 = backend.DatabaseWrapper({ - 'DATABASE_HOST': settings.DATABASE_HOST, - 'DATABASE_NAME': settings.DATABASE_NAME, - 'DATABASE_OPTIONS': settings.DATABASE_OPTIONS, - 'DATABASE_PASSWORD': settings.DATABASE_PASSWORD, - 'DATABASE_PORT': settings.DATABASE_PORT, - 'DATABASE_USER': settings.DATABASE_USER, + 'HOST': conn_settings['HOST'], + 'NAME': conn_settings['NAME'], + 'OPTIONS': conn_settings['OPTIONS'], + 'PASSWORD': conn_settings['PASSWORD'], + 'PORT': conn_settings['PORT'], + 'USER': conn_settings['USER'], 'TIME_ZONE': settings.TIME_ZONE, }) diff --git a/tests/regressiontests/dispatch/tests/test_dispatcher.py b/tests/regressiontests/dispatch/tests/test_dispatcher.py index adf760386c..ad3a05f2cd 100644 --- a/tests/regressiontests/dispatch/tests/test_dispatcher.py +++ b/tests/regressiontests/dispatch/tests/test_dispatcher.py @@ -1,8 +1,8 @@ from django.dispatch import Signal import unittest -import copy import sys import gc +import django.utils.copycompat as copy if sys.platform.startswith('java'): def garbage_collect(): diff --git a/tests/regressiontests/expressions_regress/models.py b/tests/regressiontests/expressions_regress/models.py index 7c9ca61ccc..a517780323 100644 --- a/tests/regressiontests/expressions_regress/models.py +++ b/tests/regressiontests/expressions_regress/models.py @@ -2,7 +2,7 @@ Spanning tests for all the operations that F() expressions can perform. """ from django.conf import settings -from django.db import models +from django.db import models, DEFAULT_DB_ALIAS # # Model for testing arithmetic expressions. @@ -121,7 +121,7 @@ Complex expressions of different connection types are possible. """} # Oracle doesn't support the Bitwise OR operator. -if settings.DATABASE_ENGINE != 'oracle': +if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] != 'django.db.backends.oracle': __test__['API_TESTS'] += """ >>> _ = Number.objects.filter(pk=n.pk).update(integer=42, float=15.5) diff --git a/tests/regressiontests/extra_regress/models.py b/tests/regressiontests/extra_regress/models.py index 5d22d6cc07..d4d7cb86e5 100644 --- a/tests/regressiontests/extra_regress/models.py +++ b/tests/regressiontests/extra_regress/models.py @@ -1,6 +1,7 @@ -import copy import datetime +import django.utils.copycompat as copy + from django.contrib.auth.models import User from django.db import models from django.db.models.query import Q @@ -15,11 +16,13 @@ class RevisionableModel(models.Model): def __unicode__(self): return u"%s (%s, %s)" % (self.title, self.id, self.base.id) - def save(self, force_insert=False, force_update=False): - super(RevisionableModel, self).save(force_insert, force_update) + def save(self, *args, **kwargs): + super(RevisionableModel, self).save(*args, **kwargs) if not self.base: self.base = self - super(RevisionableModel, self).save() + kwargs.pop('force_insert', None) + kwargs.pop('force_update', None) + super(RevisionableModel, self).save(*args, **kwargs) def new_revision(self): new_revision = copy.copy(self) diff --git a/tests/regressiontests/fixtures_regress/fixtures/forward_ref_lookup.json b/tests/regressiontests/fixtures_regress/fixtures/forward_ref_lookup.json new file mode 100644 index 0000000000..fe50c653cc --- /dev/null +++ b/tests/regressiontests/fixtures_regress/fixtures/forward_ref_lookup.json @@ -0,0 +1,32 @@ +[ + { + "pk": "4", + "model": "fixtures_regress.person", + "fields": { + "name": "Neal Stephenson" + } + }, + { + "pk": "2", + "model": "fixtures_regress.store", + "fields": { + "name": "Amazon" + } + }, + { + "pk": "3", + "model": "fixtures_regress.store", + "fields": { + "name": "Borders" + } + }, + { + "pk": 1, + "model": "fixtures_regress.book", + "fields": { + "name": "Cryptonomicon", + "author": ["Neal Stephenson"], + "stores": [["Amazon"], ["Borders"]] + } + } +]
\ No newline at end of file diff --git a/tests/regressiontests/fixtures_regress/fixtures/non_natural_1.json b/tests/regressiontests/fixtures_regress/fixtures/non_natural_1.json new file mode 100644 index 0000000000..4bce792e35 --- /dev/null +++ b/tests/regressiontests/fixtures_regress/fixtures/non_natural_1.json @@ -0,0 +1,25 @@ +[ + { + "pk": 12, + "model": "fixtures_regress.person", + "fields": { + "name": "Greg Egan" + } + }, + { + "pk": 11, + "model": "fixtures_regress.store", + "fields": { + "name": "Angus and Robertson" + } + }, + { + "pk": 10, + "model": "fixtures_regress.book", + "fields": { + "name": "Permutation City", + "author": 12, + "stores": [11] + } + } +]
\ No newline at end of file diff --git a/tests/regressiontests/fixtures_regress/fixtures/non_natural_2.xml b/tests/regressiontests/fixtures_regress/fixtures/non_natural_2.xml new file mode 100644 index 0000000000..280ad3758b --- /dev/null +++ b/tests/regressiontests/fixtures_regress/fixtures/non_natural_2.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="utf-8"?> +<django-objects version="1.0"> + <object pk="22" model="fixtures_regress.person"> + <field type="CharField" name="name">Orson Scott Card</field> + </object> + <object pk="21" model="fixtures_regress.store"> + <field type="CharField" name="name">Collins Bookstore</field> + </object> + <object pk="20" model="fixtures_regress.book"> + <field type="CharField" name="name">Ender's Game</field> + <field to="fixtures_regress.person" name="author" rel="ManyToOneRel">22</field> + <field to="fixtures_regress.store" name="stores" rel="ManyToManyRel"> + <object pk="21"/> + </field> + </object> +</django-objects>
\ No newline at end of file diff --git a/tests/regressiontests/fixtures_regress/models.py b/tests/regressiontests/fixtures_regress/models.py index e33471646c..4294ffb293 100644 --- a/tests/regressiontests/fixtures_regress/models.py +++ b/tests/regressiontests/fixtures_regress/models.py @@ -1,4 +1,4 @@ -from django.db import models +from django.db import models, DEFAULT_DB_ALIAS from django.contrib.auth.models import User from django.conf import settings import os @@ -13,7 +13,7 @@ class Animal(models.Model): specimens = models.Manager() def __unicode__(self): - return self.common_name + return self.name def animal_pre_save_check(signal, sender, instance, **kwargs): "A signal that is used to check the type of data loaded from fixtures" @@ -35,7 +35,8 @@ class Stuff(models.Model): # Oracle doesn't distinguish between None and the empty string. # This hack makes the test case pass using Oracle. name = self.name - if settings.DATABASE_ENGINE == 'oracle' and name == u'': + if (settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] == 'django.db.backends.oracle' + and name == u''): name = None return unicode(name) + u' is owned by ' + unicode(self.owner) @@ -69,10 +70,109 @@ class Article(models.Model): class Widget(models.Model): name = models.CharField(max_length=255) + class Meta: + ordering = ('name',) + + def __unicode__(self): + return self.name + class WidgetProxy(Widget): class Meta: proxy = True +# Check for forward references in FKs and M2Ms with natural keys + +class TestManager(models.Manager): + def get_by_natural_key(self, key): + return self.get(name=key) + +class Store(models.Model): + objects = TestManager() + name = models.CharField(max_length=255) + + class Meta: + ordering = ('name',) + + def __unicode__(self): + return self.name + + def natural_key(self): + return (self.name,) + +class Person(models.Model): + objects = TestManager() + name = models.CharField(max_length=255) + + class Meta: + ordering = ('name',) + + def __unicode__(self): + return self.name + + # Person doesn't actually have a dependency on store, but we need to define + # one to test the behaviour of the dependency resolution algorithm. + def natural_key(self): + return (self.name,) + natural_key.dependencies = ['fixtures_regress.store'] + +class Book(models.Model): + name = models.CharField(max_length=255) + author = models.ForeignKey(Person) + stores = models.ManyToManyField(Store) + + class Meta: + ordering = ('name',) + + def __unicode__(self): + return u'%s by %s (available at %s)' % ( + self.name, + self.author.name, + ', '.join(s.name for s in self.stores.all()) + ) + +# ome models with pathological circular dependencies +class Circle1(models.Model): + name = models.CharField(max_length=255) + def natural_key(self): + return self.name + natural_key.dependencies = ['fixtures_regress.circle2'] + +class Circle2(models.Model): + name = models.CharField(max_length=255) + def natural_key(self): + return self.name + natural_key.dependencies = ['fixtures_regress.circle1'] + +class Circle3(models.Model): + name = models.CharField(max_length=255) + def natural_key(self): + return self.name + natural_key.dependencies = ['fixtures_regress.circle3'] + +class Circle4(models.Model): + name = models.CharField(max_length=255) + def natural_key(self): + return self.name + natural_key.dependencies = ['fixtures_regress.circle5'] + +class Circle5(models.Model): + name = models.CharField(max_length=255) + def natural_key(self): + return self.name + natural_key.dependencies = ['fixtures_regress.circle6'] + +class Circle6(models.Model): + name = models.CharField(max_length=255) + def natural_key(self): + return self.name + natural_key.dependencies = ['fixtures_regress.circle4'] + +class ExternalDependency(models.Model): + name = models.CharField(max_length=255) + def natural_key(self): + return self.name + natural_key.dependencies = ['fixtures_regress.book'] + __test__ = {'API_TESTS':""" >>> from django.core import management @@ -179,8 +279,7 @@ Weight = 1.2 (<type 'float'>) [{"pk": 1, "model": "fixtures_regress.animal", "fields": {"count": 3, "weight": 1.2, "name": "Lion", "latin_name": "Panthera leo"}}, {"pk": 2, "model": "fixtures_regress.animal", "fields": {"count": 2, "weight": 2.29..., "name": "Platypus", "latin_name": "Ornithorhynchus anatinus"}}, {"pk": 10, "model": "fixtures_regress.animal", "fields": {"count": 42, "weight": 1.2, "name": "Emu", "latin_name": "Dromaius novaehollandiae"}}] ############################################### -# Regression for #11428 - Proxy models aren't included -# when you run dumpdata over an entire app +# Regression for #11428 - Proxy models aren't included when you dumpdata # Flush out the database first >>> management.call_command('reset', 'fixtures_regress', interactive=False, verbosity=0) @@ -189,7 +288,81 @@ Weight = 1.2 (<type 'float'>) >>> Widget(name='grommet').save() # Dump data for the entire app. The proxy class shouldn't be included ->>> management.call_command('dumpdata', 'fixtures_regress', format='json') +>>> management.call_command('dumpdata', 'fixtures_regress.widget', 'fixtures_regress.widgetproxy', format='json') [{"pk": 1, "model": "fixtures_regress.widget", "fields": {"name": "grommet"}}] +############################################### +# Check that natural key requirements are taken into account +# when serializing models +>>> management.call_command('loaddata', 'forward_ref_lookup.json', verbosity=0) + +>>> management.call_command('dumpdata', 'fixtures_regress.book', 'fixtures_regress.person', 'fixtures_regress.store', verbosity=0, use_natural_keys=True) +[{"pk": 2, "model": "fixtures_regress.store", "fields": {"name": "Amazon"}}, {"pk": 3, "model": "fixtures_regress.store", "fields": {"name": "Borders"}}, {"pk": 4, "model": "fixtures_regress.person", "fields": {"name": "Neal Stephenson"}}, {"pk": 1, "model": "fixtures_regress.book", "fields": {"stores": [["Amazon"], ["Borders"]], "name": "Cryptonomicon", "author": ["Neal Stephenson"]}}] + +# Now lets check the dependency sorting explicitly + +# It doesn't matter what order you mention the models +# Store *must* be serialized before then Person, and both +# must be serialized before Book. +>>> from django.core.management.commands.dumpdata import sort_dependencies +>>> sort_dependencies([('fixtures_regress', [Book, Person, Store])]) +[<class 'regressiontests.fixtures_regress.models.Store'>, <class 'regressiontests.fixtures_regress.models.Person'>, <class 'regressiontests.fixtures_regress.models.Book'>] + +>>> sort_dependencies([('fixtures_regress', [Book, Store, Person])]) +[<class 'regressiontests.fixtures_regress.models.Store'>, <class 'regressiontests.fixtures_regress.models.Person'>, <class 'regressiontests.fixtures_regress.models.Book'>] + +>>> sort_dependencies([('fixtures_regress', [Store, Book, Person])]) +[<class 'regressiontests.fixtures_regress.models.Store'>, <class 'regressiontests.fixtures_regress.models.Person'>, <class 'regressiontests.fixtures_regress.models.Book'>] + +>>> sort_dependencies([('fixtures_regress', [Store, Person, Book])]) +[<class 'regressiontests.fixtures_regress.models.Store'>, <class 'regressiontests.fixtures_regress.models.Person'>, <class 'regressiontests.fixtures_regress.models.Book'>] + +>>> sort_dependencies([('fixtures_regress', [Person, Book, Store])]) +[<class 'regressiontests.fixtures_regress.models.Store'>, <class 'regressiontests.fixtures_regress.models.Person'>, <class 'regressiontests.fixtures_regress.models.Book'>] + +>>> sort_dependencies([('fixtures_regress', [Person, Store, Book])]) +[<class 'regressiontests.fixtures_regress.models.Store'>, <class 'regressiontests.fixtures_regress.models.Person'>, <class 'regressiontests.fixtures_regress.models.Book'>] + +# A dangling dependency - assume the user knows what they are doing. +>>> sort_dependencies([('fixtures_regress', [Person, Circle1, Store, Book])]) +[<class 'regressiontests.fixtures_regress.models.Circle1'>, <class 'regressiontests.fixtures_regress.models.Store'>, <class 'regressiontests.fixtures_regress.models.Person'>, <class 'regressiontests.fixtures_regress.models.Book'>] + +# A tight circular dependency +>>> sort_dependencies([('fixtures_regress', [Person, Circle2, Circle1, Store, Book])]) +Traceback (most recent call last): +... +CommandError: Can't resolve dependencies for fixtures_regress.Circle1, fixtures_regress.Circle2 in serialized app list. + +>>> sort_dependencies([('fixtures_regress', [Circle1, Book, Circle2])]) +Traceback (most recent call last): +... +CommandError: Can't resolve dependencies for fixtures_regress.Circle1, fixtures_regress.Circle2 in serialized app list. + +# A self referential dependency +>>> sort_dependencies([('fixtures_regress', [Book, Circle3])]) +Traceback (most recent call last): +... +CommandError: Can't resolve dependencies for fixtures_regress.Circle3 in serialized app list. + +# A long circular dependency +>>> sort_dependencies([('fixtures_regress', [Person, Circle2, Circle1, Circle3, Store, Book])]) +Traceback (most recent call last): +... +CommandError: Can't resolve dependencies for fixtures_regress.Circle1, fixtures_regress.Circle2, fixtures_regress.Circle3 in serialized app list. + +# A dependency on a normal, non-natural-key model +>>> sort_dependencies([('fixtures_regress', [Person, ExternalDependency, Book])]) +[<class 'regressiontests.fixtures_regress.models.Person'>, <class 'regressiontests.fixtures_regress.models.Book'>, <class 'regressiontests.fixtures_regress.models.ExternalDependency'>] + +############################################### +# Check that normal primary keys still work +# on a model with natural key capabilities + +>>> management.call_command('loaddata', 'non_natural_1.json', verbosity=0) +>>> management.call_command('loaddata', 'non_natural_2.xml', verbosity=0) + +>>> Book.objects.all() +[<Book: Cryptonomicon by Neal Stephenson (available at Amazon, Borders)>, <Book: Ender's Game by Orson Scott Card (available at Collins Bookstore)>, <Book: Permutation City by Greg Egan (available at Angus and Robertson)>] + """} + diff --git a/tests/regressiontests/forms/fields.py b/tests/regressiontests/forms/fields.py index eeff56761d..1b96450f63 100644 --- a/tests/regressiontests/forms/fields.py +++ b/tests/regressiontests/forms/fields.py @@ -27,18 +27,20 @@ __init__(). For example, CharField has a max_length option. import datetime import time import re -from unittest import TestCase import os +from unittest import TestCase + +from django.core.files.uploadedfile import SimpleUploadedFile from django.forms import * from django.forms.widgets import RadioFieldRenderer -from django.core.files.uploadedfile import SimpleUploadedFile try: from decimal import Decimal except ImportError: from django.utils._decimal import Decimal + def fix_os_paths(x): if isinstance(x, basestring): return x.replace('\\', '/') @@ -50,7 +52,8 @@ def fix_os_paths(x): return x -class TestFields(TestCase): +class FieldsTests(TestCase): + def assertRaisesErrorWithMessage(self, error, message, callable, *args, **kwargs): self.assertRaises(error, callable, *args, **kwargs) try: @@ -187,7 +190,7 @@ class TestFields(TestCase): self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure this value is greater than or equal to 0.5.']", f.clean, '0.4') self.assertEqual(1.5, f.clean('1.5')) self.assertEqual(0.5, f.clean('0.5')) - + # DecimalField ################################################################ def test_decimalfield_13(self): @@ -487,11 +490,11 @@ class TestFields(TestCase): self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, 'http://inv-.-alid.com') self.assertEqual(u'http://valid-----hyphens.com/', f.clean('http://valid-----hyphens.com')) - def test_url_regexp_for_performance(self): + def test_url_regex_ticket11198(self): f = URLField() # hangs "forever" if catastrophic backtracking in ticket:#11198 not fixed self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, 'http://%s' % ("X"*200,)) - + # a second test, to make sure the problem is really addressed, even on # domains that don't fail the domain label length check in the regex self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid URL.']", f.clean, 'http://%s' % ("X"*60,)) @@ -575,7 +578,7 @@ class TestFields(TestCase): self.assertEqual(False, f.clean('0')) self.assertEqual(True, f.clean('Django rocks')) self.assertEqual(False, f.clean('False')) - + # ChoiceField ################################################################# def test_choicefield_46(self): @@ -750,7 +753,7 @@ class TestFields(TestCase): self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid e-mail address.']", f.clean, 'not an e-mail') self.assertEqual(u'', f.clean('')) self.assertEqual(u'', f.clean(None)) - + # FilePathField ############################################################### def test_filepathfield_65(self): diff --git a/tests/regressiontests/forms/forms.py b/tests/regressiontests/forms/forms.py index bf9623fe77..627f50a6fe 100644 --- a/tests/regressiontests/forms/forms.py +++ b/tests/regressiontests/forms/forms.py @@ -1807,4 +1807,43 @@ True >>> [f.name for f in form.visible_fields()] ['artist', 'name'] +# Hidden initial input gets its own unique id ################################ + +>>> class MyForm(Form): +... field1 = CharField(max_length=50, show_hidden_initial=True) +>>> print MyForm() +<tr><th><label for="id_field1">Field1:</label></th><td><input id="id_field1" type="text" name="field1" maxlength="50" /><input type="hidden" name="initial-field1" id="initial-id_field1" /></td></tr> + +# The error_html_class and required_html_class attributes #################### + +>>> p = Person({}) +>>> p.error_css_class = 'error' +>>> p.required_css_class = 'required' + +>>> print p.as_ul() +<li class="required error"><ul class="errorlist"><li>This field is required.</li></ul><label for="id_name">Name:</label> <input type="text" name="name" id="id_name" /></li> +<li class="required"><label for="id_is_cool">Is cool:</label> <select name="is_cool" id="id_is_cool"> +<option value="1" selected="selected">Unknown</option> +<option value="2">Yes</option> +<option value="3">No</option> +</select></li> + +>>> print p.as_p() +<ul class="errorlist"><li>This field is required.</li></ul> +<p class="required error"><label for="id_name">Name:</label> <input type="text" name="name" id="id_name" /></p> +<p class="required"><label for="id_is_cool">Is cool:</label> <select name="is_cool" id="id_is_cool"> +<option value="1" selected="selected">Unknown</option> +<option value="2">Yes</option> +<option value="3">No</option> +</select></p> + +>>> print p.as_table() +<tr class="required error"><th><label for="id_name">Name:</label></th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="text" name="name" id="id_name" /></td></tr> +<tr class="required"><th><label for="id_is_cool">Is cool:</label></th><td><select name="is_cool" id="id_is_cool"> +<option value="1" selected="selected">Unknown</option> +<option value="2">Yes</option> +<option value="3">No</option> +</select></td></tr> + + """ diff --git a/tests/regressiontests/forms/localflavor/ca.py b/tests/regressiontests/forms/localflavor/ca.py index 48171b0558..7f4b3ac89c 100644 --- a/tests/regressiontests/forms/localflavor/ca.py +++ b/tests/regressiontests/forms/localflavor/ca.py @@ -60,6 +60,50 @@ ValidationError: [u'Enter a postal code in the format XXX XXX.'] u'' >>> f.clean('') u'' +>>> f.clean('W2S 2H3') +Traceback (most recent call last): +... +ValidationError: [u'Enter a postal code in the format XXX XXX.'] +>>> f.clean('T2W 2H7') +u'T2W 2H7' +>>> f.clean('T2S 2W7') +u'T2S 2W7' +>>> f.clean('Z2S 2H3') +Traceback (most recent call last): +... +ValidationError: [u'Enter a postal code in the format XXX XXX.'] +>>> f.clean('T2Z 2H7') +u'T2Z 2H7' +>>> f.clean('T2S 2Z7') +u'T2S 2Z7' +>>> f.clean('F2S 2H3') +Traceback (most recent call last): +... +ValidationError: [u'Enter a postal code in the format XXX XXX.'] +>>> f.clean('A2S 2D3') +Traceback (most recent call last): +... +ValidationError: [u'Enter a postal code in the format XXX XXX.'] +>>> f.clean('A2I 2R3') +Traceback (most recent call last): +... +ValidationError: [u'Enter a postal code in the format XXX XXX.'] +>>> f.clean('A2I 2R3') +Traceback (most recent call last): +... +ValidationError: [u'Enter a postal code in the format XXX XXX.'] +>>> f.clean('A2Q 2R3') +Traceback (most recent call last): +... +ValidationError: [u'Enter a postal code in the format XXX XXX.'] +>>> f.clean('U2B 2R3') +Traceback (most recent call last): +... +ValidationError: [u'Enter a postal code in the format XXX XXX.'] +>>> f.clean('O2B 2R3') +Traceback (most recent call last): +... +ValidationError: [u'Enter a postal code in the format XXX XXX.'] # CAPhoneNumberField ########################################################## diff --git a/tests/regressiontests/forms/localflavor/se.py b/tests/regressiontests/forms/localflavor/se.py new file mode 100644 index 0000000000..44bd953c6e --- /dev/null +++ b/tests/regressiontests/forms/localflavor/se.py @@ -0,0 +1,332 @@ +# -*- coding: utf-8 -*- +# Tests for the contrib/localflavor/se form fields. + +tests = r""" +# Monkey-patch datetime.date +>>> import datetime +>>> class MockDate(datetime.date): +... def today(cls): +... return datetime.date(2008, 5, 14) +... today = classmethod(today) +... +>>> olddate = datetime.date +>>> datetime.date = MockDate +>>> datetime.date.today() +MockDate(2008, 5, 14) + + +# SECountySelect ##################################################### +>>> from django.contrib.localflavor.se.forms import SECountySelect + +>>> w = SECountySelect() +>>> w.render('swedish_county', 'E') +u'<select name="swedish_county">\n<option value="AB">Stockholm</option>\n<option value="AC">V\xe4sterbotten</option>\n<option value="BD">Norrbotten</option>\n<option value="C">Uppsala</option>\n<option value="D">S\xf6dermanland</option>\n<option value="E" selected="selected">\xd6sterg\xf6tland</option>\n<option value="F">J\xf6nk\xf6ping</option>\n<option value="G">Kronoberg</option>\n<option value="H">Kalmar</option>\n<option value="I">Gotland</option>\n<option value="K">Blekinge</option>\n<option value="M">Sk\xe5ne</option>\n<option value="N">Halland</option>\n<option value="O">V\xe4stra G\xf6taland</option>\n<option value="S">V\xe4rmland</option>\n<option value="T">\xd6rebro</option>\n<option value="U">V\xe4stmanland</option>\n<option value="W">Dalarna</option>\n<option value="X">G\xe4vleborg</option>\n<option value="Y">V\xe4sternorrland</option>\n<option value="Z">J\xe4mtland</option>\n</select>' + +# SEOrganisationNumberField ####################################### + +>>> from django.contrib.localflavor.se.forms import SEOrganisationNumberField + +>>> f = SEOrganisationNumberField() + +# Ordinary personal identity numbers for sole proprietors +# The same rules as for SEPersonalIdentityField applies here +>>> f.clean('870512-1989') +u'198705121989' +>>> f.clean('19870512-1989') +u'198705121989' +>>> f.clean('870512-2128') +u'198705122128' +>>> f.clean('081015-6315') +u'190810156315' +>>> f.clean('081015+6315') +u'180810156315' +>>> f.clean('0810156315') +u'190810156315' + +>>> f.clean('081015 6315') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid Swedish organisation number.'] +>>> f.clean('950231-4496') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid Swedish organisation number.'] +>>> f.clean('6914104499') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid Swedish organisation number.'] +>>> f.clean('950d314496') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid Swedish organisation number.'] +>>> f.clean('invalid!!!') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid Swedish organisation number.'] +>>> f.clean('870514-1111') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid Swedish organisation number.'] + + +# Empty values +>>> f.clean('') +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] + +>>> f.clean(None) +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] + +# Co-ordination number checking +# Co-ordination numbers are not valid organisation numbers +>>> f.clean('870574-1315') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid Swedish organisation number.'] + +>>> f.clean('870573-1311') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid Swedish organisation number.'] + +# Test some different organisation numbers +>>> f.clean('556074-7569') # IKEA Linköping +u'5560747569' + +>>> f.clean('556074-3089') # Volvo Personvagnar +u'5560743089' + +>>> f.clean('822001-5476') # LJS (organisation) +u'8220015476' + +>>> f.clean('8220015476') # LJS (organisation) +u'8220015476' + +>>> f.clean('2120000449') # Katedralskolan Linköping (school) +u'2120000449' + +# Faux organisation number, which tests that the checksum can be 0 +>>> f.clean('232518-5060') +u'2325185060' + +>>> f.clean('556074+3089') # Volvo Personvagnar, bad format +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid Swedish organisation number.'] + + +# Invalid checksum +>>> f.clean('2120000441') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid Swedish organisation number.'] + +# Valid checksum but invalid organisation type +f.clean('1120000441') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid Swedish organisation number.'] + +# Empty values with required=False +>>> f = SEOrganisationNumberField(required=False) + +>>> f.clean(None) +u'' + +>>> f.clean('') +u'' + + +# SEPersonalIdentityNumberField ####################################### + +>>> from django.contrib.localflavor.se.forms import SEPersonalIdentityNumberField + +>>> f = SEPersonalIdentityNumberField() + +# Valid id numbers +>>> f.clean('870512-1989') +u'198705121989' + +>>> f.clean('870512-2128') +u'198705122128' + +>>> f.clean('19870512-1989') +u'198705121989' + +>>> f.clean('198705121989') +u'198705121989' + +>>> f.clean('081015-6315') +u'190810156315' + +>>> f.clean('0810156315') +u'190810156315' + +# This is a "special-case" in the checksum calculation, +# where the sum is divisible by 10 (the checksum digit == 0) +>>> f.clean('8705141060') +u'198705141060' + +# + means that the person is older than 100 years +>>> f.clean('081015+6315') +u'180810156315' + +# Bogus values +>>> f.clean('081015 6315') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid Swedish personal identity number.'] + +>>> f.clean('950d314496') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid Swedish personal identity number.'] + +>>> f.clean('invalid!!!') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid Swedish personal identity number.'] + + +# Invalid dates + +# February 31st does not exist +>>> f.clean('950231-4496') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid Swedish personal identity number.'] + +# Month 14 does not exist +>>> f.clean('6914104499') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid Swedish personal identity number.'] + +# There are no Swedish personal id numbers where year < 1800 +>>> f.clean('17430309-7135') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid Swedish personal identity number.'] + +# Invalid checksum +>>> f.clean('870514-1111') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid Swedish personal identity number.'] + +# Empty values +>>> f.clean('') +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] + +>>> f.clean(None) +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] + +# Co-ordination number checking +>>> f.clean('870574-1315') +u'198705741315' + +>>> f.clean('870574+1315') +u'188705741315' + +>>> f.clean('198705741315') +u'198705741315' + +# Co-ordination number with bad checksum +>>> f.clean('870573-1311') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid Swedish personal identity number.'] + + +# Check valid co-ordination numbers, that should not be accepted +# because of coordination_number=False +>>> f = SEPersonalIdentityNumberField(coordination_number=False) + +>>> f.clean('870574-1315') +Traceback (most recent call last): +... +ValidationError: [u'Co-ordination numbers are not allowed.'] + +>>> f.clean('870574+1315') +Traceback (most recent call last): +... +ValidationError: [u'Co-ordination numbers are not allowed.'] + +>>> f.clean('8705741315') +Traceback (most recent call last): +... +ValidationError: [u'Co-ordination numbers are not allowed.'] + +# Invalid co-ordination numbers should be treated as invalid, and not +# as co-ordination numbers +>>> f.clean('870573-1311') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid Swedish personal identity number.'] + +# Empty values with required=False +>>> f = SEPersonalIdentityNumberField(required=False) + +>>> f.clean(None) +u'' + +>>> f.clean('') +u'' + +# SEPostalCodeField ############################################### +>>> from django.contrib.localflavor.se.forms import SEPostalCodeField +>>> f = SEPostalCodeField() +>>> +Postal codes can have spaces +>>> f.clean('589 37') +u'58937' + +... but the dont have to +>>> f.clean('58937') +u'58937' +>>> f.clean('abcasfassadf') +Traceback (most recent call last): +... +ValidationError: [u'Enter a Swedish postal code in the format XXXXX.'] + +# Only one space is allowed for separation +>>> f.clean('589 37') +Traceback (most recent call last): +... +ValidationError: [u'Enter a Swedish postal code in the format XXXXX.'] + +# The postal code must not start with 0 +>>> f.clean('01234') +Traceback (most recent call last): +... +ValidationError: [u'Enter a Swedish postal code in the format XXXXX.'] + +# Empty values +>>> f.clean('') +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] + +>>> f.clean(None) +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] + +# Empty values, required=False +>>> f = SEPostalCodeField(required=False) +>>> f.clean('') +u'' +>>> f.clean(None) +u'' + +# Revert the monkey patching +>>> datetime.date = olddate + +""" diff --git a/tests/regressiontests/forms/tests.py b/tests/regressiontests/forms/tests.py index ef7326735d..b6b7752f5a 100644 --- a/tests/regressiontests/forms/tests.py +++ b/tests/regressiontests/forms/tests.py @@ -21,6 +21,7 @@ from localflavor.jp import tests as localflavor_jp_tests from localflavor.nl import tests as localflavor_nl_tests from localflavor.pl import tests as localflavor_pl_tests from localflavor.ro import tests as localflavor_ro_tests +from localflavor.se import tests as localflavor_se_tests from localflavor.sk import tests as localflavor_sk_tests from localflavor.uk import tests as localflavor_uk_tests from localflavor.us import tests as localflavor_us_tests @@ -31,7 +32,7 @@ from widgets import tests as widgets_tests from formsets import tests as formset_tests from media import media_tests -from fields import TestFields +from fields import FieldsTests from validators import TestFormWithValidators, TestFieldWithValidators __test__ = { @@ -57,6 +58,7 @@ __test__ = { 'localflavor_nl_tests': localflavor_nl_tests, 'localflavor_pl_tests': localflavor_pl_tests, 'localflavor_ro_tests': localflavor_ro_tests, + 'localflavor_se_tests': localflavor_se_tests, 'localflavor_sk_tests': localflavor_sk_tests, 'localflavor_uk_tests': localflavor_uk_tests, 'localflavor_us_tests': localflavor_us_tests, diff --git a/tests/regressiontests/generic_inline_admin/models.py b/tests/regressiontests/generic_inline_admin/models.py index bc6764d8b2..9f1bbadd3e 100644 --- a/tests/regressiontests/generic_inline_admin/models.py +++ b/tests/regressiontests/generic_inline_admin/models.py @@ -20,7 +20,7 @@ class Media(models.Model): class MediaInline(generic.GenericTabularInline): model = Media - + class EpisodeAdmin(admin.ModelAdmin): inlines = [ MediaInline, @@ -56,7 +56,7 @@ class MediaMaxNumInline(generic.GenericTabularInline): model = Media extra = 5 max_num = 2 - + admin.site.register(EpisodeMaxNum, inlines=[MediaMaxNumInline]) # diff --git a/tests/regressiontests/generic_inline_admin/tests.py b/tests/regressiontests/generic_inline_admin/tests.py index 60a370fb2d..0cf1f4ea69 100644 --- a/tests/regressiontests/generic_inline_admin/tests.py +++ b/tests/regressiontests/generic_inline_admin/tests.py @@ -17,7 +17,7 @@ class GenericAdminViewTest(TestCase): self.original_template_debug = settings.TEMPLATE_DEBUG settings.TEMPLATE_DEBUG = True self.client.login(username='super', password='secret') - + # Can't load content via a fixture (since the GenericForeignKey # relies on content type IDs, which will vary depending on what # other tests have been run), thus we do it here. @@ -25,26 +25,30 @@ class GenericAdminViewTest(TestCase): self.episode_pk = e.pk m = Media(content_object=e, url='http://example.com/podcast.mp3') m.save() - self.media_pk = m.pk - + self.mp3_media_pk = m.pk + + m = Media(content_object=e, url='http://example.com/logo.png') + m.save() + self.png_media_pk = m.pk + def tearDown(self): self.client.logout() settings.TEMPLATE_DEBUG = self.original_template_debug - + def testBasicAddGet(self): """ A smoke test to ensure GET on the add_view works. """ response = self.client.get('/generic_inline_admin/admin/generic_inline_admin/episode/add/') self.failUnlessEqual(response.status_code, 200) - + def testBasicEditGet(self): """ A smoke test to ensure GET on the change_view works. """ response = self.client.get('/generic_inline_admin/admin/generic_inline_admin/episode/%d/' % self.episode_pk) self.failUnlessEqual(response.status_code, 200) - + def testBasicAddPost(self): """ A smoke test to ensure POST on add_view works. @@ -57,7 +61,7 @@ class GenericAdminViewTest(TestCase): } response = self.client.post('/generic_inline_admin/admin/generic_inline_admin/episode/add/', post_data) self.failUnlessEqual(response.status_code, 302) # redirect somewhere - + def testBasicEditPost(self): """ A smoke test to ensure POST on edit_view works. @@ -65,22 +69,54 @@ class GenericAdminViewTest(TestCase): post_data = { "name": u"This Week in Django", # inline data - "generic_inline_admin-media-content_type-object_id-TOTAL_FORMS": u"2", - "generic_inline_admin-media-content_type-object_id-INITIAL_FORMS": u"1", - "generic_inline_admin-media-content_type-object_id-0-id": u"%d" % self.media_pk, + "generic_inline_admin-media-content_type-object_id-TOTAL_FORMS": u"3", + "generic_inline_admin-media-content_type-object_id-INITIAL_FORMS": u"2", + "generic_inline_admin-media-content_type-object_id-0-id": u"%d" % self.mp3_media_pk, "generic_inline_admin-media-content_type-object_id-0-url": u"http://example.com/podcast.mp3", - "generic_inline_admin-media-content_type-object_id-1-id": u"", - "generic_inline_admin-media-content_type-object_id-1-url": u"", + "generic_inline_admin-media-content_type-object_id-1-id": u"%d" % self.png_media_pk, + "generic_inline_admin-media-content_type-object_id-1-url": u"http://example.com/logo.png", + "generic_inline_admin-media-content_type-object_id-2-id": u"", + "generic_inline_admin-media-content_type-object_id-2-url": u"", } url = '/generic_inline_admin/admin/generic_inline_admin/episode/%d/' % self.episode_pk response = self.client.post(url, post_data) self.failUnlessEqual(response.status_code, 302) # redirect somewhere - + + def testGenericInlineFormset(self): + EpisodeMediaFormSet = generic_inlineformset_factory(Media, can_delete=False, extra=3) + e = Episode.objects.get(name='This Week in Django') + + # Works with no queryset + formset = EpisodeMediaFormSet(instance=e) + self.assertEquals(len(formset.forms), 5) + self.assertEquals(formset.forms[0].as_p(), '<p><label for="id_generic_inline_admin-media-content_type-object_id-0-url">Url:</label> <input id="id_generic_inline_admin-media-content_type-object_id-0-url" type="text" name="generic_inline_admin-media-content_type-object_id-0-url" value="http://example.com/podcast.mp3" maxlength="200" /><input type="hidden" name="generic_inline_admin-media-content_type-object_id-0-id" value="%s" id="id_generic_inline_admin-media-content_type-object_id-0-id" /></p>' % self.mp3_media_pk) + self.assertEquals(formset.forms[1].as_p(), '<p><label for="id_generic_inline_admin-media-content_type-object_id-1-url">Url:</label> <input id="id_generic_inline_admin-media-content_type-object_id-1-url" type="text" name="generic_inline_admin-media-content_type-object_id-1-url" value="http://example.com/logo.png" maxlength="200" /><input type="hidden" name="generic_inline_admin-media-content_type-object_id-1-id" value="%s" id="id_generic_inline_admin-media-content_type-object_id-1-id" /></p>' % self.png_media_pk) + self.assertEquals(formset.forms[2].as_p(), '<p><label for="id_generic_inline_admin-media-content_type-object_id-2-url">Url:</label> <input id="id_generic_inline_admin-media-content_type-object_id-2-url" type="text" name="generic_inline_admin-media-content_type-object_id-2-url" maxlength="200" /><input type="hidden" name="generic_inline_admin-media-content_type-object_id-2-id" id="id_generic_inline_admin-media-content_type-object_id-2-id" /></p>') + + # A queryset can be used to alter display ordering + formset = EpisodeMediaFormSet(instance=e, queryset=Media.objects.order_by('url')) + self.assertEquals(len(formset.forms), 5) + self.assertEquals(formset.forms[0].as_p(), '<p><label for="id_generic_inline_admin-media-content_type-object_id-0-url">Url:</label> <input id="id_generic_inline_admin-media-content_type-object_id-0-url" type="text" name="generic_inline_admin-media-content_type-object_id-0-url" value="http://example.com/logo.png" maxlength="200" /><input type="hidden" name="generic_inline_admin-media-content_type-object_id-0-id" value="%s" id="id_generic_inline_admin-media-content_type-object_id-0-id" /></p>' % self.png_media_pk) + self.assertEquals(formset.forms[1].as_p(), '<p><label for="id_generic_inline_admin-media-content_type-object_id-1-url">Url:</label> <input id="id_generic_inline_admin-media-content_type-object_id-1-url" type="text" name="generic_inline_admin-media-content_type-object_id-1-url" value="http://example.com/podcast.mp3" maxlength="200" /><input type="hidden" name="generic_inline_admin-media-content_type-object_id-1-id" value="%s" id="id_generic_inline_admin-media-content_type-object_id-1-id" /></p>' % self.mp3_media_pk) + self.assertEquals(formset.forms[2].as_p(), '<p><label for="id_generic_inline_admin-media-content_type-object_id-2-url">Url:</label> <input id="id_generic_inline_admin-media-content_type-object_id-2-url" type="text" name="generic_inline_admin-media-content_type-object_id-2-url" maxlength="200" /><input type="hidden" name="generic_inline_admin-media-content_type-object_id-2-id" id="id_generic_inline_admin-media-content_type-object_id-2-id" /></p>') + + + # Works with a queryset that omits items + formset = EpisodeMediaFormSet(instance=e, queryset=Media.objects.filter(url__endswith=".png")) + self.assertEquals(len(formset.forms), 4) + self.assertEquals(formset.forms[0].as_p(), '<p><label for="id_generic_inline_admin-media-content_type-object_id-0-url">Url:</label> <input id="id_generic_inline_admin-media-content_type-object_id-0-url" type="text" name="generic_inline_admin-media-content_type-object_id-0-url" value="http://example.com/logo.png" maxlength="200" /><input type="hidden" name="generic_inline_admin-media-content_type-object_id-0-id" value="%s" id="id_generic_inline_admin-media-content_type-object_id-0-id" /></p>' % self.png_media_pk) + self.assertEquals(formset.forms[1].as_p(), '<p><label for="id_generic_inline_admin-media-content_type-object_id-1-url">Url:</label> <input id="id_generic_inline_admin-media-content_type-object_id-1-url" type="text" name="generic_inline_admin-media-content_type-object_id-1-url" maxlength="200" /><input type="hidden" name="generic_inline_admin-media-content_type-object_id-1-id" id="id_generic_inline_admin-media-content_type-object_id-1-id" /></p>') + def testGenericInlineFormsetFactory(self): # Regression test for #10522. inline_formset = generic_inlineformset_factory(Media, exclude=('url',)) + # Regression test for #12340. + e = Episode.objects.get(name='This Week in Django') + formset = inline_formset(instance=e) + self.failUnless(formset.get_queryset().ordered) + class GenericInlineAdminParametersTest(TestCase): fixtures = ['users.xml'] @@ -139,4 +175,4 @@ class GenericInlineAdminParametersTest(TestCase): e = self._create_object(EpisodeExclude) response = self.client.get('/generic_inline_admin/admin/generic_inline_admin/episodeexclude/%s/' % e.pk) formset = response.context['inline_admin_formsets'][0].formset - self.failIf('url' in formset.forms[0], 'The formset has excluded "url" field.')
\ No newline at end of file + self.failIf('url' in formset.forms[0], 'The formset has excluded "url" field.') diff --git a/tests/regressiontests/generic_inline_admin/urls.py b/tests/regressiontests/generic_inline_admin/urls.py index 04d68cd26c..c3e8af8fe1 100644 --- a/tests/regressiontests/generic_inline_admin/urls.py +++ b/tests/regressiontests/generic_inline_admin/urls.py @@ -2,5 +2,5 @@ from django.conf.urls.defaults import * from django.contrib import admin urlpatterns = patterns('', - (r'^admin/(.*)', admin.site.root), + (r'^admin/', include(admin.site.urls)), ) diff --git a/tests/regressiontests/i18n/forms.py b/tests/regressiontests/i18n/forms.py new file mode 100644 index 0000000000..b8244f6ca7 --- /dev/null +++ b/tests/regressiontests/i18n/forms.py @@ -0,0 +1,13 @@ +from django import template, forms +from django.forms.extras import SelectDateWidget + +class I18nForm(forms.Form): + decimal_field = forms.DecimalField() + float_field = forms.FloatField() + date_field = forms.DateField() + datetime_field = forms.DateTimeField() + time_field = forms.TimeField() + +class SelectDateForm(forms.Form): + date_field = forms.DateField(widget=SelectDateWidget) + diff --git a/tests/regressiontests/i18n/misc.py b/tests/regressiontests/i18n/misc.py deleted file mode 100644 index f8f35ad4e4..0000000000 --- a/tests/regressiontests/i18n/misc.py +++ /dev/null @@ -1,114 +0,0 @@ -import sys - -tests = """ ->>> from django.utils.translation.trans_real import parse_accept_lang_header ->>> p = parse_accept_lang_header - -# -# Testing HTTP header parsing. First, we test that we can parse the values -# according to the spec (and that we extract all the pieces in the right order). -# - -Good headers. ->>> p('de') -[('de', 1.0)] ->>> p('en-AU') -[('en-AU', 1.0)] ->>> p('*;q=1.00') -[('*', 1.0)] ->>> p('en-AU;q=0.123') -[('en-AU', 0.123)] ->>> p('en-au;q=0.1') -[('en-au', 0.10000000000000001)] ->>> p('en-au;q=1.0') -[('en-au', 1.0)] ->>> p('da, en-gb;q=0.25, en;q=0.5') -[('da', 1.0), ('en', 0.5), ('en-gb', 0.25)] ->>> p('en-au-xx') -[('en-au-xx', 1.0)] ->>> p('de,en-au;q=0.75,en-us;q=0.5,en;q=0.25,es;q=0.125,fa;q=0.125') -[('de', 1.0), ('en-au', 0.75), ('en-us', 0.5), ('en', 0.25), ('es', 0.125), ('fa', 0.125)] ->>> p('*') -[('*', 1.0)] ->>> p('de;q=0.') -[('de', 1.0)] ->>> p('') -[] - -Bad headers; should always return []. ->>> p('en-gb;q=1.0000') -[] ->>> p('en;q=0.1234') -[] ->>> p('en;q=.2') -[] ->>> p('abcdefghi-au') -[] ->>> p('**') -[] ->>> p('en,,gb') -[] ->>> p('en-au;q=0.1.0') -[] ->>> p('XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXZ,en') -[] ->>> p('da, en-gb;q=0.8, en;q=0.7,#') -[] ->>> p('de;q=2.0') -[] ->>> p('de;q=0.a') -[] ->>> p('') -[] - -# -# Now test that we parse a literal HTTP header correctly. -# - ->>> from django.utils.translation.trans_real import get_language_from_request ->>> g = get_language_from_request ->>> from django.http import HttpRequest ->>> r = HttpRequest ->>> r.COOKIES = {} - -These tests assumes the es, es_AR, pt and pt_BR translations exit in the Django -source tree. ->>> r.META = {'HTTP_ACCEPT_LANGUAGE': 'pt-br'} ->>> g(r) -'pt-br' ->>> r.META = {'HTTP_ACCEPT_LANGUAGE': 'pt'} ->>> g(r) -'pt' ->>> r.META = {'HTTP_ACCEPT_LANGUAGE': 'es,de'} ->>> g(r) -'es' ->>> r.META = {'HTTP_ACCEPT_LANGUAGE': 'es-ar,de'} ->>> g(r) -'es-ar' -""" - -# Python 2.3 and 2.4 return slightly different results for completely bogus -# locales, so we omit this test for that anything below 2.4. It's relatively -# harmless in any cases (GIGO). This also means this won't be executed on -# Jython currently, but life's like that sometimes. (On those platforms, -# passing in a truly bogus locale will get you the default locale back.) -if sys.version_info >= (2, 5): - tests += """ -This test assumes there won't be a Django translation to a US variation -of the Spanish language, a safe assumption. When the user sets it -as the preferred language, the main 'es' translation should be selected -instead. ->>> r.META = {'HTTP_ACCEPT_LANGUAGE': 'es-us'} ->>> g(r) -'es' -""" - -tests += """ -This tests the following scenario: there isn't a main language (zh) -translation of Django but there is a translation to variation (zh_CN) -the user sets zh-cn as the preferred language, it should be selected by -Django without falling back nor ignoring it. ->>> r.META = {'HTTP_ACCEPT_LANGUAGE': 'zh-cn,de'} ->>> g(r) -'zh-cn' -""" diff --git a/tests/regressiontests/i18n/tests.py b/tests/regressiontests/i18n/tests.py index 94e792cf54..42dfe91659 100644 --- a/tests/regressiontests/i18n/tests.py +++ b/tests/regressiontests/i18n/tests.py @@ -1,72 +1,404 @@ -# coding: utf-8 -import misc +import sys +import decimal +import datetime -regressions = ur""" -Format string interpolation should work with *_lazy objects. +from django.template import Template, Context +from django.conf import settings +from django.utils.formats import get_format, date_format, number_format, localize +from django.utils.numberformat import format +from django.test import TestCase, client +from django.utils.translation import ugettext, ugettext_lazy, activate, deactivate, gettext_lazy ->>> from django.utils.translation import ugettext, ugettext_lazy, activate, deactivate, gettext_lazy ->>> s = ugettext_lazy('Add %(name)s') ->>> d = {'name': 'Ringo'} ->>> s % d -u'Add Ringo' ->>> activate('de') ->>> s % d -u'Ringo hinzuf\xfcgen' ->>> activate('pl') ->>> s % d -u'Dodaj Ringo' ->>> deactivate() +from forms import I18nForm, SelectDateForm, SelectDateWidget -It should be possible to compare *_lazy objects. +class TranslationTests(TestCase): ->>> s1 = ugettext_lazy('Add %(name)s') ->>> s == s1 -True ->>> s2 = gettext_lazy('Add %(name)s') ->>> s3 = gettext_lazy('Add %(name)s') ->>> s2 == s3 -True ->>> s == s2 -True ->>> s4 = ugettext_lazy('Some other string') ->>> s == s4 -False + def test_lazy_objects(self): + """ + Format string interpolation should work with *_lazy objects. + """ + s = ugettext_lazy('Add %(name)s') + d = {'name': 'Ringo'} + self.assertEqual(u'Add Ringo', s % d) + activate('de') + try: + self.assertEqual(u'Ringo hinzuf\xfcgen', s % d) + activate('pl') + self.assertEqual(u'Dodaj Ringo', s % d) + finally: + deactivate() -unicode(string_concat(...)) should not raise a TypeError - #4796 + # It should be possible to compare *_lazy objects. + s1 = ugettext_lazy('Add %(name)s') + self.assertEqual(True, s == s1) + s2 = gettext_lazy('Add %(name)s') + s3 = gettext_lazy('Add %(name)s') + self.assertEqual(True, s2 == s3) + self.assertEqual(True, s == s2) + s4 = ugettext_lazy('Some other string') + self.assertEqual(False, s == s4) ->>> import django.utils.translation ->>> reload(django.utils.translation) -<module 'django.utils.translation' from ...> ->>> unicode(django.utils.translation.string_concat("dja", "ngo")) -u'django' + def test_string_concat(self): + """ + unicode(string_concat(...)) should not raise a TypeError - #4796 + """ + import django.utils.translation + self.assertEqual(django.utils.translation, reload(django.utils.translation)) + self.assertEqual(u'django', unicode(django.utils.translation.string_concat("dja", "ngo"))) -Translating a string requiring no auto-escaping shouldn't change the "safe" -status. + def test_safe_status(self): + """ + Translating a string requiring no auto-escaping shouldn't change the "safe" status. + """ + from django.utils.safestring import mark_safe, SafeString, SafeUnicode + s = mark_safe('Password') + self.assertEqual(SafeString, type(s)) + activate('de') + try: + self.assertEqual(SafeUnicode, type(ugettext(s))) + finally: + deactivate() + self.assertEqual('aPassword', SafeString('a') + s) + self.assertEqual('Passworda', s + SafeString('a')) + self.assertEqual('Passworda', s + mark_safe('a')) + self.assertEqual('aPassword', mark_safe('a') + s) + self.assertEqual('as', mark_safe('a') + mark_safe('s')) ->>> from django.utils.safestring import mark_safe, SafeString ->>> s = mark_safe('Password') ->>> type(s) -<class 'django.utils.safestring.SafeString'> ->>> activate('de') ->>> type(ugettext(s)) -<class 'django.utils.safestring.SafeUnicode'> ->>> deactivate() + def test_maclines(self): + """ + Translations on files with mac or dos end of lines will be converted + to unix eof in .po catalogs, and they have to match when retrieved + """ + from django.utils.translation.trans_real import translation + ca_translation = translation('ca') + ca_translation._catalog[u'Mac\nEOF\n'] = u'Catalan Mac\nEOF\n' + ca_translation._catalog[u'Win\nEOF\n'] = u'Catalan Win\nEOF\n' + activate('ca') + try: + self.assertEqual(u'Catalan Mac\nEOF\n', ugettext(u'Mac\rEOF\r')) + self.assertEqual(u'Catalan Win\nEOF\n', ugettext(u'Win\r\nEOF\r\n')) + finally: + deactivate() ->>> SafeString('a') + s -'aPassword' ->>> s + SafeString('a') -'Passworda' ->>> s + mark_safe('a') -'Passworda' ->>> mark_safe('a') + s -'aPassword' ->>> mark_safe('a') + mark_safe('s') -'as' ->>> print s -Password -""" +class FormattingTests(TestCase): -__test__ = { - 'regressions': regressions, - 'misc': misc.tests, -} + def setUp(self): + self._use_i18n = settings.USE_I18N + self._use_l10n = settings.USE_L10N + self._use_thousand_separator = settings.USE_THOUSAND_SEPARATOR + self.n = decimal.Decimal('66666.666') + self.f = 99999.999 + self.d = datetime.date(2009, 12, 31) + self.dt = datetime.datetime(2009, 12, 31, 20, 50) + self.ctxt = Context({ + 'n': self.n, + 'd': self.d, + 'dt': self.dt, + 'f': self.f + }) + + def tearDown(self): + # Restore defaults + settings.USE_I18N = self._use_i18n + settings.USE_L10N = self._use_l10n + settings.USE_THOUSAND_SEPARATOR = self._use_thousand_separator + + def test_locale_independent(self): + """ + Localization of dates and numbers + """ + settings.USE_L10N = True + settings.USE_THOUSAND_SEPARATOR = False + self.assertEqual(u'66666.66', format(self.n, decimal_sep='.', decimal_pos=2, grouping=3, thousand_sep=',')) + self.assertEqual(u'66666A6', format(self.n, decimal_sep='A', decimal_pos=1, grouping=1, thousand_sep='B')) + + settings.USE_THOUSAND_SEPARATOR = True + self.assertEqual(u'66,666.66', format(self.n, decimal_sep='.', decimal_pos=2, grouping=3, thousand_sep=',')) + self.assertEqual(u'6B6B6B6B6A6', format(self.n, decimal_sep='A', decimal_pos=1, grouping=1, thousand_sep='B')) + self.assertEqual(u'-66666.6', format(-66666.666, decimal_sep='.', decimal_pos=1)) + self.assertEqual(u'-66666.0', format(int('-66666'), decimal_sep='.', decimal_pos=1)) + + def test_l10n_disabled(self): + """ + Catalan locale with format i18n disabled translations will be used, + but not formats + """ + settings.USE_L10N = False + activate('ca') + try: + self.assertEqual('N j, Y', get_format('DATE_FORMAT')) + self.assertEqual(0, get_format('FIRST_DAY_OF_WEEK')) + self.assertEqual('.', get_format('DECIMAL_SEPARATOR')) + self.assertEqual(u'des. 31, 2009', date_format(self.d)) + self.assertEqual(u'desembre 2009', date_format(self.d, 'YEAR_MONTH_FORMAT')) + self.assertEqual(u'12/31/2009 8:50 p.m.', date_format(self.dt, 'SHORT_DATETIME_FORMAT')) + self.assertEqual('No localizable', localize('No localizable')) + self.assertEqual(decimal.Decimal('66666.666'), localize(self.n)) + self.assertEqual(99999.999, localize(self.f)) + self.assertEqual(datetime.date(2009, 12, 31), localize(self.d)) + self.assertEqual(datetime.datetime(2009, 12, 31, 20, 50), localize(self.dt)) + self.assertEqual(u'66666.666', Template('{{ n }}').render(self.ctxt)) + self.assertEqual(u'99999.999', Template('{{ f }}').render(self.ctxt)) + self.assertEqual(u'2009-12-31', Template('{{ d }}').render(self.ctxt)) + self.assertEqual(u'2009-12-31 20:50:00', Template('{{ dt }}').render(self.ctxt)) + self.assertEqual(u'66666.67', Template('{{ n|floatformat:2 }}').render(self.ctxt)) + self.assertEqual(u'100000.0', Template('{{ f|floatformat }}').render(self.ctxt)) + self.assertEqual(u'12/31/2009', Template('{{ d|date:"SHORT_DATE_FORMAT" }}').render(self.ctxt)) + self.assertEqual(u'12/31/2009 8:50 p.m.', Template('{{ dt|date:"SHORT_DATETIME_FORMAT" }}').render(self.ctxt)) + + form = I18nForm({ + 'decimal_field': u'66666,666', + 'float_field': u'99999,999', + 'date_field': u'31/12/2009', + 'datetime_field': u'31/12/2009 20:50', + 'time_field': u'20:50' + }) + self.assertEqual(False, form.is_valid()) + self.assertEqual([u'Introdu\xefu un n\xfamero.'], form.errors['float_field']) + self.assertEqual([u'Introdu\xefu un n\xfamero.'], form.errors['decimal_field']) + self.assertEqual([u'Introdu\xefu una data v\xe0lida.'], form.errors['date_field']) + self.assertEqual([u'Introdu\xefu una data/hora v\xe0lides.'], form.errors['datetime_field']) + + form2 = SelectDateForm({ + 'date_field_month': u'12', + 'date_field_day': u'31', + 'date_field_year': u'2009' + }) + self.assertEqual(True, form2.is_valid()) + self.assertEqual(datetime.date(2009, 12, 31), form2.cleaned_data['date_field']) + self.assertEqual( + u'<select name="mydate_month" id="id_mydate_month">\n<option value="1">gener</option>\n<option value="2">febrer</option>\n<option value="3">mar\xe7</option>\n<option value="4">abril</option>\n<option value="5">maig</option>\n<option value="6">juny</option>\n<option value="7">juliol</option>\n<option value="8">agost</option>\n<option value="9">setembre</option>\n<option value="10">octubre</option>\n<option value="11">novembre</option>\n<option value="12" selected="selected">desembre</option>\n</select>\n<select name="mydate_day" id="id_mydate_day">\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31" selected="selected">31</option>\n</select>\n<select name="mydate_year" id="id_mydate_year">\n<option value="2009" selected="selected">2009</option>\n<option value="2010">2010</option>\n<option value="2011">2011</option>\n<option value="2012">2012</option>\n<option value="2013">2013</option>\n<option value="2014">2014</option>\n<option value="2015">2015</option>\n<option value="2016">2016</option>\n<option value="2017">2017</option>\n<option value="2018">2018</option>\n</select>', + SelectDateWidget().render('mydate', datetime.date(2009, 12, 31)) + ) + finally: + deactivate() + + def test_l10n_enabled(self): + """ + Catalan locale + """ + settings.USE_L10N = True + activate('ca') + try: + self.assertEqual('j \de F \de Y', get_format('DATE_FORMAT')) + self.assertEqual(1, get_format('FIRST_DAY_OF_WEEK')) + self.assertEqual(',', get_format('DECIMAL_SEPARATOR')) + self.assertEqual(u'31 de desembre de 2009', date_format(self.d)) + self.assertEqual(u'desembre del 2009', date_format(self.d, 'YEAR_MONTH_FORMAT')) + self.assertEqual(u'31/12/2009 20:50', date_format(self.dt, 'SHORT_DATETIME_FORMAT')) + self.assertEqual('No localizable', localize('No localizable')) + + settings.USE_THOUSAND_SEPARATOR = True + self.assertEqual(u'66.666,666', localize(self.n)) + self.assertEqual(u'99.999,999', localize(self.f)) + + settings.USE_THOUSAND_SEPARATOR = False + self.assertEqual(u'66666,666', localize(self.n)) + self.assertEqual(u'99999,999', localize(self.f)) + self.assertEqual(u'31 de desembre de 2009', localize(self.d)) + self.assertEqual(u'31 de desembre de 2009 a les 20:50', localize(self.dt)) + + settings.USE_THOUSAND_SEPARATOR = True + self.assertEqual(u'66.666,666', Template('{{ n }}').render(self.ctxt)) + self.assertEqual(u'99.999,999', Template('{{ f }}').render(self.ctxt)) + + settings.USE_THOUSAND_SEPARATOR = False + self.assertEqual(u'66666,666', Template('{{ n }}').render(self.ctxt)) + self.assertEqual(u'99999,999', Template('{{ f }}').render(self.ctxt)) + self.assertEqual(u'31 de desembre de 2009', Template('{{ d }}').render(self.ctxt)) + self.assertEqual(u'31 de desembre de 2009 a les 20:50', Template('{{ dt }}').render(self.ctxt)) + self.assertEqual(u'66666,67', Template('{{ n|floatformat:2 }}').render(self.ctxt)) + self.assertEqual(u'100000,0', Template('{{ f|floatformat }}').render(self.ctxt)) + self.assertEqual(u'31/12/2009', Template('{{ d|date:"SHORT_DATE_FORMAT" }}').render(self.ctxt)) + self.assertEqual(u'31/12/2009 20:50', Template('{{ dt|date:"SHORT_DATETIME_FORMAT" }}').render(self.ctxt)) + + form3 = I18nForm({ + 'decimal_field': u'66666,666', + 'float_field': u'99999,999', + 'date_field': u'31/12/2009', + 'datetime_field': u'31/12/2009 20:50', + 'time_field': u'20:50' + }) + self.assertEqual(True, form3.is_valid()) + self.assertEqual(decimal.Decimal('66666.666'), form3.cleaned_data['decimal_field']) + self.assertEqual(99999.999, form3.cleaned_data['float_field']) + self.assertEqual(datetime.date(2009, 12, 31), form3.cleaned_data['date_field']) + self.assertEqual(datetime.datetime(2009, 12, 31, 20, 50), form3.cleaned_data['datetime_field']) + self.assertEqual(datetime.time(20, 50), form3.cleaned_data['time_field']) + + form4 = SelectDateForm({ + 'date_field_month': u'12', + 'date_field_day': u'31', + 'date_field_year': u'2009' + }) + self.assertEqual(True, form4.is_valid()) + self.assertEqual(datetime.date(2009, 12, 31), form4.cleaned_data['date_field']) + self.assertEqual( + u'<select name="mydate_day" id="id_mydate_day">\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31" selected="selected">31</option>\n</select>\n<select name="mydate_month" id="id_mydate_month">\n<option value="1">gener</option>\n<option value="2">febrer</option>\n<option value="3">mar\xe7</option>\n<option value="4">abril</option>\n<option value="5">maig</option>\n<option value="6">juny</option>\n<option value="7">juliol</option>\n<option value="8">agost</option>\n<option value="9">setembre</option>\n<option value="10">octubre</option>\n<option value="11">novembre</option>\n<option value="12" selected="selected">desembre</option>\n</select>\n<select name="mydate_year" id="id_mydate_year">\n<option value="2009" selected="selected">2009</option>\n<option value="2010">2010</option>\n<option value="2011">2011</option>\n<option value="2012">2012</option>\n<option value="2013">2013</option>\n<option value="2014">2014</option>\n<option value="2015">2015</option>\n<option value="2016">2016</option>\n<option value="2017">2017</option>\n<option value="2018">2018</option>\n</select>', + SelectDateWidget().render('mydate', datetime.date(2009, 12, 31)) + ) + finally: + deactivate() + + # English locale + + settings.USE_L10N = True + activate('en') + try: + self.assertEqual('N j, Y', get_format('DATE_FORMAT')) + self.assertEqual(0, get_format('FIRST_DAY_OF_WEEK')) + self.assertEqual('.', get_format('DECIMAL_SEPARATOR')) + self.assertEqual(u'Dec. 31, 2009', date_format(self.d)) + self.assertEqual(u'December 2009', date_format(self.d, 'YEAR_MONTH_FORMAT')) + self.assertEqual(u'12/31/2009 8:50 p.m.', date_format(self.dt, 'SHORT_DATETIME_FORMAT')) + self.assertEqual('No localizable', localize('No localizable')) + + settings.USE_THOUSAND_SEPARATOR = True + self.assertEqual(u'66,666.666', localize(self.n)) + self.assertEqual(u'99,999.999', localize(self.f)) + + settings.USE_THOUSAND_SEPARATOR = False + self.assertEqual(u'66666.666', localize(self.n)) + self.assertEqual(u'99999.999', localize(self.f)) + self.assertEqual(u'Dec. 31, 2009', localize(self.d)) + self.assertEqual(u'Dec. 31, 2009, 8:50 p.m.', localize(self.dt)) + + settings.USE_THOUSAND_SEPARATOR = True + self.assertEqual(u'66,666.666', Template('{{ n }}').render(self.ctxt)) + self.assertEqual(u'99,999.999', Template('{{ f }}').render(self.ctxt)) + + settings.USE_THOUSAND_SEPARATOR = False + self.assertEqual(u'66666.666', Template('{{ n }}').render(self.ctxt)) + self.assertEqual(u'99999.999', Template('{{ f }}').render(self.ctxt)) + self.assertEqual(u'Dec. 31, 2009', Template('{{ d }}').render(self.ctxt)) + self.assertEqual(u'Dec. 31, 2009, 8:50 p.m.', Template('{{ dt }}').render(self.ctxt)) + self.assertEqual(u'66666.67', Template('{{ n|floatformat:2 }}').render(self.ctxt)) + self.assertEqual(u'100000.0', Template('{{ f|floatformat }}').render(self.ctxt)) + self.assertEqual(u'12/31/2009', Template('{{ d|date:"SHORT_DATE_FORMAT" }}').render(self.ctxt)) + self.assertEqual(u'12/31/2009 8:50 p.m.', Template('{{ dt|date:"SHORT_DATETIME_FORMAT" }}').render(self.ctxt)) + + form5 = I18nForm({ + 'decimal_field': u'66666.666', + 'float_field': u'99999.999', + 'date_field': u'12/31/2009', + 'datetime_field': u'12/31/2009 20:50', + 'time_field': u'20:50' + }) + self.assertEqual(True, form5.is_valid()) + self.assertEqual(decimal.Decimal('66666.666'), form5.cleaned_data['decimal_field']) + self.assertEqual(99999.999, form5.cleaned_data['float_field']) + self.assertEqual(datetime.date(2009, 12, 31), form5.cleaned_data['date_field']) + self.assertEqual(datetime.datetime(2009, 12, 31, 20, 50), form5.cleaned_data['datetime_field']) + self.assertEqual(datetime.time(20, 50), form5.cleaned_data['time_field']) + + form6 = SelectDateForm({ + 'date_field_month': u'12', + 'date_field_day': u'31', + 'date_field_year': u'2009' + }) + self.assertEqual(True, form6.is_valid()) + self.assertEqual(datetime.date(2009, 12, 31), form6.cleaned_data['date_field']) + self.assertEqual( + u'<select name="mydate_month" id="id_mydate_month">\n<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12" selected="selected">December</option>\n</select>\n<select name="mydate_day" id="id_mydate_day">\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31" selected="selected">31</option>\n</select>\n<select name="mydate_year" id="id_mydate_year">\n<option value="2009" selected="selected">2009</option>\n<option value="2010">2010</option>\n<option value="2011">2011</option>\n<option value="2012">2012</option>\n<option value="2013">2013</option>\n<option value="2014">2014</option>\n<option value="2015">2015</option>\n<option value="2016">2016</option>\n<option value="2017">2017</option>\n<option value="2018">2018</option>\n</select>', + SelectDateWidget().render('mydate', datetime.date(2009, 12, 31)) + ) + finally: + deactivate() + + def test_sub_locales(self): + """ + Check if sublocales fall back to the main locale + """ + settings.USE_L10N = True + activate('de-at') + settings.USE_THOUSAND_SEPARATOR = True + try: + self.assertEqual(u'66.666,666', Template('{{ n }}').render(self.ctxt)) + finally: + deactivate() + + activate('es-us') + try: + self.assertEqual(u'31 de diciembre de 2009', date_format(self.d)) + finally: + deactivate() + +class MiscTests(TestCase): + + def test_parse_spec_http_header(self): + """ + Testing HTTP header parsing. First, we test that we can parse the + values according to the spec (and that we extract all the pieces in + the right order). + """ + from django.utils.translation.trans_real import parse_accept_lang_header + p = parse_accept_lang_header + # Good headers. + self.assertEqual([('de', 1.0)], p('de')) + self.assertEqual([('en-AU', 1.0)], p('en-AU')) + self.assertEqual([('*', 1.0)], p('*;q=1.00')) + self.assertEqual([('en-AU', 0.123)], p('en-AU;q=0.123')) + self.assertEqual([('en-au', 0.10000000000000001)], p('en-au;q=0.1')) + self.assertEqual([('en-au', 1.0)], p('en-au;q=1.0')) + self.assertEqual([('da', 1.0), ('en', 0.5), ('en-gb', 0.25)], p('da, en-gb;q=0.25, en;q=0.5')) + self.assertEqual([('en-au-xx', 1.0)], p('en-au-xx')) + self.assertEqual([('de', 1.0), ('en-au', 0.75), ('en-us', 0.5), ('en', 0.25), ('es', 0.125), ('fa', 0.125)], p('de,en-au;q=0.75,en-us;q=0.5,en;q=0.25,es;q=0.125,fa;q=0.125')) + self.assertEqual([('*', 1.0)], p('*')) + self.assertEqual([('de', 1.0)], p('de;q=0.')) + self.assertEqual([], p('')) + + # Bad headers; should always return []. + self.assertEqual([], p('en-gb;q=1.0000')) + self.assertEqual([], p('en;q=0.1234')) + self.assertEqual([], p('en;q=.2')) + self.assertEqual([], p('abcdefghi-au')) + self.assertEqual([], p('**')) + self.assertEqual([], p('en,,gb')) + self.assertEqual([], p('en-au;q=0.1.0')) + self.assertEqual([], p('XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXZ,en')) + self.assertEqual([], p('da, en-gb;q=0.8, en;q=0.7,#')) + self.assertEqual([], p('de;q=2.0')) + self.assertEqual([], p('de;q=0.a')) + self.assertEqual([], p('')) + + def test_parse_literal_http_header(self): + """ + Now test that we parse a literal HTTP header correctly. + """ + from django.utils.translation.trans_real import get_language_from_request + g = get_language_from_request + from django.http import HttpRequest + r = HttpRequest + r.COOKIES = {} + r.META = {'HTTP_ACCEPT_LANGUAGE': 'pt-br'} + self.assertEqual('pt-br', g(r)) + + r.META = {'HTTP_ACCEPT_LANGUAGE': 'pt'} + self.assertEqual('pt', g(r)) + + r.META = {'HTTP_ACCEPT_LANGUAGE': 'es,de'} + self.assertEqual('es', g(r)) + + r.META = {'HTTP_ACCEPT_LANGUAGE': 'es-ar,de'} + self.assertEqual('es-ar', g(r)) + + # Python 2.3 and 2.4 return slightly different results for completely + # bogus locales, so we omit this test for that anything below 2.4. + # It's relatively harmless in any cases (GIGO). This also means this + # won't be executed on Jython currently, but life's like that + # sometimes. (On those platforms, passing in a truly bogus locale + # will get you the default locale back.) + if sys.version_info >= (2, 5): + # This test assumes there won't be a Django translation to a US + # variation of the Spanish language, a safe assumption. When the + # user sets it as the preferred language, the main 'es' + # translation should be selected instead. + r.META = {'HTTP_ACCEPT_LANGUAGE': 'es-us'} + self.assertEqual(g(r), 'es') + + # This tests the following scenario: there isn't a main language (zh) + # translation of Django but there is a translation to variation (zh_CN) + # the user sets zh-cn as the preferred language, it should be selected + # by Django without falling back nor ignoring it. + r.META = {'HTTP_ACCEPT_LANGUAGE': 'zh-cn,de'} + self.assertEqual(g(r), 'zh-cn') diff --git a/tests/regressiontests/introspection/models.py b/tests/regressiontests/introspection/models.py index 3227067bd8..ef485e3a3c 100644 --- a/tests/regressiontests/introspection/models.py +++ b/tests/regressiontests/introspection/models.py @@ -4,6 +4,7 @@ class Reporter(models.Model): first_name = models.CharField(max_length=30) last_name = models.CharField(max_length=30) email = models.EmailField() + facebook_user_id = models.BigIntegerField() def __unicode__(self): return u"%s %s" % (self.first_name, self.last_name) diff --git a/tests/regressiontests/introspection/tests.py b/tests/regressiontests/introspection/tests.py index 1454e1e3e5..10250294c5 100644 --- a/tests/regressiontests/introspection/tests.py +++ b/tests/regressiontests/introspection/tests.py @@ -1,5 +1,5 @@ from django.conf import settings -from django.db import connection +from django.db import connection, DEFAULT_DB_ALIAS from django.test import TestCase from django.utils import functional @@ -77,10 +77,10 @@ class IntrospectionTests(TestCase): cursor = connection.cursor() desc = connection.introspection.get_table_description(cursor, Reporter._meta.db_table) self.assertEqual([datatype(r[1], r) for r in desc], - ['IntegerField', 'CharField', 'CharField', 'CharField']) + ['IntegerField', 'CharField', 'CharField', 'CharField', 'BigIntegerField']) # Regression test for #9991 - 'real' types in postgres - if settings.DATABASE_ENGINE.startswith('postgresql'): + if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'].startswith('django.db.backends.postgresql'): def test_postgresql_real_type(self): cursor = connection.cursor() cursor.execute("CREATE TABLE django_ixn_real_test_table (number REAL);") diff --git a/tests/regressiontests/auth_backends/__init__.py b/tests/regressiontests/localflavor/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/auth_backends/__init__.py +++ b/tests/regressiontests/localflavor/__init__.py diff --git a/tests/regressiontests/localflavor/forms.py b/tests/regressiontests/localflavor/forms.py new file mode 100644 index 0000000000..49635b02fb --- /dev/null +++ b/tests/regressiontests/localflavor/forms.py @@ -0,0 +1,14 @@ +from django.forms import ModelForm +from models import Place + +class PlaceForm(ModelForm): + """docstring for PlaceForm""" + class Meta: + model = Place +from django.forms import ModelForm +from models import Place + +class PlaceForm(ModelForm): + """docstring for PlaceForm""" + class Meta: + model = Place diff --git a/tests/regressiontests/localflavor/models.py b/tests/regressiontests/localflavor/models.py new file mode 100644 index 0000000000..079c7bd982 --- /dev/null +++ b/tests/regressiontests/localflavor/models.py @@ -0,0 +1,16 @@ +from django.db import models +from django.contrib.localflavor.us.models import USStateField + +class Place(models.Model): + state = USStateField(blank=True) + state_req = USStateField() + state_default = USStateField(default="CA", blank=True) + name = models.CharField(max_length=20) +from django.db import models +from django.contrib.localflavor.us.models import USStateField + +class Place(models.Model): + state = USStateField(blank=True) + state_req = USStateField() + state_default = USStateField(default="CA", blank=True) + name = models.CharField(max_length=20) diff --git a/tests/regressiontests/localflavor/tests.py b/tests/regressiontests/localflavor/tests.py new file mode 100644 index 0000000000..61e0e5b5e6 --- /dev/null +++ b/tests/regressiontests/localflavor/tests.py @@ -0,0 +1,166 @@ +from django.test import TestCase +from models import Place +from forms import PlaceForm + +class USLocalflavorTests(TestCase): + def setUp(self): + self.form = PlaceForm({'state':'GA', 'state_req':'NC', 'name':'impossible'}) + + def test_get_display_methods(self): + """Test that the get_*_display() methods are added to the model instances.""" + place = self.form.save() + self.assertEqual(place.get_state_display(), 'Georgia') + self.assertEqual(place.get_state_req_display(), 'North Carolina') + + def test_required(self): + """Test that required USStateFields throw appropriate errors.""" + form = PlaceForm({'state':'GA', 'name':'Place in GA'}) + self.assertFalse(form.is_valid()) + self.assertEqual(form.errors['state_req'], [u'This field is required.']) + + def test_field_blank_option(self): + """Test that the empty option is there.""" + state_select_html = """\ +<select name="state" id="id_state"> +<option value="">---------</option> +<option value="AL">Alabama</option> +<option value="AK">Alaska</option> +<option value="AS">American Samoa</option> +<option value="AZ">Arizona</option> +<option value="AR">Arkansas</option> +<option value="CA">California</option> +<option value="CO">Colorado</option> +<option value="CT">Connecticut</option> +<option value="DE">Delaware</option> +<option value="DC">District of Columbia</option> +<option value="FL">Florida</option> +<option value="GA" selected="selected">Georgia</option> +<option value="GU">Guam</option> +<option value="HI">Hawaii</option> +<option value="ID">Idaho</option> +<option value="IL">Illinois</option> +<option value="IN">Indiana</option> +<option value="IA">Iowa</option> +<option value="KS">Kansas</option> +<option value="KY">Kentucky</option> +<option value="LA">Louisiana</option> +<option value="ME">Maine</option> +<option value="MD">Maryland</option> +<option value="MA">Massachusetts</option> +<option value="MI">Michigan</option> +<option value="MN">Minnesota</option> +<option value="MS">Mississippi</option> +<option value="MO">Missouri</option> +<option value="MT">Montana</option> +<option value="NE">Nebraska</option> +<option value="NV">Nevada</option> +<option value="NH">New Hampshire</option> +<option value="NJ">New Jersey</option> +<option value="NM">New Mexico</option> +<option value="NY">New York</option> +<option value="NC">North Carolina</option> +<option value="ND">North Dakota</option> +<option value="MP">Northern Mariana Islands</option> +<option value="OH">Ohio</option> +<option value="OK">Oklahoma</option> +<option value="OR">Oregon</option> +<option value="PA">Pennsylvania</option> +<option value="PR">Puerto Rico</option> +<option value="RI">Rhode Island</option> +<option value="SC">South Carolina</option> +<option value="SD">South Dakota</option> +<option value="TN">Tennessee</option> +<option value="TX">Texas</option> +<option value="UT">Utah</option> +<option value="VT">Vermont</option> +<option value="VI">Virgin Islands</option> +<option value="VA">Virginia</option> +<option value="WA">Washington</option> +<option value="WV">West Virginia</option> +<option value="WI">Wisconsin</option> +<option value="WY">Wyoming</option> +</select>""" + self.assertEqual(str(self.form['state']), state_select_html) +from django.test import TestCase +from models import Place +from forms import PlaceForm + +class USLocalflavorTests(TestCase): + def setUp(self): + self.form = PlaceForm({'state':'GA', 'state_req':'NC', 'name':'impossible'}) + + def test_get_display_methods(self): + """Test that the get_*_display() methods are added to the model instances.""" + place = self.form.save() + self.assertEqual(place.get_state_display(), 'Georgia') + self.assertEqual(place.get_state_req_display(), 'North Carolina') + + def test_required(self): + """Test that required USStateFields throw appropriate errors.""" + form = PlaceForm({'state':'GA', 'name':'Place in GA'}) + self.assertFalse(form.is_valid()) + self.assertEqual(form.errors['state_req'], [u'This field is required.']) + + def test_field_blank_option(self): + """Test that the empty option is there.""" + state_select_html = """\ +<select name="state" id="id_state"> +<option value="">---------</option> +<option value="AL">Alabama</option> +<option value="AK">Alaska</option> +<option value="AS">American Samoa</option> +<option value="AZ">Arizona</option> +<option value="AR">Arkansas</option> +<option value="CA">California</option> +<option value="CO">Colorado</option> +<option value="CT">Connecticut</option> +<option value="DE">Delaware</option> +<option value="DC">District of Columbia</option> +<option value="FL">Florida</option> +<option value="GA" selected="selected">Georgia</option> +<option value="GU">Guam</option> +<option value="HI">Hawaii</option> +<option value="ID">Idaho</option> +<option value="IL">Illinois</option> +<option value="IN">Indiana</option> +<option value="IA">Iowa</option> +<option value="KS">Kansas</option> +<option value="KY">Kentucky</option> +<option value="LA">Louisiana</option> +<option value="ME">Maine</option> +<option value="MD">Maryland</option> +<option value="MA">Massachusetts</option> +<option value="MI">Michigan</option> +<option value="MN">Minnesota</option> +<option value="MS">Mississippi</option> +<option value="MO">Missouri</option> +<option value="MT">Montana</option> +<option value="NE">Nebraska</option> +<option value="NV">Nevada</option> +<option value="NH">New Hampshire</option> +<option value="NJ">New Jersey</option> +<option value="NM">New Mexico</option> +<option value="NY">New York</option> +<option value="NC">North Carolina</option> +<option value="ND">North Dakota</option> +<option value="MP">Northern Mariana Islands</option> +<option value="OH">Ohio</option> +<option value="OK">Oklahoma</option> +<option value="OR">Oregon</option> +<option value="PA">Pennsylvania</option> +<option value="PR">Puerto Rico</option> +<option value="RI">Rhode Island</option> +<option value="SC">South Carolina</option> +<option value="SD">South Dakota</option> +<option value="TN">Tennessee</option> +<option value="TX">Texas</option> +<option value="UT">Utah</option> +<option value="VT">Vermont</option> +<option value="VI">Virgin Islands</option> +<option value="VA">Virginia</option> +<option value="WA">Washington</option> +<option value="WV">West Virginia</option> +<option value="WI">Wisconsin</option> +<option value="WY">Wyoming</option> +</select>""" + self.assertEqual(str(self.form['state']), state_select_html) diff --git a/tests/regressiontests/model_fields/models.py b/tests/regressiontests/model_fields/models.py index 81e6c22f09..ab841ce409 100644 --- a/tests/regressiontests/model_fields/models.py +++ b/tests/regressiontests/model_fields/models.py @@ -51,6 +51,9 @@ class BigD(models.Model): class BigS(models.Model): s = models.SlugField(max_length=255) +class BigInt(models.Model): + value = models.BigIntegerField() + null_value = models.BigIntegerField(null = True, blank = True) ############################################################################### # ImageField diff --git a/tests/regressiontests/model_fields/tests.py b/tests/regressiontests/model_fields/tests.py index d47344d644..ea7b49ab7e 100644 --- a/tests/regressiontests/model_fields/tests.py +++ b/tests/regressiontests/model_fields/tests.py @@ -6,7 +6,7 @@ from django import forms from django.db import models from django.core.exceptions import ValidationError -from models import Foo, Bar, Whiz, BigD, BigS, Image +from models import Foo, Bar, Whiz, BigD, BigS, Image, BigInt try: from decimal import Decimal @@ -44,8 +44,9 @@ class DecimalFieldTests(django.test.TestCase): self.assertEqual(f._format(None), None) def test_get_db_prep_lookup(self): + from django.db import connection f = models.DecimalField(max_digits=5, decimal_places=1) - self.assertEqual(f.get_db_prep_lookup('exact', None), [None]) + self.assertEqual(f.get_db_prep_lookup('exact', None, connection=connection), [None]) def test_filter_with_strings(self): """ @@ -98,13 +99,14 @@ class DateTimeFieldTests(unittest.TestCase): class BooleanFieldTests(unittest.TestCase): def _test_get_db_prep_lookup(self, f): - self.assertEqual(f.get_db_prep_lookup('exact', True), [True]) - self.assertEqual(f.get_db_prep_lookup('exact', '1'), [True]) - self.assertEqual(f.get_db_prep_lookup('exact', 1), [True]) - self.assertEqual(f.get_db_prep_lookup('exact', False), [False]) - self.assertEqual(f.get_db_prep_lookup('exact', '0'), [False]) - self.assertEqual(f.get_db_prep_lookup('exact', 0), [False]) - self.assertEqual(f.get_db_prep_lookup('exact', None), [None]) + from django.db import connection + self.assertEqual(f.get_db_prep_lookup('exact', True, connection=connection), [True]) + self.assertEqual(f.get_db_prep_lookup('exact', '1', connection=connection), [True]) + self.assertEqual(f.get_db_prep_lookup('exact', 1, connection=connection), [True]) + self.assertEqual(f.get_db_prep_lookup('exact', False, connection=connection), [False]) + self.assertEqual(f.get_db_prep_lookup('exact', '0', connection=connection), [False]) + self.assertEqual(f.get_db_prep_lookup('exact', 0, connection=connection), [False]) + self.assertEqual(f.get_db_prep_lookup('exact', None, connection=connection), [None]) def test_booleanfield_get_db_prep_lookup(self): self._test_get_db_prep_lookup(models.BooleanField()) @@ -145,6 +147,7 @@ class SlugFieldTests(django.test.TestCase): bs = BigS.objects.get(pk=bs.pk) self.assertEqual(bs.s, 'slug'*50) + class ValidationTest(django.test.TestCase): def test_charfield_raises_error_on_empty_string(self): f = models.CharField() @@ -195,3 +198,32 @@ class ValidationTest(django.test.TestCase): f = models.BooleanField() self.assertRaises(ValidationError, f.clean, None, None) + +class BigIntegerFieldTests(django.test.TestCase): + def test_limits(self): + # Ensure that values that are right at the limits can be saved + # and then retrieved without corruption. + maxval = 9223372036854775807 + minval = -maxval - 1 + BigInt.objects.create(value=maxval) + qs = BigInt.objects.filter(value__gte=maxval) + self.assertEqual(qs.count(), 1) + self.assertEqual(qs[0].value, maxval) + BigInt.objects.create(value=minval) + qs = BigInt.objects.filter(value__lte=minval) + self.assertEqual(qs.count(), 1) + self.assertEqual(qs[0].value, minval) + + def test_types(self): + b = BigInt(value = 0) + self.assertTrue(isinstance(b.value, (int, long))) + b.save() + self.assertTrue(isinstance(b.value, (int, long))) + b = BigInt.objects.all()[0] + self.assertTrue(isinstance(b.value, (int, long))) + + def test_coercing(self): + BigInt.objects.create(value ='10') + b = BigInt.objects.get(value = '10') + self.assertEqual(b.value, 10) + diff --git a/tests/regressiontests/model_forms_regress/tests.py b/tests/regressiontests/model_forms_regress/tests.py index 85e284b639..f8b6511140 100644 --- a/tests/regressiontests/model_forms_regress/tests.py +++ b/tests/regressiontests/model_forms_regress/tests.py @@ -100,4 +100,16 @@ class CustomFieldSaveTests(TestCase): # It's enough that the form saves without error -- the custom save routine will # generate an AssertionError if it is called more than once during save. form = CFFForm(data = {'f': None}) - form.save()
\ No newline at end of file + form.save() + +class ModelChoiceIteratorTests(TestCase): + def test_len(self): + class Form(forms.ModelForm): + class Meta: + model = Article + fields = ["publications"] + + Publication.objects.create(title="Pravda", + date_published=date(1991, 8, 22)) + f = Form() + self.assertEqual(len(f.fields["publications"].choices), 1) diff --git a/tests/regressiontests/model_inheritance_regress/models.py b/tests/regressiontests/model_inheritance_regress/models.py index 6a804a97c1..c669b2337a 100644 --- a/tests/regressiontests/model_inheritance_regress/models.py +++ b/tests/regressiontests/model_inheritance_regress/models.py @@ -314,7 +314,8 @@ DoesNotExist: ArticleWithAuthor matching query does not exist. # likely to ocurr naturally with model inheritance, so we check it here). # Regression test for #9390. This necessarily pokes at the SQL string for the # query, since the duplicate problems are only apparent at that late stage. ->>> sql = ArticleWithAuthor.objects.order_by('pub_date', 'pk').query.as_sql()[0] +>>> qs = ArticleWithAuthor.objects.order_by('pub_date', 'pk') +>>> sql = qs.query.get_compiler(qs.db).as_sql()[0] >>> fragment = sql[sql.find('ORDER BY'):] >>> pos = fragment.find('pub_date') >>> fragment.find('pub_date', pos + 1) == -1 diff --git a/tests/regressiontests/model_regress/models.py b/tests/regressiontests/model_regress/models.py index 668acd830a..420f2c262c 100644 --- a/tests/regressiontests/model_regress/models.py +++ b/tests/regressiontests/model_regress/models.py @@ -2,7 +2,7 @@ import datetime from django.conf import settings -from django.db import models +from django.db import models, DEFAULT_DB_ALIAS from django.utils import tzinfo CHOICES = ( @@ -149,7 +149,9 @@ datetime.datetime(2000, 1, 1, 6, 1, 1) """} -if settings.DATABASE_ENGINE not in ("mysql", "oracle"): +if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] not in ( + "django.db.backends.mysql", + "django.db.backends.oracle"): __test__["timezone-tests"] = """ # Saving an updating with timezone-aware datetime Python objects. Regression # test for #10443. @@ -167,4 +169,3 @@ if settings.DATABASE_ENGINE not in ("mysql", "oracle"): 1 """ - diff --git a/tests/regressiontests/auth_backends/models.py b/tests/regressiontests/multiple_database/__init__.py index e69de29bb2..e69de29bb2 100644 --- a/tests/regressiontests/auth_backends/models.py +++ b/tests/regressiontests/multiple_database/__init__.py diff --git a/tests/regressiontests/multiple_database/fixtures/multidb-common.json b/tests/regressiontests/multiple_database/fixtures/multidb-common.json new file mode 100644 index 0000000000..33134173b9 --- /dev/null +++ b/tests/regressiontests/multiple_database/fixtures/multidb-common.json @@ -0,0 +1,10 @@ +[ + { + "pk": 1, + "model": "multiple_database.book", + "fields": { + "title": "The Definitive Guide to Django", + "published": "2009-7-8" + } + } +]
\ No newline at end of file diff --git a/tests/regressiontests/multiple_database/fixtures/multidb.default.json b/tests/regressiontests/multiple_database/fixtures/multidb.default.json new file mode 100644 index 0000000000..379b18a803 --- /dev/null +++ b/tests/regressiontests/multiple_database/fixtures/multidb.default.json @@ -0,0 +1,26 @@ +[ + { + "pk": 1, + "model": "multiple_database.person", + "fields": { + "name": "Marty Alchin" + } + }, + { + "pk": 2, + "model": "multiple_database.person", + "fields": { + "name": "George Vilches" + } + }, + { + "pk": 2, + "model": "multiple_database.book", + "fields": { + "title": "Pro Django", + "published": "2008-12-16", + "authors": [["Marty Alchin"]], + "editor": ["George Vilches"] + } + } +] diff --git a/tests/regressiontests/multiple_database/fixtures/multidb.other.json b/tests/regressiontests/multiple_database/fixtures/multidb.other.json new file mode 100644 index 0000000000..c64f490201 --- /dev/null +++ b/tests/regressiontests/multiple_database/fixtures/multidb.other.json @@ -0,0 +1,26 @@ +[ + { + "pk": 1, + "model": "multiple_database.person", + "fields": { + "name": "Mark Pilgrim" + } + }, + { + "pk": 2, + "model": "multiple_database.person", + "fields": { + "name": "Chris Mills" + } + }, + { + "pk": 2, + "model": "multiple_database.book", + "fields": { + "title": "Dive into Python", + "published": "2009-5-4", + "authors": [["Mark Pilgrim"]], + "editor": ["Chris Mills"] + } + } +]
\ No newline at end of file diff --git a/tests/regressiontests/multiple_database/models.py b/tests/regressiontests/multiple_database/models.py new file mode 100644 index 0000000000..33cb7fb745 --- /dev/null +++ b/tests/regressiontests/multiple_database/models.py @@ -0,0 +1,49 @@ +from django.conf import settings +from django.contrib.auth.models import User +from django.contrib.contenttypes.models import ContentType +from django.contrib.contenttypes import generic +from django.db import models, DEFAULT_DB_ALIAS + +class Review(models.Model): + source = models.CharField(max_length=100) + content_type = models.ForeignKey(ContentType) + object_id = models.PositiveIntegerField() + content_object = generic.GenericForeignKey() + + def __unicode__(self): + return self.source + + class Meta: + ordering = ('source',) + +class PersonManager(models.Manager): + def get_by_natural_key(self, name): + return self.get(name=name) + +class Person(models.Model): + objects = PersonManager() + name = models.CharField(max_length=100) + + def __unicode__(self): + return self.name + + class Meta: + ordering = ('name',) + +class Book(models.Model): + title = models.CharField(max_length=100) + published = models.DateField() + authors = models.ManyToManyField(Person) + editor = models.ForeignKey(Person, null=True, related_name='edited') + reviews = generic.GenericRelation(Review) + + def __unicode__(self): + return self.title + + class Meta: + ordering = ('title',) + +class UserProfile(models.Model): + user = models.OneToOneField(User) + flavor = models.CharField(max_length=100) + diff --git a/tests/regressiontests/multiple_database/tests.py b/tests/regressiontests/multiple_database/tests.py new file mode 100644 index 0000000000..300ed5e0a6 --- /dev/null +++ b/tests/regressiontests/multiple_database/tests.py @@ -0,0 +1,705 @@ +import datetime +import pickle + +from django.conf import settings +from django.contrib.auth.models import User +from django.db import connections +from django.test import TestCase + +from models import Book, Person, Review, UserProfile + +try: + # we only have these models if the user is using multi-db, it's safe the + # run the tests without them though. + from models import Article, article_using +except ImportError: + pass + +class QueryTestCase(TestCase): + multi_db = True + + def test_default_creation(self): + "Objects created on the default database don't leak onto other databases" + # Create a book on the default database using create() + Book.objects.create(title="Pro Django", + published=datetime.date(2008, 12, 16)) + + # Create a book on the default database using a save + dive = Book() + dive.title="Dive into Python" + dive.published = datetime.date(2009, 5, 4) + dive.save() + + # Check that book exists on the default database, but not on other database + try: + Book.objects.get(title="Pro Django") + Book.objects.using('default').get(title="Pro Django") + except Book.DoesNotExist: + self.fail('"Dive Into Python" should exist on default database') + + self.assertRaises(Book.DoesNotExist, + Book.objects.using('other').get, + title="Pro Django" + ) + + try: + Book.objects.get(title="Dive into Python") + Book.objects.using('default').get(title="Dive into Python") + except Book.DoesNotExist: + self.fail('"Dive into Python" should exist on default database') + + self.assertRaises(Book.DoesNotExist, + Book.objects.using('other').get, + title="Dive into Python" + ) + + + def test_other_creation(self): + "Objects created on another database don't leak onto the default database" + # Create a book on the second database + Book.objects.using('other').create(title="Pro Django", + published=datetime.date(2008, 12, 16)) + + # Create a book on the default database using a save + dive = Book() + dive.title="Dive into Python" + dive.published = datetime.date(2009, 5, 4) + dive.save(using='other') + + # Check that book exists on the default database, but not on other database + try: + Book.objects.using('other').get(title="Pro Django") + except Book.DoesNotExist: + self.fail('"Dive Into Python" should exist on other database') + + self.assertRaises(Book.DoesNotExist, + Book.objects.get, + title="Pro Django" + ) + self.assertRaises(Book.DoesNotExist, + Book.objects.using('default').get, + title="Pro Django" + ) + + try: + Book.objects.using('other').get(title="Dive into Python") + except Book.DoesNotExist: + self.fail('"Dive into Python" should exist on other database') + + self.assertRaises(Book.DoesNotExist, + Book.objects.get, + title="Dive into Python" + ) + self.assertRaises(Book.DoesNotExist, + Book.objects.using('default').get, + title="Dive into Python" + ) + + def test_basic_queries(self): + "Queries are constrained to a single database" + dive = Book.objects.using('other').create(title="Dive into Python", + published=datetime.date(2009, 5, 4)) + + dive = Book.objects.using('other').get(published=datetime.date(2009, 5, 4)) + self.assertEqual(dive.title, "Dive into Python") + self.assertRaises(Book.DoesNotExist, Book.objects.using('default').get, published=datetime.date(2009, 5, 4)) + + dive = Book.objects.using('other').get(title__icontains="dive") + self.assertEqual(dive.title, "Dive into Python") + self.assertRaises(Book.DoesNotExist, Book.objects.using('default').get, title__icontains="dive") + + dive = Book.objects.using('other').get(title__iexact="dive INTO python") + self.assertEqual(dive.title, "Dive into Python") + self.assertRaises(Book.DoesNotExist, Book.objects.using('default').get, title__iexact="dive INTO python") + + dive = Book.objects.using('other').get(published__year=2009) + self.assertEqual(dive.title, "Dive into Python") + self.assertEqual(dive.published, datetime.date(2009, 5, 4)) + self.assertRaises(Book.DoesNotExist, Book.objects.using('default').get, published__year=2009) + + years = Book.objects.using('other').dates('published', 'year') + self.assertEqual([o.year for o in years], [2009]) + years = Book.objects.using('default').dates('published', 'year') + self.assertEqual([o.year for o in years], []) + + months = Book.objects.using('other').dates('published', 'month') + self.assertEqual([o.month for o in months], [5]) + months = Book.objects.using('default').dates('published', 'month') + self.assertEqual([o.month for o in months], []) + + def test_m2m_separation(self): + "M2M fields are constrained to a single database" + # Create a book and author on the default database + pro = Book.objects.create(title="Pro Django", + published=datetime.date(2008, 12, 16)) + + marty = Person.objects.create(name="Marty Alchin") + + # Create a book and author on the other database + dive = Book.objects.using('other').create(title="Dive into Python", + published=datetime.date(2009, 5, 4)) + + mark = Person.objects.using('other').create(name="Mark Pilgrim") + + # Save the author relations + pro.authors = [marty] + dive.authors = [mark] + + # Inspect the m2m tables directly. + # There should be 1 entry in each database + self.assertEquals(Book.authors.through.objects.using('default').count(), 1) + self.assertEquals(Book.authors.through.objects.using('other').count(), 1) + + # Check that queries work across m2m joins + self.assertEquals(list(Book.objects.using('default').filter(authors__name='Marty Alchin').values_list('title', flat=True)), + [u'Pro Django']) + self.assertEquals(list(Book.objects.using('other').filter(authors__name='Marty Alchin').values_list('title', flat=True)), + []) + + self.assertEquals(list(Book.objects.using('default').filter(authors__name='Mark Pilgrim').values_list('title', flat=True)), + []) + self.assertEquals(list(Book.objects.using('other').filter(authors__name='Mark Pilgrim').values_list('title', flat=True)), + [u'Dive into Python']) + + # Reget the objects to clear caches + dive = Book.objects.using('other').get(title="Dive into Python") + mark = Person.objects.using('other').get(name="Mark Pilgrim") + + # Retrive related object by descriptor. Related objects should be database-baound + self.assertEquals(list(dive.authors.all().values_list('name', flat=True)), + [u'Mark Pilgrim']) + + self.assertEquals(list(mark.book_set.all().values_list('title', flat=True)), + [u'Dive into Python']) + + def test_m2m_forward_operations(self): + "M2M forward manipulations are all constrained to a single DB" + # Create a book and author on the other database + dive = Book.objects.using('other').create(title="Dive into Python", + published=datetime.date(2009, 5, 4)) + + mark = Person.objects.using('other').create(name="Mark Pilgrim") + + # Save the author relations + dive.authors = [mark] + + # Add a second author + john = Person.objects.using('other').create(name="John Smith") + self.assertEquals(list(Book.objects.using('other').filter(authors__name='John Smith').values_list('title', flat=True)), + []) + + + dive.authors.add(john) + self.assertEquals(list(Book.objects.using('other').filter(authors__name='Mark Pilgrim').values_list('title', flat=True)), + [u'Dive into Python']) + self.assertEquals(list(Book.objects.using('other').filter(authors__name='John Smith').values_list('title', flat=True)), + [u'Dive into Python']) + + # Remove the second author + dive.authors.remove(john) + self.assertEquals(list(Book.objects.using('other').filter(authors__name='Mark Pilgrim').values_list('title', flat=True)), + [u'Dive into Python']) + self.assertEquals(list(Book.objects.using('other').filter(authors__name='John Smith').values_list('title', flat=True)), + []) + + # Clear all authors + dive.authors.clear() + self.assertEquals(list(Book.objects.using('other').filter(authors__name='Mark Pilgrim').values_list('title', flat=True)), + []) + self.assertEquals(list(Book.objects.using('other').filter(authors__name='John Smith').values_list('title', flat=True)), + []) + + # Create an author through the m2m interface + dive.authors.create(name='Jane Brown') + self.assertEquals(list(Book.objects.using('other').filter(authors__name='Mark Pilgrim').values_list('title', flat=True)), + []) + self.assertEquals(list(Book.objects.using('other').filter(authors__name='Jane Brown').values_list('title', flat=True)), + [u'Dive into Python']) + + def test_m2m_reverse_operations(self): + "M2M reverse manipulations are all constrained to a single DB" + # Create a book and author on the other database + dive = Book.objects.using('other').create(title="Dive into Python", + published=datetime.date(2009, 5, 4)) + + mark = Person.objects.using('other').create(name="Mark Pilgrim") + + # Save the author relations + dive.authors = [mark] + + # Create a second book on the other database + grease = Book.objects.using('other').create(title="Greasemonkey Hacks", + published=datetime.date(2005, 11, 1)) + + # Add a books to the m2m + mark.book_set.add(grease) + self.assertEquals(list(Person.objects.using('other').filter(book__title='Dive into Python').values_list('name', flat=True)), + [u'Mark Pilgrim']) + self.assertEquals(list(Person.objects.using('other').filter(book__title='Greasemonkey Hacks').values_list('name', flat=True)), + [u'Mark Pilgrim']) + + # Remove a book from the m2m + mark.book_set.remove(grease) + self.assertEquals(list(Person.objects.using('other').filter(book__title='Dive into Python').values_list('name', flat=True)), + [u'Mark Pilgrim']) + self.assertEquals(list(Person.objects.using('other').filter(book__title='Greasemonkey Hacks').values_list('name', flat=True)), + []) + + # Clear the books associated with mark + mark.book_set.clear() + self.assertEquals(list(Person.objects.using('other').filter(book__title='Dive into Python').values_list('name', flat=True)), + []) + self.assertEquals(list(Person.objects.using('other').filter(book__title='Greasemonkey Hacks').values_list('name', flat=True)), + []) + + # Create a book through the m2m interface + mark.book_set.create(title="Dive into HTML5", published=datetime.date(2020, 1, 1)) + self.assertEquals(list(Person.objects.using('other').filter(book__title='Dive into Python').values_list('name', flat=True)), + []) + self.assertEquals(list(Person.objects.using('other').filter(book__title='Dive into HTML5').values_list('name', flat=True)), + [u'Mark Pilgrim']) + +# def test_m2m_cross_database_protection(self): +# "Operations that involve sharing M2M objects across databases raise an error" +# # Create a book and author on the default database +# pro = Book.objects.create(title="Pro Django", +# published=datetime.date(2008, 12, 16)) + +# marty = Person.objects.create(name="Marty Alchin") + +# # Create a book and author on the other database +# dive = Book.objects.using('other').create(title="Dive into Python", +# published=datetime.date(2009, 5, 4)) + +# mark = Person.objects.using('other').create(name="Mark Pilgrim") +# # Set a foreign key set with an object from a different database +# try: +# marty.book_set = [pro, dive] +# self.fail("Shouldn't be able to assign across databases") +# except ValueError: +# pass + +# # Add to an m2m with an object from a different database +# try: +# marty.book_set.add(dive) +# self.fail("Shouldn't be able to assign across databases") +# except ValueError: +# pass + +# # Set a m2m with an object from a different database +# try: +# marty.book_set = [pro, dive] +# self.fail("Shouldn't be able to assign across databases") +# except ValueError: +# pass + +# # Add to a reverse m2m with an object from a different database +# try: +# dive.authors.add(marty) +# self.fail("Shouldn't be able to assign across databases") +# except ValueError: +# pass + +# # Set a reverse m2m with an object from a different database +# try: +# dive.authors = [mark, marty] +# self.fail("Shouldn't be able to assign across databases") +# except ValueError: +# pass + + def test_foreign_key_separation(self): + "FK fields are constrained to a single database" + # Create a book and author on the default database + pro = Book.objects.create(title="Pro Django", + published=datetime.date(2008, 12, 16)) + + marty = Person.objects.create(name="Marty Alchin") + george = Person.objects.create(name="George Vilches") + + # Create a book and author on the other database + dive = Book.objects.using('other').create(title="Dive into Python", + published=datetime.date(2009, 5, 4)) + + mark = Person.objects.using('other').create(name="Mark Pilgrim") + chris = Person.objects.using('other').create(name="Chris Mills") + + # Save the author's favourite books + pro.editor = george + pro.save() + + dive.editor = chris + dive.save() + + pro = Book.objects.using('default').get(title="Pro Django") + self.assertEquals(pro.editor.name, "George Vilches") + + dive = Book.objects.using('other').get(title="Dive into Python") + self.assertEquals(dive.editor.name, "Chris Mills") + + # Check that queries work across foreign key joins + self.assertEquals(list(Person.objects.using('default').filter(edited__title='Pro Django').values_list('name', flat=True)), + [u'George Vilches']) + self.assertEquals(list(Person.objects.using('other').filter(edited__title='Pro Django').values_list('name', flat=True)), + []) + + self.assertEquals(list(Person.objects.using('default').filter(edited__title='Dive into Python').values_list('name', flat=True)), + []) + self.assertEquals(list(Person.objects.using('other').filter(edited__title='Dive into Python').values_list('name', flat=True)), + [u'Chris Mills']) + + # Reget the objects to clear caches + chris = Person.objects.using('other').get(name="Chris Mills") + dive = Book.objects.using('other').get(title="Dive into Python") + + # Retrive related object by descriptor. Related objects should be database-baound + self.assertEquals(list(chris.edited.values_list('title', flat=True)), + [u'Dive into Python']) + + def test_foreign_key_reverse_operations(self): + "FK reverse manipulations are all constrained to a single DB" + dive = Book.objects.using('other').create(title="Dive into Python", + published=datetime.date(2009, 5, 4)) + + mark = Person.objects.using('other').create(name="Mark Pilgrim") + chris = Person.objects.using('other').create(name="Chris Mills") + + # Save the author relations + dive.editor = chris + dive.save() + + # Add a second book edited by chris + html5 = Book.objects.using('other').create(title="Dive into HTML5", published=datetime.date(2010, 3, 15)) + self.assertEquals(list(Person.objects.using('other').filter(edited__title='Dive into HTML5').values_list('name', flat=True)), + []) + + chris.edited.add(html5) + self.assertEquals(list(Person.objects.using('other').filter(edited__title='Dive into HTML5').values_list('name', flat=True)), + [u'Chris Mills']) + self.assertEquals(list(Person.objects.using('other').filter(edited__title='Dive into Python').values_list('name', flat=True)), + [u'Chris Mills']) + + # Remove the second editor + chris.edited.remove(html5) + self.assertEquals(list(Person.objects.using('other').filter(edited__title='Dive into HTML5').values_list('name', flat=True)), + []) + self.assertEquals(list(Person.objects.using('other').filter(edited__title='Dive into Python').values_list('name', flat=True)), + [u'Chris Mills']) + + # Clear all edited books + chris.edited.clear() + self.assertEquals(list(Person.objects.using('other').filter(edited__title='Dive into HTML5').values_list('name', flat=True)), + []) + self.assertEquals(list(Person.objects.using('other').filter(edited__title='Dive into Python').values_list('name', flat=True)), + []) + + # Create an author through the m2m interface + chris.edited.create(title='Dive into Water', published=datetime.date(2010, 3, 15)) + self.assertEquals(list(Person.objects.using('other').filter(edited__title='Dive into HTML5').values_list('name', flat=True)), + []) + self.assertEquals(list(Person.objects.using('other').filter(edited__title='Dive into Water').values_list('name', flat=True)), + [u'Chris Mills']) + self.assertEquals(list(Person.objects.using('other').filter(edited__title='Dive into Python').values_list('name', flat=True)), + []) + +# def test_foreign_key_cross_database_protection(self): +# "Operations that involve sharing FK objects across databases raise an error" +# # Create a book and author on the default database +# pro = Book.objects.create(title="Pro Django", +# published=datetime.date(2008, 12, 16)) + +# marty = Person.objects.create(name="Marty Alchin") + +# # Create a book and author on the other database +# dive = Book.objects.using('other').create(title="Dive into Python", +# published=datetime.date(2009, 5, 4)) + +# mark = Person.objects.using('other').create(name="Mark Pilgrim") + +# # Set a foreign key with an object from a different database +# try: +# dive.editor = marty +# self.fail("Shouldn't be able to assign across databases") +# except ValueError: +# pass + +# # Set a foreign key set with an object from a different database +# try: +# marty.edited = [pro, dive] +# self.fail("Shouldn't be able to assign across databases") +# except ValueError: +# pass + +# # Add to a foreign key set with an object from a different database +# try: +# marty.edited.add(dive) +# self.fail("Shouldn't be able to assign across databases") +# except ValueError: +# pass + +# # BUT! if you assign a FK object when the base object hasn't +# # been saved yet, you implicitly assign the database for the +# # base object. +# chris = Person(name="Chris Mills") +# html5 = Book(title="Dive into HTML5", published=datetime.date(2010, 3, 15)) +# # initially, no db assigned +# self.assertEquals(chris._state.db, None) +# self.assertEquals(html5._state.db, None) + +# # old object comes from 'other', so the new object is set to use 'other'... +# dive.editor = chris +# html5.editor = mark +# # self.assertEquals(chris._state.db, 'other') +# self.assertEquals(html5._state.db, 'other') +# # ... but it isn't saved yet +# self.assertEquals(list(Person.objects.using('other').values_list('name',flat=True)), +# [u'Mark Pilgrim']) +# self.assertEquals(list(Book.objects.using('other').values_list('title',flat=True)), +# [u'Dive into Python']) + +# # When saved (no using required), new objects goes to 'other' +# chris.save() +# html5.save() +# self.assertEquals(list(Person.objects.using('default').values_list('name',flat=True)), +# [u'Marty Alchin']) +# self.assertEquals(list(Person.objects.using('other').values_list('name',flat=True)), +# [u'Chris Mills', u'Mark Pilgrim']) +# self.assertEquals(list(Book.objects.using('default').values_list('title',flat=True)), +# [u'Pro Django']) +# self.assertEquals(list(Book.objects.using('other').values_list('title',flat=True)), +# [u'Dive into HTML5', u'Dive into Python']) + +# # This also works if you assign the FK in the constructor +# water = Book(title="Dive into Water", published=datetime.date(2001, 1, 1), editor=mark) +# self.assertEquals(water._state.db, 'other') +# # ... but it isn't saved yet +# self.assertEquals(list(Book.objects.using('default').values_list('title',flat=True)), +# [u'Pro Django']) +# self.assertEquals(list(Book.objects.using('other').values_list('title',flat=True)), +# [u'Dive into HTML5', u'Dive into Python']) + +# # When saved, the new book goes to 'other' +# water.save() +# self.assertEquals(list(Book.objects.using('default').values_list('title',flat=True)), +# [u'Pro Django']) +# self.assertEquals(list(Book.objects.using('other').values_list('title',flat=True)), +# [u'Dive into HTML5', u'Dive into Python', u'Dive into Water']) + + def test_generic_key_separation(self): + "Generic fields are constrained to a single database" + # Create a book and author on the default database + pro = Book.objects.create(title="Pro Django", + published=datetime.date(2008, 12, 16)) + + review1 = Review.objects.create(source="Python Monthly", content_object=pro) + + # Create a book and author on the other database + dive = Book.objects.using('other').create(title="Dive into Python", + published=datetime.date(2009, 5, 4)) + + review2 = Review.objects.using('other').create(source="Python Weekly", content_object=dive) + + review1 = Review.objects.using('default').get(source="Python Monthly") + self.assertEquals(review1.content_object.title, "Pro Django") + + review2 = Review.objects.using('other').get(source="Python Weekly") + self.assertEquals(review2.content_object.title, "Dive into Python") + + # Reget the objects to clear caches + dive = Book.objects.using('other').get(title="Dive into Python") + + # Retrive related object by descriptor. Related objects should be database-bound + self.assertEquals(list(dive.reviews.all().values_list('source', flat=True)), + [u'Python Weekly']) + + def test_generic_key_reverse_operations(self): + "Generic reverse manipulations are all constrained to a single DB" + dive = Book.objects.using('other').create(title="Dive into Python", + published=datetime.date(2009, 5, 4)) + + temp = Book.objects.using('other').create(title="Temp", + published=datetime.date(2009, 5, 4)) + + review1 = Review.objects.using('other').create(source="Python Weekly", content_object=dive) + review2 = Review.objects.using('other').create(source="Python Monthly", content_object=temp) + + self.assertEquals(list(Review.objects.using('default').filter(object_id=dive.pk).values_list('source', flat=True)), + []) + self.assertEquals(list(Review.objects.using('other').filter(object_id=dive.pk).values_list('source', flat=True)), + [u'Python Weekly']) + + # Add a second review + dive.reviews.add(review2) + self.assertEquals(list(Review.objects.using('default').filter(object_id=dive.pk).values_list('source', flat=True)), + []) + self.assertEquals(list(Review.objects.using('other').filter(object_id=dive.pk).values_list('source', flat=True)), + [u'Python Monthly', u'Python Weekly']) + + # Remove the second author + dive.reviews.remove(review1) + self.assertEquals(list(Review.objects.using('default').filter(object_id=dive.pk).values_list('source', flat=True)), + []) + self.assertEquals(list(Review.objects.using('other').filter(object_id=dive.pk).values_list('source', flat=True)), + [u'Python Monthly']) + + # Clear all reviews + dive.reviews.clear() + self.assertEquals(list(Review.objects.using('default').filter(object_id=dive.pk).values_list('source', flat=True)), + []) + self.assertEquals(list(Review.objects.using('other').filter(object_id=dive.pk).values_list('source', flat=True)), + []) + + # Create an author through the generic interface + dive.reviews.create(source='Python Daily') + self.assertEquals(list(Review.objects.using('default').filter(object_id=dive.pk).values_list('source', flat=True)), + []) + self.assertEquals(list(Review.objects.using('other').filter(object_id=dive.pk).values_list('source', flat=True)), + [u'Python Daily']) + +# def test_generic_key_cross_database_protection(self): +## "Operations that involve sharing FK objects across databases raise an error" +## # Create a book and author on the default database +## pro = Book.objects.create(title="Pro Django", +## published=datetime.date(2008, 12, 16)) + +## review1 = Review.objects.create(source="Python Monthly", content_object=pro) + +## # Create a book and author on the other database +## dive = Book.objects.using('other').create(title="Dive into Python", +## published=datetime.date(2009, 5, 4)) + +## review2 = Review.objects.using('other').create(source="Python Weekly", content_object=dive) + +## # Set a foreign key with an object from a different database +## try: +## review1.content_object = dive +## self.fail("Shouldn't be able to assign across databases") +## except ValueError: +## pass + +# # Add to a foreign key set with an object from a different database +# try: +# dive.reviews.add(review1) +# self.fail("Shouldn't be able to assign across databases") +# except ValueError: +# pass + +# # BUT! if you assign a FK object when the base object hasn't +# # been saved yet, you implicitly assign the database for the +# # base object. +# review3 = Review(source="Python Daily") +# # initially, no db assigned +# self.assertEquals(review3._state.db, None) + +# # Dive comes from 'other', so review3 is set to use 'other'... +# review3.content_object = dive +# self.assertEquals(review3._state.db, 'other') +# # ... but it isn't saved yet +# self.assertEquals(list(Review.objects.using('default').filter(object_id=pro.pk).values_list('source', flat=True)), +# [u'Python Monthly']) +# self.assertEquals(list(Review.objects.using('other').filter(object_id=dive.pk).values_list('source',flat=True)), +# [u'Python Weekly']) + +# # When saved, John goes to 'other' +# review3.save() +# self.assertEquals(list(Review.objects.using('default').filter(object_id=pro.pk).values_list('source', flat=True)), +# [u'Python Monthly']) +# self.assertEquals(list(Review.objects.using('other').filter(object_id=dive.pk).values_list('source',flat=True)), +# [u'Python Daily', u'Python Weekly']) + + def test_ordering(self): + "get_next_by_XXX commands stick to a single database" + pro = Book.objects.create(title="Pro Django", + published=datetime.date(2008, 12, 16)) + + dive = Book.objects.using('other').create(title="Dive into Python", + published=datetime.date(2009, 5, 4)) + + learn = Book.objects.using('other').create(title="Learning Python", + published=datetime.date(2008, 7, 16)) + + self.assertEquals(learn.get_next_by_published().title, "Dive into Python") + self.assertEquals(dive.get_previous_by_published().title, "Learning Python") + + def test_raw(self): + "test the raw() method across databases" + dive = Book.objects.using('other').create(title="Dive into Python", + published=datetime.date(2009, 5, 4)) + val = Book.objects.db_manager("other").raw('SELECT id FROM "multiple_database_book"') + self.assertEqual(map(lambda o: o.pk, val), [dive.pk]) + + val = Book.objects.raw('SELECT id FROM "multiple_database_book"').using('other') + self.assertEqual(map(lambda o: o.pk, val), [dive.pk]) + + +class UserProfileTestCase(TestCase): + def setUp(self): + self.old_auth_profile_module = getattr(settings, 'AUTH_PROFILE_MODULE', None) + settings.AUTH_PROFILE_MODULE = 'multiple_database.UserProfile' + + def tearDown(self): + settings.AUTH_PROFILE_MODULE = self.old_auth_profile_module + + def test_user_profiles(self): + + alice = User.objects.create_user('alice', 'alice@example.com') + bob = User.objects.db_manager('other').create_user('bob', 'bob@example.com') + + alice_profile = UserProfile(user=alice, flavor='chocolate') + alice_profile.save() + + bob_profile = UserProfile(user=bob, flavor='crunchy frog') + bob_profile.save() + + self.assertEquals(alice.get_profile().flavor, 'chocolate') + self.assertEquals(bob.get_profile().flavor, 'crunchy frog') + +class FixtureTestCase(TestCase): + multi_db = True + fixtures = ['multidb-common', 'multidb'] + + def test_fixture_loading(self): + "Multi-db fixtures are loaded correctly" + # Check that "Pro Django" exists on the default database, but not on other database + try: + Book.objects.get(title="Pro Django") + Book.objects.using('default').get(title="Pro Django") + except Book.DoesNotExist: + self.fail('"Pro Django" should exist on default database') + + self.assertRaises(Book.DoesNotExist, + Book.objects.using('other').get, + title="Pro Django" + ) + + # Check that "Dive into Python" exists on the default database, but not on other database + try: + Book.objects.using('other').get(title="Dive into Python") + except Book.DoesNotExist: + self.fail('"Dive into Python" should exist on other database') + + self.assertRaises(Book.DoesNotExist, + Book.objects.get, + title="Dive into Python" + ) + self.assertRaises(Book.DoesNotExist, + Book.objects.using('default').get, + title="Dive into Python" + ) + + # Check that "Definitive Guide" exists on the both databases + try: + Book.objects.get(title="The Definitive Guide to Django") + Book.objects.using('default').get(title="The Definitive Guide to Django") + Book.objects.using('other').get(title="The Definitive Guide to Django") + except Book.DoesNotExist: + self.fail('"The Definitive Guide to Django" should exist on both databases') + + +class PickleQuerySetTestCase(TestCase): + multi_db = True + + def test_pickling(self): + for db in connections: + Book.objects.using(db).create(title='Dive into Python', published=datetime.date(2009, 5, 4)) + qs = Book.objects.all() + self.assertEqual(qs.db, pickle.loads(pickle.dumps(qs)).db) diff --git a/tests/regressiontests/queries/models.py b/tests/regressiontests/queries/models.py index ddac509ede..aababffbab 100644 --- a/tests/regressiontests/queries/models.py +++ b/tests/regressiontests/queries/models.py @@ -7,7 +7,7 @@ import pickle import sys from django.conf import settings -from django.db import models +from django.db import models, DEFAULT_DB_ALIAS from django.db.models.query import Q, ITER_CHUNK_SIZE # Python 2.3 doesn't have sorted() @@ -822,8 +822,8 @@ We can do slicing beyond what is currently in the result cache, too. Bug #7045 -- extra tables used to crash SQL construction on the second use. >>> qs = Ranking.objects.extra(tables=['django_site']) ->>> s = qs.query.as_sql() ->>> s = qs.query.as_sql() # test passes if this doesn't raise an exception. +>>> s = qs.query.get_compiler(qs.db).as_sql() +>>> s = qs.query.get_compiler(qs.db).as_sql() # test passes if this doesn't raise an exception. Bug #7098 -- Make sure semi-deprecated ordering by related models syntax still works. @@ -912,9 +912,9 @@ We should also be able to pickle things that use select_related(). The only tricky thing here is to ensure that we do the related selections properly after unpickling. >>> qs = Item.objects.select_related() ->>> query = qs.query.as_sql()[0] +>>> query = qs.query.get_compiler(qs.db).as_sql()[0] >>> query2 = pickle.loads(pickle.dumps(qs.query)) ->>> query2.as_sql()[0] == query +>>> query2.get_compiler(qs.db).as_sql()[0] == query True Check pickling of deferred-loading querysets @@ -1051,7 +1051,7 @@ sufficient that this query runs without error. Calling order_by() with no parameters removes any existing ordering on the model. But it should still be possible to add new ordering after that. >>> qs = Author.objects.order_by().order_by('name') ->>> 'ORDER BY' in qs.query.as_sql()[0] +>>> 'ORDER BY' in qs.query.get_compiler(qs.db).as_sql()[0] True Incorrect SQL was being generated for certain types of exclude() queries that @@ -1085,7 +1085,8 @@ performance problems on backends like MySQL. Nested queries should not evaluate the inner query as part of constructing the SQL (so we should see a nested query here, indicated by two "SELECT" calls). ->>> Annotation.objects.filter(notes__in=Note.objects.filter(note="xyzzy")).query.as_sql()[0].count('SELECT') +>>> qs = Annotation.objects.filter(notes__in=Note.objects.filter(note="xyzzy")) +>>> qs.query.get_compiler(qs.db).as_sql()[0].count('SELECT') 2 Bug #10181 -- Avoid raising an EmptyResultSet if an inner query is provably @@ -1222,20 +1223,20 @@ FieldError: Infinite loop caused by ordering. # In Oracle, we expect a null CharField to return u'' instead of None. -if settings.DATABASE_ENGINE == "oracle": +if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] == "django.db.backends.oracle": __test__["API_TESTS"] = __test__["API_TESTS"].replace("<NONE_OR_EMPTY_UNICODE>", "u''") else: __test__["API_TESTS"] = __test__["API_TESTS"].replace("<NONE_OR_EMPTY_UNICODE>", "None") -if settings.DATABASE_ENGINE == "mysql": +if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] == "django.db.backends.mysql": __test__["API_TESTS"] += """ When grouping without specifying ordering, we add an explicit "ORDER BY NULL" portion in MySQL to prevent unnecessary sorting. >>> query = Tag.objects.values_list('parent_id', flat=True).order_by().query >>> query.group_by = ['parent_id'] ->>> sql = query.as_sql()[0] +>>> sql = query.get_compiler(DEFAULT_DB_ALIAS).as_sql()[0] >>> fragment = "ORDER BY " >>> pos = sql.find(fragment) >>> sql.find(fragment, pos + 1) == -1 diff --git a/tests/regressiontests/serializers_regress/models.py b/tests/regressiontests/serializers_regress/models.py index 313ed8fc3a..ccd850326d 100644 --- a/tests/regressiontests/serializers_regress/models.py +++ b/tests/regressiontests/serializers_regress/models.py @@ -43,6 +43,9 @@ class FloatData(models.Model): class IntegerData(models.Model): data = models.IntegerField(null=True) +class BigIntegerData(models.Model): + data = models.BigIntegerField(null=True) + # class ImageData(models.Model): # data = models.ImageField(null=True) diff --git a/tests/regressiontests/serializers_regress/tests.py b/tests/regressiontests/serializers_regress/tests.py index de7ddcc9f7..11b53a5277 100644 --- a/tests/regressiontests/serializers_regress/tests.py +++ b/tests/regressiontests/serializers_regress/tests.py @@ -13,7 +13,7 @@ from cStringIO import StringIO from django.utils.functional import curry from django.core import serializers -from django.db import transaction +from django.db import transaction, DEFAULT_DB_ALIAS from django.core import management from django.conf import settings @@ -260,19 +260,19 @@ The end."""), (fk_obj, 452, FKDataToField, None), (fk_obj, 460, FKDataToO2O, 300), - + (im2m_obj, 470, M2MIntermediateData, None), - + #testing post- and prereferences and extra fields (im_obj, 480, Intermediate, {'right': 300, 'left': 470}), - (im_obj, 481, Intermediate, {'right': 300, 'left': 490}), - (im_obj, 482, Intermediate, {'right': 500, 'left': 470}), - (im_obj, 483, Intermediate, {'right': 500, 'left': 490}), - (im_obj, 484, Intermediate, {'right': 300, 'left': 470, 'extra': "extra"}), - (im_obj, 485, Intermediate, {'right': 300, 'left': 490, 'extra': "extra"}), - (im_obj, 486, Intermediate, {'right': 500, 'left': 470, 'extra': "extra"}), - (im_obj, 487, Intermediate, {'right': 500, 'left': 490, 'extra': "extra"}), - + (im_obj, 481, Intermediate, {'right': 300, 'left': 490}), + (im_obj, 482, Intermediate, {'right': 500, 'left': 470}), + (im_obj, 483, Intermediate, {'right': 500, 'left': 490}), + (im_obj, 484, Intermediate, {'right': 300, 'left': 470, 'extra': "extra"}), + (im_obj, 485, Intermediate, {'right': 300, 'left': 490, 'extra': "extra"}), + (im_obj, 486, Intermediate, {'right': 500, 'left': 470, 'extra': "extra"}), + (im_obj, 487, Intermediate, {'right': 500, 'left': 490, 'extra': "extra"}), + (im2m_obj, 490, M2MIntermediateData, []), (data_obj, 500, Anchor, "Anchor 3"), @@ -321,12 +321,17 @@ The end."""), (inherited_obj, 900, InheritAbstractModel, {'child_data':37,'parent_data':42}), (inherited_obj, 910, ExplicitInheritBaseModel, {'child_data':37,'parent_data':42}), (inherited_obj, 920, InheritBaseModel, {'child_data':37,'parent_data':42}), + + (data_obj, 1000, BigIntegerData, 9223372036854775807), + (data_obj, 1001, BigIntegerData, -9223372036854775808), + (data_obj, 1002, BigIntegerData, 0), + (data_obj, 1003, BigIntegerData, None), ] # Because Oracle treats the empty string as NULL, Oracle is expected to fail # when field.empty_strings_allowed is True and the value is None; skip these # tests. -if settings.DATABASE_ENGINE == 'oracle': +if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] == 'django.db.backends.oracle': test_data = [data for data in test_data if not (data[0] == data_obj and data[2]._meta.get_field('data').empty_strings_allowed and @@ -335,7 +340,7 @@ if settings.DATABASE_ENGINE == 'oracle': # Regression test for #8651 -- a FK to an object iwth PK of 0 # This won't work on MySQL since it won't let you create an object # with a primary key of 0, -if settings.DATABASE_ENGINE != 'mysql': +if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] != 'django.db.backends.mysql': test_data.extend([ (data_obj, 0, Anchor, "Anchor 0"), (fk_obj, 465, FKData, 0), diff --git a/tests/regressiontests/settings_tests/__init__.py b/tests/regressiontests/settings_tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/regressiontests/settings_tests/__init__.py diff --git a/tests/regressiontests/settings_tests/models.py b/tests/regressiontests/settings_tests/models.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/regressiontests/settings_tests/models.py diff --git a/tests/regressiontests/settings_tests/tests.py b/tests/regressiontests/settings_tests/tests.py new file mode 100644 index 0000000000..fa217b1d79 --- /dev/null +++ b/tests/regressiontests/settings_tests/tests.py @@ -0,0 +1,17 @@ +import unittest +from django.conf import settings + +class SettingsTests(unittest.TestCase): + + # + # Regression tests for #10130: deleting settings. + # + + def test_settings_delete(self): + settings.TEST = 'test' + self.assertEqual('test', settings.TEST) + del settings.TEST + self.assertRaises(AttributeError, getattr, settings, 'TEST') + + def test_settings_delete_wrapped(self): + self.assertRaises(TypeError, delattr, settings, '_wrapped') diff --git a/tests/regressiontests/templates/context.py b/tests/regressiontests/templates/context.py index d8b0f39abe..7886c8328b 100644 --- a/tests/regressiontests/templates/context.py +++ b/tests/regressiontests/templates/context.py @@ -10,9 +10,13 @@ context_tests = r""" >>> c['a'] = 2 >>> c['a'] 2 +>>> c.get('a') +2 >>> c.pop() {'a': 2} >>> c['a'] 1 +>>> c.get('foo', 42) +42 """ diff --git a/tests/regressiontests/templates/filters.py b/tests/regressiontests/templates/filters.py index 2b448574f7..88266e107b 100644 --- a/tests/regressiontests/templates/filters.py +++ b/tests/regressiontests/templates/filters.py @@ -120,13 +120,19 @@ def get_filter_tests(): # Notice that escaping is applied *after* any filters, so the string # formatting here only needs to deal with pre-escaped characters. - 'filter-stringformat01': ('{% autoescape off %}.{{ a|stringformat:"5s" }}. .{{ b|stringformat:"5s" }}.{% endautoescape %}', {"a": "a<b", "b": mark_safe("a<b")}, u". a<b. . a<b."), - 'filter-stringformat02': ('.{{ a|stringformat:"5s" }}. .{{ b|stringformat:"5s" }}.', {"a": "a<b", "b": mark_safe("a<b")}, u". a<b. . a<b."), + 'filter-stringformat01': ('{% autoescape off %}.{{ a|stringformat:"5s" }}. .{{ b|stringformat:"5s" }}.{% endautoescape %}', + {"a": "a<b", "b": mark_safe("a<b")}, u". a<b. . a<b."), + 'filter-stringformat02': ('.{{ a|stringformat:"5s" }}. .{{ b|stringformat:"5s" }}.', {"a": "a<b", "b": mark_safe("a<b")}, + u". a<b. . a<b."), - # XXX No test for "title" filter; needs an actual object. + # Test the title filter + 'filter-title1' : ('{{ a|title }}', {'a' : 'JOE\'S CRAB SHACK'}, u'Joe's Crab Shack'), + 'filter-title2' : ('{{ a|title }}', {'a' : '555 WEST 53RD STREET'}, u'555 West 53rd Street'), - 'filter-truncatewords01': ('{% autoescape off %}{{ a|truncatewords:"2" }} {{ b|truncatewords:"2"}}{% endautoescape %}', {"a": "alpha & bravo", "b": mark_safe("alpha & bravo")}, u"alpha & ... alpha & ..."), - 'filter-truncatewords02': ('{{ a|truncatewords:"2" }} {{ b|truncatewords:"2"}}', {"a": "alpha & bravo", "b": mark_safe("alpha & bravo")}, u"alpha & ... alpha & ..."), + 'filter-truncatewords01': ('{% autoescape off %}{{ a|truncatewords:"2" }} {{ b|truncatewords:"2"}}{% endautoescape %}', + {"a": "alpha & bravo", "b": mark_safe("alpha & bravo")}, u"alpha & ... alpha & ..."), + 'filter-truncatewords02': ('{{ a|truncatewords:"2" }} {{ b|truncatewords:"2"}}', + {"a": "alpha & bravo", "b": mark_safe("alpha & bravo")}, u"alpha & ... alpha & ..."), # The "upper" filter messes up entities (which are case-sensitive), # so it's not safe for non-escaping purposes. diff --git a/tests/regressiontests/templates/smartif.py b/tests/regressiontests/templates/smartif.py new file mode 100644 index 0000000000..0114753830 --- /dev/null +++ b/tests/regressiontests/templates/smartif.py @@ -0,0 +1,46 @@ +import unittest +from django.template.smartif import IfParser, Literal + +class SmartIfTests(unittest.TestCase): + + def assertCalcEqual(self, expected, tokens): + self.assertEqual(expected, IfParser(tokens).parse().eval({})) + + # We only test things here that are difficult to test elsewhere + # Many other tests are found in the main tests for builtin template tags + # Test parsing via the printed parse tree + def test_not(self): + var = IfParser(["not", False]).parse() + self.assertEqual("(not (literal False))", repr(var)) + self.assert_(var.eval({})) + + self.assertFalse(IfParser(["not", True]).parse().eval({})) + + def test_or(self): + var = IfParser([True, "or", False]).parse() + self.assertEqual("(or (literal True) (literal False))", repr(var)) + self.assert_(var.eval({})) + + def test_in(self): + list_ = [1,2,3] + self.assertCalcEqual(True, [1, 'in', list_]) + self.assertCalcEqual(False, [1, 'in', None]) + self.assertCalcEqual(False, [None, 'in', list_]) + + def test_precedence(self): + # (False and False) or True == True <- we want this one, like Python + # False and (False or True) == False + self.assertCalcEqual(True, [False, 'and', False, 'or', True]) + + # True or (False and False) == True <- we want this one, like Python + # (True or False) and False == False + self.assertCalcEqual(True, [True, 'or', False, 'and', False]) + + # (1 or 1) == 2 -> False + # 1 or (1 == 2) -> True <- we want this one + self.assertCalcEqual(True, [1, 'or', 1, '==', 2]) + + self.assertCalcEqual(True, [True, '==', True, 'or', True, '==', False]) + + self.assertEqual("(or (and (== (literal 1) (literal 2)) (literal 3)) (literal 4))", + repr(IfParser([1, '==', 2, 'and', 3, 'or', 4]).parse())) diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py index 9c01b492e3..29462086d8 100644 --- a/tests/regressiontests/templates/tests.py +++ b/tests/regressiontests/templates/tests.py @@ -15,7 +15,7 @@ import unittest from django import template from django.core import urlresolvers from django.template import loader -from django.template.loaders import app_directories, filesystem +from django.template.loaders import app_directories, filesystem, cached from django.utils.translation import activate, deactivate, ugettext as _ from django.utils.safestring import mark_safe from django.utils.tzinfo import LocalTimezone @@ -24,6 +24,7 @@ from context import context_tests from custom import custom_filters from parser import filter_parsing, variable_parsing from unicode import unicode_tests +from smartif import * try: from loaders import * @@ -100,13 +101,15 @@ class UTF8Class: class Templates(unittest.TestCase): def test_loaders_security(self): + ad_loader = app_directories.Loader() + fs_loader = filesystem.Loader() def test_template_sources(path, template_dirs, expected_sources): if isinstance(expected_sources, list): # Fix expected sources so they are normcased and abspathed expected_sources = [os.path.normcase(os.path.abspath(s)) for s in expected_sources] # Test the two loaders (app_directores and filesystem). - func1 = lambda p, t: list(app_directories.get_template_sources(p, t)) - func2 = lambda p, t: list(filesystem.get_template_sources(p, t)) + func1 = lambda p, t: list(ad_loader.get_template_sources(p, t)) + func2 = lambda p, t: list(fs_loader.get_template_sources(p, t)) for func in (func1, func2): if isinstance(expected_sources, list): self.assertEqual(func(path, template_dirs), expected_sources) @@ -197,8 +200,11 @@ class Templates(unittest.TestCase): except KeyError: raise template.TemplateDoesNotExist, template_name + cache_loader = cached.Loader(('test_template_loader',)) + cache_loader._cached_loaders = (test_template_loader,) + old_template_loaders = loader.template_source_loaders - loader.template_source_loaders = [test_template_loader] + loader.template_source_loaders = [cache_loader] failures = [] tests = template_tests.items() @@ -231,20 +237,22 @@ class Templates(unittest.TestCase): for invalid_str, result in [('', normal_string_result), (expected_invalid_str, invalid_string_result)]: settings.TEMPLATE_STRING_IF_INVALID = invalid_str - try: - test_template = loader.get_template(name) - output = self.render(test_template, vals) - except ContextStackException: - failures.append("Template test (TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Context stack was left imbalanced" % (invalid_str, name)) - continue - except Exception: - exc_type, exc_value, exc_tb = sys.exc_info() - if exc_type != result: - tb = '\n'.join(traceback.format_exception(exc_type, exc_value, exc_tb)) - failures.append("Template test (TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Got %s, exception: %s\n%s" % (invalid_str, name, exc_type, exc_value, tb)) - continue - if output != result: - failures.append("Template test (TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Expected %r, got %r" % (invalid_str, name, result, output)) + for is_cached in (False, True): + try: + test_template = loader.get_template(name) + output = self.render(test_template, vals) + except ContextStackException: + failures.append("Template test (Cached='%s', TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Context stack was left imbalanced" % (is_cached, invalid_str, name)) + continue + except Exception: + exc_type, exc_value, exc_tb = sys.exc_info() + if exc_type != result: + tb = '\n'.join(traceback.format_exception(exc_type, exc_value, exc_tb)) + failures.append("Template test (Cached='%s', TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Got %s, exception: %s\n%s" % (is_cached, invalid_str, name, exc_type, exc_value, tb)) + continue + if output != result: + failures.append("Template test (Cached='%s', TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Expected %r, got %r" % (is_cached, invalid_str, name, result, output)) + cache_loader.reset() if 'LANGUAGE_CODE' in vals[1]: deactivate() @@ -534,6 +542,27 @@ class Templates(unittest.TestCase): 'if-tag02': ("{% if foo %}yes{% else %}no{% endif %}", {"foo": False}, "no"), 'if-tag03': ("{% if foo %}yes{% else %}no{% endif %}", {}, "no"), + # Filters + 'if-tag-filter01': ("{% if foo|length == 5 %}yes{% else %}no{% endif %}", {'foo': 'abcde'}, "yes"), + 'if-tag-filter02': ("{% if foo|upper == 'ABC' %}yes{% else %}no{% endif %}", {}, "no"), + + # Equality + 'if-tag-eq01': ("{% if foo == bar %}yes{% else %}no{% endif %}", {}, "yes"), + 'if-tag-eq02': ("{% if foo == bar %}yes{% else %}no{% endif %}", {'foo': 1}, "no"), + 'if-tag-eq03': ("{% if foo == bar %}yes{% else %}no{% endif %}", {'foo': 1, 'bar': 1}, "yes"), + 'if-tag-eq04': ("{% if foo == bar %}yes{% else %}no{% endif %}", {'foo': 1, 'bar': 2}, "no"), + 'if-tag-eq05': ("{% if foo == '' %}yes{% else %}no{% endif %}", {}, "no"), + + # Comparison + 'if-tag-gt-01': ("{% if 2 > 1 %}yes{% else %}no{% endif %}", {}, "yes"), + 'if-tag-gt-02': ("{% if 1 > 1 %}yes{% else %}no{% endif %}", {}, "no"), + 'if-tag-gte-01': ("{% if 1 >= 1 %}yes{% else %}no{% endif %}", {}, "yes"), + 'if-tag-gte-02': ("{% if 1 >= 2 %}yes{% else %}no{% endif %}", {}, "no"), + 'if-tag-lt-01': ("{% if 1 < 2 %}yes{% else %}no{% endif %}", {}, "yes"), + 'if-tag-lt-02': ("{% if 1 < 1 %}yes{% else %}no{% endif %}", {}, "no"), + 'if-tag-lte-01': ("{% if 1 <= 1 %}yes{% else %}no{% endif %}", {}, "yes"), + 'if-tag-lte-02': ("{% if 2 <= 1 %}yes{% else %}no{% endif %}", {}, "no"), + # AND 'if-tag-and01': ("{% if foo and bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': True}, 'yes'), 'if-tag-and02': ("{% if foo and bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': False}, 'no'), @@ -554,14 +583,13 @@ class Templates(unittest.TestCase): 'if-tag-or07': ("{% if foo or bar %}yes{% else %}no{% endif %}", {'foo': True}, 'yes'), 'if-tag-or08': ("{% if foo or bar %}yes{% else %}no{% endif %}", {'bar': True}, 'yes'), - # TODO: multiple ORs + # multiple ORs + 'if-tag-or09': ("{% if foo or bar or baz %}yes{% else %}no{% endif %}", {'baz': True}, 'yes'), # NOT 'if-tag-not01': ("{% if not foo %}no{% else %}yes{% endif %}", {'foo': True}, 'yes'), - 'if-tag-not02': ("{% if not %}yes{% else %}no{% endif %}", {'foo': True}, 'no'), - 'if-tag-not03': ("{% if not %}yes{% else %}no{% endif %}", {'not': True}, 'yes'), - 'if-tag-not04': ("{% if not not %}no{% else %}yes{% endif %}", {'not': True}, 'yes'), - 'if-tag-not05': ("{% if not not %}no{% else %}yes{% endif %}", {}, 'no'), + 'if-tag-not02': ("{% if not not foo %}no{% else %}yes{% endif %}", {'foo': True}, 'no'), + # not03 to not05 removed, now TemplateSyntaxErrors 'if-tag-not06': ("{% if foo and not bar %}yes{% else %}no{% endif %}", {}, 'no'), 'if-tag-not07': ("{% if foo and not bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': True}, 'no'), @@ -599,12 +627,21 @@ class Templates(unittest.TestCase): 'if-tag-not34': ("{% if not foo or not bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': True}, 'yes'), 'if-tag-not35': ("{% if not foo or not bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': False}, 'yes'), - # AND and OR raises a TemplateSyntaxError - 'if-tag-error01': ("{% if foo or bar and baz %}yes{% else %}no{% endif %}", {'foo': False, 'bar': False}, template.TemplateSyntaxError), + # Various syntax errors + 'if-tag-error01': ("{% if %}yes{% endif %}", {}, template.TemplateSyntaxError), 'if-tag-error02': ("{% if foo and %}yes{% else %}no{% endif %}", {'foo': True}, template.TemplateSyntaxError), 'if-tag-error03': ("{% if foo or %}yes{% else %}no{% endif %}", {'foo': True}, template.TemplateSyntaxError), 'if-tag-error04': ("{% if not foo and %}yes{% else %}no{% endif %}", {'foo': True}, template.TemplateSyntaxError), 'if-tag-error05': ("{% if not foo or %}yes{% else %}no{% endif %}", {'foo': True}, template.TemplateSyntaxError), + 'if-tag-error06': ("{% if abc def %}yes{% endif %}", {}, template.TemplateSyntaxError), + 'if-tag-error07': ("{% if not %}yes{% endif %}", {}, template.TemplateSyntaxError), + 'if-tag-error08': ("{% if and %}yes{% endif %}", {}, template.TemplateSyntaxError), + 'if-tag-error09': ("{% if or %}yes{% endif %}", {}, template.TemplateSyntaxError), + 'if-tag-error10': ("{% if == %}yes{% endif %}", {}, template.TemplateSyntaxError), + 'if-tag-error11': ("{% if 1 == %}yes{% endif %}", {}, template.TemplateSyntaxError), + 'if-tag-error12': ("{% if a not b %}yes{% endif %}", {}, template.TemplateSyntaxError), + + # Additional, more precise parsing tests are in SmartIfTests ### IFCHANGED TAG ######################################################### 'ifchanged01': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% endfor %}', {'num': (1,2,3)}, '123'), diff --git a/tests/regressiontests/test_client_regress/models.py b/tests/regressiontests/test_client_regress/models.py index 58693cc395..e532c90afc 100644 --- a/tests/regressiontests/test_client_regress/models.py +++ b/tests/regressiontests/test_client_regress/models.py @@ -10,6 +10,7 @@ from django.test.utils import ContextList from django.core.urlresolvers import reverse from django.core.exceptions import SuspiciousOperation from django.template import TemplateDoesNotExist, TemplateSyntaxError, Context +from django.template import loader class AssertContainsTests(TestCase): def setUp(self): @@ -436,6 +437,11 @@ class ExceptionTests(TestCase): class TemplateExceptionTests(TestCase): def setUp(self): + # Reset the loaders so they don't try to render cached templates. + if loader.template_source_loaders is not None: + for template_loader in loader.template_source_loaders: + if hasattr(template_loader, 'reset'): + template_loader.reset() self.old_templates = settings.TEMPLATE_DIRS settings.TEMPLATE_DIRS = () diff --git a/tests/regressiontests/test_runner/__init__.py b/tests/regressiontests/test_runner/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/regressiontests/test_runner/__init__.py diff --git a/tests/regressiontests/test_runner/models.py b/tests/regressiontests/test_runner/models.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/regressiontests/test_runner/models.py diff --git a/tests/regressiontests/test_runner/tests.py b/tests/regressiontests/test_runner/tests.py new file mode 100644 index 0000000000..d8db7baf94 --- /dev/null +++ b/tests/regressiontests/test_runner/tests.py @@ -0,0 +1,29 @@ +""" +Tests for django test runner +""" +import StringIO +import unittest +import django +from django.test import simple + +class DjangoTestRunnerTests(unittest.TestCase): + + def test_failfast(self): + class MockTestOne(unittest.TestCase): + def runTest(self): + assert False + class MockTestTwo(unittest.TestCase): + def runTest(self): + assert False + + suite = unittest.TestSuite([MockTestOne(), MockTestTwo()]) + mock_stream = StringIO.StringIO() + dtr = simple.DjangoTestRunner(verbosity=0, failfast=False, stream=mock_stream) + result = dtr.run(suite) + self.assertEqual(2, result.testsRun) + self.assertEqual(2, len(result.failures)) + + dtr = simple.DjangoTestRunner(verbosity=0, failfast=True, stream=mock_stream) + result = dtr.run(suite) + self.assertEqual(1, result.testsRun) + self.assertEqual(1, len(result.failures)) diff --git a/tests/regressiontests/urlpatterns_reverse/tests.py b/tests/regressiontests/urlpatterns_reverse/tests.py index 34c9db25d3..1dc85b0424 100644 --- a/tests/regressiontests/urlpatterns_reverse/tests.py +++ b/tests/regressiontests/urlpatterns_reverse/tests.py @@ -244,7 +244,6 @@ 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 @@ -276,3 +275,25 @@ class RequestURLconfTests(TestCase): response = self.client.get('/second_test/') self.assertEqual(response.status_code, 200) self.assertEqual(response.content, 'outer:,inner:/second_test/') + +class ErrorHandlerResolutionTests(TestCase): + """Tests for handler404 and handler500""" + + def setUp(self): + from django.core.urlresolvers import RegexURLResolver + urlconf = 'regressiontests.urlpatterns_reverse.urls_error_handlers' + urlconf_callables = 'regressiontests.urlpatterns_reverse.urls_error_handlers_callables' + self.resolver = RegexURLResolver(r'^$', urlconf) + self.callable_resolver = RegexURLResolver(r'^$', urlconf_callables) + + def test_named_handlers(self): + from views import empty_view + handler = (empty_view, {}) + self.assertEqual(self.resolver.resolve404(), handler) + self.assertEqual(self.resolver.resolve500(), handler) + + def test_callable_handers(self): + from views import empty_view + handler = (empty_view, {}) + self.assertEqual(self.callable_resolver.resolve404(), handler) + self.assertEqual(self.callable_resolver.resolve500(), handler) diff --git a/tests/regressiontests/urlpatterns_reverse/urls_error_handlers.py b/tests/regressiontests/urlpatterns_reverse/urls_error_handlers.py new file mode 100644 index 0000000000..c2e0d32498 --- /dev/null +++ b/tests/regressiontests/urlpatterns_reverse/urls_error_handlers.py @@ -0,0 +1,8 @@ +# Used by the ErrorHandlerResolutionTests test case. + +from django.conf.urls.defaults import patterns + +urlpatterns = patterns('') + +handler404 = 'regressiontests.urlpatterns_reverse.views.empty_view' +handler500 = 'regressiontests.urlpatterns_reverse.views.empty_view' diff --git a/tests/regressiontests/urlpatterns_reverse/urls_error_handlers_callables.py b/tests/regressiontests/urlpatterns_reverse/urls_error_handlers_callables.py new file mode 100644 index 0000000000..00f25a7236 --- /dev/null +++ b/tests/regressiontests/urlpatterns_reverse/urls_error_handlers_callables.py @@ -0,0 +1,9 @@ +# Used by the ErrorHandlerResolutionTests test case. + +from django.conf.urls.defaults import patterns +from views import empty_view + +urlpatterns = patterns('') + +handler404 = empty_view +handler500 = empty_view diff --git a/tests/regressiontests/utils/termcolors.py b/tests/regressiontests/utils/termcolors.py new file mode 100644 index 0000000000..7c8fe0d63b --- /dev/null +++ b/tests/regressiontests/utils/termcolors.py @@ -0,0 +1,149 @@ +from unittest import TestCase + +from django.utils.termcolors import parse_color_setting, PALETTES, DEFAULT_PALETTE, LIGHT_PALETTE, DARK_PALETTE, NOCOLOR_PALETTE + +class TermColorTests(TestCase): + + def test_empty_string(self): + self.assertEquals(parse_color_setting(''), PALETTES[DEFAULT_PALETTE]) + + def test_simple_palette(self): + self.assertEquals(parse_color_setting('light'), PALETTES[LIGHT_PALETTE]) + self.assertEquals(parse_color_setting('dark'), PALETTES[DARK_PALETTE]) + self.assertEquals(parse_color_setting('nocolor'), None) + + def test_fg(self): + self.assertEquals(parse_color_setting('error=green'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green'})) + + def test_fg_bg(self): + self.assertEquals(parse_color_setting('error=green/blue'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green', 'bg':'blue'})) + + def test_fg_opts(self): + self.assertEquals(parse_color_setting('error=green,blink'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green', 'opts': ('blink',)})) + self.assertEquals(parse_color_setting('error=green,bold,blink'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green', 'opts': ('blink','bold')})) + + def test_fg_bg_opts(self): + self.assertEquals(parse_color_setting('error=green/blue,blink'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green', 'bg':'blue', 'opts': ('blink',)})) + self.assertEquals(parse_color_setting('error=green/blue,bold,blink'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green', 'bg':'blue', 'opts': ('blink','bold')})) + + def test_override_palette(self): + self.assertEquals(parse_color_setting('light;error=green'), + dict(PALETTES[LIGHT_PALETTE], + ERROR={'fg':'green'})) + + def test_override_nocolor(self): + self.assertEquals(parse_color_setting('nocolor;error=green'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg': 'green'})) + + def test_reverse_override(self): + self.assertEquals(parse_color_setting('error=green;light'), PALETTES[LIGHT_PALETTE]) + + def test_multiple_roles(self): + self.assertEquals(parse_color_setting('error=green;sql_field=blue'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green'}, + SQL_FIELD={'fg':'blue'})) + + def test_override_with_multiple_roles(self): + self.assertEquals(parse_color_setting('light;error=green;sql_field=blue'), + dict(PALETTES[LIGHT_PALETTE], + ERROR={'fg':'green'}, + SQL_FIELD={'fg':'blue'})) + + def test_empty_definition(self): + self.assertEquals(parse_color_setting(';'), None) + self.assertEquals(parse_color_setting('light;'), PALETTES[LIGHT_PALETTE]) + self.assertEquals(parse_color_setting(';;;'), None) + + def test_empty_options(self): + self.assertEquals(parse_color_setting('error=green,'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green'})) + self.assertEquals(parse_color_setting('error=green,,,'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green'})) + self.assertEquals(parse_color_setting('error=green,,blink,,'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green', 'opts': ('blink',)})) + + def test_bad_palette(self): + self.assertEquals(parse_color_setting('unknown'), None) + + def test_bad_role(self): + self.assertEquals(parse_color_setting('unknown='), None) + self.assertEquals(parse_color_setting('unknown=green'), None) + self.assertEquals(parse_color_setting('unknown=green;sql_field=blue'), + dict(PALETTES[NOCOLOR_PALETTE], + SQL_FIELD={'fg':'blue'})) + + def test_bad_color(self): + self.assertEquals(parse_color_setting('error='), None) + self.assertEquals(parse_color_setting('error=;sql_field=blue'), + dict(PALETTES[NOCOLOR_PALETTE], + SQL_FIELD={'fg':'blue'})) + self.assertEquals(parse_color_setting('error=unknown'), None) + self.assertEquals(parse_color_setting('error=unknown;sql_field=blue'), + dict(PALETTES[NOCOLOR_PALETTE], + SQL_FIELD={'fg':'blue'})) + self.assertEquals(parse_color_setting('error=green/unknown'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green'})) + self.assertEquals(parse_color_setting('error=green/blue/something'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green', 'bg': 'blue'})) + self.assertEquals(parse_color_setting('error=green/blue/something,blink'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green', 'bg': 'blue', 'opts': ('blink',)})) + + def test_bad_option(self): + self.assertEquals(parse_color_setting('error=green,unknown'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green'})) + self.assertEquals(parse_color_setting('error=green,unknown,blink'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green', 'opts': ('blink',)})) + + def test_role_case(self): + self.assertEquals(parse_color_setting('ERROR=green'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green'})) + self.assertEquals(parse_color_setting('eRrOr=green'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green'})) + + def test_color_case(self): + self.assertEquals(parse_color_setting('error=GREEN'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green'})) + self.assertEquals(parse_color_setting('error=GREEN/BLUE'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green', 'bg':'blue'})) + + self.assertEquals(parse_color_setting('error=gReEn'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green'})) + self.assertEquals(parse_color_setting('error=gReEn/bLuE'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green', 'bg':'blue'})) + + def test_opts_case(self): + self.assertEquals(parse_color_setting('error=green,BLINK'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green', 'opts': ('blink',)})) + + self.assertEquals(parse_color_setting('error=green,bLiNk'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green', 'opts': ('blink',)})) diff --git a/tests/regressiontests/utils/tests.py b/tests/regressiontests/utils/tests.py index a7a6e4c3a1..fe5f463110 100644 --- a/tests/regressiontests/utils/tests.py +++ b/tests/regressiontests/utils/tests.py @@ -9,8 +9,8 @@ from django.utils.functional import SimpleLazyObject import timesince import datastructures -import dateformat import itercompat + from decorators import DecoratorFromMiddlewareTests # We need this because "datastructures" uses sorted() and the tests are run in @@ -28,6 +28,7 @@ __test__ = { } from dateformat import * +from termcolors import * class TestUtilsHtml(TestCase): @@ -220,7 +221,7 @@ class TestUtilsSimpleLazyObject(TestCase): self.assertEqual(_ComplexObject, SimpleLazyObject(complex_object).__class__) def test_deepcopy(self): - import copy + import django.utils.copycompat as copy # Check that we *can* do deep copy, and that it returns the right # objects. diff --git a/tests/regressiontests/views/tests/defaults.py b/tests/regressiontests/views/tests/defaults.py index bf490d7cf0..1d1b3c3501 100644 --- a/tests/regressiontests/views/tests/defaults.py +++ b/tests/regressiontests/views/tests/defaults.py @@ -10,8 +10,8 @@ class DefaultsTests(TestCase): """Test django views in django/views/defaults.py""" fixtures = ['testdata.json'] - def test_shorcut_with_absolute_url(self): - "Can view a shortcut an Author object that has with a get_absolute_url method" + def test_shortcut_with_absolute_url(self): + "Can view a shortcut for an Author object that has a get_absolute_url method" for obj in Author.objects.all(): short_url = '/views/shortcut/%s/%s/' % (ContentType.objects.get_for_model(Author).id, obj.pk) response = self.client.get(short_url) @@ -19,12 +19,34 @@ class DefaultsTests(TestCase): status_code=302, target_status_code=404) def test_shortcut_no_absolute_url(self): - "Shortcuts for an object that has with a get_absolute_url method raises 404" + "Shortcuts for an object that has no get_absolute_url method raises 404" for obj in Article.objects.all(): short_url = '/views/shortcut/%s/%s/' % (ContentType.objects.get_for_model(Article).id, obj.pk) response = self.client.get(short_url) self.assertEquals(response.status_code, 404) + def test_wrong_type_pk(self): + short_url = '/views/shortcut/%s/%s/' % (ContentType.objects.get_for_model(Author).id, 'nobody/expects') + response = self.client.get(short_url) + self.assertEquals(response.status_code, 404) + + def test_shortcut_bad_pk(self): + short_url = '/views/shortcut/%s/%s/' % (ContentType.objects.get_for_model(Author).id, '4242424242') + response = self.client.get(short_url) + self.assertEquals(response.status_code, 404) + + def test_nonint_content_type(self): + an_author = Author.objects.all()[0] + short_url = '/views/shortcut/%s/%s/' % ('spam', an_author.pk) + response = self.client.get(short_url) + self.assertEquals(response.status_code, 404) + + def test_bad_content_type(self): + an_author = Author.objects.all()[0] + short_url = '/views/shortcut/%s/%s/' % (4242424242, an_author.pk) + response = self.client.get(short_url) + self.assertEquals(response.status_code, 404) + def test_page_not_found(self): "A 404 status is returned by the page_not_found view" non_existing_urls = ['/views/non_existing_url/', # this is in urls.py |
