Ticket #6095: 6095-trunk-withdocs.rc3.diff
File 6095-trunk-withdocs.rc3.diff, 32.4 KB (added by , 16 years ago) |
---|
-
django/db/models/options.py
10 10 from django.db.models.fields.related import ManyToManyRel 11 11 from django.db.models.fields import AutoField, FieldDoesNotExist 12 12 from django.db.models.fields.proxy import OrderWrt 13 from django.db.models.loading import get_models, app_cache_ready13 from django.db.models.loading import get_models, get_model, app_cache_ready 14 14 from django.db.models import Manager 15 15 from django.utils.translation import activate, deactivate_all, get_language, string_concat 16 16 from django.utils.encoding import force_unicode, smart_str … … 402 402 follow = self.get_follow() 403 403 return [f for f in self.get_all_related_objects() if follow.get(f.name, None)] 404 404 405 def get_related_object(self, from_model, self_ref=False): 406 "Gets the RelatedObject which links from from_model to this model." 407 for related_object in self.get_all_related_objects(): 408 if related_object.model == from_model: 409 if self_ref: 410 self_ref = False 411 else: 412 return related_object 413 return None 414 405 415 def get_data_holders(self, follow=None): 406 416 if follow == None: 407 417 follow = self.get_follow() -
django/db/models/fields/related.py
340 340 manager.clear() 341 341 manager.add(*value) 342 342 343 def create_many_related_manager(superclass ):343 def create_many_related_manager(superclass, through=False): 344 344 """Creates a manager that subclasses 'superclass' (which is a Manager) 345 345 and adds behavior for many-to-many related objects.""" 346 346 class ManyRelatedManager(superclass): … … 354 354 self.join_table = join_table 355 355 self.source_col_name = source_col_name 356 356 self.target_col_name = target_col_name 357 self.through = through 357 358 self._pk_val = self.instance._get_pk_val() 358 359 if self._pk_val is None: 359 360 raise ValueError("%r instance needs to have a primary key value before a many-to-many relationship can be used." % instance.__class__.__name__) … … 386 387 clear.alters_data = True 387 388 388 389 def create(self, **kwargs): 390 # This check needs to be done here, since we can't later remove this 391 # from the method lookup table, as we do with add and remove. 392 if through is not None: 393 raise AttributeError, "'ManyRelatedManager' object has no attribute 'create'" 389 394 new_obj = self.model(**kwargs) 390 395 new_obj.save() 391 396 self.add(new_obj) … … 453 458 [self._pk_val]) 454 459 transaction.commit_unless_managed() 455 460 461 # If it's an intermediary model, detach the add and remove methods from this 462 # ManyRelatedManager. Create cannot be detached this way due to an 463 # inherited method in the dynamic method lookup table that gets in the way. 464 if through is not None: 465 del ManyRelatedManager.add 466 del ManyRelatedManager.remove 467 456 468 return ManyRelatedManager 457 469 458 470 class ManyRelatedObjectsDescriptor(object): … … 473 485 # model's default manager. 474 486 rel_model = self.related.model 475 487 superclass = rel_model._default_manager.__class__ 476 RelatedManager = create_many_related_manager(superclass )488 RelatedManager = create_many_related_manager(superclass, self.related.field.rel.through) 477 489 478 490 qn = connection.ops.quote_name 479 491 manager = RelatedManager( … … 492 504 if instance is None: 493 505 raise AttributeError, "Manager must be accessed via instance" 494 506 507 through = getattr(self.related.field.rel, 'through', None) 508 if through is not None: 509 raise AttributeError, "Cannot set values on a ManyToManyField which specifies an intermediary model. Use %s's Manager instead." % through 510 495 511 manager = self.__get__(instance) 496 512 manager.clear() 497 513 manager.add(*value) … … 514 530 # model's default manager. 515 531 rel_model=self.field.rel.to 516 532 superclass = rel_model._default_manager.__class__ 517 RelatedManager = create_many_related_manager(superclass )533 RelatedManager = create_many_related_manager(superclass, self.field.rel.through) 518 534 519 535 qn = connection.ops.quote_name 520 536 manager = RelatedManager( … … 533 549 if instance is None: 534 550 raise AttributeError, "Manager must be accessed via instance" 535 551 552 through = getattr(self.field.rel, 'through', None) 553 if through is not None: 554 raise AttributeError, "Cannot set values on a ManyToManyField which specifies an intermediary model. Use %s's Manager instead." % through 555 536 556 manager = self.__get__(instance) 537 557 manager.clear() 538 558 manager.add(*value) … … 586 606 587 607 class ManyToManyRel(object): 588 608 def __init__(self, to, num_in_admin=0, related_name=None, 589 filter_interface=None, limit_choices_to=None, raw_id_admin=False, symmetrical=True ):609 filter_interface=None, limit_choices_to=None, raw_id_admin=False, symmetrical=True, through=None): 590 610 self.to = to 591 611 self.num_in_admin = num_in_admin 592 612 self.related_name = related_name … … 598 618 self.raw_id_admin = raw_id_admin 599 619 self.symmetrical = symmetrical 600 620 self.multiple = True 621 self.through = through 622 self.through_app_label = None 623 self._model_cache = None 601 624 602 625 assert not (self.raw_id_admin and self.filter_interface), "ManyToManyRels may not use both raw_id_admin and filter_interface" 603 626 627 def _get_through_model(self): 628 if self._model_cache: 629 return self._model_cache 630 if self.through and self.through_app_label: 631 self._model_cache = get_model(self.through_app_label, self.through) 632 return self._model_cache 633 return None 634 through_model = property(_get_through_model) 635 604 636 class ForeignKey(RelatedField, Field): 605 637 empty_strings_allowed = False 606 638 def __init__(self, to, to_field=None, rel_class=ManyToOneRel, **kwargs): … … 746 778 filter_interface=kwargs.pop('filter_interface', None), 747 779 limit_choices_to=kwargs.pop('limit_choices_to', None), 748 780 raw_id_admin=kwargs.pop('raw_id_admin', False), 749 symmetrical=kwargs.pop('symmetrical', True)) 781 symmetrical=kwargs.pop('symmetrical', True), 782 through=kwargs.pop('through', None)) 750 783 self.db_table = kwargs.pop('db_table', None) 784 if kwargs['rel'].through is not None: 785 self.creates_table = False 786 assert self.db_table is None, "Cannot specify a db_table if an intermediary model is used." 787 else: 788 self.creates_table = True 751 789 if kwargs["rel"].raw_id_admin: 752 790 kwargs.setdefault("validator_list", []).append(self.isValidIDList) 753 791 Field.__init__(self, **kwargs) … … 770 808 771 809 def _get_m2m_db_table(self, opts): 772 810 "Function that can be curried to provide the m2m table name for this relation" 773 if self.db_table: 811 if self.rel.through is not None: 812 return self.rel.through_model._meta.db_table 813 elif self.db_table: 774 814 return self.db_table 775 815 else: 776 816 return '%s_%s' % (opts.db_table, self.name) 777 817 778 818 def _get_m2m_column_name(self, related): 779 819 "Function that can be curried to provide the source column name for the m2m table" 820 if self.rel.through is not None: 821 field = related.model._meta.get_related_object(self.rel.through_model).field 822 return field.get_attname_column()[1] 780 823 # If this is an m2m relation to self, avoid the inevitable name clash 781 if related.model == related.parent_model:824 elif related.model == related.parent_model: 782 825 return 'from_' + related.model._meta.object_name.lower() + '_id' 783 826 else: 784 827 return related.model._meta.object_name.lower() + '_id' 785 828 786 829 def _get_m2m_reverse_name(self, related): 787 830 "Function that can be curried to provide the related column name for the m2m table" 831 if self.rel.through is not None: 832 meta = related.parent_model._meta 833 if self.parent == self.rel.to: 834 related = meta.get_related_object(self.rel.through_model, self_ref = True) 835 else: 836 related = meta.get_related_object(self.rel.through_model) 837 return related.field.get_attname_column()[1] 788 838 # If this is an m2m relation to self, avoid the inevitable name clash 789 if related.model == related.parent_model:839 elif related.model == related.parent_model: 790 840 return 'to_' + related.parent_model._meta.object_name.lower() + '_id' 791 841 else: 792 842 return related.parent_model._meta.object_name.lower() + '_id' … … 832 882 833 883 # Set up the accessor for the m2m table name for the relation 834 884 self.m2m_db_table = curry(self._get_m2m_db_table, cls._meta) 885 886 # Get a reference to the parent model 887 self.parent = cls 888 889 # Populate some necessary rel arguments so that cross-app relations 890 # work correctly. 891 if isinstance(self.rel.through, basestring): 892 try: 893 self.rel.through_app_label, self.rel.through = self.rel.through.split('.') 894 except ValueError: 895 self.rel.through_app_label = cls._meta.app_label 835 896 836 897 if isinstance(self.rel.to, basestring): 837 898 target = self.rel.to -
django/core/management/validation.py
104 104 if r.get_accessor_name() == rel_query_name: 105 105 e.add(opts, "Reverse query name for field '%s' clashes with related field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name)) 106 106 107 seen_intermediary_signatures = [] 107 108 for i, f in enumerate(opts.local_many_to_many): 108 109 # Check to see if the related m2m field will clash with any 109 110 # existing fields, m2m fields, m2m related objects or related … … 114 115 # so skip the next section 115 116 if isinstance(f.rel.to, (str, unicode)): 116 117 continue 117 118 if getattr(f.rel, 'through', None) is not None: 119 intermediary_model = None 120 for model in models.get_models(): 121 if model._meta.module_name == f.rel.through.lower(): 122 intermediary_model = model 123 if intermediary_model is None: 124 e.add(opts, "%s has a manually-defined m2m relation through model %s, which does not exist." % (f.name, f.rel.through)) 125 else: 126 signature = (f.rel.to, cls, intermediary_model) 127 if signature in seen_intermediary_signatures: 128 e.add(opts, "%s has two manually-defined m2m relations through model %s, which is not possible. Please consider using an extra field on your intermediary model instead." % (cls._meta.object_name, intermediary_model._meta.object_name)) 129 else: 130 seen_intermediary_signatures.append(signature) 131 seen_related_fk, seen_this_fk = False, False 132 for field in intermediary_model._meta.fields: 133 if field.rel: 134 if not seen_related_fk and field.rel.to == f.rel.to: 135 seen_related_fk = True 136 elif field.rel.to == cls: 137 seen_this_fk = True 138 if not seen_related_fk or not seen_this_fk: 139 e.add(opts, "%s has a manually-defined m2m relation through model %s, which does not have foreign keys to %s and %s" % (f.name, f.rel.through, f.rel.to._meta.object_name, cls._meta.object_name)) 140 118 141 rel_opts = f.rel.to._meta 119 142 rel_name = RelatedObject(f.rel.to, cls, f).get_accessor_name() 120 143 rel_query_name = f.related_query_name() -
django/core/management/sql.py
353 353 qn = connection.ops.quote_name 354 354 inline_references = connection.features.inline_fk_references 355 355 for f in opts.local_many_to_many: 356 if not isinstance(f.rel, generic.GenericRel):356 if f.creates_table: 357 357 tablespace = f.db_tablespace or opts.db_tablespace 358 358 if tablespace and connection.features.supports_tablespaces: 359 359 tablespace_sql = ' ' + connection.ops.tablespace_sql(tablespace, inline=True) -
django/contrib/contenttypes/generic.py
104 104 limit_choices_to=kwargs.pop('limit_choices_to', None), 105 105 symmetrical=kwargs.pop('symmetrical', True)) 106 106 107 # By its very nature, a GenericRelation doesn't create a table. 108 self.creates_table = False 109 107 110 # Override content-type/object-id field names on the related class 108 111 self.object_id_field_name = kwargs.pop("object_id_field", "object_id") 109 112 self.content_type_field_name = kwargs.pop("content_type_field", "content_type") -
tests/modeltests/invalid_models/models.py
111 111 class MissingRelations(models.Model): 112 112 rel1 = models.ForeignKey("Rel1") 113 113 rel2 = models.ManyToManyField("Rel2") 114 115 class MissingManualM2MModel(models.Model): 116 name = models.CharField(max_length=5) 117 missing_m2m = models.ManyToManyField(Model, through="MissingM2MModel") 118 119 class Person(models.Model): 120 name = models.CharField(max_length=5) 114 121 122 class Group(models.Model): 123 name = models.CharField(max_length=5) 124 primary = models.ManyToManyField(Person, through="Membership", related_name="primary") 125 secondary = models.ManyToManyField(Person, through="Membership", related_name="secondary") 126 127 class GroupTwo(models.Model): 128 name = models.CharField(max_length=5) 129 primary = models.ManyToManyField(Person, through="Membership") 130 secondary = models.ManyToManyField(Group, through="MembershipMissingFK") 131 132 class Membership(models.Model): 133 person = models.ForeignKey(Person) 134 group = models.ForeignKey(Group) 135 not_default_or_null = models.CharField(max_length=5) 136 137 class MembershipMissingFK(models.Model): 138 person = models.ForeignKey(Person) 139 140 class PersonSelfRefM2M(models.Model): 141 name = models.CharField(max_length=5) 142 friends = models.ManyToManyField('self', through="Relationship") 143 144 class Relationship(models.Model): 145 first = models.ForeignKey(PersonSelfRefM2M, related_name="rel_from_set") 146 second = models.ForeignKey(PersonSelfRefM2M, related_name="rel_to_set") 147 date_added = models.DateTimeField() 148 115 149 model_errors = """invalid_models.fielderrors: "charfield": CharFields require a "max_length" attribute. 116 150 invalid_models.fielderrors: "decimalfield": DecimalFields require a "decimal_places" attribute. 117 151 invalid_models.fielderrors: "decimalfield": DecimalFields require a "max_digits" attribute. … … 197 231 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'. 198 232 invalid_models.missingrelations: 'rel2' has m2m relation with model Rel2, which has not been installed 199 233 invalid_models.missingrelations: 'rel1' has relation with model Rel1, which has not been installed 234 invalid_models.group: Group has two manually-defined m2m relations through model Membership, which is not possible. Please consider using an extra field on your intermediary model instead. 235 invalid_models.missingmanualm2mmodel: missing_m2m has a manually-defined m2m relation through model MissingM2MModel, which does not exist. 236 invalid_models.grouptwo: primary has a manually-defined m2m relation through model Membership, which does not have foreign keys to Person and GroupTwo 237 invalid_models.grouptwo: secondary has a manually-defined m2m relation through model MembershipMissingFK, which does not have foreign keys to Group and GroupTwo 200 238 """ -
tests/modeltests/m2m_through/__init__.py
1 -
tests/modeltests/m2m_through/models.py
1 from django.db import models 2 from datetime import datetime 3 4 # M2M described on one of the models 5 class Person(models.Model): 6 name = models.CharField(max_length=128) 7 8 def __unicode__(self): 9 return self.name 10 11 class Group(models.Model): 12 name = models.CharField(max_length=128) 13 members = models.ManyToManyField(Person, through='Membership') 14 custom_members = models.ManyToManyField(Person, through='CustomMembership', related_name="custom") 15 nodefaultsnonulls = models.ManyToManyField(Person, through='TestNoDefaultsOrNulls', related_name="testnodefaultsnonulls") 16 17 def __unicode__(self): 18 return self.name 19 20 class Membership(models.Model): 21 person = models.ForeignKey(Person) 22 group = models.ForeignKey(Group) 23 date_joined = models.DateTimeField(default=datetime.now) 24 invite_reason = models.CharField(max_length=64, null=True) 25 26 def __unicode__(self): 27 return "%s is a member of %s" % (self.person.name, self.group.name) 28 29 class CustomMembership(models.Model): 30 person = models.ForeignKey(Person, db_column="custom_person_column", related_name="custom_person_related_name") 31 group = models.ForeignKey(Group) 32 weird_fk = models.ForeignKey(Membership, null=True) 33 date_joined = models.DateTimeField(default=datetime.now) 34 35 def __unicode__(self): 36 return "%s is a member of %s" % (self.person.name, self.group.name) 37 38 class Meta: 39 db_table = "test_table" 40 41 class TestNoDefaultsOrNulls(models.Model): 42 person = models.ForeignKey(Person) 43 group = models.ForeignKey(Group) 44 nodefaultnonull = models.CharField(max_length=5) 45 46 class PersonSelfRefM2M(models.Model): 47 name = models.CharField(max_length=5) 48 friends = models.ManyToManyField('self', through="Friendship") 49 50 def __unicode__(self): 51 return self.name 52 53 class Friendship(models.Model): 54 first = models.ForeignKey(PersonSelfRefM2M, related_name="rel_from_set") 55 second = models.ForeignKey(PersonSelfRefM2M, related_name="rel_to_set") 56 date_friended = models.DateTimeField() 57 58 __test__ = {'API_TESTS':""" 59 >>> from datetime import datetime 60 61 ### Creation and Saving Tests ### 62 63 >>> bob = Person.objects.create(name = 'Bob') 64 >>> jim = Person.objects.create(name = 'Jim') 65 >>> jane = Person.objects.create(name = 'Jane') 66 >>> rock = Group.objects.create(name = 'Rock') 67 >>> roll = Group.objects.create(name = 'Roll') 68 69 # We start out by making sure that the Group 'rock' has no members. 70 >>> rock.members.all() 71 [] 72 73 # To make Jim a member of Group Rock, simply create a Membership object. 74 >>> m1 = Membership.objects.create(person = jim, group = rock) 75 76 # We can do the same for Jane and Rock. 77 >>> m2 = Membership.objects.create(person = jane, group = rock) 78 79 # Let's check to make sure that it worked. Jane and Jim should be members of Rock. 80 >>> rock.members.all() 81 [<Person: Jim>, <Person: Jane>] 82 83 # Now we can add a bunch more Membership objects to test with. 84 >>> m3 = Membership.objects.create(person = bob, group = roll) 85 >>> m4 = Membership.objects.create(person = jim, group = roll) 86 >>> m5 = Membership.objects.create(person = jane, group = roll) 87 88 # We can get Jim's Group membership as with any ForeignKey. 89 >>> jim.group_set.all() 90 [<Group: Rock>, <Group: Roll>] 91 92 # Querying the intermediary model works like normal. 93 # In this case we get Jane's membership to Rock. 94 >>> m = Membership.objects.get(person = jane, group = rock) 95 >>> m 96 <Membership: Jane is a member of Rock> 97 98 # Now we set some date_joined dates for further testing. 99 >>> m2.invite_reason = "She was just awesome." 100 >>> m2.date_joined = datetime(2006, 1, 1) 101 >>> m2.save() 102 103 >>> m5.date_joined = datetime(2004, 1, 1) 104 >>> m5.save() 105 106 >>> m3.date_joined = datetime(2004, 1, 1) 107 >>> m3.save() 108 109 # It's not only get that works. Filter works like normal as well. 110 >>> Membership.objects.filter(person = jim) 111 [<Membership: Jim is a member of Rock>, <Membership: Jim is a member of Roll>] 112 113 114 ### Forward Descriptors Tests ### 115 116 # Due to complications with adding via an intermediary model, the add method is 117 # not provided. 118 >>> rock.members.add(bob) 119 Traceback (most recent call last): 120 ... 121 AttributeError: 'ManyRelatedManager' object has no attribute 'add' 122 123 >>> rock.members.create(name = 'Anne') 124 Traceback (most recent call last): 125 ... 126 AttributeError: 'ManyRelatedManager' object has no attribute 'create' 127 128 # Remove has similar complications, and is not provided either. 129 >>> rock.members.remove(jim) 130 Traceback (most recent call last): 131 ... 132 AttributeError: 'ManyRelatedManager' object has no attribute 'remove' 133 134 # Here we back up the list of all members of Rock. 135 >>> backup = list(rock.members.all()) 136 137 # ...and we verify that it has worked. 138 >>> backup 139 [<Person: Jim>, <Person: Jane>] 140 141 # The clear function should still work. 142 >>> rock.members.clear() 143 144 # Now there will be no members of Rock. 145 >>> rock.members.all() 146 [] 147 148 # Assignment should not work with models specifying a through model for many of 149 # the same reasons as adding. 150 >>> rock.members = backup 151 Traceback (most recent call last): 152 ... 153 AttributeError: Cannot set values on a ManyToManyField which specifies an intermediary model. Use Membership's Manager instead. 154 155 # Let's re-save those instances that we've cleared. 156 >>> m1.save() 157 >>> m2.save() 158 159 # Verifying that those instances were re-saved successfully. 160 >>> rock.members.all() 161 [<Person: Jim>, <Person: Jane>] 162 163 164 ### Reverse Descriptors Tests ### 165 166 # Due to complications with adding via an intermediary model, the add method is 167 # not provided. 168 >>> bob.group_set.add(rock) 169 Traceback (most recent call last): 170 ... 171 AttributeError: 'ManyRelatedManager' object has no attribute 'add' 172 173 # Create is another method that should not work correctly, as it suffers from 174 # the same problems as add. 175 >>> bob.group_set.create(name = 'Funk') 176 Traceback (most recent call last): 177 ... 178 AttributeError: 'ManyRelatedManager' object has no attribute 'create' 179 180 # Remove has similar complications, and is not provided either. 181 >>> jim.group_set.remove(rock) 182 Traceback (most recent call last): 183 ... 184 AttributeError: 'ManyRelatedManager' object has no attribute 'remove' 185 186 # Here we back up the list of all of Jim's groups. 187 >>> backup = list(jim.group_set.all()) 188 >>> backup 189 [<Group: Rock>, <Group: Roll>] 190 191 # The clear function should still work. 192 >>> jim.group_set.clear() 193 194 # Now Jim will be in no groups. 195 >>> jim.group_set.all() 196 [] 197 198 # Assignment should not work with models specifying a through model for many of 199 # the same reasons as adding. 200 >>> jim.group_set = backup 201 Traceback (most recent call last): 202 ... 203 AttributeError: Cannot set values on a ManyToManyField which specifies an intermediary model. Use Membership's Manager instead. 204 205 # Let's re-save those instances that we've cleared. 206 >>> m1.save() 207 >>> m4.save() 208 209 # Verifying that those instances were re-saved successfully. 210 >>> jim.group_set.all() 211 [<Group: Rock>, <Group: Roll>] 212 213 ### Custom Tests ### 214 215 # Let's see if we can query through our second relationship. 216 >>> rock.custom_members.all() 217 [] 218 219 # We can query in the opposite direction as well. 220 >>> bob.custom.all() 221 [] 222 223 # Let's create some membership objects in this custom relationship. 224 >>> cm1 = CustomMembership.objects.create(person = bob, group = rock) 225 >>> cm2 = CustomMembership.objects.create(person = jim, group = rock) 226 227 # If we get the number of people in Rock, it should be both Bob and Jim. 228 >>> rock.custom_members.all() 229 [<Person: Bob>, <Person: Jim>] 230 231 # Bob should only be in one custom group. 232 >>> bob.custom.all() 233 [<Group: Rock>] 234 235 # Let's make sure our new descriptors don't conflict with the FK related_name. 236 >>> bob.custom_person_related_name.all() 237 [<CustomMembership: Bob is a member of Rock>] 238 239 ### SELF-REFERENTIAL TESTS ### 240 241 # Let's first create a person who has no friends. 242 >>> tony = PersonSelfRefM2M.objects.create(name="Tony") 243 >>> tony.friends.all() 244 [] 245 246 # Now let's create another person for Tony to be friends with. 247 >>> chris = PersonSelfRefM2M.objects.create(name="Chris") 248 >>> f = Friendship.objects.create(first=tony, second=chris, 249 ... date_friended=datetime.now()) 250 251 # Tony should now show that Chris is his friend. 252 >>> tony.friends.all() 253 [<PersonSelfRefM2M: Chris>] 254 255 # But we haven't established that Chris is Tony's Friend. 256 >>> chris.friends.all() 257 [] 258 259 # So let's do that now. 260 >>> f2 = Friendship.objects.create(first=chris, second=tony, 261 ... date_friended=datetime.now()) 262 263 # Having added Chris as a friend, let's make sure that his friend set reflects 264 # that addition. 265 >>> chris.friends.all() 266 [<PersonSelfRefM2M: Tony>] 267 268 # Chris gets mad and wants to get rid of all of his friends. 269 >>> chris.friends.clear() 270 271 # Now he should not have any more friends. 272 >>> chris.friends.all() 273 [] 274 275 # Since this is a symmetrical relation, Tony's friend link is deleted as well. 276 >>> tony.friends.all() 277 [] 278 279 280 281 ### QUERY TESTS ### 282 283 # We can query for the related model by using its attribute name (members, in 284 # this case). 285 >>> Group.objects.filter(members__name='Bob') 286 [<Group: Roll>] 287 288 # To query through the intermediary model, we specify its model name. 289 # In this case, membership. 290 >>> Group.objects.filter(membership__invite_reason = "She was just awesome.") 291 [<Group: Rock>] 292 293 # If we want to query in the reverse direction by the related model, use its 294 # model name (group, in this case). 295 >>> Person.objects.filter(group__name="Rock") 296 [<Person: Jim>, <Person: Jane>] 297 298 # If the m2m field has specified a related_name, using that will work. 299 >>> Person.objects.filter(custom__name="Rock") 300 [<Person: Bob>, <Person: Jim>] 301 302 # To query through the intermediary model in the reverse direction, we again 303 # specify its model name (membership, in this case). 304 >>> Person.objects.filter(membership__invite_reason = "She was just awesome.") 305 [<Person: Jane>] 306 307 # Let's see all of the groups that Jane joined after 1 Jan 2005: 308 >>> Group.objects.filter(membership__date_joined__gt = datetime(2005, 1, 1), 309 ... membership__person = jane) 310 [<Group: Rock>] 311 312 # Queries also work in the reverse direction: Now let's see all of the people 313 # that have joined Rock since 1 Jan 2005: 314 >>> Person.objects.filter(membership__date_joined__gt = datetime(2005, 1, 1), 315 ... membership__group = rock) 316 [<Person: Jim>, <Person: Jane>] 317 318 # Conceivably, queries through membership could return correct, but non-unique 319 # querysets. To demonstrate this, we query for all people who have joined a 320 # group after 2004: 321 >>> Person.objects.filter(membership__date_joined__gt = datetime(2004, 1, 1)) 322 [<Person: Jim>, <Person: Jim>, <Person: Jane>] 323 324 # Jim showed up twice, because he joined two groups ('Rock', and 'Roll'): 325 >>> [(m.person.name, m.group.name) for m in 326 ... Membership.objects.filter(date_joined__gt = datetime(2004, 1, 1))] 327 [(u'Jim', u'Rock'), (u'Jane', u'Rock'), (u'Jim', u'Roll')] 328 329 # QuerySet's distinct() method can correct this problem. 330 >>> Person.objects.filter(membership__date_joined__gt = datetime(2004, 1, 1)).distinct() 331 [<Person: Jim>, <Person: Jane>] 332 """} 333 No newline at end of file -
AUTHORS
151 151 J. Pablo Fernandez <pupeno@pupeno.com> 152 152 Matthew Flanagan <http://wadofstuff.blogspot.com> 153 153 Eric Floehr <eric@intellovations.com> 154 Eric Florenzano <floguy@gmail.com> 154 155 Vincent Foley <vfoleybourgon@yahoo.ca> 155 156 Rudolph Froger <rfroger@estrate.nl> 156 157 Jorge Gajon <gajon@gajon.org> -
docs/model-api.txt
991 991 992 992 ======================= ============================================================ 993 993 994 Extra fields on many-to-many relationships 995 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 996 997 When you're only dealing with mixing and matching pizzas and toppings, a standard 998 ``ManyToManyField`` works great. For many situations, however, some extra 999 fields are required on the relationships between models. For situations like 1000 this, Django allows for the specification of an intermediary many-to-many model. 1001 To enable this functionality, simply specify a ``through`` keyword argument onto 1002 the ``ManyToManyField``. This is best illustrated with an example:: 1003 1004 class Person(models.Model): 1005 # ... 1006 name = models.CharField(max_length=128) 1007 1008 def __unicode__(self): 1009 return self.name 1010 1011 class Group(models.Model): 1012 # ... 1013 name = models.CharField(max_length=128) 1014 members = models.ManyToManyField(Person, through='Membership') 1015 1016 def __unicode__(self): 1017 return self.name 1018 1019 class Membership(models.Model): 1020 person = models.ForeignKey(Person) 1021 group = models.ForeignKey(Group) 1022 date_joined = models.DateTimeField() 1023 invite_reason = models.CharField(max_length=64) 1024 1025 .. note:: 1026 1027 The intermediary model must provide foreign keys to both of the models in 1028 the relation. This explicit declaration makes it clear how two models are 1029 related. More importantly, perhaps, these foreign keys also provide access 1030 to the intermediary model from either of the other models in the relation 1031 (as with any other foreign key). 1032 1033 Now that you have set up your ``ManyToManyField`` to use your intermediary 1034 model (Membership, in this case), you're ready to use the convenience methods 1035 provided by that ``ManyToManyField``. Here's an example of how you can query 1036 for and use these models:: 1037 1038 >>> ringo = Person.objects.create(name="Ringo Starr") 1039 >>> paul = Person.objects.create(name="Paul McCartney") 1040 >>> beatles = Group.objects.create(name="The Beatles") 1041 >>> m1 = Membership.objects.create(person=ringo, group=beatles, 1042 ... date_joined=datetime(1962, 8, 16), 1043 ... invite_reason= "Needed a new drummer.") 1044 >>> beatles.members.all() 1045 [<Person: Ringo Starr>] 1046 >>> ringo.group_set.all() 1047 [<Group: The Beatles>] 1048 >>> m2 = Membership.objects.create(person=paul, group=beatles, 1049 ... date_joined=datetime(1960, 8, 1), 1050 ... invite_reason= "Wanted to form a band.") 1051 >>> beatles.members.all() 1052 [<Person: Ringo Starr>, <Person: Paul McCartney>] 1053 1054 As you can see, creating ``Membership`` objects automatically adds the 1055 ``Person`` objects to the ``beatles.members`` queryset. This means that you 1056 can do anything that you would do on a normal queryset, like ``filter`` or 1057 ``exclude``. 1058 1059 .. note:: 1060 1061 As soon as an intermediary model is specified, the ``add`` and 1062 ``remove`` methods become unavailable on the descriptors added by the 1063 ``ManyToManyField``. For example, something like 1064 ``beatles.members.add(paul)`` will no longer work. 1065 1066 For more examples and ideas on how to work with intermediary models, 1067 `see the tests`_. 1068 1069 .. _`see the tests`: http://code.djangoproject.com/browser/django/trunk/tests/modeltests/m2m_through/models.py 1070 994 1071 One-to-one relationships 995 1072 ~~~~~~~~~~~~~~~~~~~~~~~~ 996 1073