diff --git a/tests/modeltests/m2m_through/models.py b/tests/modeltests/m2m_through/models.py index 16f303d02e..d41fe8d26d 100644 --- a/tests/modeltests/m2m_through/models.py +++ b/tests/modeltests/m2m_through/models.py @@ -63,275 +63,3 @@ 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() -[, ] - -# 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() -[, ] - -# 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 - - -# 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) -[, ] - - -### 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 m2m_through.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 -[, ] - -# 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 m2m_through.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() -[, ] - - -### 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 m2m_through.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 -[, ] - -# 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 m2m_through.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() -[, ] - -### 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() -[, ] - -# Bob should only be in one custom group. ->>> bob.custom.all() -[] - -# Let's make sure our new descriptors don't conflict with the FK related_name. ->>> bob.custom_person_related_name.all() -[] - -### 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() -[] - -# 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() -[] - -# 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() -[] - - - -### QUERY TESTS ### - -# We can query for the related model by using its attribute name (members, in -# this case). ->>> Group.objects.filter(members__name='Bob') -[] - -# 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.") -[] - -# 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") -[, ] - -# If the m2m field has specified a related_name, using that will work. ->>> Person.objects.filter(custom__name="Rock") -[, ] - -# 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.") -[] - -# 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) -[] - -# 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) -[, ] - -# 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)) -[, , ] - -# 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() -[, ] -"""} diff --git a/tests/modeltests/m2m_through/tests.py b/tests/modeltests/m2m_through/tests.py new file mode 100644 index 0000000000..807e952949 --- /dev/null +++ b/tests/modeltests/m2m_through/tests.py @@ -0,0 +1,343 @@ +from datetime import datetime +from operator import attrgetter + +from django.test import TestCase + +from models import Person, Group, Membership, CustomMembership, \ + TestNoDefaultsOrNulls, PersonSelfRefM2M, Friendship + + +class M2mThroughTests(TestCase): + def setUp(self): + self.bob = Person.objects.create(name='Bob') + self.jim = Person.objects.create(name='Jim') + self.jane = Person.objects.create(name='Jane') + self.rock = Group.objects.create(name='Rock') + self.roll = Group.objects.create(name='Roll') + + def test_m2m_through(self): + # We start out by making sure that the Group 'rock' has no members. + self.assertQuerysetEqual( + self.rock.members.all(), + [] + ) + # To make Jim a member of Group Rock, simply create a Membership object. + m1 = Membership.objects.create(person=self.jim, group=self.rock) + # We can do the same for Jane and Rock. + m2 = Membership.objects.create(person=self.jane, group=self.rock) + # Let's check to make sure that it worked. Jane and Jim should be members of Rock. + self.assertQuerysetEqual( + self.rock.members.all(), [ + 'Jane', + 'Jim' + ], + attrgetter("name") + ) + # Now we can add a bunch more Membership objects to test with. + m3 = Membership.objects.create(person=self.bob, group=self.roll) + m4 = Membership.objects.create(person=self.jim, group=self.roll) + m5 = Membership.objects.create(person=self.jane, group=self.roll) + # We can get Jim's Group membership as with any ForeignKey. + self.assertQuerysetEqual( + self.jim.group_set.all(), [ + 'Rock', + 'Roll' + ], + attrgetter("name") + ) + # Querying the intermediary model works like normal. + self.assertEqual( + repr(Membership.objects.get(person=self.jane, group=self.rock)), + '' + ) + # It's not only get that works. Filter works like normal as well. + self.assertQuerysetEqual( + Membership.objects.filter(person=self.jim), [ + '', + '' + ] + ) + self.rock.members.clear() + # Now there will be no members of Rock. + self.assertQuerysetEqual( + self.rock.members.all(), + [] + ) + + + + def test_forward_descriptors(self): + # Due to complications with adding via an intermediary model, + # the add method is not provided. + self.assertRaises(AttributeError, lambda: self.rock.members.add(self.bob)) + # Create is also disabled as it suffers from the same problems as add. + self.assertRaises(AttributeError, lambda: self.rock.members.create(name='Anne')) + # Remove has similar complications, and is not provided either. + self.assertRaises(AttributeError, lambda: self.rock.members.remove(self.jim)) + + m1 = Membership.objects.create(person=self.jim, group=self.rock) + m2 = Membership.objects.create(person=self.jane, group=self.rock) + + # Here we back up the list of all members of Rock. + backup = list(self.rock.members.all()) + # ...and we verify that it has worked. + self.assertEqual( + [p.name for p in backup], + ['Jane', 'Jim'] + ) + # The clear function should still work. + self.rock.members.clear() + # Now there will be no members of Rock. + self.assertQuerysetEqual( + self.rock.members.all(), + [] + ) + + # Assignment should not work with models specifying a through model for many of + # the same reasons as adding. + self.assertRaises(AttributeError, setattr, self.rock, "members", backup) + # Let's re-save those instances that we've cleared. + m1.save() + m2.save() + # Verifying that those instances were re-saved successfully. + self.assertQuerysetEqual( + self.rock.members.all(),[ + 'Jane', + 'Jim' + ], + attrgetter("name") + ) + + def test_reverse_descriptors(self): + # Due to complications with adding via an intermediary model, + # the add method is not provided. + self.assertRaises(AttributeError, lambda: self.bob.group_set.add(self.rock)) + # Create is also disabled as it suffers from the same problems as add. + self.assertRaises(AttributeError, lambda: self.bob.group_set.create(name="funk")) + # Remove has similar complications, and is not provided either. + self.assertRaises(AttributeError, lambda: self.jim.group_set.remove(self.rock)) + + m1 = Membership.objects.create(person=self.jim, group=self.rock) + m2 = Membership.objects.create(person=self.jim, group=self.roll) + + # Here we back up the list of all of Jim's groups. + backup = list(self.jim.group_set.all()) + self.assertEqual( + [g.name for g in backup], + ['Rock', 'Roll'] + ) + # The clear function should still work. + self.jim.group_set.clear() + # Now Jim will be in no groups. + self.assertQuerysetEqual( + self.jim.group_set.all(), + [] + ) + # Assignment should not work with models specifying a through model for many of + # the same reasons as adding. + self.assertRaises(AttributeError, setattr, self.jim, "group_set", backup) + # Let's re-save those instances that we've cleared. + + m1.save() + m2.save() + # Verifying that those instances were re-saved successfully. + self.assertQuerysetEqual( + self.jim.group_set.all(),[ + 'Rock', + 'Roll' + ], + attrgetter("name") + ) + + def test_custom_tests(self): + # Let's see if we can query through our second relationship. + self.assertQuerysetEqual( + self.rock.custom_members.all(), + [] + ) + # We can query in the opposite direction as well. + self.assertQuerysetEqual( + self.bob.custom.all(), + [] + ) + + cm1 = CustomMembership.objects.create(person=self.bob, group=self.rock) + cm2 = CustomMembership.objects.create(person=self.jim, group=self.rock) + + # If we get the number of people in Rock, it should be both Bob and Jim. + self.assertQuerysetEqual( + self.rock.custom_members.all(),[ + 'Bob', + 'Jim' + ], + attrgetter("name") + ) + # Bob should only be in one custom group. + self.assertQuerysetEqual( + self.bob.custom.all(),[ + 'Rock' + ], + attrgetter("name") + ) + # Let's make sure our new descriptors don't conflict with the FK related_name. + self.assertQuerysetEqual( + self.bob.custom_person_related_name.all(),[ + '' + ] + ) + + def test_self_referential_tests(self): + # Let's first create a person who has no friends. + tony = PersonSelfRefM2M.objects.create(name="Tony") + self.assertQuerysetEqual( + tony.friends.all(), + [] + ) + + 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. + self.assertQuerysetEqual( + tony.friends.all(),[ + 'Chris' + ], + attrgetter("name") + ) + # But we haven't established that Chris is Tony's Friend. + self.assertQuerysetEqual( + chris.friends.all(), + [] + ) + 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. + self.assertQuerysetEqual( + chris.friends.all(),[ + 'Tony' + ], + attrgetter("name") + ) + + # Chris gets mad and wants to get rid of all of his friends. + chris.friends.clear() + # Now he should not have any more friends. + self.assertQuerysetEqual( + chris.friends.all(), + [] + ) + # Since this isn't a symmetrical relation, Tony's friend link still exists. + self.assertQuerysetEqual( + tony.friends.all(),[ + 'Chris' + ], + attrgetter("name") + ) + + def test_query_tests(self): + m1 = Membership.objects.create(person=self.jim, group=self.rock) + m2 = Membership.objects.create(person=self.jane, group=self.rock) + m3 = Membership.objects.create(person=self.bob, group=self.roll) + m4 = Membership.objects.create(person=self.jim, group=self.roll) + m5 = Membership.objects.create(person=self.jane, group=self.roll) + + m2.invite_reason = "She was just awesome." + m2.date_joined = datetime(2006, 1, 1) + m2.save() + m3.date_joined = datetime(2004, 1, 1) + m3.save() + m5.date_joined = datetime(2004, 1, 1) + m5.save() + + # We can query for the related model by using its attribute name (members, in + # this case). + self.assertQuerysetEqual( + Group.objects.filter(members__name='Bob'),[ + 'Roll' + ], + attrgetter("name") + ) + + # To query through the intermediary model, we specify its model name. + # In this case, membership. + self.assertQuerysetEqual( + Group.objects.filter(membership__invite_reason="She was just awesome."),[ + 'Rock' + ], + attrgetter("name") + ) + + # If we want to query in the reverse direction by the related model, use its + # model name (group, in this case). + self.assertQuerysetEqual( + Person.objects.filter(group__name="Rock"),[ + 'Jane', + 'Jim' + ], + attrgetter("name") + ) + + cm1 = CustomMembership.objects.create(person=self.bob, group=self.rock) + cm2 = CustomMembership.objects.create(person=self.jim, group=self.rock) + # If the m2m field has specified a related_name, using that will work. + self.assertQuerysetEqual( + Person.objects.filter(custom__name="Rock"),[ + 'Bob', + 'Jim' + ], + attrgetter("name") + ) + + # To query through the intermediary model in the reverse direction, we again + # specify its model name (membership, in this case). + self.assertQuerysetEqual( + Person.objects.filter(membership__invite_reason="She was just awesome."),[ + 'Jane' + ], + attrgetter("name") + ) + + # Let's see all of the groups that Jane joined after 1 Jan 2005: + self.assertQuerysetEqual( + Group.objects.filter(membership__date_joined__gt=datetime(2005, 1, 1), membership__person=self.jane),[ + 'Rock' + ], + attrgetter("name") + ) + + # Queries also work in the reverse direction: Now let's see all of the people + # that have joined Rock since 1 Jan 2005: + self.assertQuerysetEqual( + Person.objects.filter(membership__date_joined__gt=datetime(2005, 1, 1), membership__group=self.rock),[ + 'Jane', + 'Jim' + ], + attrgetter("name") + ) + + # 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: + self.assertQuerysetEqual( + Person.objects.filter(membership__date_joined__gt=datetime(2004, 1, 1)),[ + 'Jane', + 'Jim', + 'Jim' + ], + attrgetter("name") + ) + + # Jim showed up twice, because he joined two groups ('Rock', and 'Roll'): + self.assertEqual( + [(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. + self.assertQuerysetEqual( + Person.objects.filter(membership__date_joined__gt=datetime(2004, 1, 1)).distinct(),[ + 'Jane', + 'Jim' + ], + attrgetter("name") + )