Ticket #6095: 6095-r8068.diff
File 6095-r8068.diff, 34.9 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.utils.translation import activate, deactivate_all, get_language, string_concat 15 15 from django.utils.encoding import force_unicode, smart_str 16 16 from django.utils.datastructures import SortedDict … … 401 401 follow = self.get_follow() 402 402 return [f for f in self.get_all_related_objects() if follow.get(f.name, None)] 403 403 404 def get_related_object(self, from_model, self_ref=False): 405 "Gets the RelatedObject which links from from_model to this model." 406 for related_object in self.get_all_related_objects(): 407 if related_object.model == from_model: 408 if self_ref: 409 self_ref = False 410 else: 411 return related_object 412 return None 413 404 414 def get_data_holders(self, follow=None): 405 415 if follow == None: 406 416 follow = self.get_follow() -
django/db/models/fields/related.py
339 339 manager.clear() 340 340 manager.add(*value) 341 341 342 def create_many_related_manager(superclass ):342 def create_many_related_manager(superclass, through=False): 343 343 """Creates a manager that subclasses 'superclass' (which is a Manager) 344 344 and adds behavior for many-to-many related objects.""" 345 345 class ManyRelatedManager(superclass): … … 353 353 self.join_table = join_table 354 354 self.source_col_name = source_col_name 355 355 self.target_col_name = target_col_name 356 self.through = through 356 357 self._pk_val = self.instance._get_pk_val() 357 358 if self._pk_val is None: 358 359 raise ValueError("%r instance needs to have a primary key value before a many-to-many relationship can be used." % instance.__class__.__name__) … … 360 361 def get_query_set(self): 361 362 return superclass.get_query_set(self).filter(**(self.core_filters)) 362 363 363 def add(self, *objs): 364 self._add_items(self.source_col_name, self.target_col_name, *objs) 364 # If the ManyToMany relation has an intermediary model, 365 # the add and remove methods do not exist. 366 if through is None: 367 def add(self, *objs): 368 self._add_items(self.source_col_name, self.target_col_name, *objs) 365 369 366 # If this is a symmetrical m2m relation to self, add the mirror entry in the m2m table367 if self.symmetrical:368 self._add_items(self.target_col_name, self.source_col_name, *objs)369 add.alters_data = True370 # If this is a symmetrical m2m relation to self, add the mirror entry in the m2m table 371 if self.symmetrical: 372 self._add_items(self.target_col_name, self.source_col_name, *objs) 373 add.alters_data = True 370 374 371 def remove(self, *objs):372 self._remove_items(self.source_col_name, self.target_col_name, *objs)375 def remove(self, *objs): 376 self._remove_items(self.source_col_name, self.target_col_name, *objs) 373 377 374 # If this is a symmetrical m2m relation to self, remove the mirror entry in the m2m table375 if self.symmetrical:376 self._remove_items(self.target_col_name, self.source_col_name, *objs)377 remove.alters_data = True378 # If this is a symmetrical m2m relation to self, remove the mirror entry in the m2m table 379 if self.symmetrical: 380 self._remove_items(self.target_col_name, self.source_col_name, *objs) 381 remove.alters_data = True 378 382 379 383 def clear(self): 380 384 self._clear_items(self.source_col_name) … … 385 389 clear.alters_data = True 386 390 387 391 def create(self, **kwargs): 392 # This check needs to be done here, since we can't later remove this 393 # from the method lookup table, as we do with add and remove. 394 if through is not None: 395 raise AttributeError, "Cannot use create() on a ManyToManyField which specifies an intermediary model. Use %s's Manager instead." % through 388 396 new_obj = self.model(**kwargs) 389 397 new_obj.save() 390 398 self.add(new_obj) … … 472 480 # model's default manager. 473 481 rel_model = self.related.model 474 482 superclass = rel_model._default_manager.__class__ 475 RelatedManager = create_many_related_manager(superclass )483 RelatedManager = create_many_related_manager(superclass, self.related.field.rel.through) 476 484 477 485 qn = connection.ops.quote_name 478 486 manager = RelatedManager( … … 491 499 if instance is None: 492 500 raise AttributeError, "Manager must be accessed via instance" 493 501 502 through = getattr(self.related.field.rel, 'through', None) 503 if through is not None: 504 raise AttributeError, "Cannot set values on a ManyToManyField which specifies an intermediary model. Use %s's Manager instead." % through 505 494 506 manager = self.__get__(instance) 495 507 manager.clear() 496 508 manager.add(*value) … … 513 525 # model's default manager. 514 526 rel_model=self.field.rel.to 515 527 superclass = rel_model._default_manager.__class__ 516 RelatedManager = create_many_related_manager(superclass )528 RelatedManager = create_many_related_manager(superclass, self.field.rel.through) 517 529 518 530 qn = connection.ops.quote_name 519 531 manager = RelatedManager( … … 532 544 if instance is None: 533 545 raise AttributeError, "Manager must be accessed via instance" 534 546 547 through = getattr(self.field.rel, 'through', None) 548 if through is not None: 549 raise AttributeError, "Cannot set values on a ManyToManyField which specifies an intermediary model. Use %s's Manager instead." % through 550 535 551 manager = self.__get__(instance) 536 552 manager.clear() 537 553 manager.add(*value) … … 583 599 584 600 class ManyToManyRel(object): 585 601 def __init__(self, to, num_in_admin=0, related_name=None, 586 limit_choices_to=None, symmetrical=True ):602 limit_choices_to=None, symmetrical=True, through=None): 587 603 self.to = to 588 604 self.num_in_admin = num_in_admin 589 605 self.related_name = related_name … … 593 609 self.edit_inline = False 594 610 self.symmetrical = symmetrical 595 611 self.multiple = True 612 self.through = through 613 self.through_app_label = None 614 self._model_cache = None 596 615 616 def _get_through_model(self): 617 if self._model_cache: 618 return self._model_cache 619 if self.through and self.through_app_label: 620 self._model_cache = get_model(self.through_app_label, self.through) 621 return self._model_cache 622 return None 623 through_model = property(_get_through_model) 624 625 597 626 class ForeignKey(RelatedField, Field): 598 627 empty_strings_allowed = False 599 628 def __init__(self, to, to_field=None, rel_class=ManyToOneRel, **kwargs): … … 722 751 num_in_admin=kwargs.pop('num_in_admin', 0), 723 752 related_name=kwargs.pop('related_name', None), 724 753 limit_choices_to=kwargs.pop('limit_choices_to', None), 725 symmetrical=kwargs.pop('symmetrical', True)) 754 symmetrical=kwargs.pop('symmetrical', True), 755 through=kwargs.pop('through', None)) 756 726 757 self.db_table = kwargs.pop('db_table', None) 758 if kwargs['rel'].through is not None: 759 self.creates_table = False 760 assert self.db_table is None, "Cannot specify a db_table if an intermediary model is used." 761 else: 762 self.creates_table = True 763 727 764 Field.__init__(self, **kwargs) 728 765 729 766 msg = ugettext_lazy('Hold down "Control", or "Command" on a Mac, to select more than one.') … … 738 775 739 776 def _get_m2m_db_table(self, opts): 740 777 "Function that can be curried to provide the m2m table name for this relation" 741 if self.db_table: 778 if self.rel.through is not None: 779 return self.rel.through_model._meta.db_table 780 elif self.db_table: 742 781 return self.db_table 743 782 else: 744 783 return '%s_%s' % (opts.db_table, self.name) 745 784 746 785 def _get_m2m_column_name(self, related): 747 786 "Function that can be curried to provide the source column name for the m2m table" 787 if self.rel.through is not None: 788 field = related.model._meta.get_related_object(self.rel.through_model).field 789 return field.column 748 790 # If this is an m2m relation to self, avoid the inevitable name clash 749 if related.model == related.parent_model:791 elif related.model == related.parent_model: 750 792 return 'from_' + related.model._meta.object_name.lower() + '_id' 751 793 else: 752 794 return related.model._meta.object_name.lower() + '_id' 753 795 754 796 def _get_m2m_reverse_name(self, related): 755 797 "Function that can be curried to provide the related column name for the m2m table" 798 if self.rel.through is not None: 799 meta = related.parent_model._meta 800 if self.parent == self.rel.to: 801 related = meta.get_related_object(self.rel.through_model, self_ref=True) 802 else: 803 related = meta.get_related_object(self.rel.through_model) 804 return related.field.column 756 805 # If this is an m2m relation to self, avoid the inevitable name clash 757 if related.model == related.parent_model:806 elif related.model == related.parent_model: 758 807 return 'to_' + related.parent_model._meta.object_name.lower() + '_id' 759 808 else: 760 809 return related.parent_model._meta.object_name.lower() + '_id' … … 797 846 798 847 # Set up the accessor for the m2m table name for the relation 799 848 self.m2m_db_table = curry(self._get_m2m_db_table, cls._meta) 849 850 # Get a reference to the parent model 851 self.parent = cls 852 853 # Populate some necessary rel arguments so that cross-app relations 854 # work correctly. 855 if isinstance(self.rel.through, basestring): 856 try: 857 self.rel.through_app_label, self.rel.through = self.rel.through.split('.') 858 except ValueError: 859 self.rel.through_app_label = cls._meta.app_label 800 860 801 861 if isinstance(self.rel.to, basestring): 802 862 target = self.rel.to -
django/core/management/validation.py
102 102 if r.get_accessor_name() == rel_query_name: 103 103 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)) 104 104 105 seen_intermediary_signatures = [] 105 106 for i, f in enumerate(opts.local_many_to_many): 106 107 # Check to see if the related m2m field will clash with any 107 108 # existing fields, m2m fields, m2m related objects or related … … 112 113 # so skip the next section 113 114 if isinstance(f.rel.to, (str, unicode)): 114 115 continue 115 116 if getattr(f.rel, 'through', None) is not None: 117 intermediary_model = None 118 for model in models.get_models(): 119 if model._meta.module_name == f.rel.through.lower(): 120 intermediary_model = model 121 if intermediary_model is None: 122 e.add(opts, "%s has a manually-defined m2m relation through model %s, which does not exist." % (f.name, f.rel.through)) 123 else: 124 signature = (f.rel.to, cls, intermediary_model) 125 if signature in seen_intermediary_signatures: 126 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)) 127 else: 128 seen_intermediary_signatures.append(signature) 129 seen_related_fk, seen_this_fk = False, False 130 for field in intermediary_model._meta.fields: 131 if field.rel: 132 if not seen_related_fk and field.rel.to == f.rel.to: 133 seen_related_fk = True 134 elif field.rel.to == cls: 135 seen_this_fk = True 136 if not seen_related_fk or not seen_this_fk: 137 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)) 138 116 139 rel_opts = f.rel.to._meta 117 140 rel_name = RelatedObject(f.rel.to, cls, f).get_accessor_name() 118 141 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
110 110 class MissingRelations(models.Model): 111 111 rel1 = models.ForeignKey("Rel1") 112 112 rel2 = models.ManyToManyField("Rel2") 113 114 class MissingManualM2MModel(models.Model): 115 name = models.CharField(max_length=5) 116 missing_m2m = models.ManyToManyField(Model, through="MissingM2MModel") 117 118 class Person(models.Model): 119 name = models.CharField(max_length=5) 113 120 121 class Group(models.Model): 122 name = models.CharField(max_length=5) 123 primary = models.ManyToManyField(Person, through="Membership", related_name="primary") 124 secondary = models.ManyToManyField(Person, through="Membership", related_name="secondary") 125 126 class GroupTwo(models.Model): 127 name = models.CharField(max_length=5) 128 primary = models.ManyToManyField(Person, through="Membership") 129 secondary = models.ManyToManyField(Group, through="MembershipMissingFK") 130 131 class Membership(models.Model): 132 person = models.ForeignKey(Person) 133 group = models.ForeignKey(Group) 134 not_default_or_null = models.CharField(max_length=5) 135 136 class MembershipMissingFK(models.Model): 137 person = models.ForeignKey(Person) 138 139 class PersonSelfRefM2M(models.Model): 140 name = models.CharField(max_length=5) 141 friends = models.ManyToManyField('self', through="Relationship") 142 143 class Relationship(models.Model): 144 first = models.ForeignKey(PersonSelfRefM2M, related_name="rel_from_set") 145 second = models.ForeignKey(PersonSelfRefM2M, related_name="rel_to_set") 146 date_added = models.DateTimeField() 147 114 148 model_errors = """invalid_models.fielderrors: "charfield": CharFields require a "max_length" attribute. 115 149 invalid_models.fielderrors: "decimalfield": DecimalFields require a "decimal_places" attribute. 116 150 invalid_models.fielderrors: "decimalfield": DecimalFields require a "max_digits" attribute. … … 195 229 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'. 196 230 invalid_models.missingrelations: 'rel2' has m2m relation with model Rel2, which has not been installed 197 231 invalid_models.missingrelations: 'rel1' has relation with model Rel1, which has not been installed 232 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. 233 invalid_models.missingmanualm2mmodel: missing_m2m has a manually-defined m2m relation through model MissingM2MModel, which does not exist. 234 invalid_models.grouptwo: primary has a manually-defined m2m relation through model Membership, which does not have foreign keys to Person and GroupTwo 235 invalid_models.grouptwo: secondary has a manually-defined m2m relation through model MembershipMissingFK, which does not have foreign keys to Group and GroupTwo 198 236 """ -
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, 117 # the add method is not provided. 118 >>> rock.members.add(bob) 119 Traceback (most recent call last): 120 ... 121 AttributeError: 'ManyRelatedManager' object has no attribute 'add' 122 123 # Create is also disabled as it suffers from the same problems as add. 124 >>> rock.members.create(name='Anne') 125 Traceback (most recent call last): 126 ... 127 AttributeError: Cannot use create() on a ManyToManyField which specifies an intermediary model. Use Membership's Manager instead. 128 129 # Remove has similar complications, and is not provided either. 130 >>> rock.members.remove(jim) 131 Traceback (most recent call last): 132 ... 133 AttributeError: 'ManyRelatedManager' object has no attribute 'remove' 134 135 # Here we back up the list of all members of Rock. 136 >>> backup = list(rock.members.all()) 137 138 # ...and we verify that it has worked. 139 >>> backup 140 [<Person: Jim>, <Person: Jane>] 141 142 # The clear function should still work. 143 >>> rock.members.clear() 144 145 # Now there will be no members of Rock. 146 >>> rock.members.all() 147 [] 148 149 # Assignment should not work with models specifying a through model for many of 150 # the same reasons as adding. 151 >>> rock.members = backup 152 Traceback (most recent call last): 153 ... 154 AttributeError: Cannot set values on a ManyToManyField which specifies an intermediary model. Use Membership's Manager instead. 155 156 # Let's re-save those instances that we've cleared. 157 >>> m1.save() 158 >>> m2.save() 159 160 # Verifying that those instances were re-saved successfully. 161 >>> rock.members.all() 162 [<Person: Jim>, <Person: Jane>] 163 164 165 ### Reverse Descriptors Tests ### 166 167 # Due to complications with adding via an intermediary model, 168 # the add method is not provided. 169 >>> bob.group_set.add(rock) 170 Traceback (most recent call last): 171 ... 172 AttributeError: 'ManyRelatedManager' object has no attribute 'add' 173 174 # Create is also disabled as it suffers from the same problems as add. 175 >>> bob.group_set.create(name='Funk') 176 Traceback (most recent call last): 177 ... 178 AttributeError: Cannot use create() on a ManyToManyField which specifies an intermediary model. Use Membership's Manager instead. 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, date_friended=datetime.now()) 249 250 # Tony should now show that Chris is his friend. 251 >>> tony.friends.all() 252 [<PersonSelfRefM2M: Chris>] 253 254 # But we haven't established that Chris is Tony's Friend. 255 >>> chris.friends.all() 256 [] 257 258 # So let's do that now. 259 >>> f2 = Friendship.objects.create(first=chris, second=tony, date_friended=datetime.now()) 260 261 # Having added Chris as a friend, let's make sure that his friend set reflects 262 # that addition. 263 >>> chris.friends.all() 264 [<PersonSelfRefM2M: Tony>] 265 266 # Chris gets mad and wants to get rid of all of his friends. 267 >>> chris.friends.clear() 268 269 # Now he should not have any more friends. 270 >>> chris.friends.all() 271 [] 272 273 # Since this is a symmetrical relation, Tony's friend link is deleted as well. 274 >>> tony.friends.all() 275 [] 276 277 278 279 ### QUERY TESTS ### 280 281 # We can query for the related model by using its attribute name (members, in 282 # this case). 283 >>> Group.objects.filter(members__name='Bob') 284 [<Group: Roll>] 285 286 # To query through the intermediary model, we specify its model name. 287 # In this case, membership. 288 >>> Group.objects.filter(membership__invite_reason="She was just awesome.") 289 [<Group: Rock>] 290 291 # If we want to query in the reverse direction by the related model, use its 292 # model name (group, in this case). 293 >>> Person.objects.filter(group__name="Rock") 294 [<Person: Jim>, <Person: Jane>] 295 296 # If the m2m field has specified a related_name, using that will work. 297 >>> Person.objects.filter(custom__name="Rock") 298 [<Person: Bob>, <Person: Jim>] 299 300 # To query through the intermediary model in the reverse direction, we again 301 # specify its model name (membership, in this case). 302 >>> Person.objects.filter(membership__invite_reason="She was just awesome.") 303 [<Person: Jane>] 304 305 # Let's see all of the groups that Jane joined after 1 Jan 2005: 306 >>> Group.objects.filter(membership__date_joined__gt=datetime(2005, 1, 1), membership__person =jane) 307 [<Group: Rock>] 308 309 # Queries also work in the reverse direction: Now let's see all of the people 310 # that have joined Rock since 1 Jan 2005: 311 >>> Person.objects.filter(membership__date_joined__gt=datetime(2005, 1, 1), membership__group=rock) 312 [<Person: Jim>, <Person: Jane>] 313 314 # Conceivably, queries through membership could return correct, but non-unique 315 # querysets. To demonstrate this, we query for all people who have joined a 316 # group after 2004: 317 >>> Person.objects.filter(membership__date_joined__gt=datetime(2004, 1, 1)) 318 [<Person: Jim>, <Person: Jim>, <Person: Jane>] 319 320 # Jim showed up twice, because he joined two groups ('Rock', and 'Roll'): 321 >>> [(m.person.name, m.group.name) for m in 322 ... Membership.objects.filter(date_joined__gt=datetime(2004, 1, 1))] 323 [(u'Jim', u'Rock'), (u'Jane', u'Rock'), (u'Jim', u'Roll')] 324 325 # QuerySet's distinct() method can correct this problem. 326 >>> Person.objects.filter(membership__date_joined__gt=datetime(2004, 1, 1)).distinct() 327 [<Person: Jim>, <Person: Jane>] 328 """} 329 No newline at end of file -
AUTHORS
154 154 Maciej Fijalkowski 155 155 Matthew Flanagan <http://wadofstuff.blogspot.com> 156 156 Eric Floehr <eric@intellovations.com> 157 Eric Florenzano <floguy@gmail.com> 157 158 Vincent Foley <vfoleybourgon@yahoo.ca> 158 159 Rudolph Froger <rfroger@estrate.nl> 159 160 Jorge Gajon <gajon@gajon.org> -
docs/model-api.txt
942 942 943 943 ======================= ============================================================ 944 944 945 Extra fields on many-to-many relationships 946 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 947 948 When you're only dealing with mixing and matching pizzas and toppings, a 949 standard ``ManyToManyField`` works great. However, sometimes you may want 950 to associated data with the relationship between two models. 951 952 For example, consider the case of an application tracking the musical groups 953 which musicians belong to. There is a many-to-many relationship between a person 954 and the groups of which they are a member, so you could use a ManyToManyField 955 to represent this relationship. However, there is a lot of detail about the 956 membership that you might want to collect, such as the date at which the person 957 joined the group. 958 959 For these situations, Django allows you to specify the model that will be used 960 to govern the many-to-many relationship. You can then put extra fields on the 961 intermediate model. The intermediate model is associated with the 962 ``ManyToManyField`` by using the ``through`` argument to point the model that 963 will act as an intermediary. For our musician example, the code would look 964 something like this:: 965 966 class Person(models.Model): 967 # ... 968 name = models.CharField(max_length=128) 969 970 def __unicode__(self): 971 return self.name 972 973 class Group(models.Model): 974 # ... 975 name = models.CharField(max_length=128) 976 members = models.ManyToManyField(Person, through='Membership') 977 978 def __unicode__(self): 979 return self.name 980 981 class Membership(models.Model): 982 person = models.ForeignKey(Person) 983 group = models.ForeignKey(Group) 984 date_joined = models.DateField() 985 invite_reason = models.CharField(max_length=64) 986 987 When you set up the intermediary model, you must explicitly specify foreign 988 keys to the models in ManyToMany relation. This explicit declaration makes 989 it clear how two models are related. 990 991 Now that you have set up your ``ManyToManyField`` to use your intermediary 992 model (Membership, in this case), you're ready to use the convenience methods 993 provided by that ``ManyToManyField``. Here's an example of how you can query 994 for and use these models:: 995 996 >>> ringo = Person.objects.create(name="Ringo Starr") 997 >>> paul = Person.objects.create(name="Paul McCartney") 998 >>> beatles = Group.objects.create(name="The Beatles") 999 >>> m1 = Membership.objects.create(person=ringo, group=beatles, 1000 ... date_joined=date(1962, 8, 16), 1001 ... invite_reason= "Needed a new drummer.") 1002 >>> beatles.members.all() 1003 [<Person: Ringo Starr>] 1004 >>> ringo.group_set.all() 1005 [<Group: The Beatles>] 1006 >>> m2 = Membership.objects.create(person=paul, group=beatles, 1007 ... date_joined=date(1960, 8, 1), 1008 ... invite_reason= "Wanted to form a band.") 1009 >>> beatles.members.all() 1010 [<Person: Ringo Starr>, <Person: Paul McCartney>] 1011 1012 Unlike normal many-to-many fields, you *can't* use ``add``, ``create``, 1013 or assignment (i.e., ``beatles.members = [...]``) to create relationships:: 1014 1015 # THIS WILL NOT WORK 1016 >>> beatles.members.add(john) 1017 # NEITHER WILL THIS 1018 >>> beatles.members.create(name="George Harrison") 1019 # AND NEITHER WILL THIS 1020 >>> beatles.members = [john, paul, ringo, george] 1021 1022 Why? You can't just create a relationship between a Person and a Group - you 1023 need to specify all the detail for the relationship required by the 1024 Membership table. The simple ``add``, ``create`` and assignment calls 1025 don't provide a way to specify this extra detail. As a result, they are 1026 disabled for many-to-many relationships that use an intermediate model. 1027 The only way to create a many-to-many relationship with an intermediate table 1028 is to create instances of the intermediate model. 1029 1030 The ``remove`` method is disabled for similar reasons. However, the 1031 ``clear()`` method can be used to remove all many-to-many relationships 1032 for an instance:: 1033 1034 # Beatles have broken up 1035 >>> beatles.members.clear() 1036 1037 Once you have established the many-to-many relationships by creating instances 1038 of your intermediate model, you can issue queries. You can query using the 1039 attributes of the many-to-many-related model:: 1040 1041 # Find all the people in the Beatles whose name starts with 'Paul' 1042 >>> beatles.objects.filter(person__name__startswith='Paul') 1043 [<Person: Paul McCartney>] 1044 1045 You can also query on the attributes of the intermediate model:: 1046 1047 # Find all the members of the Beatles that joined after 1 Jan 1961 1048 >>> beatles.objects.filter(membership__date_joined__gt=date(1961,1,1)) 1049 [<Person: Ringo Starr] 1050 945 1051 One-to-one relationships 946 1052 ~~~~~~~~~~~~~~~~~~~~~~~~ 947 1053