summaryrefslogtreecommitdiff
path: root/tests/modeltests/m2m_through/models.py
blob: fa9fa714a5f0fba38428429952c8268127ca8c7d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
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>]
"""}