mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	git-svn-id: http://code.djangoproject.com/svn/django/trunk@8166 bcc190cf-cafb-0310-a4f2-bffc1f526a37
		
			
				
	
	
		
			337 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			337 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| 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>]
 | |
| """} |