diff options
| author | Justin Bronn <jbronn@gmail.com> | 2008-08-05 17:15:33 +0000 |
|---|---|---|
| committer | Justin Bronn <jbronn@gmail.com> | 2008-08-05 17:15:33 +0000 |
| commit | aa239e3e5405933af6a29dac3cf587b59a099927 (patch) | |
| tree | ea2cbd139c9a8cf84c09e0b2008bff70e05927ef /tests/modeltests | |
| parent | 45b73c9a4685809236f84046cc7ffd32a50db958 (diff) | |
gis: Merged revisions 7981-8001,8003-8011,8013-8033,8035-8036,8038-8039,8041-8063,8065-8076,8078-8139,8141-8154,8156-8214 via svnmerge from trunk.archive/attic/gis
git-svn-id: http://code.djangoproject.com/svn/django/branches/gis@8215 bcc190cf-cafb-0310-a4f2-bffc1f526a37
Diffstat (limited to 'tests/modeltests')
| -rw-r--r-- | tests/modeltests/choices/models.py | 11 | ||||
| -rw-r--r-- | tests/modeltests/custom_methods/models.py | 3 | ||||
| -rw-r--r-- | tests/modeltests/delete/models.py | 31 | ||||
| -rw-r--r-- | tests/modeltests/generic_relations/models.py | 55 | ||||
| -rw-r--r-- | tests/modeltests/invalid_models/models.py | 65 | ||||
| -rw-r--r-- | tests/modeltests/m2m_through/__init__.py | 2 | ||||
| -rw-r--r-- | tests/modeltests/m2m_through/models.py | 337 | ||||
| -rw-r--r-- | tests/modeltests/many_to_one/models.py | 8 | ||||
| -rw-r--r-- | tests/modeltests/model_forms/models.py | 6 | ||||
| -rw-r--r-- | tests/modeltests/model_formsets/models.py | 56 | ||||
| -rw-r--r-- | tests/modeltests/pagination/models.py | 107 | ||||
| -rw-r--r-- | tests/modeltests/validation/models.py | 22 |
12 files changed, 581 insertions, 122 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.'] """} |
