Ticket #6095: 6095-trunk-withdocs.rc1.diff
File 6095-trunk-withdocs.rc1.diff, 29.8 KB (added by , 16 years ago) |
---|
-
AUTHORS
diff --git a/AUTHORS b/AUTHORS index b20092e..33bfed4 100644
a b answer newbie questions, and generally made Django that much better: 144 144 Afonso Fernández Nogueira <fonzzo.django@gmail.com> 145 145 Matthew Flanagan <http://wadofstuff.blogspot.com> 146 146 Eric Floehr <eric@intellovations.com> 147 Eric Florenzano <floguy@gmail.com> 147 148 Vincent Foley <vfoleybourgon@yahoo.ca> 148 149 Rudolph Froger <rfroger@estrate.nl> 149 150 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..b1fc5e0 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 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..c30fedd 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) … … class ManyToManyField(RelatedField, Field): 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" 770 799 # If this is an m2m relation to self, avoid the inevitable name clash 771 if related.model == related.parent_model: 800 if self.rel.through is not None: 801 field = related.model._meta.get_related_object(self.rel.through).field 802 return field.get_attname_column()[1] 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' … … class ManyToManyField(RelatedField, Field): 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" 778 810 # If this is an m2m relation to self, avoid the inevitable name clash 779 if related.model == related.parent_model: 811 if self.rel.through is not None: 812 field = related.parent_model._meta.get_related_object(self.rel.through).field 813 return field.get_attname_column()[1] 814 elif related.model == related.parent_model: 780 815 return 'to_' + related.parent_model._meta.object_name.lower() + '_id' 781 816 else: 782 817 return related.parent_model._meta.object_name.lower() + '_id' -
django/db/models/options.py
diff --git a/django/db/models/options.py b/django/db/models/options.py index 5802ead..10056a1 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): 374 374 follow = self.get_follow() 375 375 return [f for f in self.get_all_related_objects() if follow.get(f.name, None)] 376 376 377 def get_related_object(self, from_model): 378 "Gets the RelatedObject which links from from_model to this model." 379 if isinstance(from_model, str): 380 from_model = get_model(self.app_label, from_model) 381 for related_object in self.get_all_related_objects(): 382 if related_object.model == from_model: 383 return related_object 384 return None 385 377 386 def get_data_holders(self, follow=None): 378 387 if follow == None: 379 388 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..c80670d 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) 114 139 115 140 model_errors = """invalid_models.fielderrors: "charfield": CharFields require a "max_length" attribute. 116 141 invalid_models.fielderrors: "decimalfield": DecimalFields require a "decimal_places" attribute. … … invalid_models.selfclashm2m: Reverse query name for m2m field 'm2m_3' clashes wi 197 222 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 223 invalid_models.missingrelations: 'rel2' has m2m relation with model Rel2, which has not been installed 199 224 invalid_models.missingrelations: 'rel1' has relation with model Rel1, which has not been installed 225 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. 226 invalid_models.missingmanualm2mmodel: missing_m2m has a manually-defined m2m relation through model MissingM2MModel, which does not exist. 227 invalid_models.grouptwo: primary has a manually-defined m2m relation through model Membership, which does not have foreign keys to Person and GroupTwo 228 invalid_models.grouptwo: secondary has a manually-defined m2m relation through model MembershipMissingFK, which does not have foreign keys to Group and GroupTwo 200 229 """ -
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..a8c7e05
- + 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 __test__ = {'API_TESTS':""" 47 >>> from datetime import datetime 48 49 ### Creation and Saving Tests ### 50 51 >>> bob = Person.objects.create(name = 'Bob') 52 >>> jim = Person.objects.create(name = 'Jim') 53 >>> jane = Person.objects.create(name = 'Jane') 54 >>> rock = Group.objects.create(name = 'Rock') 55 >>> roll = Group.objects.create(name = 'Roll') 56 57 # We start out by making sure that the Group 'rock' has no members. 58 >>> rock.members.all() 59 [] 60 61 # To make Jim a member of Group Rock, simply create a Membership object. 62 >>> m1 = Membership.objects.create(person = jim, group = rock) 63 64 # We can do the same for Jane and Rock. 65 >>> m2 = Membership.objects.create(person = jane, group = rock) 66 67 # Let's check to make sure that it worked. Jane and Jim should be members of Rock. 68 >>> rock.members.all() 69 [<Person: Jim>, <Person: Jane>] 70 71 # Now we can add a bunch more Membership objects to test with. 72 >>> m3 = Membership.objects.create(person = bob, group = roll) 73 >>> m4 = Membership.objects.create(person = jim, group = roll) 74 >>> m5 = Membership.objects.create(person = jane, group = roll) 75 76 # We can get Jim's Group membership as with any ForeignKey. 77 >>> jim.group_set.all() 78 [<Group: Rock>, <Group: Roll>] 79 80 # Querying the intermediary model works like normal. 81 # In this case we get Jane's membership to Rock. 82 >>> m = Membership.objects.get(person = jane, group = rock) 83 >>> m 84 <Membership: Jane is a member of Rock> 85 86 # Now we set some date_joined dates for further testing. 87 >>> m2.invite_reason = "She was just awesome." 88 >>> m2.date_joined = datetime(2006, 1, 1) 89 >>> m2.save() 90 91 >>> m5.date_joined = datetime(2004, 1, 1) 92 >>> m5.save() 93 94 >>> m3.date_joined = datetime(2004, 1, 1) 95 >>> m3.save() 96 97 # It's not only get that works. Filter works like normal as well. 98 >>> Membership.objects.filter(person = jim) 99 [<Membership: Jim is a member of Rock>, <Membership: Jim is a member of Roll>] 100 101 102 ### Forward Descriptors Tests ### 103 104 # Due to complications with adding via an intermediary model, the add method is 105 # not provided. 106 >>> rock.members.add(bob) 107 Traceback (most recent call last): 108 ... 109 AttributeError: 'ManyRelatedManager' object has no attribute 'add' 110 111 >>> rock.members.create(name = 'Anne') 112 Traceback (most recent call last): 113 ... 114 AttributeError: 'ManyRelatedManager' object has no attribute 'create' 115 116 # Remove has similar complications, and is not provided either. 117 >>> rock.members.remove(jim) 118 Traceback (most recent call last): 119 ... 120 AttributeError: 'ManyRelatedManager' object has no attribute 'remove' 121 122 # Here we back up the list of all members of Rock. 123 >>> backup = list(rock.members.all()) 124 125 # ...and we verify that it has worked. 126 >>> backup 127 [<Person: Jim>, <Person: Jane>] 128 129 # The clear function should still work. 130 >>> rock.members.clear() 131 132 # Now there will be no members of Rock. 133 >>> rock.members.all() 134 [] 135 136 # Assignment should not work with models specifying a through model for many of 137 # the same reasons as adding. 138 >>> rock.members = backup 139 Traceback (most recent call last): 140 ... 141 AttributeError: Cannot set values on a ManyToManyField which specifies an intermediary model. Use Membership's Manager instead. 142 143 # Let's re-save those instances that we've cleared. 144 >>> m1.save() 145 >>> m2.save() 146 147 # Verifying that those instances were re-saved successfully. 148 >>> rock.members.all() 149 [<Person: Jim>, <Person: Jane>] 150 151 152 ### Reverse Descriptors Tests ### 153 154 # Due to complications with adding via an intermediary model, the add method is 155 # not provided. 156 >>> bob.group_set.add(rock) 157 Traceback (most recent call last): 158 ... 159 AttributeError: 'ManyRelatedManager' object has no attribute 'add' 160 161 # Create is another method that should not work correctly, as it suffers from 162 # the same problems as add. 163 >>> bob.group_set.create(name = 'Funk') 164 Traceback (most recent call last): 165 ... 166 AttributeError: 'ManyRelatedManager' object has no attribute 'create' 167 168 # Remove has similar complications, and is not provided either. 169 >>> jim.group_set.remove(rock) 170 Traceback (most recent call last): 171 ... 172 AttributeError: 'ManyRelatedManager' object has no attribute 'remove' 173 174 # Here we back up the list of all of Jim's groups. 175 >>> backup = list(jim.group_set.all()) 176 >>> backup 177 [<Group: Rock>, <Group: Roll>] 178 179 # The clear function should still work. 180 >>> jim.group_set.clear() 181 182 # Now Jim will be in no groups. 183 >>> jim.group_set.all() 184 [] 185 186 # Assignment should not work with models specifying a through model for many of 187 # the same reasons as adding. 188 >>> jim.group_set = backup 189 Traceback (most recent call last): 190 ... 191 AttributeError: Cannot set values on a ManyToManyField which specifies an intermediary model. Use Membership's Manager instead. 192 193 # Let's re-save those instances that we've cleared. 194 >>> m1.save() 195 >>> m4.save() 196 197 # Verifying that those instances were re-saved successfully. 198 >>> jim.group_set.all() 199 [<Group: Rock>, <Group: Roll>] 200 201 ### Custom Tests ### 202 203 # Let's see if we can query through our second relationship. 204 >>> rock.custom_members.all() 205 [] 206 207 # We can query in the opposite direction as well. 208 >>> bob.custom.all() 209 [] 210 211 # Let's create some membership objects in this custom relationship. 212 >>> cm1 = CustomMembership.objects.create(person = bob, group = rock) 213 >>> cm2 = CustomMembership.objects.create(person = jim, group = rock) 214 215 # If we get the number of people in Rock, it should be both Bob and Jim. 216 >>> rock.custom_members.all() 217 [<Person: Bob>, <Person: Jim>] 218 219 # Bob should only be in one custom group. 220 >>> bob.custom.all() 221 [<Group: Rock>] 222 223 # Let's make sure our new descriptors don't conflict with the FK related_name. 224 >>> bob.custom_person_related_name.all() 225 [<CustomMembership: Bob is a member of Rock>] 226 227 ### QUERY TESTS ### 228 229 # We can query for the related model by using its attribute name (members, in 230 # this case). 231 >>> Group.objects.filter(members__name='Bob') 232 [<Group: Roll>] 233 234 # To query through the intermediary model, we specify its model name. 235 # In this case, membership. 236 >>> Group.objects.filter(membership__invite_reason = "She was just awesome.") 237 [<Group: Rock>] 238 239 # If we want to query in the reverse direction by the related model, use its 240 # model name (group, in this case). 241 >>> Person.objects.filter(group__name="Rock") 242 [<Person: Jim>, <Person: Jane>] 243 244 # If the m2m field has specified a related_name, using that will work. 245 >>> Person.objects.filter(custom__name="Rock") 246 [<Person: Bob>, <Person: Jim>] 247 248 # To query through the intermediary model in the reverse direction, we again 249 # specify its model name (membership, in this case). 250 >>> Person.objects.filter(membership__invite_reason = "She was just awesome.") 251 [<Person: Jane>] 252 253 # Let's see all of the groups that Jane joined after 1 Jan 2005: 254 >>> Group.objects.filter(membership__date_joined__gt = datetime(2005, 1, 1), 255 ... membership__person = jane) 256 [<Group: Rock>] 257 258 # Queries also work in the reverse direction: Now let's see all of the people 259 # that have joined Rock since 1 Jan 2005: 260 >>> Person.objects.filter(membership__date_joined__gt = datetime(2005, 1, 1), 261 ... membership__group = rock) 262 [<Person: Jim>, <Person: Jane>] 263 264 # Conceivably, queries through membership could return correct, but non-unique 265 # querysets. To demonstrate this, we query for all people who have joined a 266 # group after 2004: 267 >>> Person.objects.filter(membership__date_joined__gt = datetime(2004, 1, 1)) 268 [<Person: Jim>, <Person: Jim>, <Person: Jane>] 269 270 # Jim showed up twice, because he joined two groups ('Rock', and 'Roll'): 271 >>> [(m.person.name, m.group.name) for m in 272 ... Membership.objects.filter(date_joined__gt = datetime(2004, 1, 1))] 273 [(u'Jim', u'Rock'), (u'Jane', u'Rock'), (u'Jim', u'Roll')] 274 275 # QuerySet's distinct() method can correct this problem. 276 >>> Person.objects.filter(membership__date_joined__gt = datetime(2004, 1, 1)).distinct() 277 [<Person: Jim>, <Person: Jane>] 278 """} 279 No newline at end of file