diff options
| author | Honza Král <honza.kral@gmail.com> | 2009-11-07 17:09:09 +0000 |
|---|---|---|
| committer | Honza Král <honza.kral@gmail.com> | 2009-11-07 17:09:09 +0000 |
| commit | 30ea350dabad28b0e524feabce434d446e013d6f (patch) | |
| tree | 880b6b01d6cbf9423943025f77d8b4ad0b460e83 /tests | |
| parent | dfe495fbe8e360ee3b3cd8b29e55ee19d86fc9d2 (diff) | |
[soc2009/model-validation] Merged to trunk at r11724
git-svn-id: http://code.djangoproject.com/svn/django/branches/soc2009/model-validation@11725 bcc190cf-cafb-0310-a4f2-bffc1f526a37
Diffstat (limited to 'tests')
40 files changed, 1302 insertions, 169 deletions
diff --git a/tests/modeltests/invalid_models/models.py b/tests/modeltests/invalid_models/models.py index c033d31237..af199635e6 100644 --- a/tests/modeltests/invalid_models/models.py +++ b/tests/modeltests/invalid_models/models.py @@ -182,6 +182,7 @@ class UniqueM2M(models.Model): """ Model to test for unique ManyToManyFields, which are invalid. """ unique_people = models.ManyToManyField( Person, unique=True ) + model_errors = """invalid_models.fielderrors: "charfield": CharFields require a "max_length" attribute. invalid_models.fielderrors: "decimalfield": DecimalFields require a "decimal_places" attribute. invalid_models.fielderrors: "decimalfield": DecimalFields require a "max_digits" attribute. diff --git a/tests/modeltests/lookup/models.py b/tests/modeltests/lookup/models.py index 11e2b079f0..94c16ff071 100644 --- a/tests/modeltests/lookup/models.py +++ b/tests/modeltests/lookup/models.py @@ -17,6 +17,10 @@ class Article(models.Model): return self.headline __test__ = {'API_TESTS': r""" +# We can use .exists() to check that there are none yet +>>> Article.objects.exists() +False + # Create a couple of Articles. >>> from datetime import datetime >>> a1 = Article(headline='Article 1', pub_date=datetime(2005, 7, 26)) @@ -33,6 +37,10 @@ __test__ = {'API_TESTS': r""" >>> a6.save() >>> a7 = Article(headline='Article 7', pub_date=datetime(2005, 7, 27)) >>> a7.save() + +# There should be some now! +>>> Article.objects.exists() +True """} if settings.DATABASE_ENGINE in ('postgresql', 'postgresql_pysycopg2'): diff --git a/tests/modeltests/m2m_through/models.py b/tests/modeltests/m2m_through/models.py index 10aa163343..16f303d02e 100644 --- a/tests/modeltests/m2m_through/models.py +++ b/tests/modeltests/m2m_through/models.py @@ -133,7 +133,7 @@ AttributeError: 'ManyRelatedManager' object has no attribute 'add' >>> rock.members.create(name='Anne') Traceback (most recent call last): ... -AttributeError: Cannot use create() on a ManyToManyField which specifies an intermediary model. Use Membership's Manager instead. +AttributeError: Cannot use create() on a ManyToManyField which specifies an intermediary model. Use m2m_through.Membership's Manager instead. # Remove has similar complications, and is not provided either. >>> rock.members.remove(jim) @@ -160,7 +160,7 @@ AttributeError: 'ManyRelatedManager' object has no attribute 'remove' >>> rock.members = backup Traceback (most recent call last): ... -AttributeError: Cannot set values on a ManyToManyField which specifies an intermediary model. Use Membership's Manager instead. +AttributeError: Cannot set values on a ManyToManyField which specifies an intermediary model. Use m2m_through.Membership's Manager instead. # Let's re-save those instances that we've cleared. >>> m1.save() @@ -184,7 +184,7 @@ AttributeError: 'ManyRelatedManager' object has no attribute 'add' >>> bob.group_set.create(name='Funk') Traceback (most recent call last): ... -AttributeError: Cannot use create() on a ManyToManyField which specifies an intermediary model. Use Membership's Manager instead. +AttributeError: Cannot use create() on a ManyToManyField which specifies an intermediary model. Use m2m_through.Membership's Manager instead. # Remove has similar complications, and is not provided either. >>> jim.group_set.remove(rock) @@ -209,7 +209,7 @@ AttributeError: 'ManyRelatedManager' object has no attribute 'remove' >>> jim.group_set = backup Traceback (most recent call last): ... -AttributeError: Cannot set values on a ManyToManyField which specifies an intermediary model. Use Membership's Manager instead. +AttributeError: Cannot set values on a ManyToManyField which specifies an intermediary model. Use m2m_through.Membership's Manager instead. # Let's re-save those instances that we've cleared. >>> m1.save() @@ -334,4 +334,4 @@ AttributeError: Cannot set values on a ManyToManyField which specifies an interm # QuerySet's distinct() method can correct this problem. >>> Person.objects.filter(membership__date_joined__gt=datetime(2004, 1, 1)).distinct() [<Person: Jane>, <Person: Jim>] -"""}
\ No newline at end of file +"""} diff --git a/tests/modeltests/model_package/__init__.py b/tests/modeltests/model_package/__init__.py new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/tests/modeltests/model_package/__init__.py @@ -0,0 +1 @@ + diff --git a/tests/modeltests/model_package/models/__init__.py b/tests/modeltests/model_package/models/__init__.py new file mode 100644 index 0000000000..91e1b02e7d --- /dev/null +++ b/tests/modeltests/model_package/models/__init__.py @@ -0,0 +1,3 @@ +# Import all the models from subpackages +from article import Article +from publication import Publication diff --git a/tests/modeltests/model_package/models/article.py b/tests/modeltests/model_package/models/article.py new file mode 100644 index 0000000000..c8fae1c72d --- /dev/null +++ b/tests/modeltests/model_package/models/article.py @@ -0,0 +1,10 @@ +from django.db import models +from django.contrib.sites.models import Site + +class Article(models.Model): + sites = models.ManyToManyField(Site) + headline = models.CharField(max_length=100) + publications = models.ManyToManyField("model_package.Publication", null=True, blank=True,) + + class Meta: + app_label = 'model_package' diff --git a/tests/modeltests/model_package/models/publication.py b/tests/modeltests/model_package/models/publication.py new file mode 100644 index 0000000000..4dc2d6a148 --- /dev/null +++ b/tests/modeltests/model_package/models/publication.py @@ -0,0 +1,7 @@ +from django.db import models + +class Publication(models.Model): + title = models.CharField(max_length=30) + + class Meta: + app_label = 'model_package' diff --git a/tests/modeltests/model_package/tests.py b/tests/modeltests/model_package/tests.py new file mode 100644 index 0000000000..6e8c158a68 --- /dev/null +++ b/tests/modeltests/model_package/tests.py @@ -0,0 +1,34 @@ +""" +>>> from models.publication import Publication +>>> from models.article import Article +>>> from django.contrib.auth.views import Site + +>>> p = Publication(title="FooBar") +>>> p.save() +>>> p +<Publication: Publication object> + +>>> from django.contrib.sites.models import Site +>>> current_site = Site.objects.get_current() +>>> current_site +<Site: example.com> + +# Regression for #12168: models split into subpackages still get M2M tables + +>>> a = Article(headline="a foo headline") +>>> a.save() +>>> a.publications.add(p) +>>> a.sites.add(current_site) +>>> a.save() + +>>> a = Article.objects.get(id=1) +>>> a +<Article: Article object> +>>> a.id +1 +>>> a.sites.count() +1 + +""" + + diff --git a/tests/regressiontests/admin_validation/models.py b/tests/regressiontests/admin_validation/models.py index 1ff89e2502..5506114841 100644 --- a/tests/regressiontests/admin_validation/models.py +++ b/tests/regressiontests/admin_validation/models.py @@ -4,9 +4,11 @@ Tests of ModelAdmin validation logic. from django.db import models + class Album(models.Model): title = models.CharField(max_length=150) + class Song(models.Model): title = models.CharField(max_length=150) album = models.ForeignKey(Album) @@ -17,11 +19,19 @@ class Song(models.Model): def __unicode__(self): return self.title + +class TwoAlbumFKAndAnE(models.Model): + album1 = models.ForeignKey(Album, related_name="album1_set") + album2 = models.ForeignKey(Album, related_name="album2_set") + e = models.CharField(max_length=1) + + + __test__ = {'API_TESTS':""" >>> from django import forms >>> from django.contrib import admin ->>> from django.contrib.admin.validation import validate +>>> from django.contrib.admin.validation import validate, validate_inline # Regression test for #8027: custom ModelForms with fields/fieldsets @@ -58,4 +68,31 @@ Traceback (most recent call last): ... ImproperlyConfigured: SongInline cannot exclude the field 'album' - this is the foreign key to the parent model Album. +# Regression test for #11709 - when testing for fk excluding (when exclude is +# given) make sure fk_name is honored or things blow up when there is more +# than one fk to the parent model. + +>>> class TwoAlbumFKAndAnEInline(admin.TabularInline): +... model = TwoAlbumFKAndAnE +... exclude = ("e",) +... fk_name = "album1" + +>>> validate_inline(TwoAlbumFKAndAnEInline, None, Album) + +# Ensure inlines validate that they can be used correctly. + +>>> class TwoAlbumFKAndAnEInline(admin.TabularInline): +... model = TwoAlbumFKAndAnE + +>>> validate_inline(TwoAlbumFKAndAnEInline, None, Album) +Traceback (most recent call last): + ... +Exception: <class 'regressiontests.admin_validation.models.TwoAlbumFKAndAnE'> has more than 1 ForeignKey to <class 'regressiontests.admin_validation.models.Album'> + +>>> class TwoAlbumFKAndAnEInline(admin.TabularInline): +... model = TwoAlbumFKAndAnE +... fk_name = "album1" + +>>> validate_inline(TwoAlbumFKAndAnEInline, None, Album) + """} diff --git a/tests/regressiontests/admin_views/tests.py b/tests/regressiontests/admin_views/tests.py index 7273d3f320..8607589289 100644 --- a/tests/regressiontests/admin_views/tests.py +++ b/tests/regressiontests/admin_views/tests.py @@ -885,8 +885,9 @@ class AdminViewListEditable(TestCase): # 4 action inputs (3 regular checkboxes, 1 checkbox to select all) # main form submit button = 1 # search field and search submit button = 2 - # 6 + 2 + 1 + 2 = 11 inputs - self.failUnlessEqual(response.content.count("<input"), 15) + # CSRF field = 1 + # 6 + 2 + 4 + 1 + 2 + 1 = 16 inputs + self.failUnlessEqual(response.content.count("<input"), 16) # 1 select per object = 3 selects self.failUnlessEqual(response.content.count("<select"), 4) @@ -1140,6 +1141,16 @@ class AdminActionsTest(TestCase): '<input type="checkbox" class="action-select"' not in response.content, "Found an unexpected action toggle checkboxbox in response" ) + self.assert_('action-checkbox-column' not in response.content, + "Found unexpected action-checkbox-column class in response") + + def test_action_column_class(self): + "Tests that the checkbox column class is present in the response" + response = self.client.get('/test_admin/admin/admin_views/subscriber/') + self.assertNotEquals(response.context["action_form"], None) + self.assert_('action-checkbox-column' in response.content, + "Expected an action-checkbox-column in response") + def test_multiple_actions_form(self): """ diff --git a/tests/regressiontests/cache/models.py b/tests/regressiontests/cache/models.py index e69de29bb2..643fd22629 100644 --- a/tests/regressiontests/cache/models.py +++ b/tests/regressiontests/cache/models.py @@ -0,0 +1,11 @@ +from django.db import models +from datetime import datetime + +def expensive_calculation(): + expensive_calculation.num_runs += 1 + return datetime.now() + +class Poll(models.Model): + question = models.CharField(max_length=200) + answer = models.CharField(max_length=200) + pub_date = models.DateTimeField('date published', default=expensive_calculation) diff --git a/tests/regressiontests/cache/tests.py b/tests/regressiontests/cache/tests.py index 1b54ab935f..593b7262aa 100644 --- a/tests/regressiontests/cache/tests.py +++ b/tests/regressiontests/cache/tests.py @@ -16,6 +16,7 @@ from django.core.cache.backends.base import InvalidCacheBackendError from django.http import HttpResponse, HttpRequest from django.utils.cache import patch_vary_headers, get_cache_key, learn_cache_key from django.utils.hashcompat import md5_constructor +from regressiontests.cache.models import Poll, expensive_calculation # functions/classes for complex data type tests def f(): @@ -211,6 +212,47 @@ class BaseCacheTests(object): self.cache.set("stuff", stuff) self.assertEqual(self.cache.get("stuff"), stuff) + def test_cache_read_for_model_instance(self): + # Don't want fields with callable as default to be called on cache read + expensive_calculation.num_runs = 0 + Poll.objects.all().delete() + my_poll = Poll.objects.create(question="Well?") + self.assertEqual(Poll.objects.count(), 1) + pub_date = my_poll.pub_date + self.cache.set('question', my_poll) + cached_poll = self.cache.get('question') + self.assertEqual(cached_poll.pub_date, pub_date) + # We only want the default expensive calculation run once + self.assertEqual(expensive_calculation.num_runs, 1) + + def test_cache_write_for_model_instance_with_deferred(self): + # Don't want fields with callable as default to be called on cache write + expensive_calculation.num_runs = 0 + Poll.objects.all().delete() + my_poll = Poll.objects.create(question="What?") + self.assertEqual(expensive_calculation.num_runs, 1) + defer_qs = Poll.objects.all().defer('question') + self.assertEqual(defer_qs.count(), 1) + self.assertEqual(expensive_calculation.num_runs, 1) + self.cache.set('deferred_queryset', defer_qs) + # cache set should not re-evaluate default functions + self.assertEqual(expensive_calculation.num_runs, 1) + + def test_cache_read_for_model_instance_with_deferred(self): + # Don't want fields with callable as default to be called on cache read + expensive_calculation.num_runs = 0 + Poll.objects.all().delete() + my_poll = Poll.objects.create(question="What?") + self.assertEqual(expensive_calculation.num_runs, 1) + defer_qs = Poll.objects.all().defer('question') + self.assertEqual(defer_qs.count(), 1) + self.cache.set('deferred_queryset', defer_qs) + self.assertEqual(expensive_calculation.num_runs, 1) + runs_before_cache_read = expensive_calculation.num_runs + cached_polls = self.cache.get('deferred_queryset') + # We only want the default expensive calculation run on creation and set + self.assertEqual(expensive_calculation.num_runs, runs_before_cache_read) + def test_expiration(self): # Cache values can be set to expire self.cache.set('expire1', 'very quickly', 1) diff --git a/tests/regressiontests/comment_tests/tests/moderation_view_tests.py b/tests/regressiontests/comment_tests/tests/moderation_view_tests.py index b9eadd78b4..c9b50e2983 100644 --- a/tests/regressiontests/comment_tests/tests/moderation_view_tests.py +++ b/tests/regressiontests/comment_tests/tests/moderation_view_tests.py @@ -159,31 +159,32 @@ class ApproveViewTests(CommentTestCase): response = self.client.get("/approved/", data={"c":pk}) self.assertTemplateUsed(response, "comments/approved.html") +class AdminActionsTests(CommentTestCase): + urls = "regressiontests.comment_tests.urls_admin" + + def setUp(self): + super(AdminActionsTests, self).setUp() + + # Make "normaluser" a moderator + u = User.objects.get(username="normaluser") + u.is_staff = True + perms = Permission.objects.filter( + content_type__app_label = 'comments', + codename__endswith = 'comment' + ) + for perm in perms: + u.user_permissions.add(perm) + u.save() -class ModerationQueueTests(CommentTestCase): - - def testModerationQueuePermissions(self): - """Only moderators can view the moderation queue""" + def testActionsNonModerator(self): + comments = self.createSomeComments() self.client.login(username="normaluser", password="normaluser") - response = self.client.get("/moderate/") - self.assertEqual(response["Location"], "http://testserver/accounts/login/?next=/moderate/") - - makeModerator("normaluser") - response = self.client.get("/moderate/") - self.assertEqual(response.status_code, 200) + response = self.client.get("/admin/comments/comment/") + self.assertEquals("approve_comments" in response.content, False) - def testModerationQueueContents(self): - """Moderation queue should display non-public, non-removed comments.""" - c1, c2, c3, c4 = self.createSomeComments() + def testActionsModerator(self): + comments = self.createSomeComments() makeModerator("normaluser") self.client.login(username="normaluser", password="normaluser") - - c1.is_public = c2.is_public = False - c1.save(); c2.save() - response = self.client.get("/moderate/") - self.assertEqual(list(response.context[0]["comments"]), [c1, c2]) - - c2.is_removed = True - c2.save() - response = self.client.get("/moderate/") - self.assertEqual(list(response.context[0]["comments"]), [c1]) + response = self.client.get("/admin/comments/comment/") + self.assertEquals("approve_comments" in response.content, True) diff --git a/tests/regressiontests/comment_tests/urls_admin.py b/tests/regressiontests/comment_tests/urls_admin.py new file mode 100644 index 0000000000..341285d7ef --- /dev/null +++ b/tests/regressiontests/comment_tests/urls_admin.py @@ -0,0 +1,13 @@ +from django.conf.urls.defaults import * +from django.contrib import admin +from django.contrib.comments.admin import CommentsAdmin +from django.contrib.comments.models import Comment + +# Make a new AdminSite to avoid picking up the deliberately broken admin +# modules in other tests. +admin_site = admin.AdminSite() +admin_site.register(Comment, CommentsAdmin) + +urlpatterns = patterns('', + (r'^admin/', include(admin_site.urls)), +) diff --git a/tests/regressiontests/context_processors/fixtures/context-processors-users.xml b/tests/regressiontests/context_processors/fixtures/context-processors-users.xml new file mode 100644 index 0000000000..aba8f4aace --- /dev/null +++ b/tests/regressiontests/context_processors/fixtures/context-processors-users.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="utf-8"?> +<django-objects version="1.0"> + <object pk="100" model="auth.user"> + <field type="CharField" name="username">super</field> + <field type="CharField" name="first_name">Super</field> + <field type="CharField" name="last_name">User</field> + <field type="CharField" name="email">super@example.com</field> + <field type="CharField" name="password">sha1$995a3$6011485ea3834267d719b4c801409b8b1ddd0158</field> + <field type="BooleanField" name="is_staff">True</field> + <field type="BooleanField" name="is_active">True</field> + <field type="BooleanField" name="is_superuser">True</field> + <field type="DateTimeField" name="last_login">2007-05-30 13:20:10</field> + <field type="DateTimeField" name="date_joined">2007-05-30 13:20:10</field> + <field to="auth.group" name="groups" rel="ManyToManyRel"></field> + <field to="auth.permission" name="user_permissions" rel="ManyToManyRel"></field> + </object> +</django-objects> diff --git a/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_access.html b/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_access.html new file mode 100644 index 0000000000..b5c65db28d --- /dev/null +++ b/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_access.html @@ -0,0 +1 @@ +{{ user }} diff --git a/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_messages.html b/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_messages.html new file mode 100644 index 0000000000..7b7e448ad2 --- /dev/null +++ b/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_messages.html @@ -0,0 +1 @@ +{% for m in messages %}{{ m }}{% endfor %} diff --git a/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_no_access.html b/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_no_access.html new file mode 100644 index 0000000000..8d1c8b69c3 --- /dev/null +++ b/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_no_access.html @@ -0,0 +1 @@ + diff --git a/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_perms.html b/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_perms.html new file mode 100644 index 0000000000..a5db868e9e --- /dev/null +++ b/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_perms.html @@ -0,0 +1 @@ +{% if perms.auth %}Has auth permissions{% endif %} diff --git a/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_test_access.html b/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_test_access.html new file mode 100644 index 0000000000..a28ff937f8 --- /dev/null +++ b/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_test_access.html @@ -0,0 +1 @@ +{% if session_accessed %}Session accessed{% else %}Session not accessed{% endif %} diff --git a/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_user.html b/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_user.html new file mode 100644 index 0000000000..7ed16d7867 --- /dev/null +++ b/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_user.html @@ -0,0 +1,4 @@ +unicode: {{ user }} +id: {{ user.id }} +username: {{ user.username }} +url: {% url userpage user %} diff --git a/tests/regressiontests/context_processors/tests.py b/tests/regressiontests/context_processors/tests.py index eadd6310b1..004f90eceb 100644 --- a/tests/regressiontests/context_processors/tests.py +++ b/tests/regressiontests/context_processors/tests.py @@ -3,8 +3,10 @@ Tests for Django's bundled context processors. """ from django.conf import settings +from django.contrib.auth import authenticate +from django.db.models import Q from django.test import TestCase - +from django.template import Template class RequestContextProcessorTests(TestCase): """ @@ -36,3 +38,75 @@ class RequestContextProcessorTests(TestCase): self.assertContains(response, url) response = self.client.post(url, {'path': '/blah/'}) self.assertContains(response, url) + +class AuthContextProcessorTests(TestCase): + """ + Tests for the ``django.core.context_processors.auth`` processor + """ + urls = 'regressiontests.context_processors.urls' + fixtures = ['context-processors-users.xml'] + + def test_session_not_accessed(self): + """ + Tests that the session is not accessed simply by including + the auth context processor + """ + response = self.client.get('/auth_processor_no_attr_access/') + self.assertContains(response, "Session not accessed") + + def test_session_is_accessed(self): + """ + Tests that the session is accessed if the auth context processor + is used and relevant attributes accessed. + """ + response = self.client.get('/auth_processor_attr_access/') + self.assertContains(response, "Session accessed") + + def test_perms_attrs(self): + self.client.login(username='super', password='secret') + response = self.client.get('/auth_processor_perms/') + self.assertContains(response, "Has auth permissions") + + def test_message_attrs(self): + self.client.login(username='super', password='secret') + response = self.client.get('/auth_processor_messages/') + self.assertContains(response, "Message 1") + + def test_user_attrs(self): + """ + Test that the lazy objects returned behave just like the wrapped objects. + """ + # These are 'functional' level tests for common use cases. Direct + # testing of the implementation (SimpleLazyObject) is in the 'utils' + # tests. + self.client.login(username='super', password='secret') + user = authenticate(username='super', password='secret') + response = self.client.get('/auth_processor_user/') + self.assertContains(response, "unicode: super") + self.assertContains(response, "id: 100") + self.assertContains(response, "username: super") + # bug #12037 is tested by the {% url %} in the template: + self.assertContains(response, "url: /userpage/super/") + + # See if this object can be used for queries where a Q() comparing + # a user can be used with another Q() (in an AND or OR fashion). + # This simulates what a template tag might do with the user from the + # context. Note that we don't need to execute a query, just build it. + # + # The failure case (bug #12049) on Python 2.4 with a LazyObject-wrapped + # User is a fatal TypeError: "function() takes at least 2 arguments + # (0 given)" deep inside deepcopy(). + # + # Python 2.5 and 2.6 succeeded, but logged internally caught exception + # spew: + # + # Exception RuntimeError: 'maximum recursion depth exceeded while + # calling a Python object' in <type 'exceptions.AttributeError'> + # ignored" + query = Q(user=response.context['user']) & Q(someflag=True) + + # Tests for user equality. This is hard because User defines + # equality in a non-duck-typing way + # See bug #12060 + self.assertEqual(response.context['user'], user) + self.assertEqual(user, response.context['user']) diff --git a/tests/regressiontests/context_processors/urls.py b/tests/regressiontests/context_processors/urls.py index 7e8ba967c1..30728c8df8 100644 --- a/tests/regressiontests/context_processors/urls.py +++ b/tests/regressiontests/context_processors/urls.py @@ -5,4 +5,10 @@ import views urlpatterns = patterns('', (r'^request_attrs/$', views.request_processor), + (r'^auth_processor_no_attr_access/$', views.auth_processor_no_attr_access), + (r'^auth_processor_attr_access/$', views.auth_processor_attr_access), + (r'^auth_processor_user/$', views.auth_processor_user), + (r'^auth_processor_perms/$', views.auth_processor_perms), + (r'^auth_processor_messages/$', views.auth_processor_messages), + url(r'^userpage/(.+)/$', views.userpage, name="userpage"), ) diff --git a/tests/regressiontests/context_processors/views.py b/tests/regressiontests/context_processors/views.py index 66e7132c05..3f2dcb037b 100644 --- a/tests/regressiontests/context_processors/views.py +++ b/tests/regressiontests/context_processors/views.py @@ -6,3 +6,32 @@ from django.template.context import RequestContext def request_processor(request): return render_to_response('context_processors/request_attrs.html', RequestContext(request, {}, processors=[context_processors.request])) + +def auth_processor_no_attr_access(request): + r1 = render_to_response('context_processors/auth_attrs_no_access.html', + RequestContext(request, {}, processors=[context_processors.auth])) + # *After* rendering, we check whether the session was accessed + return render_to_response('context_processors/auth_attrs_test_access.html', + {'session_accessed':request.session.accessed}) + +def auth_processor_attr_access(request): + r1 = render_to_response('context_processors/auth_attrs_access.html', + RequestContext(request, {}, processors=[context_processors.auth])) + return render_to_response('context_processors/auth_attrs_test_access.html', + {'session_accessed':request.session.accessed}) + +def auth_processor_user(request): + return render_to_response('context_processors/auth_attrs_user.html', + RequestContext(request, {}, processors=[context_processors.auth])) + +def auth_processor_perms(request): + return render_to_response('context_processors/auth_attrs_perms.html', + RequestContext(request, {}, processors=[context_processors.auth])) + +def auth_processor_messages(request): + request.user.message_set.create(message="Message 1") + return render_to_response('context_processors/auth_attrs_messages.html', + RequestContext(request, {}, processors=[context_processors.auth])) + +def userpage(request): + pass diff --git a/tests/regressiontests/csrf_tests/__init__.py b/tests/regressiontests/csrf_tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/regressiontests/csrf_tests/__init__.py diff --git a/tests/regressiontests/csrf_tests/models.py b/tests/regressiontests/csrf_tests/models.py new file mode 100644 index 0000000000..71abcc5198 --- /dev/null +++ b/tests/regressiontests/csrf_tests/models.py @@ -0,0 +1 @@ +# models.py file for tests to run. diff --git a/tests/regressiontests/csrf_tests/tests.py b/tests/regressiontests/csrf_tests/tests.py new file mode 100644 index 0000000000..5688293647 --- /dev/null +++ b/tests/regressiontests/csrf_tests/tests.py @@ -0,0 +1,324 @@ +# -*- coding: utf-8 -*- + +from django.test import TestCase +from django.http import HttpRequest, HttpResponse +from django.middleware.csrf import CsrfMiddleware, CsrfViewMiddleware +from django.views.decorators.csrf import csrf_exempt +from django.core.context_processors import csrf +from django.contrib.sessions.middleware import SessionMiddleware +from django.utils.importlib import import_module +from django.conf import settings +from django.template import RequestContext, Template + +# Response/views used for CsrfResponseMiddleware and CsrfViewMiddleware tests +def post_form_response(): + resp = HttpResponse(content=""" +<html><body><form method="POST"><input type="text" /></form></body></html> +""", mimetype="text/html") + return resp + +def post_form_response_non_html(): + resp = post_form_response() + resp["Content-Type"] = "application/xml" + return resp + +def post_form_view(request): + """A view that returns a POST form (without a token)""" + return post_form_response() + +# Response/views used for template tag tests +def _token_template(): + return Template("{% csrf_token %}") + +def _render_csrf_token_template(req): + context = RequestContext(req, processors=[csrf]) + template = _token_template() + return template.render(context) + +def token_view(request): + """A view that uses {% csrf_token %}""" + return HttpResponse(_render_csrf_token_template(request)) + +def non_token_view_using_request_processor(request): + """ + A view that doesn't use the token, but does use the csrf view processor. + """ + context = RequestContext(request, processors=[csrf]) + template = Template("") + return HttpResponse(template.render(context)) + +class TestingHttpRequest(HttpRequest): + """ + A version of HttpRequest that allows us to change some things + more easily + """ + def is_secure(self): + return getattr(self, '_is_secure', False) + +class CsrfMiddlewareTest(TestCase): + _csrf_id = "1" + + # This is a valid session token for this ID and secret key. This was generated using + # the old code that we're to be backwards-compatible with. Don't use the CSRF code + # to generate this hash, or we're merely testing the code against itself and not + # checking backwards-compatibility. This is also the output of (echo -n test1 | md5sum). + _session_token = "5a105e8b9d40e1329780d62ea2265d8a" + _session_id = "1" + _secret_key_for_session_test= "test" + + def _get_GET_no_csrf_cookie_request(self): + return TestingHttpRequest() + + def _get_GET_csrf_cookie_request(self): + req = TestingHttpRequest() + req.COOKIES[settings.CSRF_COOKIE_NAME] = self._csrf_id + return req + + def _get_POST_csrf_cookie_request(self): + req = self._get_GET_csrf_cookie_request() + req.method = "POST" + return req + + def _get_POST_no_csrf_cookie_request(self): + req = self._get_GET_no_csrf_cookie_request() + req.method = "POST" + return req + + def _get_POST_request_with_token(self): + req = self._get_POST_csrf_cookie_request() + req.POST['csrfmiddlewaretoken'] = self._csrf_id + return req + + def _get_POST_session_request_with_token(self): + req = self._get_POST_no_csrf_cookie_request() + req.COOKIES[settings.SESSION_COOKIE_NAME] = self._session_id + req.POST['csrfmiddlewaretoken'] = self._session_token + return req + + def _get_POST_session_request_no_token(self): + req = self._get_POST_no_csrf_cookie_request() + req.COOKIES[settings.SESSION_COOKIE_NAME] = self._session_id + return req + + def _check_token_present(self, response, csrf_id=None): + self.assertContains(response, "name='csrfmiddlewaretoken' value='%s'" % (csrf_id or self._csrf_id)) + + # Check the post processing and outgoing cookie + def test_process_response_no_csrf_cookie(self): + """ + When no prior CSRF cookie exists, check that the cookie is created and a + token is inserted. + """ + req = self._get_GET_no_csrf_cookie_request() + CsrfMiddleware().process_view(req, post_form_view, (), {}) + + resp = post_form_response() + resp_content = resp.content # needed because process_response modifies resp + resp2 = CsrfMiddleware().process_response(req, resp) + + csrf_cookie = resp2.cookies.get(settings.CSRF_COOKIE_NAME, False) + self.assertNotEqual(csrf_cookie, False) + self.assertNotEqual(resp_content, resp2.content) + self._check_token_present(resp2, csrf_cookie.value) + # Check the Vary header got patched correctly + self.assert_('Cookie' in resp2.get('Vary','')) + + def test_process_response_no_csrf_cookie_view_only_get_token_used(self): + """ + When no prior CSRF cookie exists, check that the cookie is created, even + if only CsrfViewMiddleware is used. + """ + # This is checking that CsrfViewMiddleware has the cookie setting + # code. Most of the other tests use CsrfMiddleware. + req = self._get_GET_no_csrf_cookie_request() + # token_view calls get_token() indirectly + CsrfViewMiddleware().process_view(req, token_view, (), {}) + resp = token_view(req) + resp2 = CsrfViewMiddleware().process_response(req, resp) + + csrf_cookie = resp2.cookies.get(settings.CSRF_COOKIE_NAME, False) + self.assertNotEqual(csrf_cookie, False) + + def test_process_response_get_token_not_used(self): + """ + Check that if get_token() is not called, the view middleware does not + add a cookie. + """ + # This is important to make pages cacheable. Pages which do call + # get_token(), assuming they use the token, are not cacheable because + # the token is specific to the user + req = self._get_GET_no_csrf_cookie_request() + # non_token_view_using_request_processor does not call get_token(), but + # does use the csrf request processor. By using this, we are testing + # that the view processor is properly lazy and doesn't call get_token() + # until needed. + CsrfViewMiddleware().process_view(req, non_token_view_using_request_processor, (), {}) + resp = non_token_view_using_request_processor(req) + resp2 = CsrfViewMiddleware().process_response(req, resp) + + csrf_cookie = resp2.cookies.get(settings.CSRF_COOKIE_NAME, False) + self.assertEqual(csrf_cookie, False) + + def test_process_response_existing_csrf_cookie(self): + """ + Check that the token is inserted when a prior CSRF cookie exists + """ + req = self._get_GET_csrf_cookie_request() + CsrfMiddleware().process_view(req, post_form_view, (), {}) + + resp = post_form_response() + resp_content = resp.content # needed because process_response modifies resp + resp2 = CsrfMiddleware().process_response(req, resp) + self.assertNotEqual(resp_content, resp2.content) + self._check_token_present(resp2) + + def test_process_response_non_html(self): + """ + Check the the post-processor does nothing for content-types not in _HTML_TYPES. + """ + req = self._get_GET_no_csrf_cookie_request() + CsrfMiddleware().process_view(req, post_form_view, (), {}) + resp = post_form_response_non_html() + resp_content = resp.content # needed because process_response modifies resp + resp2 = CsrfMiddleware().process_response(req, resp) + self.assertEquals(resp_content, resp2.content) + + def test_process_response_exempt_view(self): + """ + Check that no post processing is done for an exempt view + """ + req = self._get_POST_csrf_cookie_request() + resp = csrf_exempt(post_form_view)(req) + resp_content = resp.content + resp2 = CsrfMiddleware().process_response(req, resp) + self.assertEquals(resp_content, resp2.content) + + # Check the request processing + def test_process_request_no_session_no_csrf_cookie(self): + """ + Check that if neither a CSRF cookie nor a session cookie are present, + the middleware rejects the incoming request. This will stop login CSRF. + """ + req = self._get_POST_no_csrf_cookie_request() + req2 = CsrfMiddleware().process_view(req, post_form_view, (), {}) + self.assertEquals(403, req2.status_code) + + def test_process_request_csrf_cookie_no_token(self): + """ + Check that if a CSRF cookie is present but no token, the middleware + rejects the incoming request. + """ + req = self._get_POST_csrf_cookie_request() + req2 = CsrfMiddleware().process_view(req, post_form_view, (), {}) + self.assertEquals(403, req2.status_code) + + def test_process_request_csrf_cookie_and_token(self): + """ + Check that if both a cookie and a token is present, the middleware lets it through. + """ + req = self._get_POST_request_with_token() + req2 = CsrfMiddleware().process_view(req, post_form_view, (), {}) + self.assertEquals(None, req2) + + def test_process_request_session_cookie_no_csrf_cookie_token(self): + """ + When no CSRF cookie exists, but the user has a session, check that a token + using the session cookie as a legacy CSRF cookie is accepted. + """ + orig_secret_key = settings.SECRET_KEY + settings.SECRET_KEY = self._secret_key_for_session_test + try: + req = self._get_POST_session_request_with_token() + req2 = CsrfMiddleware().process_view(req, post_form_view, (), {}) + self.assertEquals(None, req2) + finally: + settings.SECRET_KEY = orig_secret_key + + def test_process_request_session_cookie_no_csrf_cookie_no_token(self): + """ + Check that if a session cookie is present but no token and no CSRF cookie, + the request is rejected. + """ + req = self._get_POST_session_request_no_token() + req2 = CsrfMiddleware().process_view(req, post_form_view, (), {}) + self.assertEquals(403, req2.status_code) + + def test_process_request_csrf_cookie_no_token_exempt_view(self): + """ + Check that if a CSRF cookie is present and no token, but the csrf_exempt + decorator has been applied to the view, the middleware lets it through + """ + req = self._get_POST_csrf_cookie_request() + req2 = CsrfMiddleware().process_view(req, csrf_exempt(post_form_view), (), {}) + self.assertEquals(None, req2) + + def test_ajax_exemption(self): + """ + Check that AJAX requests are automatically exempted. + """ + req = self._get_POST_csrf_cookie_request() + req.META['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest' + req2 = CsrfMiddleware().process_view(req, post_form_view, (), {}) + self.assertEquals(None, req2) + + # Tests for the template tag method + def test_token_node_no_csrf_cookie(self): + """ + Check that CsrfTokenNode works when no CSRF cookie is set + """ + req = self._get_GET_no_csrf_cookie_request() + resp = token_view(req) + self.assertEquals(u"", resp.content) + + def test_token_node_with_csrf_cookie(self): + """ + Check that CsrfTokenNode works when a CSRF cookie is set + """ + req = self._get_GET_csrf_cookie_request() + CsrfViewMiddleware().process_view(req, token_view, (), {}) + resp = token_view(req) + self._check_token_present(resp) + + def test_token_node_with_new_csrf_cookie(self): + """ + Check that CsrfTokenNode works when a CSRF cookie is created by + the middleware (when one was not already present) + """ + req = self._get_GET_no_csrf_cookie_request() + CsrfViewMiddleware().process_view(req, token_view, (), {}) + resp = token_view(req) + resp2 = CsrfViewMiddleware().process_response(req, resp) + csrf_cookie = resp2.cookies[settings.CSRF_COOKIE_NAME] + self._check_token_present(resp, csrf_id=csrf_cookie.value) + + def test_response_middleware_without_view_middleware(self): + """ + Check that CsrfResponseMiddleware finishes without error if the view middleware + has not been called, as is the case if a request middleware returns a response. + """ + req = self._get_GET_no_csrf_cookie_request() + resp = post_form_view(req) + CsrfMiddleware().process_response(req, resp) + + def test_https_bad_referer(self): + """ + Test that a POST HTTPS request with a bad referer is rejected + """ + req = self._get_POST_request_with_token() + req._is_secure = True + req.META['HTTP_HOST'] = 'www.example.com' + req.META['HTTP_REFERER'] = 'https://www.evil.org/somepage' + req2 = CsrfViewMiddleware().process_view(req, post_form_view, (), {}) + self.assertNotEqual(None, req2) + self.assertEquals(403, req2.status_code) + + def test_https_good_referer(self): + """ + Test that a POST HTTPS request with a good referer is accepted + """ + req = self._get_POST_request_with_token() + req._is_secure = True + req.META['HTTP_HOST'] = 'www.example.com' + req.META['HTTP_REFERER'] = 'https://www.example.com/somepage' + req2 = CsrfViewMiddleware().process_view(req, post_form_view, (), {}) + self.assertEquals(None, req2) diff --git a/tests/regressiontests/dateformat/tests.py b/tests/regressiontests/dateformat/tests.py index d649d4789c..bbde45b903 100644 --- a/tests/regressiontests/dateformat/tests.py +++ b/tests/regressiontests/dateformat/tests.py @@ -1,90 +1,92 @@ -r""" ->>> format(my_birthday, '') -u'' ->>> format(my_birthday, 'a') -u'p.m.' ->>> format(my_birthday, 'A') -u'PM' ->>> format(my_birthday, 'd') -u'08' ->>> format(my_birthday, 'j') -u'8' ->>> format(my_birthday, 'l') -u'Sunday' ->>> format(my_birthday, 'L') -u'False' ->>> format(my_birthday, 'm') -u'07' ->>> format(my_birthday, 'M') -u'Jul' ->>> format(my_birthday, 'b') -u'jul' ->>> format(my_birthday, 'n') -u'7' ->>> format(my_birthday, 'N') -u'July' ->>> no_tz or format(my_birthday, 'O') == '+0100' -True ->>> format(my_birthday, 'P') -u'10 p.m.' ->>> no_tz or format(my_birthday, 'r') == 'Sun, 8 Jul 1979 22:00:00 +0100' -True ->>> format(my_birthday, 's') -u'00' ->>> format(my_birthday, 'S') -u'th' ->>> format(my_birthday, 't') -u'31' ->>> no_tz or format(my_birthday, 'T') == 'CET' -True ->>> no_tz or format(my_birthday, 'U') == '300315600' -True ->>> format(my_birthday, 'w') -u'0' ->>> format(my_birthday, 'W') -u'27' ->>> format(my_birthday, 'y') -u'79' ->>> format(my_birthday, 'Y') -u'1979' ->>> format(my_birthday, 'z') -u'189' ->>> no_tz or format(my_birthday, 'Z') == '3600' -True ->>> no_tz or format(summertime, 'I') == '1' -True ->>> no_tz or format(summertime, 'O') == '+0200' -True ->>> no_tz or format(wintertime, 'I') == '0' -True ->>> no_tz or format(wintertime, 'O') == '+0100' -True +from django.utils import dateformat, translation +from unittest import TestCase +import datetime, os, time ->>> format(my_birthday, r'Y z \C\E\T') -u'1979 189 CET' +class DateFormatTests(TestCase): + def setUp(self): + self.old_TZ = os.environ.get('TZ') + os.environ['TZ'] = 'Europe/Copenhagen' + translation.activate('en-us') ->>> format(my_birthday, r'jS o\f F') -u'8th of July' + try: + # Check if a timezone has been set + time.tzset() + self.tz_tests = True + except AttributeError: + # No timezone available. Don't run the tests that require a TZ + self.tz_tests = False ->>> format(the_future, r'Y') -u'2100' -""" + def tearDown(self): + if self.old_TZ is None: + del os.environ['TZ'] + else: + os.environ['TZ'] = self.old_TZ -from django.utils import dateformat, translation -import datetime, os, time + # Cleanup - force re-evaluation of TZ environment variable. + if self.tz_tests: + time.tzset() + + def test_empty_format(self): + my_birthday = datetime.datetime(1979, 7, 8, 22, 00) + + self.assertEquals(dateformat.format(my_birthday, ''), u'') + + def test_am_pm(self): + my_birthday = datetime.datetime(1979, 7, 8, 22, 00) + + self.assertEquals(dateformat.format(my_birthday, 'a'), u'p.m.') + + def test_date_formats(self): + my_birthday = datetime.datetime(1979, 7, 8, 22, 00) + + self.assertEquals(dateformat.format(my_birthday, 'A'), u'PM') + self.assertEquals(dateformat.format(my_birthday, 'd'), u'08') + self.assertEquals(dateformat.format(my_birthday, 'j'), u'8') + self.assertEquals(dateformat.format(my_birthday, 'l'), u'Sunday') + self.assertEquals(dateformat.format(my_birthday, 'L'), u'False') + self.assertEquals(dateformat.format(my_birthday, 'm'), u'07') + self.assertEquals(dateformat.format(my_birthday, 'M'), u'Jul') + self.assertEquals(dateformat.format(my_birthday, 'b'), u'jul') + self.assertEquals(dateformat.format(my_birthday, 'n'), u'7') + self.assertEquals(dateformat.format(my_birthday, 'N'), u'July') + + def test_time_formats(self): + my_birthday = datetime.datetime(1979, 7, 8, 22, 00) + + self.assertEquals(dateformat.format(my_birthday, 'P'), u'10 p.m.') + self.assertEquals(dateformat.format(my_birthday, 's'), u'00') + self.assertEquals(dateformat.format(my_birthday, 'S'), u'th') + self.assertEquals(dateformat.format(my_birthday, 't'), u'31') + self.assertEquals(dateformat.format(my_birthday, 'w'), u'0') + self.assertEquals(dateformat.format(my_birthday, 'W'), u'27') + self.assertEquals(dateformat.format(my_birthday, 'y'), u'79') + self.assertEquals(dateformat.format(my_birthday, 'Y'), u'1979') + self.assertEquals(dateformat.format(my_birthday, 'z'), u'189') + + def test_dateformat(self): + my_birthday = datetime.datetime(1979, 7, 8, 22, 00) + + self.assertEquals(dateformat.format(my_birthday, r'Y z \C\E\T'), u'1979 189 CET') + + self.assertEquals(dateformat.format(my_birthday, r'jS o\f F'), u'8th of July') -format = dateformat.format -os.environ['TZ'] = 'Europe/Copenhagen' -translation.activate('en-us') + def test_futuredates(self): + the_future = datetime.datetime(2100, 10, 25, 0, 00) + self.assertEquals(dateformat.format(the_future, r'Y'), u'2100') -try: - time.tzset() - no_tz = False -except AttributeError: - no_tz = True + def test_timezones(self): + my_birthday = datetime.datetime(1979, 7, 8, 22, 00) + summertime = datetime.datetime(2005, 10, 30, 1, 00) + wintertime = datetime.datetime(2005, 10, 30, 4, 00) -my_birthday = datetime.datetime(1979, 7, 8, 22, 00) -summertime = datetime.datetime(2005, 10, 30, 1, 00) -wintertime = datetime.datetime(2005, 10, 30, 4, 00) -the_future = datetime.datetime(2100, 10, 25, 0, 00) + if self.tz_tests: + self.assertEquals(dateformat.format(my_birthday, 'O'), u'+0100') + self.assertEquals(dateformat.format(my_birthday, 'r'), u'Sun, 8 Jul 1979 22:00:00 +0100') + self.assertEquals(dateformat.format(my_birthday, 'T'), u'CET') + self.assertEquals(dateformat.format(my_birthday, 'U'), u'300315600') + self.assertEquals(dateformat.format(my_birthday, 'Z'), u'3600') + self.assertEquals(dateformat.format(summertime, 'I'), u'1') + self.assertEquals(dateformat.format(summertime, 'O'), u'+0200') + self.assertEquals(dateformat.format(wintertime, 'I'), u'0') + self.assertEquals(dateformat.format(wintertime, 'O'), u'+0100') diff --git a/tests/regressiontests/m2m_through_regress/models.py b/tests/regressiontests/m2m_through_regress/models.py index dcf5f8115b..56aecd6975 100644 --- a/tests/regressiontests/m2m_through_regress/models.py +++ b/tests/regressiontests/m2m_through_regress/models.py @@ -84,22 +84,22 @@ __test__ = {'API_TESTS':""" >>> bob.group_set = [] Traceback (most recent call last): ... -AttributeError: Cannot set values on a ManyToManyField which specifies an intermediary model. Use Membership's Manager instead. +AttributeError: Cannot set values on a ManyToManyField which specifies an intermediary model. Use m2m_through_regress.Membership's Manager instead. >>> roll.members = [] Traceback (most recent call last): ... -AttributeError: Cannot set values on a ManyToManyField which specifies an intermediary model. Use Membership's Manager instead. +AttributeError: Cannot set values on a ManyToManyField which specifies an intermediary model. Use m2m_through_regress.Membership's Manager instead. >>> rock.members.create(name='Anne') Traceback (most recent call last): ... -AttributeError: Cannot use create() on a ManyToManyField which specifies an intermediary model. Use Membership's Manager instead. +AttributeError: Cannot use create() on a ManyToManyField which specifies an intermediary model. Use m2m_through_regress.Membership's Manager instead. >>> bob.group_set.create(name='Funk') Traceback (most recent call last): ... -AttributeError: Cannot use create() on a ManyToManyField which specifies an intermediary model. Use Membership's Manager instead. +AttributeError: Cannot use create() on a ManyToManyField which specifies an intermediary model. Use m2m_through_regress.Membership's Manager instead. # Now test that the intermediate with a relationship outside # the current app (i.e., UserMembership) workds diff --git a/tests/regressiontests/mail/custombackend.py b/tests/regressiontests/mail/custombackend.py new file mode 100644 index 0000000000..6b0e15ad0a --- /dev/null +++ b/tests/regressiontests/mail/custombackend.py @@ -0,0 +1,15 @@ +"""A custom backend for testing.""" + +from django.core.mail.backends.base import BaseEmailBackend + + +class EmailBackend(BaseEmailBackend): + + def __init__(self, *args, **kwargs): + super(EmailBackend, self).__init__(*args, **kwargs) + self.test_outbox = [] + + def send_messages(self, email_messages): + # Messages are stored in a instance variable for testing. + self.test_outbox.extend(email_messages) + return len(email_messages) diff --git a/tests/regressiontests/mail/tests.py b/tests/regressiontests/mail/tests.py index e90d77366f..10d2ae7df3 100644 --- a/tests/regressiontests/mail/tests.py +++ b/tests/regressiontests/mail/tests.py @@ -1,10 +1,18 @@ # coding: utf-8 + r""" # Tests for the django.core.mail. +>>> import os +>>> import shutil +>>> import tempfile +>>> from StringIO import StringIO >>> from django.conf import settings >>> from django.core import mail >>> from django.core.mail import EmailMessage, mail_admins, mail_managers, EmailMultiAlternatives +>>> from django.core.mail import send_mail, send_mass_mail +>>> from django.core.mail.backends.base import BaseEmailBackend +>>> from django.core.mail.backends import console, dummy, locmem, filebased, smtp >>> from django.utils.translation import ugettext_lazy # Test normal ascii character case: @@ -85,8 +93,6 @@ BadHeaderError: Header values can't contain newlines (got u'Subject\nInjection T >>> mail_managers('hi','there') >>> len(mail.outbox) 1 ->>> settings.ADMINS = old_admins ->>> settings.MANAGERS = old_managers # Make sure we can manually set the From header (#9214) @@ -95,6 +101,17 @@ BadHeaderError: Header values can't contain newlines (got u'Subject\nInjection T >>> message['From'] 'from@example.com' +# Regression for #11144 - When a to/from/cc header contains unicode, +# make sure the email addresses are parsed correctly (especially +# with regards to commas) +>>> email = EmailMessage('Subject', 'Content', 'from@example.com', ['"Firstname Sürname" <to@example.com>','other@example.com']) +>>> email.message()['To'] +'=?utf-8?q?Firstname_S=C3=BCrname?= <to@example.com>, other@example.com' + +>>> email = EmailMessage('Subject', 'Content', 'from@example.com', ['"Sürname, Firstname" <to@example.com>','other@example.com']) +>>> email.message()['To'] +'=?utf-8?q?S=C3=BCrname=2C_Firstname?= <to@example.com>, other@example.com' + # Handle attachments within an multipart/alternative mail correctly (#9367) # (test is not as precise/clear as it could be w.r.t. email tree structure, # but it's good enough.) @@ -138,4 +155,217 @@ Content-Disposition: attachment; filename="an attachment.pdf" JVBERi0xLjQuJS4uLg== ... +# Make sure that the console backend writes to stdout by default +>>> connection = console.EmailBackend() +>>> email = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'}) +>>> connection.send_messages([email]) +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: quoted-printable +Subject: Subject +From: from@example.com +To: to@example.com +Date: ... +Message-ID: ... + +Content +------------------------------------------------------------------------------- +1 + +# Test that the console backend can be pointed at an arbitrary stream +>>> s = StringIO() +>>> connection = mail.get_connection('django.core.mail.backends.console', stream=s) +>>> send_mail('Subject', 'Content', 'from@example.com', ['to@example.com'], connection=connection) +1 +>>> print s.getvalue() +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: quoted-printable +Subject: Subject +From: from@example.com +To: to@example.com +Date: ... +Message-ID: ... + +Content +------------------------------------------------------------------------------- + +# Make sure that dummy backends returns correct number of sent messages +>>> connection = dummy.EmailBackend() +>>> email = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'}) +>>> connection.send_messages([email, email, email]) +3 + +# Make sure that locmen backend populates the outbox +>>> mail.outbox = [] +>>> connection = locmem.EmailBackend() +>>> email1 = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'}) +>>> email2 = EmailMessage('Subject 2', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'}) +>>> connection.send_messages([email1, email2]) +2 +>>> len(mail.outbox) +2 +>>> mail.outbox[0].subject +'Subject' +>>> mail.outbox[1].subject +'Subject 2' + +# Make sure that multiple locmem connections share mail.outbox +>>> mail.outbox = [] +>>> connection1 = locmem.EmailBackend() +>>> connection2 = locmem.EmailBackend() +>>> email = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'}) +>>> connection1.send_messages([email]) +1 +>>> connection2.send_messages([email]) +1 +>>> len(mail.outbox) +2 + +# Make sure that the file backend write to the right location +>>> tmp_dir = tempfile.mkdtemp() +>>> connection = filebased.EmailBackend(file_path=tmp_dir) +>>> email = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'}) +>>> connection.send_messages([email]) +1 +>>> len(os.listdir(tmp_dir)) +1 +>>> print open(os.path.join(tmp_dir, os.listdir(tmp_dir)[0])).read() +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: quoted-printable +Subject: Subject +From: from@example.com +To: to@example.com +Date: ... +Message-ID: ... + +Content +------------------------------------------------------------------------------- + +>>> connection2 = filebased.EmailBackend(file_path=tmp_dir) +>>> connection2.send_messages([email]) +1 +>>> len(os.listdir(tmp_dir)) +2 +>>> connection.send_messages([email]) +1 +>>> len(os.listdir(tmp_dir)) +2 +>>> email.connection = filebased.EmailBackend(file_path=tmp_dir) +>>> connection_created = connection.open() +>>> num_sent = email.send() +>>> len(os.listdir(tmp_dir)) +3 +>>> num_sent = email.send() +>>> len(os.listdir(tmp_dir)) +3 +>>> connection.close() +>>> shutil.rmtree(tmp_dir) + +# Make sure that get_connection() accepts arbitrary keyword that might be +# used with custom backends. +>>> c = mail.get_connection(fail_silently=True, foo='bar') +>>> c.fail_silently +True + +# Test custom backend defined in this suite. +>>> conn = mail.get_connection('regressiontests.mail.custombackend') +>>> hasattr(conn, 'test_outbox') +True +>>> email = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'}) +>>> conn.send_messages([email]) +1 +>>> len(conn.test_outbox) +1 + +# Test backend argument of mail.get_connection() +>>> isinstance(mail.get_connection('django.core.mail.backends.smtp'), smtp.EmailBackend) +True +>>> isinstance(mail.get_connection('django.core.mail.backends.locmem'), locmem.EmailBackend) +True +>>> isinstance(mail.get_connection('django.core.mail.backends.dummy'), dummy.EmailBackend) +True +>>> isinstance(mail.get_connection('django.core.mail.backends.console'), console.EmailBackend) +True +>>> tmp_dir = tempfile.mkdtemp() +>>> isinstance(mail.get_connection('django.core.mail.backends.filebased', file_path=tmp_dir), filebased.EmailBackend) +True +>>> shutil.rmtree(tmp_dir) +>>> isinstance(mail.get_connection(), locmem.EmailBackend) +True + +# Test connection argument of send_mail() et al +>>> connection = mail.get_connection('django.core.mail.backends.console') +>>> send_mail('Subject', 'Content', 'from@example.com', ['to@example.com'], connection=connection) +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: quoted-printable +Subject: Subject +From: from@example.com +To: to@example.com +Date: ... +Message-ID: ... + +Content +------------------------------------------------------------------------------- +1 + +>>> send_mass_mail([ +... ('Subject1', 'Content1', 'from1@example.com', ['to1@example.com']), +... ('Subject2', 'Content2', 'from2@example.com', ['to2@example.com']) +... ], connection=connection) +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: quoted-printable +Subject: Subject1 +From: from1@example.com +To: to1@example.com +Date: ... +Message-ID: ... + +Content1 +------------------------------------------------------------------------------- +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: quoted-printable +Subject: Subject2 +From: from2@example.com +To: to2@example.com +Date: ... +Message-ID: ... + +Content2 +------------------------------------------------------------------------------- +2 + +>>> mail_admins('Subject', 'Content', connection=connection) +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: quoted-printable +Subject: [Django] Subject +From: root@localhost +To: nobody@example.com +Date: ... +Message-ID: ... + +Content +------------------------------------------------------------------------------- + +>>> mail_managers('Subject', 'Content', connection=connection) +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: quoted-printable +Subject: [Django] Subject +From: root@localhost +To: nobody@example.com +Date: ... +Message-ID: ... + +Content +------------------------------------------------------------------------------- + +>>> settings.ADMINS = old_admins +>>> settings.MANAGERS = old_managers + """ diff --git a/tests/regressiontests/model_formsets_regress/tests.py b/tests/regressiontests/model_formsets_regress/tests.py index f5e9355e42..4dba9fc19f 100644 --- a/tests/regressiontests/model_formsets_regress/tests.py +++ b/tests/regressiontests/model_formsets_regress/tests.py @@ -140,3 +140,13 @@ class InlineFormsetTests(TestCase): self.assertEqual(manager[1]['name'], 'Terry Gilliam') else: self.fail('Errors found on formset:%s' % form_set.errors) + + def test_formset_with_none_instance(self): + "A formset with instance=None can be created. Regression for #11872" + Form = modelform_factory(User) + FormSet = inlineformset_factory(User, UserSite) + + # Instantiate the Form and FormSet to prove + # you can create a formset with an instance of None + form = Form(instance=None) + formset = FormSet(instance=None) diff --git a/tests/regressiontests/model_inheritance_regress/models.py b/tests/regressiontests/model_inheritance_regress/models.py index a1ee6a2d86..6a804a97c1 100644 --- a/tests/regressiontests/model_inheritance_regress/models.py +++ b/tests/regressiontests/model_inheritance_regress/models.py @@ -110,6 +110,36 @@ class DerivedM(BaseM): return "PK = %d, base_name = %s, derived_name = %s" \ % (self.customPK, self.base_name, self.derived_name) +# Check that abstract classes don't get m2m tables autocreated. +class Person(models.Model): + name = models.CharField(max_length=100) + + class Meta: + ordering = ('name',) + + def __unicode__(self): + return self.name + +class AbstractEvent(models.Model): + name = models.CharField(max_length=100) + attendees = models.ManyToManyField(Person, related_name="%(class)s_set") + + class Meta: + abstract = True + ordering = ('name',) + + def __unicode__(self): + return self.name + +class BirthdayParty(AbstractEvent): + pass + +class BachelorParty(AbstractEvent): + pass + +class MessyBachelorParty(BachelorParty): + pass + __test__ = {'API_TESTS':""" # Regression for #7350, #7202 # Check that when you create a Parent object with a specific reference to an @@ -318,5 +348,41 @@ True >>> ParkingLot3._meta.get_ancestor_link(Place).name # the child->parent link "parent" +# Check that many-to-many relations defined on an abstract base class +# are correctly inherited (and created) on the child class. +>>> p1 = Person.objects.create(name='Alice') +>>> p2 = Person.objects.create(name='Bob') +>>> p3 = Person.objects.create(name='Carol') +>>> p4 = Person.objects.create(name='Dave') + +>>> birthday = BirthdayParty.objects.create(name='Birthday party for Alice') +>>> birthday.attendees = [p1, p3] + +>>> bachelor = BachelorParty.objects.create(name='Bachelor party for Bob') +>>> bachelor.attendees = [p2, p4] + +>>> print p1.birthdayparty_set.all() +[<BirthdayParty: Birthday party for Alice>] + +>>> print p1.bachelorparty_set.all() +[] + +>>> print p2.bachelorparty_set.all() +[<BachelorParty: Bachelor party for Bob>] + +# Check that a subclass of a subclass of an abstract model +# doesn't get it's own accessor. +>>> p2.messybachelorparty_set.all() +Traceback (most recent call last): +... +AttributeError: 'Person' object has no attribute 'messybachelorparty_set' + +# ... but it does inherit the m2m from it's parent +>>> messy = MessyBachelorParty.objects.create(name='Bachelor party for Dave') +>>> messy.attendees = [p4] + +>>> p4.bachelorparty_set.all() +[<BachelorParty: Bachelor party for Bob>, <BachelorParty: Bachelor party for Dave>] + """} diff --git a/tests/regressiontests/serializers_regress/models.py b/tests/regressiontests/serializers_regress/models.py index 95119d4b05..313ed8fc3a 100644 --- a/tests/regressiontests/serializers_regress/models.py +++ b/tests/regressiontests/serializers_regress/models.py @@ -105,6 +105,9 @@ class Anchor(models.Model): data = models.CharField(max_length=30) + class Meta: + ordering = ('id',) + class UniqueAnchor(models.Model): """This is a model that can be used as something for other models to point at""" @@ -135,7 +138,7 @@ class FKDataToO2O(models.Model): class M2MIntermediateData(models.Model): data = models.ManyToManyField(Anchor, null=True, through='Intermediate') - + class Intermediate(models.Model): left = models.ForeignKey(M2MIntermediateData) right = models.ForeignKey(Anchor) @@ -242,7 +245,7 @@ class AbstractBaseModel(models.Model): class InheritAbstractModel(AbstractBaseModel): child_data = models.IntegerField() - + class BaseModel(models.Model): parent_data = models.IntegerField() @@ -252,4 +255,3 @@ class InheritBaseModel(BaseModel): class ExplicitInheritBaseModel(BaseModel): parent = models.OneToOneField(BaseModel) child_data = models.IntegerField() -
\ No newline at end of file diff --git a/tests/regressiontests/signals_regress/__init__.py b/tests/regressiontests/signals_regress/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/regressiontests/signals_regress/__init__.py diff --git a/tests/regressiontests/signals_regress/models.py b/tests/regressiontests/signals_regress/models.py new file mode 100644 index 0000000000..2b40ebc21a --- /dev/null +++ b/tests/regressiontests/signals_regress/models.py @@ -0,0 +1,89 @@ +""" +Testing signals before/after saving and deleting. +""" + +from django.db import models + +class Author(models.Model): + name = models.CharField(max_length=20) + + def __unicode__(self): + return self.name + +class Book(models.Model): + name = models.CharField(max_length=20) + authors = models.ManyToManyField(Author) + + def __unicode__(self): + return self.name + +def pre_save_test(signal, sender, instance, **kwargs): + print 'pre_save signal,', instance + if kwargs.get('raw'): + print 'Is raw' + +def post_save_test(signal, sender, instance, **kwargs): + print 'post_save signal,', instance + if 'created' in kwargs: + if kwargs['created']: + print 'Is created' + else: + print 'Is updated' + if kwargs.get('raw'): + print 'Is raw' + +def pre_delete_test(signal, sender, instance, **kwargs): + print 'pre_delete signal,', instance + print 'instance.id is not None: %s' % (instance.id != None) + +def post_delete_test(signal, sender, instance, **kwargs): + print 'post_delete signal,', instance + print 'instance.id is not None: %s' % (instance.id != None) + +__test__ = {'API_TESTS':""" + +# Save up the number of connected signals so that we can check at the end +# that all the signals we register get properly unregistered (#9989) +>>> pre_signals = (len(models.signals.pre_save.receivers), +... len(models.signals.post_save.receivers), +... len(models.signals.pre_delete.receivers), +... len(models.signals.post_delete.receivers)) + +>>> models.signals.pre_save.connect(pre_save_test) +>>> models.signals.post_save.connect(post_save_test) +>>> models.signals.pre_delete.connect(pre_delete_test) +>>> models.signals.post_delete.connect(post_delete_test) + +>>> a1 = Author(name='Neal Stephenson') +>>> a1.save() +pre_save signal, Neal Stephenson +post_save signal, Neal Stephenson +Is created + +>>> b1 = Book(name='Snow Crash') +>>> b1.save() +pre_save signal, Snow Crash +post_save signal, Snow Crash +Is created + +# Assigning to m2m shouldn't generate an m2m signal +>>> b1.authors = [a1] + +# Removing an author from an m2m shouldn't generate an m2m signal +>>> b1.authors = [] + +>>> models.signals.post_delete.disconnect(post_delete_test) +>>> models.signals.pre_delete.disconnect(pre_delete_test) +>>> models.signals.post_save.disconnect(post_save_test) +>>> models.signals.pre_save.disconnect(pre_save_test) + +# Check that all our signals got disconnected properly. +>>> post_signals = (len(models.signals.pre_save.receivers), +... len(models.signals.post_save.receivers), +... len(models.signals.pre_delete.receivers), +... len(models.signals.post_delete.receivers)) + +>>> pre_signals == post_signals +True + +"""} diff --git a/tests/regressiontests/test_client_regress/models.py b/tests/regressiontests/test_client_regress/models.py index be2cb8fa37..58693cc395 100644 --- a/tests/regressiontests/test_client_regress/models.py +++ b/tests/regressiontests/test_client_regress/models.py @@ -574,6 +574,23 @@ class RequestMethodTests(TestCase): self.assertEqual(response.status_code, 200) self.assertEqual(response.content, 'request method: DELETE') +class RequestMethodStringDataTests(TestCase): + def test_post(self): + "Request a view with string data via request method POST" + # Regression test for #11371 + data = u'{"test": "json"}' + response = self.client.post('/test_client_regress/request_methods/', data=data, content_type='application/json') + self.assertEqual(response.status_code, 200) + self.assertEqual(response.content, 'request method: POST') + + def test_put(self): + "Request a view with string data via request method PUT" + # Regression test for #11371 + data = u'{"test": "json"}' + response = self.client.put('/test_client_regress/request_methods/', data=data, content_type='application/json') + self.assertEqual(response.status_code, 200) + self.assertEqual(response.content, 'request method: PUT') + class QueryStringTests(TestCase): def test_get_like_requests(self): for method_name in ('get','head','options','put','delete'): diff --git a/tests/regressiontests/utils/dateformat.py b/tests/regressiontests/utils/dateformat.py index 63b8201752..b80010f8d8 100644 --- a/tests/regressiontests/utils/dateformat.py +++ b/tests/regressiontests/utils/dateformat.py @@ -1,48 +1,35 @@ -""" ->>> from datetime import datetime, date ->>> from django.utils.dateformat import format ->>> from django.utils.tzinfo import FixedOffset, LocalTimezone +import os +from unittest import TestCase +from datetime import datetime, date +from django.utils.dateformat import format +from django.utils.tzinfo import FixedOffset, LocalTimezone -# date ->>> d = date(2009, 5, 16) ->>> date.fromtimestamp(int(format(d, 'U'))) == d -True +class DateFormatTests(TestCase): + def test_date(self): + d = date(2009, 5, 16) + self.assertEquals(date.fromtimestamp(int(format(d, 'U'))), d) -# Naive datetime ->>> dt = datetime(2009, 5, 16, 5, 30, 30) ->>> datetime.fromtimestamp(int(format(dt, 'U'))) == dt -True + def test_naive_datetime(self): + dt = datetime(2009, 5, 16, 5, 30, 30) + self.assertEquals(datetime.fromtimestamp(int(format(dt, 'U'))), dt) -# datetime with local tzinfo ->>> ltz = LocalTimezone(datetime.now()) ->>> dt = datetime(2009, 5, 16, 5, 30, 30, tzinfo=ltz) ->>> datetime.fromtimestamp(int(format(dt, 'U')), ltz) == dt -True ->>> datetime.fromtimestamp(int(format(dt, 'U'))) == dt.replace(tzinfo=None) -True + def test_datetime_with_local_tzinfo(self): + ltz = LocalTimezone(datetime.now()) + dt = datetime(2009, 5, 16, 5, 30, 30, tzinfo=ltz) + self.assertEquals(datetime.fromtimestamp(int(format(dt, 'U')), ltz), dt) + self.assertEquals(datetime.fromtimestamp(int(format(dt, 'U'))), dt.replace(tzinfo=None)) -# datetime with arbitrary tzinfo ->>> tz = FixedOffset(-510) ->>> ltz = LocalTimezone(datetime.now()) ->>> dt = datetime(2009, 5, 16, 5, 30, 30, tzinfo=tz) ->>> datetime.fromtimestamp(int(format(dt, 'U')), tz) == dt -True ->>> datetime.fromtimestamp(int(format(dt, 'U')), ltz) == dt -True ->>> datetime.fromtimestamp(int(format(dt, 'U'))) == dt.astimezone(ltz).replace(tzinfo=None) -True ->>> datetime.fromtimestamp(int(format(dt, 'U')), tz).utctimetuple() == dt.utctimetuple() -True ->>> datetime.fromtimestamp(int(format(dt, 'U')), ltz).utctimetuple() == dt.utctimetuple() -True + def test_datetime_with_tzinfo(self): + tz = FixedOffset(-510) + ltz = LocalTimezone(datetime.now()) + dt = datetime(2009, 5, 16, 5, 30, 30, tzinfo=tz) + self.assertEquals(datetime.fromtimestamp(int(format(dt, 'U')), tz), dt) + self.assertEquals(datetime.fromtimestamp(int(format(dt, 'U')), ltz), dt) + self.assertEquals(datetime.fromtimestamp(int(format(dt, 'U'))), dt.astimezone(ltz).replace(tzinfo=None)) + self.assertEquals(datetime.fromtimestamp(int(format(dt, 'U')), tz).utctimetuple(), dt.utctimetuple()) + self.assertEquals(datetime.fromtimestamp(int(format(dt, 'U')), ltz).utctimetuple(), dt.utctimetuple()) -# Epoch ->>> utc = FixedOffset(0) ->>> udt = datetime(1970, 1, 1, tzinfo=utc) ->>> format(udt, 'U') -u'0' -""" - -if __name__ == "__main__": - import doctest - doctest.testmod() + def test_epoch(self): + utc = FixedOffset(0) + udt = datetime(1970, 1, 1, tzinfo=utc) + self.assertEquals(format(udt, 'U'), u'0') diff --git a/tests/regressiontests/utils/tests.py b/tests/regressiontests/utils/tests.py index 6d345d9afb..a7a6e4c3a1 100644 --- a/tests/regressiontests/utils/tests.py +++ b/tests/regressiontests/utils/tests.py @@ -5,6 +5,7 @@ Tests for django.utils. from unittest import TestCase from django.utils import html, checksums +from django.utils.functional import SimpleLazyObject import timesince import datastructures @@ -23,10 +24,11 @@ except NameError: __test__ = { 'timesince': timesince, 'datastructures': datastructures, - 'dateformat': dateformat, 'itercompat': itercompat, } +from dateformat import * + class TestUtilsHtml(TestCase): def check_output(self, function, value, output=None): @@ -161,6 +163,80 @@ class TestUtilsChecksums(TestCase): for value, output in items: self.check_output(f, value, output) +class _ComplexObject(object): + def __init__(self, name): + self.name = name + + def __eq__(self, other): + return self.name == other.name + + def __hash__(self): + return hash(self.name) + + def __str__(self): + return "I am _ComplexObject(%r)" % self.name + + def __unicode__(self): + return unicode(self.name) + + def __repr__(self): + return "_ComplexObject(%r)" % self.name + +complex_object = lambda: _ComplexObject("joe") + +class TestUtilsSimpleLazyObject(TestCase): + """ + Tests for SimpleLazyObject + """ + # Note that concrete use cases for SimpleLazyObject are also found in the + # auth context processor tests (unless the implementation of that function + # is changed). + + def test_equality(self): + self.assertEqual(complex_object(), SimpleLazyObject(complex_object)) + self.assertEqual(SimpleLazyObject(complex_object), complex_object()) + + def test_hash(self): + # hash() equality would not be true for many objects, but it should be + # for _ComplexObject + self.assertEqual(hash(complex_object()), + hash(SimpleLazyObject(complex_object))) + + def test_repr(self): + # For debugging, it will really confuse things if there is no clue that + # SimpleLazyObject is actually a proxy object. So we don't + # proxy __repr__ + self.assert_("SimpleLazyObject" in repr(SimpleLazyObject(complex_object))) + + def test_str(self): + self.assertEqual("I am _ComplexObject('joe')", str(SimpleLazyObject(complex_object))) + + def test_unicode(self): + self.assertEqual(u"joe", unicode(SimpleLazyObject(complex_object))) + + def test_class(self): + # This is important for classes that use __class__ in things like + # equality tests. + self.assertEqual(_ComplexObject, SimpleLazyObject(complex_object).__class__) + + def test_deepcopy(self): + import copy + # Check that we *can* do deep copy, and that it returns the right + # objects. + + # First, for an unevaluated SimpleLazyObject + s = SimpleLazyObject(complex_object) + assert s._wrapped is None + s2 = copy.deepcopy(s) + assert s._wrapped is None # something has gone wrong is s is evaluated + self.assertEqual(s2, complex_object()) + + # Second, for an evaluated SimpleLazyObject + name = s.name # evaluate + assert s._wrapped is not None + s3 = copy.deepcopy(s) + self.assertEqual(s3, complex_object()) + if __name__ == "__main__": import doctest doctest.testmod() diff --git a/tests/regressiontests/views/tests/generic/date_based.py b/tests/regressiontests/views/tests/generic/date_based.py index e0bae28ed0..2ca1bfd090 100644 --- a/tests/regressiontests/views/tests/generic/date_based.py +++ b/tests/regressiontests/views/tests/generic/date_based.py @@ -100,7 +100,7 @@ class MonthArchiveTest(TestCase): now = datetime.now() prev_month = now.date().replace(day=1) - if prev_month.month == 11: + if prev_month.month == 1: prev_month = prev_month.replace(year=prev_month.year-1, month=12) else: prev_month = prev_month.replace(month=prev_month.month-1) @@ -121,4 +121,4 @@ class DayArchiveTests(TestCase): article = Article.objects.create(title="example", author=author, date_created=datetime(2004, 1, 21, 0, 0, 1)) response = self.client.get('/views/date_based/archive_day/2004/1/21/') self.assertEqual(response.status_code, 200) - self.assertEqual(response.context['object_list'][0], article)
\ No newline at end of file + self.assertEqual(response.context['object_list'][0], article) |
