summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/modeltests/choices/models.py11
-rw-r--r--tests/modeltests/custom_methods/models.py3
-rw-r--r--tests/modeltests/delete/models.py31
-rw-r--r--tests/modeltests/generic_relations/models.py55
-rw-r--r--tests/modeltests/invalid_models/models.py65
-rw-r--r--tests/modeltests/m2m_through/__init__.py2
-rw-r--r--tests/modeltests/m2m_through/models.py337
-rw-r--r--tests/modeltests/many_to_one/models.py8
-rw-r--r--tests/modeltests/model_forms/models.py6
-rw-r--r--tests/modeltests/model_formsets/models.py56
-rw-r--r--tests/modeltests/pagination/models.py107
-rw-r--r--tests/modeltests/validation/models.py22
-rw-r--r--tests/regressiontests/admin_registration/__init__.py (renamed from tests/regressiontests/maxlength/__init__.py)0
-rw-r--r--tests/regressiontests/admin_registration/models.py64
-rw-r--r--tests/regressiontests/admin_scripts/tests.py181
-rw-r--r--tests/regressiontests/admin_views/tests.py8
-rw-r--r--tests/regressiontests/auth_backends/tests.py8
-rw-r--r--tests/regressiontests/cache/tests.py33
-rw-r--r--tests/regressiontests/context_processors/__init__.py (renamed from tests/regressiontests/maxlength/models.py)0
-rw-r--r--tests/regressiontests/context_processors/models.py1
-rw-r--r--tests/regressiontests/context_processors/templates/context_processors/request_attrs.html13
-rw-r--r--tests/regressiontests/context_processors/tests.py38
-rw-r--r--tests/regressiontests/context_processors/urls.py8
-rw-r--r--tests/regressiontests/context_processors/views.py8
-rw-r--r--tests/regressiontests/dispatch/tests/test_dispatcher.py18
-rw-r--r--tests/regressiontests/file_uploads/models.py11
-rw-r--r--tests/regressiontests/file_uploads/tests.py76
-rw-r--r--tests/regressiontests/forms/fields.py106
-rw-r--r--tests/regressiontests/forms/forms.py4
-rw-r--r--tests/regressiontests/forms/formsets.py16
-rw-r--r--tests/regressiontests/forms/localflavor/at.py80
-rw-r--r--tests/regressiontests/forms/localflavor/ro.py175
-rw-r--r--tests/regressiontests/forms/models.py19
-rw-r--r--tests/regressiontests/forms/tests.py4
-rw-r--r--tests/regressiontests/m2m_through_regress/__init__.py2
-rw-r--r--tests/regressiontests/m2m_through_regress/models.py204
-rw-r--r--tests/regressiontests/mail/tests.py9
-rw-r--r--tests/regressiontests/many_to_one_regress/models.py52
-rw-r--r--tests/regressiontests/max_lengths/tests.py2
-rw-r--r--tests/regressiontests/maxlength/tests.py160
-rw-r--r--tests/regressiontests/model_fields/models.py60
-rw-r--r--tests/regressiontests/model_fields/tests.py38
-rw-r--r--tests/regressiontests/model_inheritance_regress/models.py35
-rw-r--r--tests/regressiontests/model_regress/models.py19
-rw-r--r--tests/regressiontests/modeladmin/models.py14
-rw-r--r--tests/regressiontests/null_fk/models.py18
-rw-r--r--tests/regressiontests/one_to_one_regress/models.py35
-rw-r--r--tests/regressiontests/pagination_regress/__init__.py0
-rw-r--r--tests/regressiontests/pagination_regress/models.py1
-rw-r--r--tests/regressiontests/pagination_regress/tests.py157
-rw-r--r--tests/regressiontests/queries/models.py69
-rw-r--r--tests/regressiontests/requests/tests.py5
-rw-r--r--tests/regressiontests/reverse_single_related/__init__.py0
-rw-r--r--tests/regressiontests/reverse_single_related/models.py54
-rw-r--r--tests/regressiontests/serializers_regress/tests.py36
-rw-r--r--tests/regressiontests/templates/loaders.py5
-rw-r--r--tests/regressiontests/templates/tests.py25
-rw-r--r--tests/regressiontests/test_client_regress/models.py12
-rw-r--r--tests/regressiontests/test_utils/__init__.py0
-rw-r--r--tests/regressiontests/test_utils/models.py0
-rw-r--r--tests/regressiontests/test_utils/tests.py72
-rw-r--r--tests/regressiontests/utils/datastructures.py6
-rw-r--r--tests/regressiontests/utils/tests.py7
63 files changed, 2135 insertions, 536 deletions
diff --git a/tests/modeltests/choices/models.py b/tests/modeltests/choices/models.py
index 550e655e46..e378260598 100644
--- a/tests/modeltests/choices/models.py
+++ b/tests/modeltests/choices/models.py
@@ -36,4 +36,15 @@ __test__ = {'API_TESTS':"""
u'Male'
>>> s.get_gender_display()
u'Female'
+
+# If the value for the field doesn't correspond to a valid choice,
+# the value itself is provided as a display value.
+>>> a.gender = ''
+>>> a.get_gender_display()
+u''
+
+>>> a.gender = 'U'
+>>> a.get_gender_display()
+u'U'
+
"""}
diff --git a/tests/modeltests/custom_methods/models.py b/tests/modeltests/custom_methods/models.py
index b0ca4131a5..d420871373 100644
--- a/tests/modeltests/custom_methods/models.py
+++ b/tests/modeltests/custom_methods/models.py
@@ -31,7 +31,8 @@ class Article(models.Model):
SELECT id, headline, pub_date
FROM custom_methods_article
WHERE pub_date = %s
- AND id != %s""", [str(self.pub_date), self.id])
+ AND id != %s""", [connection.ops.value_to_db_date(self.pub_date),
+ self.id])
# The asterisk in "(*row)" tells Python to expand the list into
# positional arguments to Article().
return [self.__class__(*row) for row in cursor.fetchall()]
diff --git a/tests/modeltests/delete/models.py b/tests/modeltests/delete/models.py
index f5b423e9ff..49aab1fb1b 100644
--- a/tests/modeltests/delete/models.py
+++ b/tests/modeltests/delete/models.py
@@ -42,7 +42,9 @@ class F(DefaultRepr, models.Model):
__test__ = {'API_TESTS': """
-# First, some tests for the datastructure we use
+### Tests for models A,B,C,D ###
+
+## First, test the CollectedObjects data structure directly
>>> from django.db.models.query import CollectedObjects
@@ -72,6 +74,7 @@ Traceback (most recent call last):
CyclicDependency: There is a cyclic dependency of items to be processed.
+## Second, test the usage of CollectedObjects by Model.delete()
# Due to the way that transactions work in the test harness,
# doing m.delete() here can work but fail in a real situation,
@@ -84,14 +87,21 @@ CyclicDependency: There is a cyclic dependency of items to be processed.
# then try again with a known 'tricky' order. Slightly naughty
# access to internals here :-)
+# If implementation changes, then the tests may need to be simplified:
+# - remove the lines that set the .keyOrder and clear the related
+# object caches
+# - remove the second set of tests (with a2, b2 etc)
+
>>> from django.db.models.loading import cache
+>>> def clear_rel_obj_caches(models):
+... for m in models:
+... if hasattr(m._meta, '_related_objects_cache'):
+... del m._meta._related_objects_cache
+
# Nice order
>>> cache.app_models['delete'].keyOrder = ['a', 'b', 'c', 'd']
->>> del A._meta._related_objects_cache
->>> del B._meta._related_objects_cache
->>> del C._meta._related_objects_cache
->>> del D._meta._related_objects_cache
+>>> clear_rel_obj_caches([A, B, C, D])
>>> a1 = A()
>>> a1.save()
@@ -110,10 +120,7 @@ CyclicDependency: There is a cyclic dependency of items to be processed.
# Same again with a known bad order
>>> cache.app_models['delete'].keyOrder = ['d', 'c', 'b', 'a']
->>> del A._meta._related_objects_cache
->>> del B._meta._related_objects_cache
->>> del C._meta._related_objects_cache
->>> del D._meta._related_objects_cache
+>>> clear_rel_obj_caches([A, B, C, D])
>>> a2 = A()
>>> a2.save()
@@ -130,7 +137,9 @@ CyclicDependency: There is a cyclic dependency of items to be processed.
[<class 'modeltests.delete.models.D'>, <class 'modeltests.delete.models.C'>, <class 'modeltests.delete.models.B'>, <class 'modeltests.delete.models.A'>]
>>> a2.delete()
-# Tests for nullable related fields
+### Tests for models E,F - nullable related fields ###
+
+## First, test the CollectedObjects data structure directly
>>> g = CollectedObjects()
>>> g.add("key1", 1, "item1", None)
@@ -142,6 +151,8 @@ True
>>> g.ordered_keys()
['key1', 'key2']
+## Second, test the usage of CollectedObjects by Model.delete()
+
>>> e1 = E()
>>> e1.save()
>>> f1 = F(e=e1)
diff --git a/tests/modeltests/generic_relations/models.py b/tests/modeltests/generic_relations/models.py
index ff86823d07..d6a7c38e63 100644
--- a/tests/modeltests/generic_relations/models.py
+++ b/tests/modeltests/generic_relations/models.py
@@ -27,11 +27,32 @@ class TaggedItem(models.Model):
def __unicode__(self):
return self.tag
+class Comparison(models.Model):
+ """
+ A model that tests having multiple GenericForeignKeys
+ """
+ comparative = models.CharField(max_length=50)
+
+ content_type1 = models.ForeignKey(ContentType, related_name="comparative1_set")
+ object_id1 = models.PositiveIntegerField()
+
+ content_type2 = models.ForeignKey(ContentType, related_name="comparative2_set")
+ object_id2 = models.PositiveIntegerField()
+
+ first_obj = generic.GenericForeignKey(ct_field="content_type1", fk_field="object_id1")
+ other_obj = generic.GenericForeignKey(ct_field="content_type2", fk_field="object_id2")
+
+ def __unicode__(self):
+ return u"%s is %s than %s" % (self.first_obj, self.comparative, self.other_obj)
+
class Animal(models.Model):
common_name = models.CharField(max_length=150)
latin_name = models.CharField(max_length=150)
tags = generic.GenericRelation(TaggedItem)
+ comparisons = generic.GenericRelation(Comparison,
+ object_id_field="object_id1",
+ content_type_field="content_type1")
def __unicode__(self):
return self.common_name
@@ -136,4 +157,38 @@ __test__ = {'API_TESTS':"""
>>> Animal.objects.filter(tags__content_type=ctype)
[<Animal: Platypus>]
+# Simple tests for multiple GenericForeignKeys
+# only uses one model, since the above tests should be sufficient.
+>>> tiger, cheetah, bear = Animal(common_name="tiger"), Animal(common_name="cheetah"), Animal(common_name="bear")
+>>> for o in [tiger, cheetah, bear]: o.save()
+
+# Create directly
+>>> Comparison(first_obj=cheetah, other_obj=tiger, comparative="faster").save()
+>>> Comparison(first_obj=tiger, other_obj=cheetah, comparative="cooler").save()
+
+# Create using GenericRelation
+>>> tiger.comparisons.create(other_obj=bear, comparative="cooler")
+<Comparison: tiger is cooler than bear>
+>>> tiger.comparisons.create(other_obj=cheetah, comparative="stronger")
+<Comparison: tiger is stronger than cheetah>
+
+>>> cheetah.comparisons.all()
+[<Comparison: cheetah is faster than tiger>]
+
+# Filtering works
+>>> tiger.comparisons.filter(comparative="cooler")
+[<Comparison: tiger is cooler than cheetah>, <Comparison: tiger is cooler than bear>]
+
+# Filtering and deleting works
+>>> subjective = ["cooler"]
+>>> tiger.comparisons.filter(comparative__in=subjective).delete()
+>>> Comparison.objects.all()
+[<Comparison: cheetah is faster than tiger>, <Comparison: tiger is stronger than cheetah>]
+
+# If we delete cheetah, Comparisons with cheetah as 'first_obj' will be deleted
+# since Animal has an explicit GenericRelation to Comparison through first_obj.
+# Comparisons with cheetah as 'other_obj' will not be deleted.
+>>> cheetah.delete()
+>>> Comparison.objects.all()
+[<Comparison: tiger is stronger than None>]
"""}
diff --git a/tests/modeltests/invalid_models/models.py b/tests/modeltests/invalid_models/models.py
index 48e574af48..470afff4fe 100644
--- a/tests/modeltests/invalid_models/models.py
+++ b/tests/modeltests/invalid_models/models.py
@@ -110,6 +110,63 @@ class Car(models.Model):
class MissingRelations(models.Model):
rel1 = models.ForeignKey("Rel1")
rel2 = models.ManyToManyField("Rel2")
+
+class MissingManualM2MModel(models.Model):
+ name = models.CharField(max_length=5)
+ missing_m2m = models.ManyToManyField(Model, through="MissingM2MModel")
+
+class Person(models.Model):
+ name = models.CharField(max_length=5)
+
+class Group(models.Model):
+ name = models.CharField(max_length=5)
+ primary = models.ManyToManyField(Person, through="Membership", related_name="primary")
+ secondary = models.ManyToManyField(Person, through="Membership", related_name="secondary")
+ tertiary = models.ManyToManyField(Person, through="RelationshipDoubleFK", related_name="tertiary")
+
+class GroupTwo(models.Model):
+ name = models.CharField(max_length=5)
+ primary = models.ManyToManyField(Person, through="Membership")
+ secondary = models.ManyToManyField(Group, through="MembershipMissingFK")
+
+class Membership(models.Model):
+ person = models.ForeignKey(Person)
+ group = models.ForeignKey(Group)
+ not_default_or_null = models.CharField(max_length=5)
+
+class MembershipMissingFK(models.Model):
+ person = models.ForeignKey(Person)
+
+class PersonSelfRefM2M(models.Model):
+ name = models.CharField(max_length=5)
+ friends = models.ManyToManyField('self', through="Relationship")
+ too_many_friends = models.ManyToManyField('self', through="RelationshipTripleFK")
+
+class PersonSelfRefM2MExplicit(models.Model):
+ name = models.CharField(max_length=5)
+ friends = models.ManyToManyField('self', through="ExplicitRelationship", symmetrical=True)
+
+class Relationship(models.Model):
+ first = models.ForeignKey(PersonSelfRefM2M, related_name="rel_from_set")
+ second = models.ForeignKey(PersonSelfRefM2M, related_name="rel_to_set")
+ date_added = models.DateTimeField()
+
+class ExplicitRelationship(models.Model):
+ first = models.ForeignKey(PersonSelfRefM2MExplicit, related_name="rel_from_set")
+ second = models.ForeignKey(PersonSelfRefM2MExplicit, related_name="rel_to_set")
+ date_added = models.DateTimeField()
+
+class RelationshipTripleFK(models.Model):
+ first = models.ForeignKey(PersonSelfRefM2M, related_name="rel_from_set_2")
+ second = models.ForeignKey(PersonSelfRefM2M, related_name="rel_to_set_2")
+ third = models.ForeignKey(PersonSelfRefM2M, related_name="too_many_by_far")
+ date_added = models.DateTimeField()
+
+class RelationshipDoubleFK(models.Model):
+ first = models.ForeignKey(Person, related_name="first_related_name")
+ second = models.ForeignKey(Person, related_name="second_related_name")
+ third = models.ForeignKey(Group, related_name="rel_to_set")
+ date_added = models.DateTimeField()
model_errors = """invalid_models.fielderrors: "charfield": CharFields require a "max_length" attribute.
invalid_models.fielderrors: "decimalfield": DecimalFields require a "decimal_places" attribute.
@@ -195,4 +252,12 @@ invalid_models.selfclashm2m: Reverse query name for m2m field 'm2m_3' clashes wi
invalid_models.selfclashm2m: Reverse query name for m2m field 'm2m_4' clashes with field 'SelfClashM2M.selfclashm2m'. Add a related_name argument to the definition for 'm2m_4'.
invalid_models.missingrelations: 'rel2' has m2m relation with model Rel2, which has not been installed
invalid_models.missingrelations: 'rel1' has relation with model Rel1, which has not been installed
+invalid_models.grouptwo: 'primary' has a manually-defined m2m relation through model Membership, which does not have foreign keys to Person and GroupTwo
+invalid_models.grouptwo: 'secondary' has a manually-defined m2m relation through model MembershipMissingFK, which does not have foreign keys to Group and GroupTwo
+invalid_models.missingmanualm2mmodel: 'missing_m2m' specifies an m2m relation through model MissingM2MModel, which has not been installed
+invalid_models.group: The model Group has two manually-defined m2m relations through the model Membership, which is not permitted. Please consider using an extra field on your intermediary model instead.
+invalid_models.group: Intermediary model RelationshipDoubleFK has more than one foreign key to Person, which is ambiguous and is not permitted.
+invalid_models.personselfrefm2m: Many-to-many fields with intermediate tables cannot be symmetrical.
+invalid_models.personselfrefm2m: Intermediary model RelationshipTripleFK has more than two foreign keys to PersonSelfRefM2M, which is ambiguous and is not permitted.
+invalid_models.personselfrefm2mexplicit: Many-to-many fields with intermediate tables cannot be symmetrical.
"""
diff --git a/tests/modeltests/m2m_through/__init__.py b/tests/modeltests/m2m_through/__init__.py
new file mode 100644
index 0000000000..139597f9cb
--- /dev/null
+++ b/tests/modeltests/m2m_through/__init__.py
@@ -0,0 +1,2 @@
+
+
diff --git a/tests/modeltests/m2m_through/models.py b/tests/modeltests/m2m_through/models.py
new file mode 100644
index 0000000000..fa9fa714a5
--- /dev/null
+++ b/tests/modeltests/m2m_through/models.py
@@ -0,0 +1,337 @@
+from django.db import models
+from datetime import datetime
+
+# M2M described on one of the models
+class Person(models.Model):
+ name = models.CharField(max_length=128)
+
+ class Meta:
+ ordering = ('name',)
+
+ def __unicode__(self):
+ return self.name
+
+class Group(models.Model):
+ name = models.CharField(max_length=128)
+ members = models.ManyToManyField(Person, through='Membership')
+ custom_members = models.ManyToManyField(Person, through='CustomMembership', related_name="custom")
+ nodefaultsnonulls = models.ManyToManyField(Person, through='TestNoDefaultsOrNulls', related_name="testnodefaultsnonulls")
+
+ class Meta:
+ ordering = ('name',)
+
+ def __unicode__(self):
+ return self.name
+
+class Membership(models.Model):
+ person = models.ForeignKey(Person)
+ group = models.ForeignKey(Group)
+ date_joined = models.DateTimeField(default=datetime.now)
+ invite_reason = models.CharField(max_length=64, null=True)
+
+ class Meta:
+ ordering = ('date_joined', 'invite_reason', 'group')
+
+ def __unicode__(self):
+ return "%s is a member of %s" % (self.person.name, self.group.name)
+
+class CustomMembership(models.Model):
+ person = models.ForeignKey(Person, db_column="custom_person_column", related_name="custom_person_related_name")
+ group = models.ForeignKey(Group)
+ weird_fk = models.ForeignKey(Membership, null=True)
+ date_joined = models.DateTimeField(default=datetime.now)
+
+ def __unicode__(self):
+ return "%s is a member of %s" % (self.person.name, self.group.name)
+
+ class Meta:
+ db_table = "test_table"
+
+class TestNoDefaultsOrNulls(models.Model):
+ person = models.ForeignKey(Person)
+ group = models.ForeignKey(Group)
+ nodefaultnonull = models.CharField(max_length=5)
+
+class PersonSelfRefM2M(models.Model):
+ name = models.CharField(max_length=5)
+ friends = models.ManyToManyField('self', through="Friendship", symmetrical=False)
+
+ def __unicode__(self):
+ return self.name
+
+class Friendship(models.Model):
+ first = models.ForeignKey(PersonSelfRefM2M, related_name="rel_from_set")
+ second = models.ForeignKey(PersonSelfRefM2M, related_name="rel_to_set")
+ date_friended = models.DateTimeField()
+
+__test__ = {'API_TESTS':"""
+>>> from datetime import datetime
+
+### Creation and Saving Tests ###
+
+>>> bob = Person.objects.create(name='Bob')
+>>> jim = Person.objects.create(name='Jim')
+>>> jane = Person.objects.create(name='Jane')
+>>> rock = Group.objects.create(name='Rock')
+>>> roll = Group.objects.create(name='Roll')
+
+# We start out by making sure that the Group 'rock' has no members.
+>>> rock.members.all()
+[]
+
+# To make Jim a member of Group Rock, simply create a Membership object.
+>>> m1 = Membership.objects.create(person=jim, group=rock)
+
+# We can do the same for Jane and Rock.
+>>> m2 = Membership.objects.create(person=jane, group=rock)
+
+# Let's check to make sure that it worked. Jane and Jim should be members of Rock.
+>>> rock.members.all()
+[<Person: Jane>, <Person: Jim>]
+
+# Now we can add a bunch more Membership objects to test with.
+>>> m3 = Membership.objects.create(person=bob, group=roll)
+>>> m4 = Membership.objects.create(person=jim, group=roll)
+>>> m5 = Membership.objects.create(person=jane, group=roll)
+
+# We can get Jim's Group membership as with any ForeignKey.
+>>> jim.group_set.all()
+[<Group: Rock>, <Group: Roll>]
+
+# Querying the intermediary model works like normal.
+# In this case we get Jane's membership to Rock.
+>>> m = Membership.objects.get(person=jane, group=rock)
+>>> m
+<Membership: Jane is a member of Rock>
+
+# Now we set some date_joined dates for further testing.
+>>> m2.invite_reason = "She was just awesome."
+>>> m2.date_joined = datetime(2006, 1, 1)
+>>> m2.save()
+
+>>> m5.date_joined = datetime(2004, 1, 1)
+>>> m5.save()
+
+>>> m3.date_joined = datetime(2004, 1, 1)
+>>> m3.save()
+
+# It's not only get that works. Filter works like normal as well.
+>>> Membership.objects.filter(person=jim)
+[<Membership: Jim is a member of Rock>, <Membership: Jim is a member of Roll>]
+
+
+### Forward Descriptors Tests ###
+
+# Due to complications with adding via an intermediary model,
+# the add method is not provided.
+>>> rock.members.add(bob)
+Traceback (most recent call last):
+...
+AttributeError: 'ManyRelatedManager' object has no attribute 'add'
+
+# Create is also disabled as it suffers from the same problems as 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.
+
+# Remove has similar complications, and is not provided either.
+>>> rock.members.remove(jim)
+Traceback (most recent call last):
+...
+AttributeError: 'ManyRelatedManager' object has no attribute 'remove'
+
+# Here we back up the list of all members of Rock.
+>>> backup = list(rock.members.all())
+
+# ...and we verify that it has worked.
+>>> backup
+[<Person: Jane>, <Person: Jim>]
+
+# The clear function should still work.
+>>> rock.members.clear()
+
+# Now there will be no members of Rock.
+>>> rock.members.all()
+[]
+
+# Assignment should not work with models specifying a through model for many of
+# the same reasons as adding.
+>>> 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.
+
+# Let's re-save those instances that we've cleared.
+>>> m1.save()
+>>> m2.save()
+
+# Verifying that those instances were re-saved successfully.
+>>> rock.members.all()
+[<Person: Jane>, <Person: Jim>]
+
+
+### Reverse Descriptors Tests ###
+
+# Due to complications with adding via an intermediary model,
+# the add method is not provided.
+>>> bob.group_set.add(rock)
+Traceback (most recent call last):
+...
+AttributeError: 'ManyRelatedManager' object has no attribute 'add'
+
+# Create is also disabled as it suffers from the same problems as 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.
+
+# Remove has similar complications, and is not provided either.
+>>> jim.group_set.remove(rock)
+Traceback (most recent call last):
+...
+AttributeError: 'ManyRelatedManager' object has no attribute 'remove'
+
+# Here we back up the list of all of Jim's groups.
+>>> backup = list(jim.group_set.all())
+>>> backup
+[<Group: Rock>, <Group: Roll>]
+
+# The clear function should still work.
+>>> jim.group_set.clear()
+
+# Now Jim will be in no groups.
+>>> jim.group_set.all()
+[]
+
+# Assignment should not work with models specifying a through model for many of
+# the same reasons as adding.
+>>> 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.
+
+# Let's re-save those instances that we've cleared.
+>>> m1.save()
+>>> m4.save()
+
+# Verifying that those instances were re-saved successfully.
+>>> jim.group_set.all()
+[<Group: Rock>, <Group: Roll>]
+
+### Custom Tests ###
+
+# Let's see if we can query through our second relationship.
+>>> rock.custom_members.all()
+[]
+
+# We can query in the opposite direction as well.
+>>> bob.custom.all()
+[]
+
+# Let's create some membership objects in this custom relationship.
+>>> cm1 = CustomMembership.objects.create(person=bob, group=rock)
+>>> cm2 = CustomMembership.objects.create(person=jim, group=rock)
+
+# If we get the number of people in Rock, it should be both Bob and Jim.
+>>> rock.custom_members.all()
+[<Person: Bob>, <Person: Jim>]
+
+# Bob should only be in one custom group.
+>>> bob.custom.all()
+[<Group: Rock>]
+
+# Let's make sure our new descriptors don't conflict with the FK related_name.
+>>> bob.custom_person_related_name.all()
+[<CustomMembership: Bob is a member of Rock>]
+
+### SELF-REFERENTIAL TESTS ###
+
+# Let's first create a person who has no friends.
+>>> tony = PersonSelfRefM2M.objects.create(name="Tony")
+>>> tony.friends.all()
+[]
+
+# Now let's create another person for Tony to be friends with.
+>>> chris = PersonSelfRefM2M.objects.create(name="Chris")
+>>> f = Friendship.objects.create(first=tony, second=chris, date_friended=datetime.now())
+
+# Tony should now show that Chris is his friend.
+>>> tony.friends.all()
+[<PersonSelfRefM2M: Chris>]
+
+# But we haven't established that Chris is Tony's Friend.
+>>> chris.friends.all()
+[]
+
+# So let's do that now.
+>>> f2 = Friendship.objects.create(first=chris, second=tony, date_friended=datetime.now())
+
+# Having added Chris as a friend, let's make sure that his friend set reflects
+# that addition.
+>>> chris.friends.all()
+[<PersonSelfRefM2M: Tony>]
+
+# Chris gets mad and wants to get rid of all of his friends.
+>>> chris.friends.clear()
+
+# Now he should not have any more friends.
+>>> chris.friends.all()
+[]
+
+# Since this isn't a symmetrical relation, Tony's friend link still exists.
+>>> tony.friends.all()
+[<PersonSelfRefM2M: Chris>]
+
+
+
+### QUERY TESTS ###
+
+# We can query for the related model by using its attribute name (members, in
+# this case).
+>>> Group.objects.filter(members__name='Bob')
+[<Group: Roll>]
+
+# To query through the intermediary model, we specify its model name.
+# In this case, membership.
+>>> Group.objects.filter(membership__invite_reason="She was just awesome.")
+[<Group: Rock>]
+
+# If we want to query in the reverse direction by the related model, use its
+# model name (group, in this case).
+>>> Person.objects.filter(group__name="Rock")
+[<Person: Jane>, <Person: Jim>]
+
+# If the m2m field has specified a related_name, using that will work.
+>>> Person.objects.filter(custom__name="Rock")
+[<Person: Bob>, <Person: Jim>]
+
+# To query through the intermediary model in the reverse direction, we again
+# specify its model name (membership, in this case).
+>>> Person.objects.filter(membership__invite_reason="She was just awesome.")
+[<Person: Jane>]
+
+# Let's see all of the groups that Jane joined after 1 Jan 2005:
+>>> Group.objects.filter(membership__date_joined__gt=datetime(2005, 1, 1), membership__person =jane)
+[<Group: Rock>]
+
+# Queries also work in the reverse direction: Now let's see all of the people
+# that have joined Rock since 1 Jan 2005:
+>>> Person.objects.filter(membership__date_joined__gt=datetime(2005, 1, 1), membership__group=rock)
+[<Person: Jane>, <Person: Jim>]
+
+# Conceivably, queries through membership could return correct, but non-unique
+# querysets. To demonstrate this, we query for all people who have joined a
+# group after 2004:
+>>> Person.objects.filter(membership__date_joined__gt=datetime(2004, 1, 1))
+[<Person: Jane>, <Person: Jim>, <Person: Jim>]
+
+# Jim showed up twice, because he joined two groups ('Rock', and 'Roll'):
+>>> [(m.person.name, m.group.name) for m in
+... Membership.objects.filter(date_joined__gt=datetime(2004, 1, 1))]
+[(u'Jane', u'Rock'), (u'Jim', u'Rock'), (u'Jim', u'Roll')]
+
+# 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/many_to_one/models.py b/tests/modeltests/many_to_one/models.py
index 081cffb807..2dd1226e97 100644
--- a/tests/modeltests/many_to_one/models.py
+++ b/tests/modeltests/many_to_one/models.py
@@ -46,8 +46,12 @@ __test__ = {'API_TESTS':"""
# Article objects have access to their related Reporter objects.
>>> r = a.reporter
+
+# These are strings instead of unicode strings because that's what was used in
+# the creation of this reporter (and we haven't refreshed the data from the
+# database, which always returns unicode strings).
>>> r.first_name, r.last_name
-(u'John', u'Smith')
+('John', 'Smith')
# Create an Article via the Reporter object.
>>> new_article = r.article_set.create(headline="John's second story", pub_date=datetime(2005, 7, 29))
@@ -176,7 +180,7 @@ False
[<Article: John's second story>, <Article: Paul's story>, <Article: This is a test>]
# You can also use a queryset instead of a literal list of instances.
-# The queryset must be reduced to a list of values using values(),
+# The queryset must be reduced to a list of values using values(),
# then converted into a query
>>> Article.objects.filter(reporter__in=Reporter.objects.filter(first_name='John').values('pk').query).distinct()
[<Article: John's second story>, <Article: This is a test>]
diff --git a/tests/modeltests/model_forms/models.py b/tests/modeltests/model_forms/models.py
index cc9efd0f94..be2a8ba835 100644
--- a/tests/modeltests/model_forms/models.py
+++ b/tests/modeltests/model_forms/models.py
@@ -69,8 +69,10 @@ class ImageFile(models.Model):
description = models.CharField(max_length=20)
try:
# If PIL is available, try testing PIL.
- # Otherwise, it's equivalent to TextFile above.
- import Image
+ # Checking for the existence of Image is enough for CPython, but
+ # for PyPy, you need to check for the underlying modules
+ # If PIL is not available, this test is equivalent to TextFile above.
+ import Image, _imaging
image = models.ImageField(upload_to=tempfile.gettempdir())
except ImportError:
image = models.FileField(upload_to=tempfile.gettempdir())
diff --git a/tests/modeltests/model_formsets/models.py b/tests/modeltests/model_formsets/models.py
index 5958b8c27a..5b66d1560b 100644
--- a/tests/modeltests/model_formsets/models.py
+++ b/tests/modeltests/model_formsets/models.py
@@ -1,8 +1,16 @@
from django.db import models
+try:
+ sorted
+except NameError:
+ from django.utils.itercompat import sorted
+
class Author(models.Model):
name = models.CharField(max_length=100)
+ class Meta:
+ ordering = ('name',)
+
def __unicode__(self):
return self.name
@@ -17,10 +25,14 @@ class AuthorMeeting(models.Model):
name = models.CharField(max_length=100)
authors = models.ManyToManyField(Author)
created = models.DateField(editable=False)
-
+
def __unicode__(self):
return self.name
+class CustomPrimaryKey(models.Model):
+ my_pk = models.CharField(max_length=10, primary_key=True)
+ some_field = models.CharField(max_length=100)
+
__test__ = {'API_TESTS': """
@@ -41,7 +53,6 @@ __test__ = {'API_TESTS': """
>>> data = {
... 'form-TOTAL_FORMS': '3', # the number of forms rendered
... 'form-INITIAL_FORMS': '0', # the number of forms with initial data
-... 'form-MAX_FORMS': '0', # the max number of forms
... 'form-0-name': 'Charles Baudelaire',
... 'form-1-name': 'Arthur Rimbaud',
... 'form-2-name': '',
@@ -79,7 +90,6 @@ them in alphabetical order by name.
>>> data = {
... 'form-TOTAL_FORMS': '3', # the number of forms rendered
... 'form-INITIAL_FORMS': '2', # the number of forms with initial data
-... 'form-MAX_FORMS': '0', # the max number of forms
... 'form-0-id': '2',
... 'form-0-name': 'Arthur Rimbaud',
... 'form-1-id': '1',
@@ -123,7 +133,6 @@ deltetion, make sure we don't save that form.
>>> data = {
... 'form-TOTAL_FORMS': '4', # the number of forms rendered
... 'form-INITIAL_FORMS': '3', # the number of forms with initial data
-... 'form-MAX_FORMS': '0', # the max number of forms
... 'form-0-id': '2',
... 'form-0-name': 'Arthur Rimbaud',
... 'form-1-id': '1',
@@ -153,7 +162,6 @@ Let's edit a record to ensure save only returns that one record.
>>> data = {
... 'form-TOTAL_FORMS': '4', # the number of forms rendered
... 'form-INITIAL_FORMS': '3', # the number of forms with initial data
-... 'form-MAX_FORMS': '0', # the max number of forms
... 'form-0-id': '2',
... 'form-0-name': 'Walt Whitman',
... 'form-1-id': '1',
@@ -184,7 +192,6 @@ Test the behavior of commit=False and save_m2m
>>> data = {
... 'form-TOTAL_FORMS': '2', # the number of forms rendered
... 'form-INITIAL_FORMS': '1', # the number of forms with initial data
-... 'form-MAX_FORMS': '0', # the max number of forms
... 'form-0-id': '1',
... 'form-0-name': '2nd Tuesday of the Week Meeting',
... 'form-0-authors': [2, 1, 3, 4],
@@ -201,7 +208,7 @@ True
... instance.save()
>>> formset.save_m2m()
>>> instances[0].authors.all()
-[<Author: Charles Baudelaire>, <Author: Walt Whitman>, <Author: Paul Verlaine>, <Author: John Steinbeck>]
+[<Author: Charles Baudelaire>, <Author: John Steinbeck>, <Author: Paul Verlaine>, <Author: Walt Whitman>]
# delete the author we created to allow later tests to continue working.
>>> new_author.delete()
@@ -214,13 +221,14 @@ used.
>>> AuthorFormSet = modelformset_factory(Author, max_num=2)
>>> formset = AuthorFormSet(queryset=qs)
->>> formset.initial
-[{'id': 1, 'name': u'Charles Baudelaire'}, {'id': 3, 'name': u'Paul Verlaine'}]
+>>> [sorted(x.items()) for x in formset.initial]
+[[('id', 1), ('name', u'Charles Baudelaire')], [('id', 3), ('name', u'Paul Verlaine')]]
>>> AuthorFormSet = modelformset_factory(Author, max_num=3)
>>> formset = AuthorFormSet(queryset=qs)
->>> formset.initial
-[{'id': 1, 'name': u'Charles Baudelaire'}, {'id': 3, 'name': u'Paul Verlaine'}, {'id': 2, 'name': u'Walt Whitman'}]
+>>> [sorted(x.items()) for x in formset.initial]
+[[('id', 1), ('name', u'Charles Baudelaire')], [('id', 3), ('name', u'Paul Verlaine')], [('id', 2), ('name', u'Walt Whitman')]]
+
# Inline Formsets ############################################################
@@ -242,7 +250,6 @@ admin system's edit inline functionality works.
>>> data = {
... 'book_set-TOTAL_FORMS': '3', # the number of forms rendered
... 'book_set-INITIAL_FORMS': '0', # the number of forms with initial data
-... 'book_set-MAX_FORMS': '0', # the max number of forms
... 'book_set-0-title': 'Les Fleurs du Mal',
... 'book_set-1-title': '',
... 'book_set-2-title': '',
@@ -277,7 +284,6 @@ book.
>>> data = {
... 'book_set-TOTAL_FORMS': '3', # the number of forms rendered
... 'book_set-INITIAL_FORMS': '1', # the number of forms with initial data
-... 'book_set-MAX_FORMS': '0', # the max number of forms
... 'book_set-0-id': '1',
... 'book_set-0-title': 'Les Fleurs du Mal',
... 'book_set-1-title': 'Le Spleen de Paris',
@@ -293,10 +299,10 @@ True
As you can see, 'Le Spleen de Paris' is now a book belonging to Charles Baudelaire.
->>> for book in author.book_set.order_by('title'):
+>>> for book in author.book_set.order_by('id'):
... print book.title
-Le Spleen de Paris
Les Fleurs du Mal
+Le Spleen de Paris
The save_as_new parameter lets you re-associate the data to a new instance.
This is used in the admin for save_as functionality.
@@ -304,7 +310,6 @@ This is used in the admin for save_as functionality.
>>> data = {
... 'book_set-TOTAL_FORMS': '3', # the number of forms rendered
... 'book_set-INITIAL_FORMS': '2', # the number of forms with initial data
-... 'book_set-MAX_FORMS': '0', # the max number of forms
... 'book_set-0-id': '1',
... 'book_set-0-title': 'Les Fleurs du Mal',
... 'book_set-1-id': '2',
@@ -321,4 +326,23 @@ True
>>> [book for book in formset.save() if book.author.pk == new_author.pk]
[<Book: Les Fleurs du Mal>, <Book: Le Spleen de Paris>]
+Test using a custom prefix on an inline formset.
+
+>>> formset = AuthorBooksFormSet(prefix="test")
+>>> for form in formset.forms:
+... print form.as_p()
+<p><label for="id_test-0-title">Title:</label> <input id="id_test-0-title" type="text" name="test-0-title" maxlength="100" /><input type="hidden" name="test-0-id" id="id_test-0-id" /></p>
+<p><label for="id_test-1-title">Title:</label> <input id="id_test-1-title" type="text" name="test-1-title" maxlength="100" /><input type="hidden" name="test-1-id" id="id_test-1-id" /></p>
+
+# Test a custom primary key ###################################################
+
+We need to ensure that it is displayed
+
+>>> CustomPrimaryKeyFormSet = modelformset_factory(CustomPrimaryKey)
+>>> formset = CustomPrimaryKeyFormSet()
+>>> for form in formset.forms:
+... print form.as_p()
+<p><label for="id_form-0-my_pk">My pk:</label> <input id="id_form-0-my_pk" type="text" name="form-0-my_pk" maxlength="10" /></p>
+<p><label for="id_form-0-some_field">Some field:</label> <input id="id_form-0-some_field" type="text" name="form-0-some_field" maxlength="100" /></p>
+
"""}
diff --git a/tests/modeltests/pagination/models.py b/tests/modeltests/pagination/models.py
index 4b564f2f90..9b79a6a74e 100644
--- a/tests/modeltests/pagination/models.py
+++ b/tests/modeltests/pagination/models.py
@@ -4,11 +4,6 @@
Django provides a framework for paginating a list of objects in a few lines
of code. This is often useful for dividing search results or long lists of
objects into easily readable pages.
-
-In Django 0.96 and earlier, a single ObjectPaginator class implemented this
-functionality. In the Django development version, the behavior is split across
-two classes -- Paginator and Page -- that are more easier to use. The legacy
-ObjectPaginator class is deprecated.
"""
from django.db import models
@@ -27,9 +22,9 @@ __test__ = {'API_TESTS':"""
... a = Article(headline='Article %s' % x, pub_date=datetime(2005, 7, 29))
... a.save()
-####################################
-# New/current API (Paginator/Page) #
-####################################
+##################
+# Paginator/Page #
+##################
>>> from django.core.paginator import Paginator
>>> paginator = Paginator(Article.objects.all(), 5)
@@ -140,84 +135,26 @@ True
>>> p.end_index()
5
-################################
-# Legacy API (ObjectPaginator) #
-################################
-
-# Don't print out the deprecation warnings during testing.
->>> from warnings import filterwarnings
->>> filterwarnings("ignore")
-
->>> from django.core.paginator import ObjectPaginator, EmptyPage
->>> paginator = ObjectPaginator(Article.objects.all(), 5)
->>> paginator.hits
-9
->>> paginator.pages
-2
->>> paginator.page_range
-[1, 2]
-
-# Get the first page.
->>> paginator.get_page(0)
-[<Article: Article 1>, <Article: Article 2>, <Article: Article 3>, <Article: Article 4>, <Article: Article 5>]
->>> paginator.has_next_page(0)
-True
->>> paginator.has_previous_page(0)
-False
->>> paginator.first_on_page(0)
-1
->>> paginator.last_on_page(0)
-5
-
-# Get the second page.
->>> paginator.get_page(1)
-[<Article: Article 6>, <Article: Article 7>, <Article: Article 8>, <Article: Article 9>]
->>> paginator.has_next_page(1)
-False
->>> paginator.has_previous_page(1)
-True
->>> paginator.first_on_page(1)
-6
->>> paginator.last_on_page(1)
-9
-
-# Invalid pages raise EmptyPage.
->>> paginator.get_page(-1)
-Traceback (most recent call last):
-...
-EmptyPage: ...
->>> paginator.get_page(2)
-Traceback (most recent call last):
-...
-EmptyPage: ...
-
-# Empty paginators with allow_empty_first_page=True.
->>> paginator = ObjectPaginator(Article.objects.filter(id=0), 5)
+# Paginator can be passed other objects with a count() method.
+>>> class CountContainer:
+... def count(self):
+... return 42
+>>> paginator = Paginator(CountContainer(), 10)
>>> paginator.count
-0
+42
>>> paginator.num_pages
-1
->>> paginator.page_range
-[1]
-
-# ObjectPaginator can be passed lists too.
->>> paginator = ObjectPaginator([1, 2, 3], 5)
->>> paginator.hits
-3
->>> paginator.pages
-1
+5
>>> paginator.page_range
-[1]
-
+[1, 2, 3, 4, 5]
-# ObjectPaginator can be passed other objects with a count() method.
->>> class Container:
+# Paginator can be passed other objects that implement __len__.
+>>> class LenContainer:
... def __len__(self):
... return 42
->>> paginator = ObjectPaginator(Container(), 10)
->>> paginator.hits
+>>> paginator = Paginator(LenContainer(), 10)
+>>> paginator.count
42
->>> paginator.pages
+>>> paginator.num_pages
5
>>> paginator.page_range
[1, 2, 3, 4, 5]
@@ -237,17 +174,7 @@ EmptyPage: ...
1
# With orphans only set to 1, we should get two pages.
->>> paginator = ObjectPaginator(Article.objects.all(), 10, orphans=1)
+>>> paginator = Paginator(Article.objects.all(), 10, orphans=1)
>>> paginator.num_pages
2
-
-# LEGACY: With orphans set to 3 and 10 items per page, we should get all 12 items on a single page.
->>> paginator = ObjectPaginator(Article.objects.all(), 10, orphans=3)
->>> paginator.pages
-1
-
-# LEGACY: With orphans only set to 1, we should get two pages.
->>> paginator = ObjectPaginator(Article.objects.all(), 10, orphans=1)
->>> paginator.pages
-2
"""}
diff --git a/tests/modeltests/validation/models.py b/tests/modeltests/validation/models.py
index 63f9f7a361..7ed9d66674 100644
--- a/tests/modeltests/validation/models.py
+++ b/tests/modeltests/validation/models.py
@@ -16,6 +16,7 @@ class Person(models.Model):
birthdate = models.DateField()
favorite_moment = models.DateTimeField()
email = models.EmailField()
+ best_time = models.TimeField()
def __unicode__(self):
return self.name
@@ -28,7 +29,8 @@ __test__ = {'API_TESTS':"""
... 'name': 'John',
... 'birthdate': datetime.date(2000, 5, 3),
... 'favorite_moment': datetime.datetime(2002, 4, 3, 13, 23),
-... 'email': 'john@example.com'
+... 'email': 'john@example.com',
+... 'best_time': datetime.time(16, 20),
... }
>>> p = Person(**valid_params)
>>> p.validate()
@@ -130,6 +132,22 @@ datetime.datetime(2002, 4, 3, 13, 23)
>>> p.favorite_moment
datetime.datetime(2002, 4, 3, 0, 0)
+>>> p = Person(**dict(valid_params, best_time='16:20:00'))
+>>> p.validate()
+{}
+>>> p.best_time
+datetime.time(16, 20)
+
+>>> p = Person(**dict(valid_params, best_time='16:20'))
+>>> p.validate()
+{}
+>>> p.best_time
+datetime.time(16, 20)
+
+>>> p = Person(**dict(valid_params, best_time='bar'))
+>>> p.validate()['best_time']
+[u'Enter a valid time in HH:MM[:ss[.uuuuuu]] format.']
+
>>> p = Person(**dict(valid_params, email='john@example.com'))
>>> p.validate()
{}
@@ -153,5 +171,7 @@ u'john@example.com'
[u'This field is required.']
>>> errors['birthdate']
[u'This field is required.']
+>>> errors['best_time']
+[u'This field is required.']
"""}
diff --git a/tests/regressiontests/maxlength/__init__.py b/tests/regressiontests/admin_registration/__init__.py
index e69de29bb2..e69de29bb2 100644
--- a/tests/regressiontests/maxlength/__init__.py
+++ b/tests/regressiontests/admin_registration/__init__.py
diff --git a/tests/regressiontests/admin_registration/models.py b/tests/regressiontests/admin_registration/models.py
new file mode 100644
index 0000000000..fdfa3691b8
--- /dev/null
+++ b/tests/regressiontests/admin_registration/models.py
@@ -0,0 +1,64 @@
+"""
+Tests for various ways of registering models with the admin site.
+"""
+
+from django.db import models
+from django.contrib import admin
+
+class Person(models.Model):
+ name = models.CharField(max_length=200)
+
+class Place(models.Model):
+ name = models.CharField(max_length=200)
+
+__test__ = {'API_TESTS':"""
+
+
+# Bare registration
+>>> site = admin.AdminSite()
+>>> site.register(Person)
+>>> site._registry[Person]
+<django.contrib.admin.options.ModelAdmin object at ...>
+
+# Registration with a ModelAdmin
+>>> site = admin.AdminSite()
+>>> class NameAdmin(admin.ModelAdmin):
+... list_display = ['name']
+... save_on_top = True
+
+>>> site.register(Person, NameAdmin)
+>>> site._registry[Person]
+<regressiontests.admin_registration.models.NameAdmin object at ...>
+
+# You can't register the same model twice
+>>> site.register(Person)
+Traceback (most recent call last):
+ ...
+AlreadyRegistered: The model Person is already registered
+
+# Registration using **options
+>>> site = admin.AdminSite()
+>>> site.register(Person, search_fields=['name'])
+>>> site._registry[Person].search_fields
+['name']
+
+# With both admin_class and **options the **options override the fields in
+# the admin class.
+>>> site = admin.AdminSite()
+>>> site.register(Person, NameAdmin, search_fields=["name"], list_display=['__str__'])
+>>> site._registry[Person].search_fields
+['name']
+>>> site._registry[Person].list_display
+['__str__']
+>>> site._registry[Person].save_on_top
+True
+
+# You can also register iterables instead of single classes -- a nice shortcut
+>>> site = admin.AdminSite()
+>>> site.register([Person, Place], search_fields=['name'])
+>>> site._registry[Person]
+<django.contrib.admin.sites.PersonAdmin object at ...>
+>>> site._registry[Place]
+<django.contrib.admin.sites.PlaceAdmin object at ...>
+
+"""}
diff --git a/tests/regressiontests/admin_scripts/tests.py b/tests/regressiontests/admin_scripts/tests.py
index 442f357782..f1b167eec7 100644
--- a/tests/regressiontests/admin_scripts/tests.py
+++ b/tests/regressiontests/admin_scripts/tests.py
@@ -1,18 +1,20 @@
"""
-A series of tests to establish that the command-line managment tools work as
+A series of tests to establish that the command-line managment tools work as
advertised - especially with regards to the handling of the DJANGO_SETTINGS_MODULE
and default settings.py files.
"""
import os
import unittest
import shutil
+import sys
+import re
from django import conf, bin, get_version
from django.conf import settings
class AdminScriptTestCase(unittest.TestCase):
def write_settings(self, filename, apps=None):
- test_dir = os.path.dirname(os.path.dirname(__file__))
+ test_dir = os.path.dirname(os.path.dirname(__file__))
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 = [
@@ -27,72 +29,93 @@ class AdminScriptTestCase(unittest.TestCase):
for s in exports:
if hasattr(settings,s):
settings_file.write("%s = '%s'\n" % (s, str(getattr(settings,s))))
-
+
if apps is None:
apps = ['django.contrib.auth', 'django.contrib.contenttypes', 'admin_scripts']
if apps:
settings_file.write("INSTALLED_APPS = %s\n" % apps)
-
+
settings_file.close()
-
+
def remove_settings(self, filename):
- test_dir = os.path.dirname(os.path.dirname(__file__))
- os.remove(os.path.join(test_dir, filename))
- # Also try to remove the pyc file; if it exists, it could
+ test_dir = os.path.dirname(os.path.dirname(__file__))
+ full_name = os.path.join(test_dir, filename)
+ os.remove(full_name)
+
+ # Also try to remove the compiled file; if it exists, it could
# mess up later tests that depend upon the .py file not existing
try:
- os.remove(os.path.join(test_dir, filename + 'c'))
+ if sys.platform.startswith('java'):
+ # Jython produces module$py.class files
+ os.remove(re.sub(r'\.py$', '$py.class', full_name))
+ else:
+ # CPython produces module.pyc files
+ os.remove(full_name + 'c')
except OSError:
pass
-
+
+ def _sys_executable(self):
+ """
+ Returns the command line needed to run a python interpreter, including
+ the options for setting sys.path on Jython, which doesn't recognize
+ PYTHONPATH.
+ """
+ if sys.platform.startswith('java'):
+ return "%s -J-Dpython.path=%s" % \
+ (sys.executable, os.environ['PYTHONPATH'])
+ else:
+ return sys.executable
+
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)
-
- # Build the command line
- cmd = 'python "%s"' % script
- cmd += ''.join([' %s' % arg for arg in args])
-
+
# Remember the old environment
old_django_settings_module = os.environ.get('DJANGO_SETTINGS_MODULE', None)
old_python_path = os.environ.get('PYTHONPATH', None)
old_cwd = os.getcwd()
-
+
# Set the test environment
if settings_file:
os.environ['DJANGO_SETTINGS_MODULE'] = settings_file
elif 'DJANGO_SETTINGS_MODULE' in os.environ:
del os.environ['DJANGO_SETTINGS_MODULE']
-
- os.environ['PYTHONPATH'] = os.pathsep.join([test_dir,base_dir])
+
+ if old_python_path:
+ os.environ['PYTHONPATH'] = os.pathsep.join([test_dir, base_dir, old_python_path])
+ else:
+ os.environ['PYTHONPATH'] = os.pathsep.join([test_dir, base_dir])
+
+ # Build the command line
+ cmd = '%s "%s"' % (self._sys_executable(), script)
+ cmd += ''.join([' %s' % arg for arg in args])
# Move to the test directory and run
os.chdir(test_dir)
stdin, stdout, stderr = os.popen3(cmd)
out, err = stdout.read(), stderr.read()
-
+
# Restore the old environment
if old_django_settings_module:
os.environ['DJANGO_SETTINGS_MODULE'] = old_django_settings_module
if old_python_path:
os.environ['PYTHONPATH'] = old_python_path
-
# Move back to the old working directory
os.chdir(old_cwd)
-
+
return out, err
-
+
def run_django_admin(self, args, settings_file=None):
bin_dir = os.path.dirname(bin.__file__)
return self.run_test(os.path.join(bin_dir,'django-admin.py'), args, settings_file)
-
+
def run_manage(self, args, settings_file=None):
conf_dir = os.path.dirname(conf.__file__)
template_manage_py = os.path.join(conf_dir, 'project_template', 'manage.py')
- test_dir = os.path.dirname(os.path.dirname(__file__))
+ test_dir = os.path.dirname(os.path.dirname(__file__))
test_manage_py = os.path.join(test_dir, 'manage.py')
shutil.copyfile(template_manage_py, test_manage_py)
@@ -100,7 +123,7 @@ class AdminScriptTestCase(unittest.TestCase):
# Cleanup - remove the generated manage.py script
os.remove(test_manage_py)
-
+
return stdout, stderr
def assertNoOutput(self, stream):
@@ -119,14 +142,14 @@ class AdminScriptTestCase(unittest.TestCase):
class DjangoAdminNoSettings(AdminScriptTestCase):
"A series of tests for django-admin.py when there is no settings.py file."
-
+
def test_builtin_command(self):
"no settings: django-admin builtin commands fail with an import error when no settings provided"
args = ['sqlall','admin_scripts']
out, err = self.run_django_admin(args)
self.assertNoOutput(out)
self.assertOutput(err, 'environment variable DJANGO_SETTINGS_MODULE is undefined')
-
+
def test_builtin_with_bad_settings(self):
"no settings: django-admin builtin commands fail if settings file (from argument) doesn't exist"
args = ['sqlall','--settings=bad_settings', 'admin_scripts']
@@ -148,17 +171,17 @@ class DjangoAdminDefaultSettings(AdminScriptTestCase):
"""
def setUp(self):
self.write_settings('settings.py')
-
+
def tearDown(self):
self.remove_settings('settings.py')
-
+
def test_builtin_command(self):
"default: django-admin builtin commands fail with an import error when no settings provided"
args = ['sqlall','admin_scripts']
out, err = self.run_django_admin(args)
self.assertNoOutput(out)
self.assertOutput(err, 'environment variable DJANGO_SETTINGS_MODULE is undefined')
-
+
def test_builtin_with_settings(self):
"default: django-admin builtin commands succeed if settings are provided as argument"
args = ['sqlall','--settings=settings', 'admin_scripts']
@@ -193,14 +216,14 @@ class DjangoAdminDefaultSettings(AdminScriptTestCase):
out, err = self.run_django_admin(args)
self.assertNoOutput(out)
self.assertOutput(err, "Unknown command: 'noargs_command'")
-
+
def test_custom_command_with_settings(self):
"default: django-admin can't execute user commands, even if settings are provided as argument"
args = ['noargs_command', '--settings=settings']
out, err = self.run_django_admin(args)
self.assertNoOutput(out)
self.assertOutput(err, "Unknown command: 'noargs_command'")
-
+
def test_custom_command_with_environment(self):
"default: django-admin can't execute user commands, even if settings are provided in environment"
args = ['noargs_command']
@@ -214,17 +237,17 @@ class DjangoAdminMinimalSettings(AdminScriptTestCase):
"""
def setUp(self):
self.write_settings('settings.py', apps=['django.contrib.auth','django.contrib.contenttypes'])
-
+
def tearDown(self):
self.remove_settings('settings.py')
-
+
def test_builtin_command(self):
"minimal: django-admin builtin commands fail with an import error when no settings provided"
args = ['sqlall','admin_scripts']
out, err = self.run_django_admin(args)
self.assertNoOutput(out)
self.assertOutput(err, 'environment variable DJANGO_SETTINGS_MODULE is undefined')
-
+
def test_builtin_with_settings(self):
"minimal: django-admin builtin commands fail if settings are provided as argument"
args = ['sqlall','--settings=settings', 'admin_scripts']
@@ -252,21 +275,21 @@ class DjangoAdminMinimalSettings(AdminScriptTestCase):
out, err = self.run_django_admin(args,'bad_settings')
self.assertNoOutput(out)
self.assertOutput(err, "Could not import settings 'bad_settings'")
-
+
def test_custom_command(self):
"minimal: django-admin can't execute user commands"
args = ['noargs_command']
out, err = self.run_django_admin(args)
self.assertNoOutput(out)
self.assertOutput(err, "Unknown command: 'noargs_command'")
-
+
def test_custom_command_with_settings(self):
"minimal: django-admin can't execute user commands, even if settings are provided as argument"
args = ['noargs_command', '--settings=settings']
out, err = self.run_django_admin(args)
self.assertNoOutput(out)
self.assertOutput(err, "Unknown command: 'noargs_command'")
-
+
def test_custom_command_with_environment(self):
"minimal: django-admin can't execute user commands, even if settings are provided in environment"
args = ['noargs_command']
@@ -280,10 +303,10 @@ class DjangoAdminAlternateSettings(AdminScriptTestCase):
"""
def setUp(self):
self.write_settings('alternate_settings.py')
-
+
def tearDown(self):
self.remove_settings('alternate_settings.py')
-
+
def test_builtin_command(self):
"alternate: django-admin builtin commands fail with an import error when no settings provided"
args = ['sqlall','admin_scripts']
@@ -344,17 +367,17 @@ class DjangoAdminAlternateSettings(AdminScriptTestCase):
class DjangoAdminMultipleSettings(AdminScriptTestCase):
"""A series of tests for django-admin.py when multiple settings files
(including the default 'settings.py') are available. The default settings
- file is insufficient for performing the operations described, so the
+ file is insufficient for performing the operations described, so the
alternate settings must be used by the running script.
"""
def setUp(self):
self.write_settings('settings.py', apps=['django.contrib.auth','django.contrib.contenttypes'])
self.write_settings('alternate_settings.py')
-
+
def tearDown(self):
self.remove_settings('settings.py')
self.remove_settings('alternate_settings.py')
-
+
def test_builtin_command(self):
"alternate: django-admin builtin commands fail with an import error when no settings provided"
args = ['sqlall','admin_scripts']
@@ -409,7 +432,7 @@ class DjangoAdminMultipleSettings(AdminScriptTestCase):
out, err = self.run_django_admin(args,'alternate_settings')
self.assertNoOutput(out)
self.assertOutput(err, "Unknown command: 'noargs_command'")
-
+
##########################################################################
# MANAGE.PY TESTS
# This next series of test classes checks the environment processing
@@ -418,14 +441,14 @@ class DjangoAdminMultipleSettings(AdminScriptTestCase):
class ManageNoSettings(AdminScriptTestCase):
"A series of tests for manage.py when there is no settings.py file."
-
+
def test_builtin_command(self):
"no settings: manage.py builtin commands fail with an import error when no settings provided"
args = ['sqlall','admin_scripts']
out, err = self.run_manage(args)
self.assertNoOutput(out)
self.assertOutput(err, "Can't find the file 'settings.py' in the directory containing './manage.py'")
-
+
def test_builtin_with_bad_settings(self):
"no settings: manage.py builtin commands fail if settings file (from argument) doesn't exist"
args = ['sqlall','--settings=bad_settings', 'admin_scripts']
@@ -447,17 +470,17 @@ class ManageDefaultSettings(AdminScriptTestCase):
"""
def setUp(self):
self.write_settings('settings.py')
-
+
def tearDown(self):
self.remove_settings('settings.py')
-
+
def test_builtin_command(self):
"default: manage.py builtin commands succeed when default settings are appropriate"
args = ['sqlall','admin_scripts']
out, err = self.run_manage(args)
self.assertNoOutput(err)
self.assertOutput(out, 'CREATE TABLE')
-
+
def test_builtin_with_settings(self):
"default: manage.py builtin commands succeed if settings are provided as argument"
args = ['sqlall','--settings=settings', 'admin_scripts']
@@ -492,14 +515,14 @@ class ManageDefaultSettings(AdminScriptTestCase):
out, err = self.run_manage(args)
self.assertNoOutput(err)
self.assertOutput(out, "EXECUTE:NoArgsCommand")
-
+
def test_custom_command_with_settings(self):
"default: manage.py can execute user commands when settings are provided as argument"
args = ['noargs_command', '--settings=settings']
out, err = self.run_manage(args)
self.assertNoOutput(err)
self.assertOutput(out, "EXECUTE:NoArgsCommand")
-
+
def test_custom_command_with_environment(self):
"default: manage.py can execute user commands when settings are provided in environment"
args = ['noargs_command']
@@ -513,17 +536,17 @@ class ManageMinimalSettings(AdminScriptTestCase):
"""
def setUp(self):
self.write_settings('settings.py', apps=['django.contrib.auth','django.contrib.contenttypes'])
-
+
def tearDown(self):
self.remove_settings('settings.py')
-
+
def test_builtin_command(self):
"minimal: manage.py builtin commands fail with an import error when no settings provided"
args = ['sqlall','admin_scripts']
out, err = self.run_manage(args)
self.assertNoOutput(out)
self.assertOutput(err, 'App with label admin_scripts could not be found')
-
+
def test_builtin_with_settings(self):
"minimal: manage.py builtin commands fail if settings are provided as argument"
args = ['sqlall','--settings=settings', 'admin_scripts']
@@ -551,21 +574,21 @@ class ManageMinimalSettings(AdminScriptTestCase):
out, err = self.run_manage(args,'bad_settings')
self.assertNoOutput(out)
self.assertOutput(err, 'App with label admin_scripts could not be found')
-
+
def test_custom_command(self):
"minimal: manage.py can't execute user commands without appropriate settings"
args = ['noargs_command']
out, err = self.run_manage(args)
self.assertNoOutput(out)
self.assertOutput(err, "Unknown command: 'noargs_command'")
-
+
def test_custom_command_with_settings(self):
"minimal: manage.py can't execute user commands, even if settings are provided as argument"
args = ['noargs_command', '--settings=settings']
out, err = self.run_manage(args)
self.assertNoOutput(out)
self.assertOutput(err, "Unknown command: 'noargs_command'")
-
+
def test_custom_command_with_environment(self):
"minimal: manage.py can't execute user commands, even if settings are provided in environment"
args = ['noargs_command']
@@ -579,10 +602,10 @@ class ManageAlternateSettings(AdminScriptTestCase):
"""
def setUp(self):
self.write_settings('alternate_settings.py')
-
+
def tearDown(self):
self.remove_settings('alternate_settings.py')
-
+
def test_builtin_command(self):
"alternate: manage.py builtin commands fail with an import error when no default settings provided"
args = ['sqlall','admin_scripts']
@@ -643,17 +666,17 @@ class ManageAlternateSettings(AdminScriptTestCase):
class ManageMultipleSettings(AdminScriptTestCase):
"""A series of tests for manage.py when multiple settings files
(including the default 'settings.py') are available. The default settings
- file is insufficient for performing the operations described, so the
+ file is insufficient for performing the operations described, so the
alternate settings must be used by the running script.
"""
def setUp(self):
self.write_settings('settings.py', apps=['django.contrib.auth','django.contrib.contenttypes'])
self.write_settings('alternate_settings.py')
-
+
def tearDown(self):
self.remove_settings('settings.py')
self.remove_settings('alternate_settings.py')
-
+
def test_builtin_command(self):
"multiple: manage.py builtin commands fail with an import error when no settings provided"
args = ['sqlall','admin_scripts']
@@ -721,10 +744,10 @@ class CommandTypes(AdminScriptTestCase):
"Tests for the various types of base command types that can be defined."
def setUp(self):
self.write_settings('settings.py')
-
+
def tearDown(self):
self.remove_settings('settings.py')
-
+
def test_version(self):
"--version is handled as a special case"
args = ['--version']
@@ -737,7 +760,10 @@ class CommandTypes(AdminScriptTestCase):
"--help is handled as a special case"
args = ['--help']
out, err = self.run_manage(args)
- self.assertOutput(out, "Usage: manage.py [options]")
+ if sys.version_info < (2, 5):
+ self.assertOutput(out, "usage: manage.py [options]")
+ else:
+ self.assertOutput(out, "Usage: manage.py [options]")
self.assertOutput(err, "Type 'manage.py help <subcommand>' for help on a specific subcommand.")
def test_specific_help(self):
@@ -746,14 +772,14 @@ class CommandTypes(AdminScriptTestCase):
out, err = self.run_manage(args)
self.assertNoOutput(err)
self.assertOutput(out, "Prints the CREATE TABLE, custom SQL and CREATE INDEX SQL statements for the given model module name(s).")
-
+
def test_base_command(self):
"User BaseCommands can execute when a label is provided"
args = ['base_command','testlabel']
out, err = self.run_manage(args)
self.assertNoOutput(err)
self.assertOutput(out, "EXECUTE:BaseCommand labels=('testlabel',), options=[('option_a', '1'), ('option_b', '2'), ('option_c', '3'), ('pythonpath', None), ('settings', None), ('traceback', None)]")
-
+
def test_base_command_no_label(self):
"User BaseCommands can execute when no labels are provided"
args = ['base_command']
@@ -781,7 +807,7 @@ class CommandTypes(AdminScriptTestCase):
out, err = self.run_manage(args)
self.assertNoOutput(err)
self.assertOutput(out, "EXECUTE:BaseCommand labels=('testlabel',), options=[('option_a', 'x'), ('option_b', 'y'), ('option_c', '3'), ('pythonpath', None), ('settings', None), ('traceback', None)]")
-
+
def test_noargs(self):
"NoArg Commands can be executed"
args = ['noargs_command']
@@ -794,16 +820,16 @@ class CommandTypes(AdminScriptTestCase):
args = ['noargs_command','argument']
out, err = self.run_manage(args)
self.assertOutput(err, "Error: Command doesn't accept any arguments")
-
+
def test_app_command(self):
"User AppCommands can execute when a single app name is provided"
args = ['app_command', 'auth']
out, err = self.run_manage(args)
self.assertNoOutput(err)
self.assertOutput(out, "EXECUTE:AppCommand app=<module 'django.contrib.auth.models'")
- self.assertOutput(out, os.sep.join(['django','contrib','auth','models.py']))
+ self.assertOutput(out, os.sep.join(['django','contrib','auth','models.py']))
self.assertOutput(out, "'>, options=[('pythonpath', None), ('settings', None), ('traceback', None)]")
-
+
def test_app_command_no_apps(self):
"User AppCommands raise an error when no app name is provided"
args = ['app_command']
@@ -816,7 +842,8 @@ class CommandTypes(AdminScriptTestCase):
out, err = self.run_manage(args)
self.assertNoOutput(err)
self.assertOutput(out, "EXECUTE:AppCommand app=<module 'django.contrib.auth.models'")
- self.assertOutput(out, os.sep.join(['django','contrib','auth','models.pyc']) + "'>, options=[('pythonpath', None), ('settings', None), ('traceback', None)]")
+ self.assertOutput(out, os.sep.join(['django','contrib','auth','models.py']))
+ self.assertOutput(out, "'>, options=[('pythonpath', None), ('settings', None), ('traceback', None)]")
self.assertOutput(out, "EXECUTE:AppCommand app=<module 'django.contrib.contenttypes.models'")
self.assertOutput(out, os.sep.join(['django','contrib','contenttypes','models.py']))
self.assertOutput(out, "'>, options=[('pythonpath', None), ('settings', None), ('traceback', None)]")
@@ -826,7 +853,7 @@ class CommandTypes(AdminScriptTestCase):
args = ['app_command', 'NOT_AN_APP']
out, err = self.run_manage(args)
self.assertOutput(err, "App with label NOT_AN_APP could not be found")
-
+
def test_app_command_some_invalid_appnames(self):
"User AppCommands can execute when some of the provided app names are invalid"
args = ['app_command', 'auth', 'NOT_AN_APP']
@@ -839,7 +866,7 @@ class CommandTypes(AdminScriptTestCase):
out, err = self.run_manage(args)
self.assertNoOutput(err)
self.assertOutput(out, "EXECUTE:LabelCommand label=testlabel, options=[('pythonpath', None), ('settings', None), ('traceback', None)]")
-
+
def test_label_command_no_label(self):
"User LabelCommands raise an error if no label is provided"
args = ['label_command']
@@ -859,14 +886,14 @@ class ArgumentOrder(AdminScriptTestCase):
django-admin command arguments are parsed in 2 parts; the core arguments
(--settings, --traceback and --pythonpath) are parsed using a Lax parser.
- This Lax parser ignores any unknown options. Then the full settings are
+ This Lax parser ignores any unknown options. Then the full settings are
passed to the command parser, which extracts commands of interest to the
- individual command.
+ individual command.
"""
def setUp(self):
self.write_settings('settings.py', apps=['django.contrib.auth','django.contrib.contenttypes'])
self.write_settings('alternate_settings.py')
-
+
def tearDown(self):
self.remove_settings('settings.py')
self.remove_settings('alternate_settings.py')
diff --git a/tests/regressiontests/admin_views/tests.py b/tests/regressiontests/admin_views/tests.py
index ad50928aa9..acdf9f5742 100644
--- a/tests/regressiontests/admin_views/tests.py
+++ b/tests/regressiontests/admin_views/tests.py
@@ -102,7 +102,7 @@ class AdminViewPermissionsTest(TestCase):
self.failUnlessEqual(request.status_code, 200)
login = self.client.post('/test_admin/admin/', self.super_login)
self.assertRedirects(login, '/test_admin/admin/')
- self.assertFalse(login.context)
+ self.failIf(login.context)
self.client.get('/test_admin/admin/logout/')
# Test if user enters e-mail address
@@ -124,7 +124,7 @@ class AdminViewPermissionsTest(TestCase):
self.failUnlessEqual(request.status_code, 200)
login = self.client.post('/test_admin/admin/', self.adduser_login)
self.assertRedirects(login, '/test_admin/admin/')
- self.assertFalse(login.context)
+ self.failIf(login.context)
self.client.get('/test_admin/admin/logout/')
# Change User
@@ -132,7 +132,7 @@ class AdminViewPermissionsTest(TestCase):
self.failUnlessEqual(request.status_code, 200)
login = self.client.post('/test_admin/admin/', self.changeuser_login)
self.assertRedirects(login, '/test_admin/admin/')
- self.assertFalse(login.context)
+ self.failIf(login.context)
self.client.get('/test_admin/admin/logout/')
# Delete User
@@ -140,7 +140,7 @@ class AdminViewPermissionsTest(TestCase):
self.failUnlessEqual(request.status_code, 200)
login = self.client.post('/test_admin/admin/', self.deleteuser_login)
self.assertRedirects(login, '/test_admin/admin/')
- self.assertFalse(login.context)
+ self.failIf(login.context)
self.client.get('/test_admin/admin/logout/')
# Regular User should not be able to login.
diff --git a/tests/regressiontests/auth_backends/tests.py b/tests/regressiontests/auth_backends/tests.py
index 3ec2a059ad..d22f0bf939 100644
--- a/tests/regressiontests/auth_backends/tests.py
+++ b/tests/regressiontests/auth_backends/tests.py
@@ -4,7 +4,7 @@ except NameError:
from sets import Set as set # Python 2.3 fallback
__test__ = {'API_TESTS': """
->>> from django.contrib.auth.models import User, Group, Permission
+>>> 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
@@ -69,4 +69,10 @@ True
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/cache/tests.py b/tests/regressiontests/cache/tests.py
index f050348c77..78c32288b6 100644
--- a/tests/regressiontests/cache/tests.py
+++ b/tests/regressiontests/cache/tests.py
@@ -3,11 +3,17 @@
# Unit tests for cache framework
# Uses whatever cache backend is set in the test settings file.
+import os
+import shutil
+import tempfile
import time
import unittest
+
from django.core.cache import cache
-from django.utils.cache import patch_vary_headers
+from django.core.cache.backends.filebased import CacheClass as FileCache
from django.http import HttpResponse
+from django.utils.cache import patch_vary_headers
+from django.utils.hashcompat import md5_constructor
# functions/classes for complex data type tests
def f():
@@ -27,7 +33,7 @@ class Cache(unittest.TestCase):
cache.add("addkey1", "value")
cache.add("addkey1", "newvalue")
self.assertEqual(cache.get("addkey1"), "value")
-
+
def test_non_existent(self):
# get with non-existent keys
self.assertEqual(cache.get("does_not_exist"), None)
@@ -80,9 +86,9 @@ class Cache(unittest.TestCase):
cache.set('expire2', 'very quickly', 1)
cache.set('expire3', 'very quickly', 1)
- time.sleep(2)
+ time.sleep(2)
self.assertEqual(cache.get("expire1"), None)
-
+
cache.add("expire2", "newvalue")
self.assertEqual(cache.get("expire2"), "newvalue")
self.assertEqual(cache.has_key("expire3"), False)
@@ -98,11 +104,6 @@ class Cache(unittest.TestCase):
cache.set(key, value)
self.assertEqual(cache.get(key), value)
-import os
-import md5
-import shutil
-import tempfile
-from django.core.cache.backends.filebased import CacheClass as FileCache
class FileBasedCacheTests(unittest.TestCase):
"""
@@ -112,23 +113,23 @@ class FileBasedCacheTests(unittest.TestCase):
self.dirname = tempfile.mktemp()
os.mkdir(self.dirname)
self.cache = FileCache(self.dirname, {})
-
+
def tearDown(self):
shutil.rmtree(self.dirname)
-
+
def test_hashing(self):
"""Test that keys are hashed into subdirectories correctly"""
self.cache.set("foo", "bar")
- keyhash = md5.new("foo").hexdigest()
+ keyhash = md5_constructor("foo").hexdigest()
keypath = os.path.join(self.dirname, keyhash[:2], keyhash[2:4], keyhash[4:])
self.assert_(os.path.exists(keypath))
-
+
def test_subdirectory_removal(self):
"""
Make sure that the created subdirectories are correctly removed when empty.
"""
self.cache.set("foo", "bar")
- keyhash = md5.new("foo").hexdigest()
+ keyhash = md5_constructor("foo").hexdigest()
keypath = os.path.join(self.dirname, keyhash[:2], keyhash[2:4], keyhash[4:])
self.assert_(os.path.exists(keypath))
@@ -139,9 +140,9 @@ class FileBasedCacheTests(unittest.TestCase):
class CacheUtils(unittest.TestCase):
"""TestCase for django.utils.cache functions."""
-
+
def test_patch_vary_headers(self):
- headers = (
+ headers = (
# Initial vary, new headers, resulting vary.
(None, ('Accept-Encoding',), 'Accept-Encoding'),
('Accept-Encoding', ('accept-encoding',), 'Accept-Encoding'),
diff --git a/tests/regressiontests/maxlength/models.py b/tests/regressiontests/context_processors/__init__.py
index e69de29bb2..e69de29bb2 100644
--- a/tests/regressiontests/maxlength/models.py
+++ b/tests/regressiontests/context_processors/__init__.py
diff --git a/tests/regressiontests/context_processors/models.py b/tests/regressiontests/context_processors/models.py
new file mode 100644
index 0000000000..cde172db68
--- /dev/null
+++ b/tests/regressiontests/context_processors/models.py
@@ -0,0 +1 @@
+# Models file for tests to run.
diff --git a/tests/regressiontests/context_processors/templates/context_processors/request_attrs.html b/tests/regressiontests/context_processors/templates/context_processors/request_attrs.html
new file mode 100644
index 0000000000..3978e9d680
--- /dev/null
+++ b/tests/regressiontests/context_processors/templates/context_processors/request_attrs.html
@@ -0,0 +1,13 @@
+{% if request %}
+Have request
+{% else %}
+No request
+{% endif %}
+
+{% if request.is_secure %}
+Secure
+{% else %}
+Not secure
+{% endif %}
+
+{{ request.path }}
diff --git a/tests/regressiontests/context_processors/tests.py b/tests/regressiontests/context_processors/tests.py
new file mode 100644
index 0000000000..eadd6310b1
--- /dev/null
+++ b/tests/regressiontests/context_processors/tests.py
@@ -0,0 +1,38 @@
+"""
+Tests for Django's bundled context processors.
+"""
+
+from django.conf import settings
+from django.test import TestCase
+
+
+class RequestContextProcessorTests(TestCase):
+ """
+ Tests for the ``django.core.context_processors.request`` processor.
+ """
+
+ urls = 'regressiontests.context_processors.urls'
+
+ def test_request_attributes(self):
+ """
+ Test that the request object is available in the template and that its
+ attributes can't be overridden by GET and POST parameters (#3828).
+ """
+ url = '/request_attrs/'
+ # We should have the request object in the template.
+ response = self.client.get(url)
+ self.assertContains(response, 'Have request')
+ # Test is_secure.
+ response = self.client.get(url)
+ self.assertContains(response, 'Not secure')
+ response = self.client.get(url, {'is_secure': 'blah'})
+ self.assertContains(response, 'Not secure')
+ response = self.client.post(url, {'is_secure': 'blah'})
+ self.assertContains(response, 'Not secure')
+ # Test path.
+ response = self.client.get(url)
+ self.assertContains(response, url)
+ response = self.client.get(url, {'path': '/blah/'})
+ self.assertContains(response, url)
+ response = self.client.post(url, {'path': '/blah/'})
+ self.assertContains(response, url)
diff --git a/tests/regressiontests/context_processors/urls.py b/tests/regressiontests/context_processors/urls.py
new file mode 100644
index 0000000000..7e8ba967c1
--- /dev/null
+++ b/tests/regressiontests/context_processors/urls.py
@@ -0,0 +1,8 @@
+from django.conf.urls.defaults import *
+
+import views
+
+
+urlpatterns = patterns('',
+ (r'^request_attrs/$', views.request_processor),
+)
diff --git a/tests/regressiontests/context_processors/views.py b/tests/regressiontests/context_processors/views.py
new file mode 100644
index 0000000000..66e7132c05
--- /dev/null
+++ b/tests/regressiontests/context_processors/views.py
@@ -0,0 +1,8 @@
+from django.core import context_processors
+from django.shortcuts import render_to_response
+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]))
diff --git a/tests/regressiontests/dispatch/tests/test_dispatcher.py b/tests/regressiontests/dispatch/tests/test_dispatcher.py
index 0bc99a1505..f34173972d 100644
--- a/tests/regressiontests/dispatch/tests/test_dispatcher.py
+++ b/tests/regressiontests/dispatch/tests/test_dispatcher.py
@@ -2,6 +2,18 @@ from django.dispatch.dispatcher import *
from django.dispatch import dispatcher, robust
import unittest
import copy
+import sys
+import gc
+
+if sys.platform.startswith('java'):
+ def garbage_collect():
+ """Run the garbage collector and wait a bit to let it do his work"""
+ import time
+ gc.collect()
+ time.sleep(0.1)
+else:
+ def garbage_collect():
+ gc.collect()
def x(a):
return a
@@ -21,6 +33,7 @@ class DispatcherTests(unittest.TestCase):
def setUp(self):
# track the initial state, since it's possible that others have bleed receivers in
+ garbage_collect()
self.sendersBack = copy.copy(dispatcher.sendersBack)
self.connections = copy.copy(dispatcher.connections)
self.senders = copy.copy(dispatcher.senders)
@@ -86,6 +99,7 @@ class DispatcherTests(unittest.TestCase):
connect(a.a, signal, b)
expected = []
del a
+ garbage_collect()
result = send('this',b, a=b)
self.assertEqual(result, expected)
self.assertEqual(list(getAllReceivers(b,signal)), [])
@@ -101,6 +115,7 @@ class DispatcherTests(unittest.TestCase):
connect(a, signal, b)
expected = []
del a
+ garbage_collect()
result = send('this',b, a=b)
self.assertEqual(result, expected)
self.assertEqual(list(getAllReceivers(b,signal)), [])
@@ -123,6 +138,7 @@ class DispatcherTests(unittest.TestCase):
del a
del b
del result
+ garbage_collect()
self._testIsClean()
def testRobust(self):
@@ -141,4 +157,4 @@ def getSuite():
return unittest.makeSuite(DispatcherTests,'test')
if __name__ == "__main__":
- unittest.main ()
+ unittest.main()
diff --git a/tests/regressiontests/file_uploads/models.py b/tests/regressiontests/file_uploads/models.py
index 2d5607b2a7..3701750afe 100644
--- a/tests/regressiontests/file_uploads/models.py
+++ b/tests/regressiontests/file_uploads/models.py
@@ -1,2 +1,9 @@
-# This file unintentionally left blank.
-# Oops. \ No newline at end of file
+import tempfile
+import os
+from django.db import models
+
+UPLOAD_ROOT = tempfile.mkdtemp()
+UPLOAD_TO = os.path.join(UPLOAD_ROOT, 'test_upload')
+
+class FileModel(models.Model):
+ testfile = models.FileField(upload_to=UPLOAD_TO)
diff --git a/tests/regressiontests/file_uploads/tests.py b/tests/regressiontests/file_uploads/tests.py
index d2b581686f..dd6b7c4181 100644
--- a/tests/regressiontests/file_uploads/tests.py
+++ b/tests/regressiontests/file_uploads/tests.py
@@ -1,8 +1,15 @@
import os
-import sha
-import tempfile
+import errno
+import shutil
+import unittest
+
+from django.core.files import temp as tempfile
+from django.core.files.uploadedfile import SimpleUploadedFile
from django.test import TestCase, client
from django.utils import simplejson
+from django.utils.hashcompat import sha_constructor
+
+from models import FileModel, UPLOAD_ROOT, UPLOAD_TO
class FileUploadTests(TestCase):
def test_simple_upload(self):
@@ -15,7 +22,7 @@ class FileUploadTests(TestCase):
def test_large_upload(self):
tdir = tempfile.gettempdir()
-
+
file1 = tempfile.NamedTemporaryFile(suffix=".file1", dir=tdir)
file1.write('a' * (2 ** 21))
file1.seek(0)
@@ -38,10 +45,10 @@ class FileUploadTests(TestCase):
for key in post_data.keys():
try:
- post_data[key + '_hash'] = sha.new(post_data[key].read()).hexdigest()
+ post_data[key + '_hash'] = sha_constructor(post_data[key].read()).hexdigest()
post_data[key].seek(0)
except AttributeError:
- post_data[key + '_hash'] = sha.new(post_data[key]).hexdigest()
+ post_data[key + '_hash'] = sha_constructor(post_data[key]).hexdigest()
response = self.client.post('/file_uploads/verify/', post_data)
@@ -51,11 +58,11 @@ class FileUploadTests(TestCase):
pass
self.assertEqual(response.status_code, 200)
-
+
def test_dangerous_file_names(self):
"""Uploaded file names should be sanitized before ever reaching the view."""
# This test simulates possible directory traversal attacks by a
- # malicious uploader We have to do some monkeybusiness here to construct
+ # malicious uploader We have to do some monkeybusiness here to construct
# a malicious payload with an invalid file name (containing os.sep or
# os.pardir). This similar to what an attacker would need to do when
# trying such an attack.
@@ -72,7 +79,7 @@ class FileUploadTests(TestCase):
"..\\..\\hax0rd.txt", # Relative path, win-style.
"../..\\hax0rd.txt" # Relative path, mixed.
]
-
+
payload = []
for i, name in enumerate(scary_file_names):
payload.extend([
@@ -86,7 +93,7 @@ class FileUploadTests(TestCase):
'--' + client.BOUNDARY + '--',
'',
])
-
+
payload = "\r\n".join(payload)
r = {
'CONTENT_LENGTH': len(payload),
@@ -102,7 +109,7 @@ class FileUploadTests(TestCase):
for i, name in enumerate(scary_file_names):
got = recieved["file%s" % i]
self.assertEqual(got, "hax0rd.txt")
-
+
def test_filename_overflow(self):
"""File names over 256 characters (dangerous on some platforms) get fixed up."""
name = "%s.txt" % ("f"*500)
@@ -124,26 +131,26 @@ class FileUploadTests(TestCase):
}
got = simplejson.loads(self.client.request(**r).content)
self.assert_(len(got['file']) < 256, "Got a long file name (%s characters)." % len(got['file']))
-
+
def test_custom_upload_handler(self):
- # A small file (under the 5M quota)
+ # A small file (under the 5M quota)
smallfile = tempfile.NamedTemporaryFile()
smallfile.write('a' * (2 ** 21))
# A big file (over the quota)
bigfile = tempfile.NamedTemporaryFile()
bigfile.write('a' * (10 * 2 ** 20))
-
+
# Small file posting should work.
response = self.client.post('/file_uploads/quota/', {'f': open(smallfile.name)})
got = simplejson.loads(response.content)
self.assert_('f' in got)
-
+
# Large files don't go through.
response = self.client.post("/file_uploads/quota/", {'f': open(bigfile.name)})
got = simplejson.loads(response.content)
self.assert_('f' not in got)
-
+
def test_broken_custom_upload_handler(self):
f = tempfile.NamedTemporaryFile()
f.write('a' * (2 ** 21))
@@ -179,3 +186,42 @@ class FileUploadTests(TestCase):
self.assertEqual(got.get('file1'), 1)
self.assertEqual(got.get('file2'), 2)
+
+class DirectoryCreationTests(unittest.TestCase):
+ """
+ Tests for error handling during directory creation
+ via _save_FIELD_file (ticket #6450)
+ """
+ def setUp(self):
+ self.obj = FileModel()
+ if not os.path.isdir(UPLOAD_ROOT):
+ os.makedirs(UPLOAD_ROOT)
+
+ def tearDown(self):
+ os.chmod(UPLOAD_ROOT, 0700)
+ shutil.rmtree(UPLOAD_ROOT)
+
+ def test_readonly_root(self):
+ """Permission errors are not swallowed"""
+ os.chmod(UPLOAD_ROOT, 0500)
+ try:
+ self.obj.save_testfile_file('foo.txt', SimpleUploadedFile('foo.txt', 'x'))
+ except OSError, err:
+ self.assertEquals(err.errno, errno.EACCES)
+ except:
+ self.fail("OSError [Errno %s] not raised" % errno.EACCES)
+
+ def test_not_a_directory(self):
+ """The correct IOError is raised when the upload directory name exists but isn't a directory"""
+ # Create a file with the upload directory name
+ fd = open(UPLOAD_TO, 'w')
+ fd.close()
+ try:
+ self.obj.save_testfile_file('foo.txt', SimpleUploadedFile('foo.txt', 'x'))
+ except IOError, err:
+ # The test needs to be done on a specific string as IOError
+ # is raised even without the patch (just not early enough)
+ self.assertEquals(err.args[0],
+ "%s exists and is not a directory" % UPLOAD_TO)
+ except:
+ self.fail("IOError not raised")
diff --git a/tests/regressiontests/forms/fields.py b/tests/regressiontests/forms/fields.py
index c70ff2dff3..a9aae4f442 100644
--- a/tests/regressiontests/forms/fields.py
+++ b/tests/regressiontests/forms/fields.py
@@ -308,18 +308,18 @@ ValidationError: [u'This field is required.']
Traceback (most recent call last):
...
ValidationError: [u'This field is required.']
->>> f.clean('1')
-Decimal("1")
+>>> f.clean('1') == Decimal("1")
+True
>>> isinstance(f.clean('1'), Decimal)
True
->>> f.clean('23')
-Decimal("23")
->>> f.clean('3.14')
-Decimal("3.14")
->>> f.clean(3.14)
-Decimal("3.14")
->>> f.clean(Decimal('3.14'))
-Decimal("3.14")
+>>> f.clean('23') == Decimal("23")
+True
+>>> f.clean('3.14') == Decimal("3.14")
+True
+>>> f.clean(3.14) == Decimal("3.14")
+True
+>>> f.clean(Decimal('3.14')) == Decimal("3.14")
+True
>>> f.clean('a')
Traceback (most recent call last):
...
@@ -328,12 +328,12 @@ ValidationError: [u'Enter a number.']
Traceback (most recent call last):
...
ValidationError: [u'Enter a number.']
->>> f.clean('1.0 ')
-Decimal("1.0")
->>> f.clean(' 1.0')
-Decimal("1.0")
->>> f.clean(' 1.0 ')
-Decimal("1.0")
+>>> f.clean('1.0 ') == Decimal("1.0")
+True
+>>> f.clean(' 1.0') == Decimal("1.0")
+True
+>>> f.clean(' 1.0 ') == Decimal("1.0")
+True
>>> f.clean('1.0a')
Traceback (most recent call last):
...
@@ -350,18 +350,18 @@ ValidationError: [u'Ensure that there are no more than 2 decimal places.']
Traceback (most recent call last):
...
ValidationError: [u'Ensure that there are no more than 2 digits before the decimal point.']
->>> f.clean('-12.34')
-Decimal("-12.34")
+>>> f.clean('-12.34') == Decimal("-12.34")
+True
>>> f.clean('-123.45')
Traceback (most recent call last):
...
ValidationError: [u'Ensure that there are no more than 4 digits in total.']
->>> f.clean('-.12')
-Decimal("-0.12")
->>> f.clean('-00.12')
-Decimal("-0.12")
->>> f.clean('-000.12')
-Decimal("-0.12")
+>>> f.clean('-.12') == Decimal("-0.12")
+True
+>>> f.clean('-00.12') == Decimal("-0.12")
+True
+>>> f.clean('-000.12') == Decimal("-0.12")
+True
>>> f.clean('-000.123')
Traceback (most recent call last):
...
@@ -380,8 +380,8 @@ ValidationError: [u'Enter a number.']
>>> f.clean(None)
->>> f.clean('1')
-Decimal("1")
+>>> f.clean('1') == Decimal("1")
+True
DecimalField accepts min_value and max_value just like IntegerField:
>>> f = DecimalField(max_digits=4, decimal_places=2, max_value=Decimal('1.5'), min_value=Decimal('0.5'))
@@ -394,14 +394,14 @@ ValidationError: [u'Ensure this value is less than or equal to 1.5.']
Traceback (most recent call last):
...
ValidationError: [u'Ensure this value is greater than or equal to 0.5.']
->>> f.clean('1.5')
-Decimal("1.5")
->>> f.clean('0.5')
-Decimal("0.5")
->>> f.clean('.5')
-Decimal("0.5")
->>> f.clean('00.50')
-Decimal("0.50")
+>>> f.clean('1.5') == Decimal("1.5")
+True
+>>> f.clean('0.5') == Decimal("0.5")
+True
+>>> f.clean('.5') == Decimal("0.5")
+True
+>>> f.clean('00.50') == Decimal("0.50")
+True
# DateField ###################################################################
@@ -802,6 +802,9 @@ ValidationError: [u'The submitted file is empty.']
>>> type(f.clean(SimpleUploadedFile('name', 'Some File Content')))
<class 'django.core.files.uploadedfile.SimpleUploadedFile'>
+>>> type(f.clean(SimpleUploadedFile('我隻氣墊船裝滿晒鱔.txt', 'मेरी मँडराने वाली नाव सर्पमीनों से भरी ह')))
+<class 'django.core.files.uploadedfile.SimpleUploadedFile'>
+
>>> type(f.clean(SimpleUploadedFile('name', 'Some File Content'), 'files/test4.pdf'))
<class 'django.core.files.uploadedfile.SimpleUploadedFile'>
@@ -817,15 +820,15 @@ Traceback (most recent call last):
...
ValidationError: [u'This field is required.']
>>> f.clean('http://localhost')
-u'http://localhost'
+u'http://localhost/'
>>> f.clean('http://example.com')
-u'http://example.com'
+u'http://example.com/'
>>> f.clean('http://www.example.com')
-u'http://www.example.com'
+u'http://www.example.com/'
>>> f.clean('http://www.example.com:8000/test')
u'http://www.example.com:8000/test'
>>> f.clean('http://200.8.9.10')
-u'http://200.8.9.10'
+u'http://200.8.9.10/'
>>> f.clean('http://200.8.9.10:8000/test')
u'http://200.8.9.10:8000/test'
>>> f.clean('foo')
@@ -855,9 +858,9 @@ u''
>>> f.clean(None)
u''
>>> f.clean('http://example.com')
-u'http://example.com'
+u'http://example.com/'
>>> f.clean('http://www.example.com')
-u'http://www.example.com'
+u'http://www.example.com/'
>>> f.clean('foo')
Traceback (most recent call last):
...
@@ -883,7 +886,7 @@ URLField takes an optional verify_exists parameter, which is False by default.
This verifies that the URL is live on the Internet and doesn't return a 404 or 500:
>>> f = URLField(verify_exists=True)
>>> f.clean('http://www.google.com') # This will fail if there's no Internet connection
-u'http://www.google.com'
+u'http://www.google.com/'
>>> f.clean('http://example')
Traceback (most recent call last):
...
@@ -900,29 +903,38 @@ ValidationError: [u'This URL appears to be a broken link.']
>>> f.clean('')
u''
>>> f.clean('http://www.google.com') # This will fail if there's no Internet connection
-u'http://www.google.com'
+u'http://www.google.com/'
URLField also access min_length and max_length parameters, for convenience.
>>> f = URLField(min_length=15, max_length=20)
>>> f.clean('http://f.com')
Traceback (most recent call last):
...
-ValidationError: [u'Ensure this value has at least 15 characters (it has 12).']
+ValidationError: [u'Ensure this value has at least 15 characters (it has 13).']
>>> f.clean('http://example.com')
-u'http://example.com'
+u'http://example.com/'
>>> f.clean('http://abcdefghijklmnopqrstuvwxyz.com')
Traceback (most recent call last):
...
-ValidationError: [u'Ensure this value has at most 20 characters (it has 37).']
+ValidationError: [u'Ensure this value has at most 20 characters (it has 38).']
URLField should prepend 'http://' if no scheme was given
>>> f = URLField(required=False)
>>> f.clean('example.com')
-u'http://example.com'
+u'http://example.com/'
>>> f.clean('')
u''
>>> f.clean('https://example.com')
-u'https://example.com'
+u'https://example.com/'
+
+URLField should append '/' if no path was given
+>>> f = URLField()
+>>> f.clean('http://example.com')
+u'http://example.com/'
+
+URLField shouldn't change the path if it was given
+>>> f.clean('http://example.com/test')
+u'http://example.com/test'
# BooleanField ################################################################
diff --git a/tests/regressiontests/forms/forms.py b/tests/regressiontests/forms/forms.py
index 6e6e4f79bf..d834bdaccc 100644
--- a/tests/regressiontests/forms/forms.py
+++ b/tests/regressiontests/forms/forms.py
@@ -1480,6 +1480,10 @@ not request.POST.
>>> f.is_valid()
True
+>>> f = FileForm(data={}, files={'file1': SimpleUploadedFile('我隻氣墊船裝滿晒鱔.txt', 'मेरी मँडराने वाली नाव सर्पमीनों से भरी ह')}, auto_id=False)
+>>> print f
+<tr><th>File1:</th><td><input type="file" name="file1" /></td></tr>
+
# Basic form processing in a view #############################################
>>> from django.template import Template, Context
diff --git a/tests/regressiontests/forms/formsets.py b/tests/regressiontests/forms/formsets.py
index bbbd4cee5a..4fd0c17032 100644
--- a/tests/regressiontests/forms/formsets.py
+++ b/tests/regressiontests/forms/formsets.py
@@ -20,7 +20,7 @@ but we'll look at how to do so later.
>>> formset = ChoiceFormSet(auto_id=False, prefix='choices')
>>> print formset
-<input type="hidden" name="choices-TOTAL_FORMS" value="1" /><input type="hidden" name="choices-INITIAL_FORMS" value="0" /><input type="hidden" name="choices-MAX_FORMS" value="0" />
+<input type="hidden" name="choices-TOTAL_FORMS" value="1" /><input type="hidden" name="choices-INITIAL_FORMS" value="0" />
<tr><th>Choice:</th><td><input type="text" name="choices-0-choice" /></td></tr>
<tr><th>Votes:</th><td><input type="text" name="choices-0-votes" /></td></tr>
@@ -34,7 +34,6 @@ the TOTAL_FORMS field appropriately.
>>> data = {
... 'choices-TOTAL_FORMS': '1', # the number of forms rendered
... 'choices-INITIAL_FORMS': '0', # the number of forms with initial data
-... 'choices-MAX_FORMS': '0', # the max number of forms
... 'choices-0-choice': 'Calexico',
... 'choices-0-votes': '100',
... }
@@ -61,7 +60,6 @@ any of the forms.
>>> data = {
... 'choices-TOTAL_FORMS': '1', # the number of forms rendered
... 'choices-INITIAL_FORMS': '0', # the number of forms with initial data
-... 'choices-MAX_FORMS': '0', # the max number of forms
... 'choices-0-choice': 'Calexico',
... 'choices-0-votes': '',
... }
@@ -92,7 +90,6 @@ Let's simulate what would happen if we submitted this form.
>>> data = {
... 'choices-TOTAL_FORMS': '2', # the number of forms rendered
... 'choices-INITIAL_FORMS': '1', # the number of forms with initial data
-... 'choices-MAX_FORMS': '0', # the max number of forms
... 'choices-0-choice': 'Calexico',
... 'choices-0-votes': '100',
... 'choices-1-choice': '',
@@ -114,7 +111,6 @@ handle that later.
>>> data = {
... 'choices-TOTAL_FORMS': '2', # the number of forms rendered
... 'choices-INITIAL_FORMS': '1', # the number of forms with initial data
-... 'choices-MAX_FORMS': '0', # the max number of forms
... 'choices-0-choice': 'Calexico',
... 'choices-0-votes': '100',
... 'choices-1-choice': 'The Decemberists',
@@ -134,7 +130,6 @@ handle that case later.
>>> data = {
... 'choices-TOTAL_FORMS': '2', # the number of forms rendered
... 'choices-INITIAL_FORMS': '1', # the number of forms with initial data
-... 'choices-MAX_FORMS': '0', # the max number of forms
... 'choices-0-choice': '', # deleted value
... 'choices-0-votes': '', # deleted value
... 'choices-1-choice': '',
@@ -172,7 +167,6 @@ number of forms to be completed.
>>> data = {
... 'choices-TOTAL_FORMS': '3', # the number of forms rendered
... 'choices-INITIAL_FORMS': '0', # the number of forms with initial data
-... 'choices-MAX_FORMS': '0', # the max number of forms
... 'choices-0-choice': '',
... 'choices-0-votes': '',
... 'choices-1-choice': '',
@@ -193,7 +187,6 @@ We can just fill out one of the forms.
>>> data = {
... 'choices-TOTAL_FORMS': '3', # the number of forms rendered
... 'choices-INITIAL_FORMS': '0', # the number of forms with initial data
-... 'choices-MAX_FORMS': '0', # the max number of forms
... 'choices-0-choice': 'Calexico',
... 'choices-0-votes': '100',
... 'choices-1-choice': '',
@@ -214,7 +207,6 @@ And once again, if we try to partially complete a form, validation will fail.
>>> data = {
... 'choices-TOTAL_FORMS': '3', # the number of forms rendered
... 'choices-INITIAL_FORMS': '0', # the number of forms with initial data
-... 'choices-MAX_FORMS': '0', # the max number of forms
... 'choices-0-choice': 'Calexico',
... 'choices-0-votes': '100',
... 'choices-1-choice': 'The Decemberists',
@@ -275,7 +267,6 @@ To delete something, we just need to set that form's special delete field to
>>> data = {
... 'choices-TOTAL_FORMS': '3', # the number of forms rendered
... 'choices-INITIAL_FORMS': '2', # the number of forms with initial data
-... 'choices-MAX_FORMS': '0', # the max number of forms
... 'choices-0-choice': 'Calexico',
... 'choices-0-votes': '100',
... 'choices-0-DELETE': '',
@@ -325,7 +316,6 @@ something at the front of the list, you'd need to set it's order to 0.
>>> data = {
... 'choices-TOTAL_FORMS': '3', # the number of forms rendered
... 'choices-INITIAL_FORMS': '2', # the number of forms with initial data
-... 'choices-MAX_FORMS': '0', # the max number of forms
... 'choices-0-choice': 'Calexico',
... 'choices-0-votes': '100',
... 'choices-0-ORDER': '1',
@@ -352,7 +342,6 @@ they will be sorted below everything else.
>>> data = {
... 'choices-TOTAL_FORMS': '4', # the number of forms rendered
... 'choices-INITIAL_FORMS': '3', # the number of forms with initial data
-... 'choices-MAX_FORMS': '0', # the max number of forms
... 'choices-0-choice': 'Calexico',
... 'choices-0-votes': '100',
... 'choices-0-ORDER': '1',
@@ -414,7 +403,6 @@ Let's delete Fergie, and put The Decemberists ahead of Calexico.
>>> data = {
... 'choices-TOTAL_FORMS': '4', # the number of forms rendered
... 'choices-INITIAL_FORMS': '3', # the number of forms with initial data
-... 'choices-MAX_FORMS': '0', # the max number of forms
... 'choices-0-choice': 'Calexico',
... 'choices-0-votes': '100',
... 'choices-0-ORDER': '1',
@@ -473,7 +461,6 @@ We start out with a some duplicate data.
>>> data = {
... 'drinks-TOTAL_FORMS': '2', # the number of forms rendered
... 'drinks-INITIAL_FORMS': '0', # the number of forms with initial data
-... 'drinks-MAX_FORMS': '0', # the max number of forms
... 'drinks-0-name': 'Gin and Tonic',
... 'drinks-1-name': 'Gin and Tonic',
... }
@@ -495,7 +482,6 @@ Make sure we didn't break the valid case.
>>> data = {
... 'drinks-TOTAL_FORMS': '2', # the number of forms rendered
... 'drinks-INITIAL_FORMS': '0', # the number of forms with initial data
-... 'drinks-MAX_FORMS': '0', # the max number of forms
... 'drinks-0-name': 'Gin and Tonic',
... 'drinks-1-name': 'Bloody Mary',
... }
diff --git a/tests/regressiontests/forms/localflavor/at.py b/tests/regressiontests/forms/localflavor/at.py
new file mode 100644
index 0000000000..54ca46898e
--- /dev/null
+++ b/tests/regressiontests/forms/localflavor/at.py
@@ -0,0 +1,80 @@
+# -*- coding: utf-8 -*-
+# Tests for the contrib/localflavor/ AT form fields.
+
+tests = r"""
+# ATZipCodeField ###########################################################
+
+>>> from django.contrib.localflavor.at.forms import ATZipCodeField
+>>> f = ATZipCodeField()
+>>> f.clean('1150')
+u'1150'
+>>> f.clean('4020')
+u'4020'
+>>> f.clean('8020')
+u'8020'
+>>> f.clean('111222')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a zip code in the format XXXX.']
+>>> f.clean('eeffee')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a zip code in the format XXXX.']
+>>> f.clean(u'')
+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.']
+>>> f.clean('')
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+
+
+>>> f = ATZipCodeField(required=False)
+>>> f.clean('1150')
+u'1150'
+>>> f.clean('4020')
+u'4020'
+>>> f.clean('8020')
+u'8020'
+>>> f.clean('111222')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a zip code in the format XXXX.']
+>>> f.clean('eeffee')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a zip code in the format XXXX.']
+>>> f.clean(None)
+u''
+>>> f.clean('')
+u''
+>>> f.clean(u'')
+u''
+
+# ATStateSelect ##################################################################
+
+>>> from django.contrib.localflavor.at.forms import ATStateSelect
+>>> f = ATStateSelect()
+>>> f.render('bundesland', 'WI')
+u'<select name="bundesland">\n<option value="BL">Burgenland</option>\n<option value="KA">Carinthia</option>\n<option value="NO">Lower Austria</option>\n<option value="OO">Upper Austria</option>\n<option value="SA">Salzburg</option>\n<option value="ST">Styria</option>\n<option value="TI">Tyrol</option>\n<option value="VO">Vorarlberg</option>\n<option value="WI" selected="selected">Vienna</option>\n</select>'
+
+# ATSocialSecurityNumberField ################################################
+
+>>> from django.contrib.localflavor.at.forms import ATSocialSecurityNumberField
+>>> f = ATSocialSecurityNumberField()
+>>> f.clean('1237 010180')
+u'1237 010180'
+>>> f.clean('1237 010181')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid Austrian Social Security Number in XXXX XXXXXX format.']
+>>> f.clean('12370 010180')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid Austrian Social Security Number in XXXX XXXXXX format.']
+"""
diff --git a/tests/regressiontests/forms/localflavor/ro.py b/tests/regressiontests/forms/localflavor/ro.py
new file mode 100644
index 0000000000..e885030029
--- /dev/null
+++ b/tests/regressiontests/forms/localflavor/ro.py
@@ -0,0 +1,175 @@
+# -*- coding: utf-8 -*-
+# Tests for the contrib/localflavor/ RO form fields.
+
+tests = r"""
+>>> from django.contrib.localflavor.ro.forms import *
+
+##ROCIFField ################################################################
+
+f = ROCIFField()
+f.clean('21694681')
+u'21694681'
+f.clean('RO21694681')
+u'21694681'
+f.clean('21694680')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid CIF']
+f.clean('21694680000')
+Traceback (most recent call last):
+...
+ValidationError: [u'Ensure this value has at most 10 characters (it has 11).']
+f.clean('0')
+Traceback (most recent call last):
+...
+ValidationError: [u'Ensure this value has at least 2 characters (it has 1).']
+f.clean(None)
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+f.clean('')
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+
+##ROCNPField #################################################################
+
+f = ROCNPField()
+f.clean('1981211204489')
+u'1981211204489'
+f.clean('1981211204487')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid CNP']
+f.clean('1981232204489')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid CNP']
+f.clean('9981211204489')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid CNP']
+f.clean('9981211209')
+Traceback (most recent call last):
+...
+ValidationError: [u'Ensure this value has at least 13 characters (it has 10).']
+f.clean('19812112044891')
+Traceback (most recent call last):
+...
+ValidationError: [u'Ensure this value has at most 13 characters (it has 14).']
+f.clean('')
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+
+##ROCountyField ##############################################################
+
+f = ROCountyField()
+f.clean('CJ')
+'CJ'
+f.clean('cj')
+'CJ'
+f.clean('Argeş')
+'AG'
+f.clean('argeş')
+'AG'
+f.clean('Arges')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a Romanian county code or name.']
+f.clean('')
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+
+##ROCountySelect #############################################################
+
+f = ROCountySelect()
+f.render('county','CJ')
+u'<select name="county">\n<option value="AB">Alba</option>\n<option value="AR">A
+rad</option>\n<option value="AG">Arge\u015f</option>\n<option value="BC">Bac\u01
+03u</option>\n<option value="BH">Bihor</option>\n<option value="BN">Bistri\u0163
+a-N\u0103s\u0103ud</option>\n<option value="BT">Boto\u015fani</option>\n<option
+value="BV">Bra\u015fov</option>\n<option value="BR">Br\u0103ila</option>\n<optio
+n value="B">Bucure\u015fti</option>\n<option value="BZ">Buz\u0103u</option>\n<op
+tion value="CS">Cara\u015f-Severin</option>\n<option value="CL">C\u0103l\u0103ra
+\u015fi</option>\n<option value="CJ" selected="selected">Cluj</option>\n<option
+value="CT">Constan\u0163a</option>\n<option value="CV">Covasna</option>\n<option
+ value="DB">D\xe2mbovi\u0163a</option>\n<option value="DJ">Dolj</option>\n<optio
+n value="GL">Gala\u0163i</option>\n<option value="GR">Giurgiu</option>\n<option
+value="GJ">Gorj</option>\n<option value="HR">Harghita</option>\n<option value="H
+D">Hunedoara</option>\n<option value="IL">Ialomi\u0163a</option>\n<option value=
+"IS">Ia\u015fi</option>\n<option value="IF">Ilfov</option>\n<option value="MM">M
+aramure\u015f</option>\n<option value="MH">Mehedin\u0163i</option>\n<option valu
+e="MS">Mure\u015f</option>\n<option value="NT">Neam\u0163</option>\n<option valu
+e="OT">Olt</option>\n<option value="PH">Prahova</option>\n<option value="SM">Sat
+u Mare</option>\n<option value="SJ">S\u0103laj</option>\n<option value="SB">Sibi
+u</option>\n<option value="SV">Suceava</option>\n<option value="TR">Teleorman</o
+ption>\n<option value="TM">Timi\u015f</option>\n<option value="TL">Tulcea</optio
+n>\n<option value="VS">Vaslui</option>\n<option value="VL">V\xe2lcea</option>\n<
+option value="VN">Vrancea</option>\n</select>'
+
+##ROIBANField #################################################################
+
+f = ROIBANField()
+f.clean('RO56RZBR0000060003291177')
+u'RO56RZBR0000060003291177'
+f.clean('RO56RZBR0000060003291176')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid IBAN in ROXX-XXXX-XXXX-XXXX-XXXX-XXXX format']
+
+f.clean('RO56-RZBR-0000-0600-0329-1177')
+u'RO56RZBR0000060003291177'
+f.clean('AT61 1904 3002 3457 3201')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid IBAN in ROXX-XXXX-XXXX-XXXX-XXXX-XXXX format']
+
+f.clean('RO56RZBR000006000329117')
+Traceback (most recent call last):
+...
+ValidationError: [u'Ensure this value has at least 24 characters (it has 23).']
+f.clean('')
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+
+##ROPhoneNumberField ##########################################################
+
+f = ROPhoneNumberField()
+f.clean('0264485936')
+u'0264485936'
+f.clean('(0264)-485936')
+u'0264485936'
+f.clean('02644859368')
+Traceback (most recent call last):
+...
+ValidationError: [u'Phone numbers must be in XXXX-XXXXXX format.']
+f.clean('026448593')
+Traceback (most recent call last):
+...
+ValidationError: [u'Ensure this value has at least 10 characters (it has 9).']
+f.clean(None)
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+
+##ROPostalCodeField ###########################################################
+
+f = ROPostalCodeField()
+f.clean('400473')
+u'400473'
+f.clean('40047')
+Traceback (most recent call last):
+...
+ValidationError: [u'Ensure this value has at least 6 characters (it has 5).']
+f.clean('4004731')
+Traceback (most recent call last):
+...
+ValidationError: [u'Ensure this value has at most 6 characters (it has 7).']
+f.clean('')
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+"""
diff --git a/tests/regressiontests/forms/models.py b/tests/regressiontests/forms/models.py
index 98b9233d80..a6baa79811 100644
--- a/tests/regressiontests/forms/models.py
+++ b/tests/regressiontests/forms/models.py
@@ -1,6 +1,10 @@
+# -*- coding: utf-8 -*-
import datetime
from django.db import models
+# Can't import as "forms" due to implementation details in the test suite (the
+# current file is called "forms" and is already imported).
+from django import forms as django_forms
class BoundaryModel(models.Model):
positive_integer = models.PositiveIntegerField(null=True, blank=True)
@@ -14,8 +18,23 @@ class ChoiceModel(models.Model):
"""For ModelChoiceField and ModelMultipleChoiceField tests."""
name = models.CharField(max_length=10)
+class FileModel(models.Model):
+ file = models.FileField(upload_to='/')
+
+class FileForm(django_forms.Form):
+ file1 = django_forms.FileField()
+
__test__ = {'API_TESTS': """
>>> from django.forms import form_for_model, form_for_instance
+>>> from django.core.files.uploadedfile import SimpleUploadedFile
+
+# FileModel with unicode filename and data #########################
+>>> f = FileForm(data={}, files={'file1': SimpleUploadedFile('我隻氣墊船裝滿晒鱔.txt', 'मेरी मँडराने वाली नाव सर्पमीनों से भरी ह')}, auto_id=False)
+>>> f.is_valid()
+True
+>>> f.cleaned_data
+{'file1': <SimpleUploadedFile: 我隻氣墊船裝滿晒鱔.txt (text/plain)>}
+>>> m = FileModel.objects.create(file=f.cleaned_data['file1'])
# Boundary conditions on a PostitiveIntegerField #########################
>>> BoundaryForm = form_for_model(BoundaryModel)
diff --git a/tests/regressiontests/forms/tests.py b/tests/regressiontests/forms/tests.py
index ff8213c8d9..6a8b017f44 100644
--- a/tests/regressiontests/forms/tests.py
+++ b/tests/regressiontests/forms/tests.py
@@ -4,6 +4,7 @@ from fields import tests as fields_tests
from forms import tests as form_tests
from error_messages import tests as custom_error_message_tests
from localflavor.ar import tests as localflavor_ar_tests
+from localflavor.at import tests as localflavor_at_tests
from localflavor.au import tests as localflavor_au_tests
from localflavor.br import tests as localflavor_br_tests
from localflavor.ca import tests as localflavor_ca_tests
@@ -19,6 +20,7 @@ from localflavor.it import tests as localflavor_it_tests
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.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
@@ -35,6 +37,7 @@ __test__ = {
'form_tests': form_tests,
'custom_error_message_tests': custom_error_message_tests,
'localflavor_ar_tests': localflavor_ar_tests,
+ 'localflavor_at_tests': localflavor_at_tests,
'localflavor_au_tests': localflavor_au_tests,
'localflavor_br_tests': localflavor_br_tests,
'localflavor_ca_tests': localflavor_ca_tests,
@@ -50,6 +53,7 @@ __test__ = {
'localflavor_jp_tests': localflavor_jp_tests,
'localflavor_nl_tests': localflavor_nl_tests,
'localflavor_pl_tests': localflavor_pl_tests,
+ 'localflavor_ro_tests': localflavor_ro_tests,
'localflavor_sk_tests': localflavor_sk_tests,
'localflavor_uk_tests': localflavor_uk_tests,
'localflavor_us_tests': localflavor_us_tests,
diff --git a/tests/regressiontests/m2m_through_regress/__init__.py b/tests/regressiontests/m2m_through_regress/__init__.py
new file mode 100644
index 0000000000..139597f9cb
--- /dev/null
+++ b/tests/regressiontests/m2m_through_regress/__init__.py
@@ -0,0 +1,2 @@
+
+
diff --git a/tests/regressiontests/m2m_through_regress/models.py b/tests/regressiontests/m2m_through_regress/models.py
new file mode 100644
index 0000000000..16d2c4ec1b
--- /dev/null
+++ b/tests/regressiontests/m2m_through_regress/models.py
@@ -0,0 +1,204 @@
+from django.db import models
+from datetime import datetime
+from django.contrib.auth.models import User
+
+# Forward declared intermediate model
+class Membership(models.Model):
+ person = models.ForeignKey('Person')
+ group = models.ForeignKey('Group')
+ date_joined = models.DateTimeField(default=datetime.now)
+
+ def __unicode__(self):
+ return "%s is a member of %s" % (self.person.name, self.group.name)
+
+class UserMembership(models.Model):
+ user = models.ForeignKey(User)
+ group = models.ForeignKey('Group')
+ date_joined = models.DateTimeField(default=datetime.now)
+
+ def __unicode__(self):
+ return "%s is a user and member of %s" % (self.user.username, self.group.name)
+
+class Person(models.Model):
+ name = models.CharField(max_length=128)
+
+ def __unicode__(self):
+ return self.name
+
+class Group(models.Model):
+ name = models.CharField(max_length=128)
+ # Membership object defined as a class
+ members = models.ManyToManyField(Person, through=Membership)
+ user_members = models.ManyToManyField(User, through='UserMembership')
+
+ def __unicode__(self):
+ return self.name
+
+__test__ = {'API_TESTS':"""
+# Create some dummy data
+>>> bob = Person.objects.create(name='Bob')
+>>> jim = Person.objects.create(name='Jim')
+
+>>> rock = Group.objects.create(name='Rock')
+>>> roll = Group.objects.create(name='Roll')
+
+>>> frank = User.objects.create_user('frank','frank@example.com','password')
+>>> jane = User.objects.create_user('jane','jane@example.com','password')
+
+# Now test that the forward declared Membership works
+>>> Membership.objects.create(person=bob, group=rock)
+<Membership: Bob is a member of Rock>
+
+>>> Membership.objects.create(person=bob, group=roll)
+<Membership: Bob is a member of Roll>
+
+>>> Membership.objects.create(person=jim, group=rock)
+<Membership: Jim is a member of Rock>
+
+>>> bob.group_set.all()
+[<Group: Rock>, <Group: Roll>]
+
+>>> roll.members.all()
+[<Person: Bob>]
+
+# Error messages use the model name, not repr of the class name
+>>> 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.
+
+>>> roll.members = []
+Traceback (most recent call last):
+...
+AttributeError: Cannot set values on a ManyToManyField which specifies an intermediary model. Use 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.
+
+>>> 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.
+
+# Now test that the intermediate with a relationship outside
+# the current app (i.e., UserMembership) workds
+>>> UserMembership.objects.create(user=frank, group=rock)
+<UserMembership: frank is a user and member of Rock>
+
+>>> UserMembership.objects.create(user=frank, group=roll)
+<UserMembership: frank is a user and member of Roll>
+
+>>> UserMembership.objects.create(user=jane, group=rock)
+<UserMembership: jane is a user and member of Rock>
+
+>>> frank.group_set.all()
+[<Group: Rock>, <Group: Roll>]
+
+>>> roll.user_members.all()
+[<User: frank>]
+
+"""}
+from django.db import models
+from datetime import datetime
+from django.contrib.auth.models import User
+
+# Forward declared intermediate model
+class Membership(models.Model):
+ person = models.ForeignKey('Person')
+ group = models.ForeignKey('Group')
+ date_joined = models.DateTimeField(default=datetime.now)
+
+ def __unicode__(self):
+ return "%s is a member of %s" % (self.person.name, self.group.name)
+
+class UserMembership(models.Model):
+ user = models.ForeignKey(User)
+ group = models.ForeignKey('Group')
+ date_joined = models.DateTimeField(default=datetime.now)
+
+ def __unicode__(self):
+ return "%s is a user and member of %s" % (self.user.username, self.group.name)
+
+class Person(models.Model):
+ name = models.CharField(max_length=128)
+
+ def __unicode__(self):
+ return self.name
+
+class Group(models.Model):
+ name = models.CharField(max_length=128)
+ # Membership object defined as a class
+ members = models.ManyToManyField(Person, through=Membership)
+ user_members = models.ManyToManyField(User, through='UserMembership')
+
+ def __unicode__(self):
+ return self.name
+
+__test__ = {'API_TESTS':"""
+# Create some dummy data
+>>> bob = Person.objects.create(name='Bob')
+>>> jim = Person.objects.create(name='Jim')
+
+>>> rock = Group.objects.create(name='Rock')
+>>> roll = Group.objects.create(name='Roll')
+
+>>> frank = User.objects.create_user('frank','frank@example.com','password')
+>>> jane = User.objects.create_user('jane','jane@example.com','password')
+
+# Now test that the forward declared Membership works
+>>> Membership.objects.create(person=bob, group=rock)
+<Membership: Bob is a member of Rock>
+
+>>> Membership.objects.create(person=bob, group=roll)
+<Membership: Bob is a member of Roll>
+
+>>> Membership.objects.create(person=jim, group=rock)
+<Membership: Jim is a member of Rock>
+
+>>> bob.group_set.all()
+[<Group: Rock>, <Group: Roll>]
+
+>>> roll.members.all()
+[<Person: Bob>]
+
+# Error messages use the model name, not repr of the class name
+>>> 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.
+
+>>> roll.members = []
+Traceback (most recent call last):
+...
+AttributeError: Cannot set values on a ManyToManyField which specifies an intermediary model. Use 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.
+
+>>> 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.
+
+# Now test that the intermediate with a relationship outside
+# the current app (i.e., UserMembership) workds
+>>> UserMembership.objects.create(user=frank, group=rock)
+<UserMembership: frank is a user and member of Rock>
+
+>>> UserMembership.objects.create(user=frank, group=roll)
+<UserMembership: frank is a user and member of Roll>
+
+>>> UserMembership.objects.create(user=jane, group=rock)
+<UserMembership: jane is a user and member of Rock>
+
+>>> frank.group_set.all()
+[<Group: Rock>, <Group: Roll>]
+
+>>> roll.user_members.all()
+[<User: frank>]
+
+"""} \ No newline at end of file
diff --git a/tests/regressiontests/mail/tests.py b/tests/regressiontests/mail/tests.py
index 9d2e2abe96..be59234342 100644
--- a/tests/regressiontests/mail/tests.py
+++ b/tests/regressiontests/mail/tests.py
@@ -3,6 +3,7 @@ r"""
# Tests for the django.core.mail.
>>> from django.core.mail import EmailMessage
+>>> from django.utils.translation import ugettext_lazy
# Test normal ascii character case:
@@ -36,6 +37,12 @@ r"""
>>> message = email.message()
Traceback (most recent call last):
...
-BadHeaderError: Header values can't contain newlines (got 'Subject\nInjection Test' for header 'Subject')
+BadHeaderError: Header values can't contain newlines (got u'Subject\nInjection Test' for header 'Subject')
+
+>>> email = EmailMessage(ugettext_lazy('Subject\nInjection Test'), 'Content', 'from@example.com', ['to@example.com'])
+>>> message = email.message()
+Traceback (most recent call last):
+ ...
+BadHeaderError: Header values can't contain newlines (got u'Subject\nInjection Test' for header 'Subject')
"""
diff --git a/tests/regressiontests/many_to_one_regress/models.py b/tests/regressiontests/many_to_one_regress/models.py
index 429bdd7558..b87b36cb3b 100644
--- a/tests/regressiontests/many_to_one_regress/models.py
+++ b/tests/regressiontests/many_to_one_regress/models.py
@@ -55,31 +55,46 @@ __test__ = {'API_TESTS':"""
<Child: Child object>
#
-# Tests of ForeignKey assignment and the related-object cache (see #6886)
+# Tests of ForeignKey assignment and the related-object cache (see #6886).
#
>>> p = Parent.objects.create(name="Parent")
>>> c = Child.objects.create(name="Child", parent=p)
-# Look up the object again so that we get a "fresh" object
+# Look up the object again so that we get a "fresh" object.
>>> c = Child.objects.get(name="Child")
>>> p = c.parent
-# Accessing the related object again returns the exactly same object
+# Accessing the related object again returns the exactly same object.
>>> c.parent is p
True
-# But if we kill the cache, we get a new object
+# But if we kill the cache, we get a new object.
>>> del c._parent_cache
>>> c.parent is p
False
-# Assigning a new object results in that object getting cached immediately
+# Assigning a new object results in that object getting cached immediately.
>>> p2 = Parent.objects.create(name="Parent 2")
>>> c.parent = p2
>>> c.parent is p2
True
-# Assigning None fails: Child.parent is null=False
+# Assigning None succeeds if field is null=True.
+>>> p.bestchild = None
+>>> p.bestchild is None
+True
+
+# bestchild should still be None after saving.
+>>> p.save()
+>>> p.bestchild is None
+True
+
+# bestchild should still be None after fetching the object again.
+>>> p = Parent.objects.get(name="Parent")
+>>> p.bestchild is None
+True
+
+# Assigning None fails: Child.parent is null=False.
>>> c.parent = None
Traceback (most recent call last):
...
@@ -91,8 +106,31 @@ Traceback (most recent call last):
...
ValueError: Cannot assign "<First: First object>": "Child.parent" must be a "Parent" instance.
-# Test of multiple ForeignKeys to the same model (bug #7125)
+# Creation using keyword argument should cache the related object.
+>>> p = Parent.objects.get(name="Parent")
+>>> c = Child(parent=p)
+>>> c.parent is p
+True
+
+# Creation using keyword argument and unsaved related instance (#8070).
+>>> p = Parent()
+>>> c = Child(parent=p)
+>>> c.parent is p
+True
+# Creation using attname keyword argument and an id will cause the related
+# object to be fetched.
+>>> p = Parent.objects.get(name="Parent")
+>>> c = Child(parent_id=p.id)
+>>> c.parent is p
+False
+>>> c.parent == p
+True
+
+
+#
+# Test of multiple ForeignKeys to the same model (bug #7125).
+#
>>> c1 = Category.objects.create(name='First')
>>> c2 = Category.objects.create(name='Second')
>>> c3 = Category.objects.create(name='Third')
diff --git a/tests/regressiontests/max_lengths/tests.py b/tests/regressiontests/max_lengths/tests.py
index 0ef407f573..fb62ce634a 100644
--- a/tests/regressiontests/max_lengths/tests.py
+++ b/tests/regressiontests/max_lengths/tests.py
@@ -13,7 +13,7 @@ class MaxLengthArgumentsTests(TestCase):
self.verify_max_length(PersonWithDefaultMaxLengths, 'homepage', 200)
self.verify_max_length(PersonWithDefaultMaxLengths, 'avatar', 100)
- def test_custom_maxlengths(self):
+ def test_custom_max_lengths(self):
self.verify_max_length(PersonWithCustomMaxLengths, 'email', 384)
self.verify_max_length(PersonWithCustomMaxLengths, 'vcard', 1024)
self.verify_max_length(PersonWithCustomMaxLengths, 'homepage', 256)
diff --git a/tests/regressiontests/maxlength/tests.py b/tests/regressiontests/maxlength/tests.py
deleted file mode 100644
index c7ed1f91c0..0000000000
--- a/tests/regressiontests/maxlength/tests.py
+++ /dev/null
@@ -1,160 +0,0 @@
-# Test access to max_length while still providing full backwards compatibility
-# with legacy maxlength attribute.
-"""
-
-Don't print out the deprecation warnings during testing.
->>> from warnings import filterwarnings
->>> filterwarnings("ignore")
-
-# legacy_maxlength function
-
->>> from django.utils.maxlength import legacy_maxlength
-
->>> legacy_maxlength(None, None)
-
-
->>> legacy_maxlength(10, None)
-10
-
->>> legacy_maxlength(None, 10)
-10
-
->>> legacy_maxlength(10, 12)
-Traceback (most recent call last):
-...
-TypeError: Field cannot take both the max_length argument and the legacy maxlength argument.
-
->>> legacy_maxlength(0, 10)
-Traceback (most recent call last):
-...
-TypeError: Field cannot take both the max_length argument and the legacy maxlength argument.
-
->>> legacy_maxlength(0, None)
-0
-
->>> legacy_maxlength(None, 0)
-0
-
-#===============================================================================
-# Fields
-#===============================================================================
-
-# Set up fields
->>> from django.db.models import fields
->>> new = fields.Field(max_length=15)
->>> old = fields.Field(maxlength=10)
-
-# Ensure both max_length and legacy maxlength are not able to both be specified
->>> fields.Field(maxlength=10, max_length=15)
-Traceback (most recent call last):
- ...
-TypeError: Field cannot take both the max_length argument and the legacy maxlength argument.
-
-# Test max_length
->>> new.max_length
-15
->>> old.max_length
-10
-
-# Test accessing maxlength
->>> new.maxlength
-15
->>> old.maxlength
-10
-
-# Test setting maxlength
->>> new.maxlength += 1
->>> old.maxlength += 1
->>> new.max_length
-16
->>> old.max_length
-11
-
-# SlugField __init__ passes through max_length so test that too
->>> fields.SlugField('new', max_length=15).max_length
-15
->>> fields.SlugField('empty').max_length
-50
->>> fields.SlugField('old', maxlength=10).max_length
-10
-
-#===============================================================================
-# (old)forms
-#===============================================================================
-
->>> from django import oldforms
-
-# Test max_length attribute
-
->>> oldforms.TextField('new', max_length=15).render('')
-u'<input type="text" id="id_new" class="vTextField" name="new" size="30" value="" maxlength="15" />'
-
->>> oldforms.IntegerField('new', max_length=15).render('')
-u'<input type="text" id="id_new" class="vIntegerField" name="new" size="10" value="" maxlength="15" />'
-
->>> oldforms.SmallIntegerField('new', max_length=15).render('')
-u'<input type="text" id="id_new" class="vSmallIntegerField" name="new" size="5" value="" maxlength="15" />'
-
->>> oldforms.PositiveIntegerField('new', max_length=15).render('')
-u'<input type="text" id="id_new" class="vPositiveIntegerField" name="new" size="10" value="" maxlength="15" />'
-
->>> oldforms.PositiveSmallIntegerField('new', max_length=15).render('')
-u'<input type="text" id="id_new" class="vPositiveSmallIntegerField" name="new" size="5" value="" maxlength="15" />'
-
->>> oldforms.DatetimeField('new', max_length=15).render('')
-u'<input type="text" id="id_new" class="vDatetimeField" name="new" size="30" value="" maxlength="15" />'
-
->>> oldforms.EmailField('new', max_length=15).render('')
-u'<input type="text" id="id_new" class="vEmailField" name="new" size="50" value="" maxlength="15" />'
->>> oldforms.EmailField('new').render('')
-u'<input type="text" id="id_new" class="vEmailField" name="new" size="50" value="" maxlength="75" />'
-
->>> oldforms.URLField('new', max_length=15).render('')
-u'<input type="text" id="id_new" class="vURLField" name="new" size="50" value="" maxlength="15" />'
->>> oldforms.URLField('new').render('')
-u'<input type="text" id="id_new" class="vURLField" name="new" size="50" value="" maxlength="200" />'
-
->>> oldforms.IPAddressField('new', max_length=15).render('')
-u'<input type="text" id="id_new" class="vIPAddressField" name="new" size="15" value="" maxlength="15" />'
->>> oldforms.IPAddressField('new').render('')
-u'<input type="text" id="id_new" class="vIPAddressField" name="new" size="15" value="" maxlength="15" />'
-
->>> oldforms.CommaSeparatedIntegerField('new', max_length=15).render('')
-u'<input type="text" id="id_new" class="vCommaSeparatedIntegerField" name="new" size="20" value="" maxlength="15" />'
-
-
-# Test legacy maxlength attribute
-
->>> oldforms.TextField('old', maxlength=10).render('')
-u'<input type="text" id="id_old" class="vTextField" name="old" size="30" value="" maxlength="10" />'
-
->>> oldforms.IntegerField('old', maxlength=10).render('')
-u'<input type="text" id="id_old" class="vIntegerField" name="old" size="10" value="" maxlength="10" />'
-
->>> oldforms.SmallIntegerField('old', maxlength=10).render('')
-u'<input type="text" id="id_old" class="vSmallIntegerField" name="old" size="5" value="" maxlength="10" />'
-
->>> oldforms.PositiveIntegerField('old', maxlength=10).render('')
-u'<input type="text" id="id_old" class="vPositiveIntegerField" name="old" size="10" value="" maxlength="10" />'
-
->>> oldforms.PositiveSmallIntegerField('old', maxlength=10).render('')
-u'<input type="text" id="id_old" class="vPositiveSmallIntegerField" name="old" size="5" value="" maxlength="10" />'
-
->>> oldforms.DatetimeField('old', maxlength=10).render('')
-u'<input type="text" id="id_old" class="vDatetimeField" name="old" size="30" value="" maxlength="10" />'
-
->>> oldforms.EmailField('old', maxlength=10).render('')
-u'<input type="text" id="id_old" class="vEmailField" name="old" size="50" value="" maxlength="10" />'
-
->>> oldforms.URLField('old', maxlength=10).render('')
-u'<input type="text" id="id_old" class="vURLField" name="old" size="50" value="" maxlength="10" />'
-
->>> oldforms.IPAddressField('old', maxlength=10).render('')
-u'<input type="text" id="id_old" class="vIPAddressField" name="old" size="15" value="" maxlength="10" />'
-
->>> oldforms.CommaSeparatedIntegerField('old', maxlength=10).render('')
-u'<input type="text" id="id_old" class="vCommaSeparatedIntegerField" name="old" size="20" value="" maxlength="10" />'
-"""
-if __name__ == "__main__":
- import doctest
- doctest.testmod()
diff --git a/tests/regressiontests/model_fields/models.py b/tests/regressiontests/model_fields/models.py
index 7e07227961..455e2b3ded 100644
--- a/tests/regressiontests/model_fields/models.py
+++ b/tests/regressiontests/model_fields/models.py
@@ -1,8 +1,14 @@
from django.db import models
+try:
+ import decimal
+except ImportError:
+ from django.utils import _decimal as decimal # Python 2.3 fallback
+
class Foo(models.Model):
a = models.CharField(max_length=10)
+ d = models.DecimalField(max_digits=5, decimal_places=3)
def get_foo():
return Foo.objects.get(id=1)
@@ -11,9 +17,25 @@ class Bar(models.Model):
b = models.CharField(max_length=10)
a = models.ForeignKey(Foo, default=get_foo)
+class Whiz(models.Model):
+ CHOICES = (
+ ('Group 1', (
+ (1,'First'),
+ (2,'Second'),
+ )
+ ),
+ ('Group 2', (
+ (3,'Third'),
+ (4,'Fourth'),
+ )
+ ),
+ (0,'Other'),
+ )
+ c = models.IntegerField(choices=CHOICES, null=True)
+
__test__ = {'API_TESTS':"""
# Create a couple of Places.
->>> f = Foo.objects.create(a='abc')
+>>> f = Foo.objects.create(a='abc', d=decimal.Decimal("12.34"))
>>> f.id
1
>>> b = Bar(b = "bcd")
@@ -21,4 +43,40 @@ __test__ = {'API_TESTS':"""
<Foo: Foo object>
>>> b.save()
+# Regression tests for #7913
+# Check that get_choices and get_flatchoices interact with
+# get_FIELD_display to return the expected values.
+
+# Test a nested value
+>>> w = Whiz(c=1)
+>>> w.save()
+>>> w.get_c_display()
+u'First'
+
+# Test a top level value
+>>> w.c = 0
+>>> w.get_c_display()
+u'Other'
+
+# Test an invalid data value
+>>> w.c = 9
+>>> w.get_c_display()
+9
+
+# Test a blank data value
+>>> w.c = None
+>>> print w.get_c_display()
+None
+
+# Test an empty data value
+>>> w.c = ''
+>>> w.get_c_display()
+u''
+
+# Regression test for #8023: should be able to filter decimal fields using
+# strings (which is what gets passed through from, e.g., the admin interface).
+>>> Foo.objects.filter(d=u'1.23')
+[]
+
+
"""}
diff --git a/tests/regressiontests/model_fields/tests.py b/tests/regressiontests/model_fields/tests.py
index c2ba9ee008..5aedcd15fc 100644
--- a/tests/regressiontests/model_fields/tests.py
+++ b/tests/regressiontests/model_fields/tests.py
@@ -1,15 +1,19 @@
"""
>>> from django.db.models.fields import *
+>>> try:
+... from decimal import Decimal
+... except ImportError:
+... from django.utils._decimal import Decimal
# DecimalField
->>> f = DecimalField()
+>>> f = DecimalField(max_digits=4, decimal_places=2)
->>> f.to_python(3)
-Decimal("3")
+>>> f.to_python(3) == Decimal("3")
+True
->>> f.to_python("3.14")
-Decimal("3.14")
+>>> f.to_python("3.14") == Decimal("3.14")
+True
>>> f.to_python("abc")
Traceback (most recent call last):
@@ -20,16 +24,26 @@ ValidationError: [u'This value must be a decimal number.']
>>> x = f.to_python(2)
>>> y = f.to_python('2.6')
->>> f.get_db_prep_save(x)
+>>> f._format(x)
u'2.0'
->>> f.get_db_prep_save(y)
+>>> f._format(y)
u'2.6'
->>> f.get_db_prep_save(None)
->>> f.get_db_prep_lookup('exact', x)
-[u'2.0']
->>> f.get_db_prep_lookup('exact', y)
-[u'2.6']
+>>> f._format(None)
>>> f.get_db_prep_lookup('exact', None)
[None]
+# DateTimeField and TimeField to_python should support usecs:
+>>> f = DateTimeField()
+>>> f.to_python('2001-01-02 03:04:05.000006')
+datetime.datetime(2001, 1, 2, 3, 4, 5, 6)
+>>> f.to_python('2001-01-02 03:04:05.999999')
+datetime.datetime(2001, 1, 2, 3, 4, 5, 999999)
+
+>>> f = TimeField()
+>>> f.to_python('01:02:03.000004')
+datetime.time(1, 2, 3, 4)
+>>> f.to_python('01:02:03.999999')
+datetime.time(1, 2, 3, 999999)
+
+
"""
diff --git a/tests/regressiontests/model_inheritance_regress/models.py b/tests/regressiontests/model_inheritance_regress/models.py
index b78b493e15..716c7f5ad8 100644
--- a/tests/regressiontests/model_inheritance_regress/models.py
+++ b/tests/regressiontests/model_inheritance_regress/models.py
@@ -43,12 +43,22 @@ class ParkingLot(Place):
def __unicode__(self):
return u"%s the parking lot" % self.name
+class Supplier(models.Model):
+ restaurant = models.ForeignKey(Restaurant)
+
class Parent(models.Model):
created = models.DateTimeField(default=datetime.datetime.now)
class Child(Parent):
name = models.CharField(max_length=10)
+class SelfRefParent(models.Model):
+ parent_data = models.IntegerField()
+ self_data = models.ForeignKey('self', null=True)
+
+class SelfRefChild(SelfRefParent):
+ child_data = models.IntegerField()
+
__test__ = {'API_TESTS':"""
# Regression for #7350, #7202
# Check that when you create a Parent object with a specific reference to an
@@ -159,4 +169,29 @@ Traceback (most recent call last):
...
DoesNotExist: ItalianRestaurant matching query does not exist.
+# Regression test for #6755
+>>> r = Restaurant(serves_pizza=False)
+>>> r.save()
+>>> r.id == r.place_ptr_id
+True
+>>> orig_id = r.id
+>>> r = Restaurant(place_ptr_id=orig_id, serves_pizza=True)
+>>> r.save()
+>>> r.id == orig_id
+True
+>>> r.id == r.place_ptr_id
+True
+
+# Regression test for #7488. This looks a little crazy, but it's the equivalent
+# of what the admin interface has to do for the edit-inline case.
+>>> Supplier.objects.filter(restaurant=Restaurant(name='xx', address='yy'))
+[]
+
+# Regression test for #7853
+# If the parent class has a self-referential link, make sure that any updates
+# to that link via the child update the right table.
+
+>>> obj = SelfRefChild.objects.create(child_data=37, parent_data=42)
+>>> obj.delete()
+
"""}
diff --git a/tests/regressiontests/model_regress/models.py b/tests/regressiontests/model_regress/models.py
index 2252531564..9e11b43d2b 100644
--- a/tests/regressiontests/model_regress/models.py
+++ b/tests/regressiontests/model_regress/models.py
@@ -29,6 +29,9 @@ class Movie(models.Model):
class Party(models.Model):
when = models.DateField()
+class Event(models.Model):
+ when = models.DateTimeField()
+
__test__ = {'API_TESTS': """
(NOTE: Part of the regression test here is merely parsing the model
declaration. The verbose_name, in particular, did not always work.)
@@ -68,5 +71,21 @@ u''
>>> [p.when for p in Party.objects.filter(when__year = 1998)]
[datetime.date(1998, 12, 31)]
+# Check that get_next_by_FIELD and get_previous_by_FIELD don't crash when we
+# have usecs values stored on the database
+#
+# [It crashed after the Field.get_db_prep_* refactor, because on most backends
+# DateTimeFields supports usecs, but DateTimeField.to_python didn't recognize
+# them. (Note that Model._get_next_or_previous_by_FIELD coerces values to
+# strings)]
+#
+>>> e = Event.objects.create(when = datetime.datetime(2000, 1, 1, 16, 0, 0))
+>>> e = Event.objects.create(when = datetime.datetime(2000, 1, 1, 6, 1, 1))
+>>> e = Event.objects.create(when = datetime.datetime(2000, 1, 1, 13, 1, 1))
+>>> e = Event.objects.create(when = datetime.datetime(2000, 1, 1, 12, 0, 20, 24))
+>>> e.get_next_by_when().when
+datetime.datetime(2000, 1, 1, 13, 1, 1)
+>>> e.get_previous_by_when().when
+datetime.datetime(2000, 1, 1, 6, 1, 1)
"""
}
diff --git a/tests/regressiontests/modeladmin/models.py b/tests/regressiontests/modeladmin/models.py
index 6a7da7d362..0fdd9afdf1 100644
--- a/tests/regressiontests/modeladmin/models.py
+++ b/tests/regressiontests/modeladmin/models.py
@@ -346,6 +346,20 @@ Traceback (most recent call last):
...
ImproperlyConfigured: Both fieldsets and fields are specified in ValidationTestModelAdmin.
+>>> class ValidationTestModelAdmin(ModelAdmin):
+... fieldsets = [(None, {'fields': ['name', 'name']})]
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
+Traceback (most recent call last):
+...
+ImproperlyConfigured: There are duplicate field(s) in ValidationTestModelAdmin.fieldsets
+
+>>> class ValidationTestModelAdmin(ModelAdmin):
+... fields = ["name", "name"]
+>>> validate(ValidationTestModelAdmin, ValidationTestModel)
+Traceback (most recent call last):
+...
+ImproperlyConfigured: There are duplicate field(s) in ValidationTestModelAdmin.fields
+
# form
>>> class FakeForm(object):
diff --git a/tests/regressiontests/null_fk/models.py b/tests/regressiontests/null_fk/models.py
index 1bc266c033..529dde5039 100644
--- a/tests/regressiontests/null_fk/models.py
+++ b/tests/regressiontests/null_fk/models.py
@@ -1,8 +1,5 @@
"""
-Regression tests for proper working of ForeignKey(null=True). Tests these bugs:
-
- * #7369: FK non-null after null relationship on select_related() generates an invalid query
-
+Regression tests for proper working of ForeignKey(null=True).
"""
from django.db import models
@@ -38,7 +35,8 @@ __test__ = {'API_TESTS':"""
# Starting from comment, make sure that a .select_related(...) with a specified
# set of fields will properly LEFT JOIN multiple levels of NULLs (and the things
-# that come after the NULLs, or else data that should exist won't).
+# that come after the NULLs, or else data that should exist won't). Regression
+# test for #7369.
>>> c = Comment.objects.select_related().get(id=1)
>>> c.post
<Post: First Post>
@@ -47,9 +45,11 @@ __test__ = {'API_TESTS':"""
None
>>> comments = Comment.objects.select_related('post__forum__system_info').all()
->>> [(c.id, c.post.id) for c in comments]
-[(1, 1), (2, None)]
->>> [(c.comment_text, c.post.title) for c in comments]
-[(u'My first comment', u'First Post'), (u'My second comment', None)]
+>>> [(c.id, c.comment_text, c.post) for c in comments]
+[(1, u'My first comment', <Post: First Post>), (2, u'My second comment', None)]
+
+# Regression test for #7530, #7716.
+>>> Comment.objects.select_related('post').filter(post__isnull=True)[0].post is None
+True
"""}
diff --git a/tests/regressiontests/one_to_one_regress/models.py b/tests/regressiontests/one_to_one_regress/models.py
index 99022882f2..66d00d7223 100644
--- a/tests/regressiontests/one_to_one_regress/models.py
+++ b/tests/regressiontests/one_to_one_regress/models.py
@@ -22,6 +22,10 @@ class Bar(models.Model):
def __unicode__(self):
return u"%s the bar" % self.place.name
+class UndergroundBar(models.Model):
+ place = models.OneToOneField(Place, null=True)
+ serves_cocktails = models.BooleanField()
+
class Favorites(models.Model):
name = models.CharField(max_length = 50)
restaurants = models.ManyToManyField(Restaurant)
@@ -42,7 +46,7 @@ __test__ = {'API_TESTS':"""
>>> f.restaurants.all()
[<Restaurant: Demon Dogs the restaurant>]
-# Regression test for #7173: Check that the name of the cache for the
+# Regression test for #7173: Check that the name of the cache for the
# reverse object is correct.
>>> b = Bar(place=p1, serves_cocktails=False)
>>> b.save()
@@ -53,7 +57,7 @@ __test__ = {'API_TESTS':"""
#
# Regression test for #6886 (the related-object cache)
-#
+#
# Look up the objects again so that we get "fresh" objects
>>> p = Place.objects.get(name="Demon Dogs")
@@ -76,6 +80,12 @@ False
>>> p.restaurant is r2
True
+# Assigning None succeeds if field is null=True.
+>>> ug_bar = UndergroundBar.objects.create(place=p, serves_cocktails=False)
+>>> ug_bar.place = None
+>>> ug_bar.place is None
+True
+
# Assigning None fails: Place.restaurant is null=False
>>> p.restaurant = None
Traceback (most recent call last):
@@ -88,4 +98,25 @@ Traceback (most recent call last):
...
ValueError: Cannot assign "<Place: Demon Dogs the place>": "Place.restaurant" must be a "Restaurant" instance.
+# Creation using keyword argument should cache the related object.
+>>> p = Place.objects.get(name="Demon Dogs")
+>>> r = Restaurant(place=p)
+>>> r.place is p
+True
+
+# Creation using keyword argument and unsaved related instance (#8070).
+>>> p = Place()
+>>> r = Restaurant(place=p)
+>>> r.place is p
+True
+
+# Creation using attname keyword argument and an id will cause the related
+# object to be fetched.
+>>> p = Place.objects.get(name="Demon Dogs")
+>>> r = Restaurant(place_id=p.id)
+>>> r.place is p
+False
+>>> r.place == p
+True
+
"""}
diff --git a/tests/regressiontests/pagination_regress/__init__.py b/tests/regressiontests/pagination_regress/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/regressiontests/pagination_regress/__init__.py
diff --git a/tests/regressiontests/pagination_regress/models.py b/tests/regressiontests/pagination_regress/models.py
new file mode 100644
index 0000000000..cde172db68
--- /dev/null
+++ b/tests/regressiontests/pagination_regress/models.py
@@ -0,0 +1 @@
+# Models file for tests to run.
diff --git a/tests/regressiontests/pagination_regress/tests.py b/tests/regressiontests/pagination_regress/tests.py
new file mode 100644
index 0000000000..08436df355
--- /dev/null
+++ b/tests/regressiontests/pagination_regress/tests.py
@@ -0,0 +1,157 @@
+from unittest import TestCase
+
+from django.core.paginator import Paginator, EmptyPage
+
+class PaginatorTests(TestCase):
+ """
+ Tests for the Paginator and Page classes.
+ """
+
+ def check_paginator(self, params, output):
+ """
+ Helper method that instantiates a Paginator object from the passed
+ params and then checks that its attributes match the passed output.
+ """
+ count, num_pages, page_range = output
+ paginator = Paginator(*params)
+ self.check_attribute('count', paginator, count, params)
+ self.check_attribute('num_pages', paginator, num_pages, params)
+ self.check_attribute('page_range', paginator, page_range, params)
+
+ def check_attribute(self, name, paginator, expected, params):
+ """
+ Helper method that checks a single attribute and gives a nice error
+ message upon test failure.
+ """
+ got = getattr(paginator, name)
+ self.assertEqual(expected, got,
+ "For '%s', expected %s but got %s. Paginator parameters were: %s"
+ % (name, expected, got, params))
+
+ def test_paginator(self):
+ """
+ Tests the paginator attributes using varying inputs.
+ """
+ nine = [1, 2, 3, 4, 5, 6, 7, 8, 9]
+ ten = nine + [10]
+ eleven = ten + [11]
+ tests = (
+ # Each item is two tuples:
+ # First tuple is Paginator parameters - object_list, per_page,
+ # orphans, and allow_empty_first_page.
+ # Second tuple is resulting Paginator attributes - count,
+ # num_pages, and page_range.
+ # Ten items, varying orphans, no empty first page.
+ ((ten, 4, 0, False), (10, 3, [1, 2, 3])),
+ ((ten, 4, 1, False), (10, 3, [1, 2, 3])),
+ ((ten, 4, 2, False), (10, 2, [1, 2])),
+ ((ten, 4, 5, False), (10, 2, [1, 2])),
+ ((ten, 4, 6, False), (10, 1, [1])),
+ # Ten items, varying orphans, allow empty first page.
+ ((ten, 4, 0, True), (10, 3, [1, 2, 3])),
+ ((ten, 4, 1, True), (10, 3, [1, 2, 3])),
+ ((ten, 4, 2, True), (10, 2, [1, 2])),
+ ((ten, 4, 5, True), (10, 2, [1, 2])),
+ ((ten, 4, 6, True), (10, 1, [1])),
+ # One item, varying orphans, no empty first page.
+ (([1], 4, 0, False), (1, 1, [1])),
+ (([1], 4, 1, False), (1, 1, [1])),
+ (([1], 4, 2, False), (1, 1, [1])),
+ # One item, varying orphans, allow empty first page.
+ (([1], 4, 0, True), (1, 1, [1])),
+ (([1], 4, 1, True), (1, 1, [1])),
+ (([1], 4, 2, True), (1, 1, [1])),
+ # Zero items, varying orphans, no empty first page.
+ (([], 4, 0, False), (0, 0, [])),
+ (([], 4, 1, False), (0, 0, [])),
+ (([], 4, 2, False), (0, 0, [])),
+ # Zero items, varying orphans, allow empty first page.
+ (([], 4, 0, True), (0, 1, [1])),
+ (([], 4, 1, True), (0, 1, [1])),
+ (([], 4, 2, True), (0, 1, [1])),
+ # Number if items one less than per_page.
+ (([], 1, 0, True), (0, 1, [1])),
+ (([], 1, 0, False), (0, 0, [])),
+ (([1], 2, 0, True), (1, 1, [1])),
+ ((nine, 10, 0, True), (9, 1, [1])),
+ # Number if items equal to per_page.
+ (([1], 1, 0, True), (1, 1, [1])),
+ (([1, 2], 2, 0, True), (2, 1, [1])),
+ ((ten, 10, 0, True), (10, 1, [1])),
+ # Number if items one more than per_page.
+ (([1, 2], 1, 0, True), (2, 2, [1, 2])),
+ (([1, 2, 3], 2, 0, True), (3, 2, [1, 2])),
+ ((eleven, 10, 0, True), (11, 2, [1, 2])),
+ # Number if items one more than per_page with one orphan.
+ (([1, 2], 1, 1, True), (2, 1, [1])),
+ (([1, 2, 3], 2, 1, True), (3, 1, [1])),
+ ((eleven, 10, 1, True), (11, 1, [1])),
+ )
+ for params, output in tests:
+ self.check_paginator(params, output)
+
+ def check_indexes(self, params, page_num, indexes):
+ """
+ Helper method that instantiates a Paginator object from the passed
+ params and then checks that the start and end indexes of the passed
+ page_num match those given as a 2-tuple in indexes.
+ """
+ paginator = Paginator(*params)
+ if page_num == 'first':
+ page_num = 1
+ elif page_num == 'last':
+ page_num = paginator.num_pages
+ page = paginator.page(page_num)
+ start, end = indexes
+ msg = ("For %s of page %s, expected %s but got %s."
+ " Paginator parameters were: %s")
+ self.assertEqual(start, page.start_index(),
+ msg % ('start index', page_num, start, page.start_index(), params))
+ self.assertEqual(end, page.end_index(),
+ msg % ('end index', page_num, end, page.end_index(), params))
+
+ def test_page_indexes(self):
+ """
+ Tests that paginator pages have the correct start and end indexes.
+ """
+ ten = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
+ tests = (
+ # Each item is three tuples:
+ # First tuple is Paginator parameters - object_list, per_page,
+ # orphans, and allow_empty_first_page.
+ # Second tuple is the start and end indexes of the first page.
+ # Third tuple is the start and end indexes of the last page.
+ # Ten items, varying per_page, no orphans.
+ ((ten, 1, 0, True), (1, 1), (10, 10)),
+ ((ten, 2, 0, True), (1, 2), (9, 10)),
+ ((ten, 3, 0, True), (1, 3), (10, 10)),
+ ((ten, 5, 0, True), (1, 5), (6, 10)),
+ # Ten items, varying per_page, with orphans.
+ ((ten, 1, 1, True), (1, 1), (9, 10)),
+ ((ten, 1, 2, True), (1, 1), (8, 10)),
+ ((ten, 3, 1, True), (1, 3), (7, 10)),
+ ((ten, 3, 2, True), (1, 3), (7, 10)),
+ ((ten, 3, 4, True), (1, 3), (4, 10)),
+ ((ten, 5, 1, True), (1, 5), (6, 10)),
+ ((ten, 5, 2, True), (1, 5), (6, 10)),
+ ((ten, 5, 5, True), (1, 10), (1, 10)),
+ # One item, varying orphans, no empty first page.
+ (([1], 4, 0, False), (1, 1), (1, 1)),
+ (([1], 4, 1, False), (1, 1), (1, 1)),
+ (([1], 4, 2, False), (1, 1), (1, 1)),
+ # One item, varying orphans, allow empty first page.
+ (([1], 4, 0, True), (1, 1), (1, 1)),
+ (([1], 4, 1, True), (1, 1), (1, 1)),
+ (([1], 4, 2, True), (1, 1), (1, 1)),
+ # Zero items, varying orphans, allow empty first page.
+ (([], 4, 0, True), (0, 0), (0, 0)),
+ (([], 4, 1, True), (0, 0), (0, 0)),
+ (([], 4, 2, True), (0, 0), (0, 0)),
+ )
+ for params, first, last in tests:
+ self.check_indexes(params, 'first', first)
+ self.check_indexes(params, 'last', last)
+ # When no items and no empty first page, we should get EmptyPage error.
+ self.assertRaises(EmptyPage, self.check_indexes, ([], 4, 0, False), 1, None)
+ self.assertRaises(EmptyPage, self.check_indexes, ([], 4, 1, False), 1, None)
+ self.assertRaises(EmptyPage, self.check_indexes, ([], 4, 2, False), 1, None)
diff --git a/tests/regressiontests/queries/models.py b/tests/regressiontests/queries/models.py
index 847d515422..3a4acf350a 100644
--- a/tests/regressiontests/queries/models.py
+++ b/tests/regressiontests/queries/models.py
@@ -190,6 +190,32 @@ class CustomPk(models.Model):
class Related(models.Model):
custom = models.ForeignKey(CustomPk)
+# An inter-related setup with a model subclass that has a nullable
+# path to another model, and a return path from that model.
+
+class Celebrity(models.Model):
+ name = models.CharField("Name", max_length=20)
+ greatest_fan = models.ForeignKey("Fan", null=True, unique=True)
+
+class TvChef(Celebrity):
+ pass
+
+class Fan(models.Model):
+ fan_of = models.ForeignKey(Celebrity)
+
+# Multiple foreign keys
+class LeafA(models.Model):
+ data = models.CharField(max_length=10)
+
+ def __unicode__(self):
+ return self.data
+
+class LeafB(models.Model):
+ data = models.CharField(max_length=10)
+
+class Join(models.Model):
+ a = models.ForeignKey(LeafA)
+ b = models.ForeignKey(LeafB)
__test__ = {'API_TESTS':"""
>>> t1 = Tag.objects.create(name='t1')
@@ -321,6 +347,16 @@ constraints.
>>> Number.objects.filter(Q(num__gt=7) & Q(num__lt=12) | Q(num__lt=4))
[<Number: 8>]
+Bug #7872
+Another variation on the disjunctive filtering theme.
+
+# For the purposes of this regression test, it's important that there is no
+# Join object releated to the LeafA we create.
+>>> LeafA.objects.create(data='first')
+<LeafA: first>
+>>> LeafA.objects.filter(Q(data='first')|Q(join__b__data='second'))
+[<LeafA: first>]
+
Bug #6074
Merging two empty result sets shouldn't leave a queryset with no constraints
(which would match everything).
@@ -417,9 +453,9 @@ Bug #5324, #6704
>>> query.LOUTER not in [x[2] for x in query.alias_map.values()]
True
-Similarly, when one of the joins cannot possibly, ever, involve NULL values (Author -> ExtraInfo, in the following), it should never be promoted to a left outer join. So hte following query should only involve one "left outer" join (Author -> Item is 0-to-many).
+Similarly, when one of the joins cannot possibly, ever, involve NULL values (Author -> ExtraInfo, in the following), it should never be promoted to a left outer join. So the following query should only involve one "left outer" join (Author -> Item is 0-to-many).
>>> qs = Author.objects.filter(id=a1.id).filter(Q(extra__note=n1)|Q(item__note=n3))
->>> len([x[2] for x in qs.query.alias_map.values() if x[2] == query.LOUTER])
+>>> len([x[2] for x in qs.query.alias_map.values() if x[2] == query.LOUTER and qs.query.alias_refcount[x[1]]])
1
The previous changes shouldn't affect nullable foreign key joins.
@@ -785,6 +821,15 @@ Bug #7204, #7506 -- make sure querysets with related fields can be pickled. If
this doesn't crash, it's a Good Thing.
>>> out = pickle.dumps(Item.objects.all())
+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]
+>>> query2 = pickle.loads(pickle.dumps(qs.query))
+>>> query2.as_sql()[0] == query
+True
+
Bug #7277
>>> ann1 = Annotation.objects.create(name='a1', tag=t1)
>>> ann1.notes.add(n1)
@@ -822,6 +867,26 @@ Bug #7759 -- count should work with a partially read result set.
... break
True
+Bug #7791 -- there were "issues" when ordering and distinct-ing on fields
+related via ForeignKeys.
+>>> len(Note.objects.order_by('extrainfo__info').distinct())
+3
+
+Bug #7778 - Model subclasses could not be deleted if a nullable foreign key
+relates to a model that relates back.
+
+>>> num_celebs = Celebrity.objects.count()
+>>> tvc = TvChef.objects.create(name="Huey")
+>>> Celebrity.objects.count() == num_celebs + 1
+True
+>>> f1 = Fan.objects.create(fan_of=tvc)
+>>> f2 = Fan.objects.create(fan_of=tvc)
+>>> tvc.delete()
+
+# The parent object should have been deleted as well.
+>>> Celebrity.objects.count() == num_celebs
+True
+
"""}
# In Python 2.3, exceptions raised in __len__ are swallowed (Python issue
diff --git a/tests/regressiontests/requests/tests.py b/tests/regressiontests/requests/tests.py
index aaaef1d8b0..a091113515 100644
--- a/tests/regressiontests/requests/tests.py
+++ b/tests/regressiontests/requests/tests.py
@@ -20,8 +20,9 @@ META:{...}>
... def __init__(self, *args, **kwargs):
... super(FakeModPythonRequest, self).__init__(*args, **kwargs)
... self._get = self._post = self._meta = self._cookies = {}
->>> class Dummy: pass
-...
+>>> class Dummy:
+... def get_options(self):
+... return {}
>>> req = Dummy()
>>> req.uri = 'bogus'
>>> print repr(FakeModPythonRequest(req))
diff --git a/tests/regressiontests/reverse_single_related/__init__.py b/tests/regressiontests/reverse_single_related/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/regressiontests/reverse_single_related/__init__.py
diff --git a/tests/regressiontests/reverse_single_related/models.py b/tests/regressiontests/reverse_single_related/models.py
new file mode 100644
index 0000000000..b2b75392aa
--- /dev/null
+++ b/tests/regressiontests/reverse_single_related/models.py
@@ -0,0 +1,54 @@
+"""
+Regression tests for an object that cannot access a single related object due
+to a restrictive default manager.
+"""
+
+from django.db import models
+
+
+class SourceManager(models.Manager):
+ def get_query_set(self):
+ return super(SourceManager, self).get_query_set().filter(is_public=True)
+
+class Source(models.Model):
+ is_public = models.BooleanField()
+ objects = SourceManager()
+
+class Item(models.Model):
+ source = models.ForeignKey(Source)
+
+
+__test__ = {'API_TESTS':"""
+
+>>> public_source = Source.objects.create(is_public=True)
+>>> public_item = Item.objects.create(source=public_source)
+
+>>> private_source = Source.objects.create(is_public=False)
+>>> private_item = Item.objects.create(source=private_source)
+
+# Only one source is available via all() due to the custom default manager.
+
+>>> Source.objects.all()
+[<Source: Source object>]
+
+>>> public_item.source
+<Source: Source object>
+
+# Make sure that an item can still access its related source even if the default
+# manager doesn't normally allow it.
+
+>>> private_item.source
+<Source: Source object>
+
+# If the manager is marked "use_for_related_fields", it'll get used instead
+# of the "bare" queryset. Usually you'd define this as a property on the class,
+# but this approximates that in a way that's easier in tests.
+
+>>> Source.objects.use_for_related_fields = True
+>>> private_item = Item.objects.get(pk=private_item.pk)
+>>> private_item.source
+Traceback (most recent call last):
+ ...
+DoesNotExist: Source matching query does not exist.
+
+"""}
diff --git a/tests/regressiontests/serializers_regress/tests.py b/tests/regressiontests/serializers_regress/tests.py
index 9bc5eec1eb..f990d57a17 100644
--- a/tests/regressiontests/serializers_regress/tests.py
+++ b/tests/regressiontests/serializers_regress/tests.py
@@ -72,13 +72,13 @@ def inherited_create(pk, klass, data):
# 1) we're testing inheritance, not field behaviour, so none
# of the field values need to be protected.
# 2) saving the child class and having the parent created
- # automatically is easier than manually creating both.
+ # automatically is easier than manually creating both.
models.Model.save(instance)
created = [instance]
for klass,field in instance._meta.parents.items():
created.append(klass.objects.get(id=pk))
return created
-
+
# A set of functions that can be used to compare
# test data objects of various kinds
def data_compare(testcase, pk, klass, data):
@@ -111,7 +111,7 @@ def inherited_compare(testcase, pk, klass, data):
instance = klass.objects.get(id=pk)
for key,value in data.items():
testcase.assertEqual(value, getattr(instance,key))
-
+
# Define some data types. Each data type is
# actually a pair of functions; one to create
# and one to compare objects of that type
@@ -274,7 +274,7 @@ The end."""),
(data_obj, 800, AutoNowDateTimeData, datetime.datetime(2006,6,16,10,42,37)),
(data_obj, 810, ModifyingSaveData, 42),
-
+
(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}),
@@ -302,17 +302,19 @@ def serializerTest(format, self):
objects = []
instance_count = {}
transaction.enter_transaction_management()
- transaction.managed(True)
- for (func, pk, klass, datum) in test_data:
- objects.extend(func[0](pk, klass, datum))
- instance_count[klass] = 0
- transaction.commit()
- transaction.leave_transaction_management()
+ try:
+ transaction.managed(True)
+ for (func, pk, klass, datum) in test_data:
+ objects.extend(func[0](pk, klass, datum))
+ instance_count[klass] = 0
+ transaction.commit()
+ finally:
+ transaction.leave_transaction_management()
# Get a count of the number of objects created for each class
for klass in instance_count:
instance_count[klass] = klass.objects.count()
-
+
# Add the generic tagged objects to the object list
objects.extend(Tag.objects.all())
@@ -322,11 +324,13 @@ def serializerTest(format, self):
# Flush the database and recreate from the serialized data
management.call_command('flush', verbosity=0, interactive=False)
transaction.enter_transaction_management()
- transaction.managed(True)
- for obj in serializers.deserialize(format, serialized_data):
- obj.save()
- transaction.commit()
- transaction.leave_transaction_management()
+ try:
+ transaction.managed(True)
+ for obj in serializers.deserialize(format, serialized_data):
+ obj.save()
+ transaction.commit()
+ finally:
+ transaction.leave_transaction_management()
# Assert that the deserialized data is the same
# as the original source
diff --git a/tests/regressiontests/templates/loaders.py b/tests/regressiontests/templates/loaders.py
index db37116b94..fdaf9ac83f 100644
--- a/tests/regressiontests/templates/loaders.py
+++ b/tests/regressiontests/templates/loaders.py
@@ -14,6 +14,7 @@ import sys
import pkg_resources
import imp
import StringIO
+import os.path
from django.template import TemplateDoesNotExist
from django.template.loaders.eggs import load_template_source as lts_egg
@@ -57,8 +58,8 @@ class EggLoader(unittest.TestCase):
self.empty_egg = create_egg("egg_empty", {})
self.egg_1 = create_egg("egg_1", {
- 'templates/y.html' : StringIO.StringIO("y"),
- 'templates/x.txt' : StringIO.StringIO("x"),
+ os.path.normcase('templates/y.html') : StringIO.StringIO("y"),
+ os.path.normcase('templates/x.txt') : StringIO.StringIO("x"),
})
self._old_installed_apps = settings.INSTALLED_APPS
settings.INSTALLED_APPS = []
diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py
index 186b8aacb5..13b8c6b488 100644
--- a/tests/regressiontests/templates/tests.py
+++ b/tests/regressiontests/templates/tests.py
@@ -11,6 +11,7 @@ import unittest
from datetime import datetime, timedelta
from django import template
+from django.core import urlresolvers
from django.template import loader
from django.template.loaders import app_directories, filesystem
from django.utils.translation import activate, deactivate, ugettext as _
@@ -565,6 +566,14 @@ class Templates(unittest.TestCase):
# ifchanged for the day.
'ifchanged-param04': ('{% for d in days %}{% ifchanged d.day %}{{ d.day }}{% endifchanged %}{% for h in d.hours %}{% ifchanged d.day h %}{{ h }}{% endifchanged %}{% endfor %}{% endfor %}', {'days':[{'day':1, 'hours':[1,2,3]},{'day':2, 'hours':[3]},] }, '112323'),
+ # Test the else clause of ifchanged.
+ 'ifchanged-else01': ('{% for id in ids %}{{ id }}{% ifchanged id %}-first{% else %}-other{% endifchanged %},{% endfor %}', {'ids': [1,1,2,2,2,3]}, '1-first,1-other,2-first,2-other,2-other,3-first,'),
+
+ 'ifchanged-else02': ('{% for id in ids %}{{ id }}-{% ifchanged id %}{% cycle red,blue %}{% else %}grey{% endifchanged %},{% endfor %}', {'ids': [1,1,2,2,2,3]}, '1-red,1-grey,2-blue,2-grey,2-grey,3-red,'),
+ 'ifchanged-else03': ('{% for id in ids %}{{ id }}{% ifchanged id %}-{% cycle red,blue %}{% else %}{% endifchanged %},{% endfor %}', {'ids': [1,1,2,2,2,3]}, '1-red,1,2-blue,2,2,3-red,'),
+
+ 'ifchanged-else04': ('{% for id in ids %}{% ifchanged %}***{{ id }}*{% else %}...{% endifchanged %}{{ forloop.counter }}{% endfor %}', {'ids': [1,1,2,2,2,3,4]}, '***1*1...2***2*3...4...5***3*6***4*7'),
+
### IFEQUAL TAG ###########################################################
'ifequal01': ("{% ifequal a b %}yes{% endifequal %}", {"a": 1, "b": 2}, ""),
'ifequal02': ("{% ifequal a b %}yes{% endifequal %}", {"a": 1, "b": 1}, "yes"),
@@ -712,7 +721,7 @@ class Templates(unittest.TestCase):
# Inheritance from a template that doesn't have any blocks
'inheritance27': ("{% extends 'inheritance26' %}", {}, 'no tags'),
-
+
### I18N ##################################################################
# {% spaceless %} tag
@@ -880,8 +889,8 @@ class Templates(unittest.TestCase):
# Failures
'url-fail01': ('{% url %}', {}, template.TemplateSyntaxError),
- 'url-fail02': ('{% url no_such_view %}', {}, ''),
- 'url-fail03': ('{% url regressiontests.templates.views.client no_such_param="value" %}', {}, ''),
+ 'url-fail02': ('{% url no_such_view %}', {}, urlresolvers.NoReverseMatch),
+ 'url-fail03': ('{% url regressiontests.templates.views.client %}', {}, urlresolvers.NoReverseMatch),
### CACHE TAG ######################################################
'cache01': ('{% load cache %}{% cache -1 test %}cache01{% endcache %}', {}, 'cache01'),
@@ -893,16 +902,16 @@ class Templates(unittest.TestCase):
'cache07': ('{% load cache %}{% cache 2 test foo %}cache07{% endcache %}', {'foo': 1}, 'cache05'),
# Allow first argument to be a variable.
- 'cache08': ('{% load cache %}{% cache time test foo %}cache08{% endcache %}', {'foo': 2, 'time': 2}, 'cache06'),
- 'cache09': ('{% load cache %}{% cache time test foo %}cache09{% endcache %}', {'foo': 3, 'time': -1}, 'cache09'),
- 'cache10': ('{% load cache %}{% cache time test foo %}cache10{% endcache %}', {'foo': 3, 'time': -1}, 'cache10'),
+ 'cache08': ('{% load cache %}{% cache time test foo %}cache08{% endcache %}', {'foo': 2, 'time': 2}, 'cache06'),
+ 'cache09': ('{% load cache %}{% cache time test foo %}cache09{% endcache %}', {'foo': 3, 'time': -1}, 'cache09'),
+ 'cache10': ('{% load cache %}{% cache time test foo %}cache10{% endcache %}', {'foo': 3, 'time': -1}, 'cache10'),
# Raise exception if we don't have at least 2 args, first one integer.
'cache11': ('{% load cache %}{% cache %}{% endcache %}', {}, template.TemplateSyntaxError),
'cache12': ('{% load cache %}{% cache 1 %}{% endcache %}', {}, template.TemplateSyntaxError),
'cache13': ('{% load cache %}{% cache foo bar %}{% endcache %}', {}, template.TemplateSyntaxError),
- 'cache14': ('{% load cache %}{% cache foo bar %}{% endcache %}', {'foo': 'fail'}, template.TemplateSyntaxError),
- 'cache15': ('{% load cache %}{% cache foo bar %}{% endcache %}', {'foo': []}, template.TemplateSyntaxError),
+ 'cache14': ('{% load cache %}{% cache foo bar %}{% endcache %}', {'foo': 'fail'}, template.TemplateSyntaxError),
+ 'cache15': ('{% load cache %}{% cache foo bar %}{% endcache %}', {'foo': []}, template.TemplateSyntaxError),
### AUTOESCAPE TAG ##############################################
'autoescape-tag01': ("{% autoescape off %}hello{% endautoescape %}", {}, "hello"),
diff --git a/tests/regressiontests/test_client_regress/models.py b/tests/regressiontests/test_client_regress/models.py
index 1eb55e312e..3518df3b9f 100644
--- a/tests/regressiontests/test_client_regress/models.py
+++ b/tests/regressiontests/test_client_regress/models.py
@@ -1,12 +1,10 @@
"""
Regression tests for the Test Client, especially the customized assertions.
-
"""
+
from django.test import Client, TestCase
from django.core.urlresolvers import reverse
from django.core.exceptions import SuspiciousOperation
-import os
-import sha
class AssertContainsTests(TestCase):
def test_contains(self):
@@ -24,7 +22,7 @@ class AssertContainsTests(TestCase):
self.assertNotContains(response, 'once')
except AssertionError, e:
self.assertEquals(str(e), "Response should not contain 'once'")
-
+
try:
self.assertContains(response, 'never', 1)
except AssertionError, e:
@@ -287,7 +285,7 @@ class URLEscapingTests(TestCase):
class ExceptionTests(TestCase):
fixtures = ['testdata.json']
-
+
def test_exception_cleared(self):
"#5836 - A stale user exception isn't re-raised by the test client."
@@ -300,7 +298,7 @@ class ExceptionTests(TestCase):
pass
# At this point, an exception has been raised, and should be cleared.
-
+
# This next operation should be successful; if it isn't we have a problem.
login = self.client.login(username='staff', password='password')
self.failUnless(login, 'Could not log in')
@@ -309,7 +307,7 @@ class ExceptionTests(TestCase):
except SuspiciousOperation:
self.fail("Staff should be able to visit this page")
-# We need two different tests to check URLconf subsitution - one to check
+# We need two different tests to check URLconf substitution - one to check
# it was changed, and another one (without self.urls) to check it was reverted on
# teardown. This pair of tests relies upon the alphabetical ordering of test execution.
class UrlconfSubstitutionTests(TestCase):
diff --git a/tests/regressiontests/test_utils/__init__.py b/tests/regressiontests/test_utils/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/regressiontests/test_utils/__init__.py
diff --git a/tests/regressiontests/test_utils/models.py b/tests/regressiontests/test_utils/models.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/regressiontests/test_utils/models.py
diff --git a/tests/regressiontests/test_utils/tests.py b/tests/regressiontests/test_utils/tests.py
new file mode 100644
index 0000000000..a2539bf8c6
--- /dev/null
+++ b/tests/regressiontests/test_utils/tests.py
@@ -0,0 +1,72 @@
+r"""
+# Some checks of the doctest output normalizer.
+# Standard doctests do fairly
+>>> from django.utils import simplejson
+>>> from django.utils.xmlutils import SimplerXMLGenerator
+>>> from StringIO import StringIO
+
+>>> def produce_long():
+... return 42L
+
+>>> def produce_int():
+... return 42
+
+>>> def produce_json():
+... return simplejson.dumps(['foo', {'bar': ('baz', None, 1.0, 2), 'whiz': 42}])
+
+>>> def produce_xml():
+... stream = StringIO()
+... xml = SimplerXMLGenerator(stream, encoding='utf-8')
+... xml.startDocument()
+... xml.startElement("foo", {"aaa" : "1.0", "bbb": "2.0"})
+... xml.startElement("bar", {"ccc" : "3.0"})
+... xml.characters("Hello")
+... xml.endElement("bar")
+... xml.startElement("whiz", {})
+... xml.characters("Goodbye")
+... xml.endElement("whiz")
+... xml.endElement("foo")
+... xml.endDocument()
+... return stream.getvalue()
+
+>>> def produce_xml_fragment():
+... stream = StringIO()
+... xml = SimplerXMLGenerator(stream, encoding='utf-8')
+... xml.startElement("foo", {"aaa": "1.0", "bbb": "2.0"})
+... xml.characters("Hello")
+... xml.endElement("foo")
+... xml.startElement("bar", {"ccc": "3.0", "ddd": "4.0"})
+... xml.endElement("bar")
+... return stream.getvalue()
+
+# Long values are normalized and are comparable to normal integers ...
+>>> produce_long()
+42
+
+# ... and vice versa
+>>> produce_int()
+42L
+
+# JSON output is normalized for field order, so it doesn't matter
+# which order json dictionary attributes are listed in output
+>>> produce_json()
+'["foo", {"bar": ["baz", null, 1.0, 2], "whiz": 42}]'
+
+>>> produce_json()
+'["foo", {"whiz": 42, "bar": ["baz", null, 1.0, 2]}]'
+
+# XML output is normalized for attribute order, so it doesn't matter
+# which order XML element attributes are listed in output
+>>> produce_xml()
+'<?xml version="1.0" encoding="UTF-8"?>\n<foo aaa="1.0" bbb="2.0"><bar ccc="3.0">Hello</bar><whiz>Goodbye</whiz></foo>'
+
+>>> produce_xml()
+'<?xml version="1.0" encoding="UTF-8"?>\n<foo bbb="2.0" aaa="1.0"><bar ccc="3.0">Hello</bar><whiz>Goodbye</whiz></foo>'
+
+>>> produce_xml_fragment()
+'<foo aaa="1.0" bbb="2.0">Hello</foo><bar ccc="3.0" ddd="4.0"></bar>'
+
+>>> produce_xml_fragment()
+'<foo bbb="2.0" aaa="1.0">Hello</foo><bar ddd="4.0" ccc="3.0"></bar>'
+
+""" \ No newline at end of file
diff --git a/tests/regressiontests/utils/datastructures.py b/tests/regressiontests/utils/datastructures.py
index 5d31d21318..8ac6b9ec7e 100644
--- a/tests/regressiontests/utils/datastructures.py
+++ b/tests/regressiontests/utils/datastructures.py
@@ -50,9 +50,3 @@
['second-two', 'one']
"""
-# Python 2.3 doesn't have sorted()
-try:
- sorted
-except NameError:
- from django.utils.itercompat import sorted
- \ No newline at end of file
diff --git a/tests/regressiontests/utils/tests.py b/tests/regressiontests/utils/tests.py
index cd4762e02f..485c5faa75 100644
--- a/tests/regressiontests/utils/tests.py
+++ b/tests/regressiontests/utils/tests.py
@@ -11,6 +11,13 @@ import datastructures
import itercompat
from decorators import DecoratorFromMiddlewareTests
+# We need this because "datastructures" uses sorted() and the tests are run in
+# the scope of this module.
+try:
+ sorted
+except NameError:
+ from django.utils.itercompat import sorted # For Python 2.3
+
# Extra tests
__test__ = {
'timesince': timesince,