Ticket #6095: 6095-trunk-withdocs.rc2.diff
File 6095-trunk-withdocs.rc2.diff, 32.3 KB (added by , 16 years ago) |
---|
-
AUTHORS
diff --git a/AUTHORS b/AUTHORS index 0c0fed8..4715180 100644
a b answer newbie questions, and generally made Django that much better: 145 145 J. Pablo Fernandez <pupeno@pupeno.com> 146 146 Matthew Flanagan <http://wadofstuff.blogspot.com> 147 147 Eric Floehr <eric@intellovations.com> 148 Eric Florenzano <floguy@gmail.com> 148 149 Vincent Foley <vfoleybourgon@yahoo.ca> 149 150 Rudolph Froger <rfroger@estrate.nl> 150 151 Jorge Gajon <gajon@gajon.org> -
django/contrib/contenttypes/generic.py
diff --git a/django/contrib/contenttypes/generic.py b/django/contrib/contenttypes/generic.py index e91be70..2ff5858 100644
a b class GenericRelation(RelatedField, Field): 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") -
django/core/management/sql.py
diff --git a/django/core/management/sql.py b/django/core/management/sql.py index 574be5a..2ebc626 100644
a b def many_to_many_sql_for_model(model, style): 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 and connection.features.autoindexes_primary_keys: 359 359 tablespace_sql = ' ' + connection.ops.tablespace_sql(tablespace, inline=True) -
django/core/management/validation.py
diff --git a/django/core/management/validation.py b/django/core/management/validation.py index cd1f84f..4ff74be 100644
a b def get_validation_errors(outfile, app=None): 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 … … def get_validation_errors(outfile, app=None): 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/db/models/fields/related.py
diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index 4ea0c3f..161f7fd 100644
a b class ForeignRelatedObjectsDescriptor(object): 335 335 manager.clear() 336 336 manager.add(*value) 337 337 338 def create_many_related_manager(superclass ):338 def create_many_related_manager(superclass, through=False): 339 339 """Creates a manager that subclasses 'superclass' (which is a Manager) 340 340 and adds behavior for many-to-many related objects.""" 341 341 class ManyRelatedManager(superclass): … … def create_many_related_manager(superclass): 349 349 self.join_table = join_table 350 350 self.source_col_name = source_col_name 351 351 self.target_col_name = target_col_name 352 self.through = through 352 353 self._pk_val = self.instance._get_pk_val() 353 354 if self._pk_val is None: 354 355 raise ValueError("%r instance needs to have a primary key value before a many-to-many relationship can be used." % model) … … def create_many_related_manager(superclass): 381 382 clear.alters_data = True 382 383 383 384 def create(self, **kwargs): 385 # This check needs to be done here, since we can't later remove this 386 # from the method lookup table, as we do with add and remove. 387 if through is not None: 388 raise AttributeError, "'ManyRelatedManager' object has no attribute 'create'" 384 389 new_obj = self.model(**kwargs) 385 390 new_obj.save() 386 391 self.add(new_obj) … … def create_many_related_manager(superclass): 448 453 [self._pk_val]) 449 454 transaction.commit_unless_managed() 450 455 456 # If it's an intermediary model, detach the add and remove methods from this 457 # ManyRelatedManager. Create cannot be detached this way due to an 458 # inherited method in the dynamic method lookup table that gets in the way. 459 if through is not None: 460 del ManyRelatedManager.add 461 del ManyRelatedManager.remove 462 451 463 return ManyRelatedManager 452 464 453 465 class ManyRelatedObjectsDescriptor(object): … … class ManyRelatedObjectsDescriptor(object): 468 480 # model's default manager. 469 481 rel_model = self.related.model 470 482 superclass = rel_model._default_manager.__class__ 471 RelatedManager = create_many_related_manager(superclass )483 RelatedManager = create_many_related_manager(superclass, self.related.field.rel.through) 472 484 473 485 qn = connection.ops.quote_name 474 486 manager = RelatedManager( … … class ManyRelatedObjectsDescriptor(object): 487 499 if instance is None: 488 500 raise AttributeError, "Manager must be accessed via instance" 489 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 490 506 manager = self.__get__(instance) 491 507 manager.clear() 492 508 manager.add(*value) … … class ReverseManyRelatedObjectsDescriptor(object): 509 525 # model's default manager. 510 526 rel_model=self.field.rel.to 511 527 superclass = rel_model._default_manager.__class__ 512 RelatedManager = create_many_related_manager(superclass )528 RelatedManager = create_many_related_manager(superclass, self.field.rel.through) 513 529 514 530 qn = connection.ops.quote_name 515 531 manager = RelatedManager( … … class ReverseManyRelatedObjectsDescriptor(object): 528 544 if instance is None: 529 545 raise AttributeError, "Manager must be accessed via instance" 530 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 531 551 manager = self.__get__(instance) 532 552 manager.clear() 533 553 manager.add(*value) … … class OneToOneRel(ManyToOneRel): 581 601 582 602 class ManyToManyRel(object): 583 603 def __init__(self, to, num_in_admin=0, related_name=None, 584 filter_interface=None, limit_choices_to=None, raw_id_admin=False, symmetrical=True ):604 filter_interface=None, limit_choices_to=None, raw_id_admin=False, symmetrical=True, through=None): 585 605 self.to = to 586 606 self.num_in_admin = num_in_admin 587 607 self.related_name = related_name … … class ManyToManyRel(object): 593 613 self.raw_id_admin = raw_id_admin 594 614 self.symmetrical = symmetrical 595 615 self.multiple = True 616 self.through = through 596 617 597 618 assert not (self.raw_id_admin and self.filter_interface), "ManyToManyRels may not use both raw_id_admin and filter_interface" 598 619 … … class ManyToManyField(RelatedField, Field): 736 757 filter_interface=kwargs.pop('filter_interface', None), 737 758 limit_choices_to=kwargs.pop('limit_choices_to', None), 738 759 raw_id_admin=kwargs.pop('raw_id_admin', False), 739 symmetrical=kwargs.pop('symmetrical', True)) 760 symmetrical=kwargs.pop('symmetrical', True), 761 through=kwargs.pop('through', None)) 740 762 self.db_table = kwargs.pop('db_table', None) 763 if kwargs['rel'].through is not None: 764 self.creates_table = False 765 assert self.db_table is None, "Cannot specify a db_table if an intermediary model is used." 766 else: 767 self.creates_table = True 741 768 if kwargs["rel"].raw_id_admin: 742 769 kwargs.setdefault("validator_list", []).append(self.isValidIDList) 743 770 Field.__init__(self, **kwargs) … … class ManyToManyField(RelatedField, Field): 760 787 761 788 def _get_m2m_db_table(self, opts): 762 789 "Function that can be curried to provide the m2m table name for this relation" 763 if self.db_table: 790 if self.rel.through is not None: 791 return get_model(opts.app_label, self.rel.through)._meta.db_table 792 elif self.db_table: 764 793 return self.db_table 765 794 else: 766 795 return '%s_%s' % (opts.db_table, self.name) 767 796 768 797 def _get_m2m_column_name(self, related): 769 798 "Function that can be curried to provide the source column name for the m2m table" 799 if self.rel.through is not None: 800 field = related.model._meta.get_related_object(self.rel.through).field 801 return field.get_attname_column()[1] 770 802 # If this is an m2m relation to self, avoid the inevitable name clash 771 if related.model == related.parent_model:803 elif related.model == related.parent_model: 772 804 return 'from_' + related.model._meta.object_name.lower() + '_id' 773 805 else: 774 806 return related.model._meta.object_name.lower() + '_id' 775 807 776 808 def _get_m2m_reverse_name(self, related): 777 809 "Function that can be curried to provide the related column name for the m2m table" 810 if self.rel.through is not None: 811 meta = related.parent_model._meta 812 if self.parent == self.rel.to: 813 related = meta.get_related_object(self.rel.through, self_ref = True) 814 else: 815 related = meta.get_related_object(self.rel.through) 816 return related.field.get_attname_column()[1] 778 817 # If this is an m2m relation to self, avoid the inevitable name clash 779 if related.model == related.parent_model:818 elif related.model == related.parent_model: 780 819 return 'to_' + related.parent_model._meta.object_name.lower() + '_id' 781 820 else: 782 821 return related.parent_model._meta.object_name.lower() + '_id' … … class ManyToManyField(RelatedField, Field): 822 861 823 862 # Set up the accessor for the m2m table name for the relation 824 863 self.m2m_db_table = curry(self._get_m2m_db_table, cls._meta) 864 865 # Get a reference to the parent model 866 self.parent = cls 825 867 826 868 def contribute_to_related_class(self, cls, related): 827 869 # m2m relations to self do not have a ManyRelatedObjectsDescriptor, -
django/db/models/options.py
diff --git a/django/db/models/options.py b/django/db/models/options.py index 4036bfb..fe32fc3 100644
a b from django.db.models.related import RelatedObject 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 … … class Options(object): 378 378 follow = self.get_follow() 379 379 return [f for f in self.get_all_related_objects() if follow.get(f.name, None)] 380 380 381 def get_related_object(self, from_model, self_ref=False): 382 "Gets the RelatedObject which links from from_model to this model." 383 if isinstance(from_model, str): 384 from_model = get_model(self.app_label, from_model) 385 for related_object in self.get_all_related_objects(): 386 if related_object.model == from_model: 387 if self_ref: 388 self_ref = False 389 else: 390 return related_object 391 return None 392 381 393 def get_data_holders(self, follow=None): 382 394 if follow == None: 383 395 follow = self.get_follow() -
docs/model-api.txt
diff --git a/docs/model-api.txt b/docs/model-api.txt index 4ed4ede..d96710d 100644
a b the relationship should work. All are optional: 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_manual/models.py 1070 994 1071 One-to-one relationships 995 1072 ~~~~~~~~~~~~~~~~~~~~~~~~ 996 1073 -
tests/modeltests/invalid_models/models.py
diff --git a/tests/modeltests/invalid_models/models.py b/tests/modeltests/invalid_models/models.py index 8a480a2..8dfe808 100644
a b class Car(models.Model): 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) 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() 114 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. … … invalid_models.selfclashm2m: Reverse query name for m2m field 'm2m_3' clashes wi 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 """ -
new file tests/modeltests/m2m_manual/__init__.py
diff --git a/tests/modeltests/m2m_manual/__init__.py b/tests/modeltests/m2m_manual/__init__.py new file mode 100644 index 0000000..8b13789
- + 1 -
new file tests/modeltests/m2m_manual/models.py
diff --git a/tests/modeltests/m2m_manual/models.py b/tests/modeltests/m2m_manual/models.py new file mode 100644 index 0000000..709c541
- + 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