Django

Code

Ticket #6095: 6095-trunk-withdocs.rc2.diff

File 6095-trunk-withdocs.rc2.diff, 32.3 kB (added by floguy, 2 years ago)

Fixed bug with self-referential M2M fields not working with an intermediary model, and added tests for that problem.

  • a/AUTHORS

    old new  
    145145    J. Pablo Fernandez <pupeno@pupeno.com> 
    146146    Matthew Flanagan <http://wadofstuff.blogspot.com> 
    147147    Eric Floehr <eric@intellovations.com> 
     148    Eric Florenzano <floguy@gmail.com> 
    148149    Vincent Foley <vfoleybourgon@yahoo.ca> 
    149150    Rudolph Froger <rfroger@estrate.nl> 
    150151    Jorge Gajon <gajon@gajon.org> 
  • a/django/contrib/contenttypes/generic.py

    old new  
    104104                            limit_choices_to=kwargs.pop('limit_choices_to', None), 
    105105                            symmetrical=kwargs.pop('symmetrical', True)) 
    106106 
     107        # By its very nature, a GenericRelation doesn't create a table. 
     108        self.creates_table = False 
     109 
    107110        # Override content-type/object-id field names on the related class 
    108111        self.object_id_field_name = kwargs.pop("object_id_field", "object_id") 
    109112        self.content_type_field_name = kwargs.pop("content_type_field", "content_type") 
  • a/django/core/management/sql.py

    old new  
    353353    qn = connection.ops.quote_name 
    354354    inline_references = connection.features.inline_fk_references 
    355355    for f in opts.local_many_to_many: 
    356         if not isinstance(f.rel, generic.GenericRel)
     356        if f.creates_table
    357357            tablespace = f.db_tablespace or opts.db_tablespace 
    358358            if tablespace and connection.features.supports_tablespaces and connection.features.autoindexes_primary_keys: 
    359359                tablespace_sql = ' ' + connection.ops.tablespace_sql(tablespace, inline=True) 
  • a/django/core/management/validation.py

    old new  
    104104                        if r.get_accessor_name() == rel_query_name: 
    105105                            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)) 
    106106 
     107        seen_intermediary_signatures = []  
    107108        for i, f in enumerate(opts.local_many_to_many): 
    108109            # Check to see if the related m2m field will clash with any 
    109110            # existing fields, m2m fields, m2m related objects or related 
     
    114115                # so skip the next section 
    115116                if isinstance(f.rel.to, (str, unicode)): 
    116117                    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             
    118141            rel_opts = f.rel.to._meta 
    119142            rel_name = RelatedObject(f.rel.to, cls, f).get_accessor_name() 
    120143            rel_query_name = f.related_query_name() 
  • a/django/db/models/fields/related.py

    old new  
    335335            manager.clear() 
    336336        manager.add(*value) 
    337337 
    338 def create_many_related_manager(superclass): 
     338def create_many_related_manager(superclass, through=False): 
    339339    """Creates a manager that subclasses 'superclass' (which is a Manager) 
    340340    and adds behavior for many-to-many related objects.""" 
    341341    class ManyRelatedManager(superclass): 
     
    349349            self.join_table = join_table 
    350350            self.source_col_name = source_col_name 
    351351            self.target_col_name = target_col_name 
     352            self.through = through 
    352353            self._pk_val = self.instance._get_pk_val() 
    353354            if self._pk_val is None: 
    354355                raise ValueError("%r instance needs to have a primary key value before a many-to-many relationship can be used." % model) 
     
    381382        clear.alters_data = True 
    382383 
    383384        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'" 
    384389            new_obj = self.model(**kwargs) 
    385390            new_obj.save() 
    386391            self.add(new_obj) 
     
    448453                [self._pk_val]) 
    449454            transaction.commit_unless_managed() 
    450455 
     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 
    451463    return ManyRelatedManager 
    452464 
    453465class ManyRelatedObjectsDescriptor(object): 
     
    468480        # model's default manager. 
    469481        rel_model = self.related.model 
    470482        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
    472484 
    473485        qn = connection.ops.quote_name 
    474486        manager = RelatedManager( 
     
    487499        if instance is None: 
    488500            raise AttributeError, "Manager must be accessed via instance" 
    489501 
     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 
    490506        manager = self.__get__(instance) 
    491507        manager.clear() 
    492508        manager.add(*value) 
     
    509525        # model's default manager. 
    510526        rel_model=self.field.rel.to 
    511527        superclass = rel_model._default_manager.__class__ 
    512         RelatedManager = create_many_related_manager(superclass
     528        RelatedManager = create_many_related_manager(superclass, self.field.rel.through
    513529 
    514530        qn = connection.ops.quote_name 
    515531        manager = RelatedManager( 
     
    528544        if instance is None: 
    529545            raise AttributeError, "Manager must be accessed via instance" 
    530546 
     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 
    531551        manager = self.__get__(instance) 
    532552        manager.clear() 
    533553        manager.add(*value) 
     
    581601 
    582602class ManyToManyRel(object): 
    583603    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): 
    585605        self.to = to 
    586606        self.num_in_admin = num_in_admin 
    587607        self.related_name = related_name 
     
    593613        self.raw_id_admin = raw_id_admin 
    594614        self.symmetrical = symmetrical 
    595615        self.multiple = True 
     616        self.through = through 
    596617 
    597618        assert not (self.raw_id_admin and self.filter_interface), "ManyToManyRels may not use both raw_id_admin and filter_interface" 
    598619 
     
    736757            filter_interface=kwargs.pop('filter_interface', None), 
    737758            limit_choices_to=kwargs.pop('limit_choices_to', None), 
    738759            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)) 
    740762        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 
    741768        if kwargs["rel"].raw_id_admin: 
    742769            kwargs.setdefault("validator_list", []).append(self.isValidIDList) 
    743770        Field.__init__(self, **kwargs) 
     
    760787 
    761788    def _get_m2m_db_table(self, opts): 
    762789        "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: 
    764793            return self.db_table 
    765794        else: 
    766795            return '%s_%s' % (opts.db_table, self.name) 
    767796 
    768797    def _get_m2m_column_name(self, related): 
    769798        "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] 
    770802        # 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: 
    772804            return 'from_' + related.model._meta.object_name.lower() + '_id' 
    773805        else: 
    774806            return related.model._meta.object_name.lower() + '_id' 
    775807 
    776808    def _get_m2m_reverse_name(self, related): 
    777809        "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] 
    778817        # 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: 
    780819            return 'to_' + related.parent_model._meta.object_name.lower() + '_id' 
    781820        else: 
    782821            return related.parent_model._meta.object_name.lower() + '_id' 
     
    822861 
    823862        # Set up the accessor for the m2m table name for the relation 
    824863        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 
    825867 
    826868    def contribute_to_related_class(self, cls, related): 
    827869        # m2m relations to self do not have a ManyRelatedObjectsDescriptor, 
  • a/django/db/models/options.py

    old new  
    1010from django.db.models.fields.related import ManyToManyRel 
    1111from django.db.models.fields import AutoField, FieldDoesNotExist 
    1212from django.db.models.fields.proxy import OrderWrt 
    13 from django.db.models.loading import get_models, app_cache_ready 
     13from django.db.models.loading import get_models, get_model, app_cache_ready 
    1414from django.db.models import Manager 
    1515from django.utils.translation import activate, deactivate_all, get_language, string_concat 
    1616from django.utils.encoding import force_unicode, smart_str 
     
    378378            follow = self.get_follow() 
    379379        return [f for f in self.get_all_related_objects() if follow.get(f.name, None)] 
    380380 
     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 
    381393    def get_data_holders(self, follow=None): 
    382394        if follow == None: 
    383395            follow = self.get_follow() 
  • a/docs/model-api.txt

    old new  
    991991 
    992992    =======================  ============================================================ 
    993993 
     994Extra fields on many-to-many relationships 
     995~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
     996 
     997When you're only dealing with mixing and matching pizzas and toppings, a standard  
     998``ManyToManyField`` works great.  For many situations, however, some extra  
     999fields are required on the relationships between models.  For situations like  
     1000this, Django allows for the specification of an intermediary many-to-many model. 
     1001To enable this functionality, simply specify a ``through`` keyword argument onto  
     1002the ``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 
     1033Now that you have set up your ``ManyToManyField`` to use your intermediary  
     1034model (Membership, in this case), you're ready to use the convenience methods 
     1035provided by that ``ManyToManyField``.  Here's an example of how you can query 
     1036for 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 
     1054As you can see, creating ``Membership`` objects automatically adds the  
     1055``Person`` objects to the ``beatles.members`` queryset.  This means that you  
     1056can 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 
     1066For 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 
    9941071One-to-one relationships 
    9951072~~~~~~~~~~~~~~~~~~~~~~~~ 
    9961073 
  • a/tests/modeltests/invalid_models/models.py

    old new  
    111111class MissingRelations(models.Model): 
    112112    rel1 = models.ForeignKey("Rel1") 
    113113    rel2 = models.ManyToManyField("Rel2") 
     114     
     115class MissingManualM2MModel(models.Model): 
     116    name = models.CharField(max_length=5) 
     117    missing_m2m = models.ManyToManyField(Model, through="MissingM2MModel") 
     118     
     119class Person(models.Model): 
     120    name = models.CharField(max_length=5) 
     121 
     122class 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 
     127class 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 
     132class Membership(models.Model): 
     133    person = models.ForeignKey(Person) 
     134    group = models.ForeignKey(Group) 
     135    not_default_or_null = models.CharField(max_length=5) 
     136 
     137class MembershipMissingFK(models.Model): 
     138    person = models.ForeignKey(Person) 
     139 
     140class PersonSelfRefM2M(models.Model): 
     141    name = models.CharField(max_length=5) 
     142    friends = models.ManyToManyField('self', through="Relationship") 
     143 
     144class 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() 
    114148 
    115149model_errors = """invalid_models.fielderrors: "charfield": CharFields require a "max_length" attribute. 
    116150invalid_models.fielderrors: "decimalfield": DecimalFields require a "decimal_places" attribute. 
     
    197231invalid_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'. 
    198232invalid_models.missingrelations: 'rel2' has m2m relation with model Rel2, which has not been installed 
    199233invalid_models.missingrelations: 'rel1' has relation with model Rel1, which has not been installed 
     234invalid_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. 
     235invalid_models.missingmanualm2mmodel: missing_m2m has a manually-defined m2m relation through model MissingM2MModel, which does not exist. 
     236invalid_models.grouptwo: primary has a manually-defined m2m relation through model Membership, which does not have foreign keys to Person and GroupTwo 
     237invalid_models.grouptwo: secondary has a manually-defined m2m relation through model MembershipMissingFK, which does not have foreign keys to Group and GroupTwo 
    200238""" 
  • /dev/null

    old new  
  • /dev/null

    old new  
     1from django.db import models 
     2from datetime import datetime 
     3 
     4# M2M described on one of the models 
     5class Person(models.Model): 
     6    name = models.CharField(max_length=128) 
     7 
     8    def __unicode__(self): 
     9        return self.name 
     10 
     11class 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 
     20class 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 
     29class 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 
     41class TestNoDefaultsOrNulls(models.Model): 
     42    person = models.ForeignKey(Person) 
     43    group = models.ForeignKey(Group) 
     44    nodefaultnonull = models.CharField(max_length=5) 
     45 
     46class 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 
     53class 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) 
     119Traceback (most recent call last): 
     120... 
     121AttributeError: 'ManyRelatedManager' object has no attribute 'add' 
     122 
     123>>> rock.members.create(name = 'Anne') 
     124Traceback (most recent call last): 
     125... 
     126AttributeError: 'ManyRelatedManager' object has no attribute 'create' 
     127 
     128# Remove has similar complications, and is not provided either. 
     129>>> rock.members.remove(jim) 
     130Traceback (most recent call last): 
     131... 
     132AttributeError: '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 
     151Traceback (most recent call last): 
     152... 
     153AttributeError: 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) 
     169Traceback (most recent call last): 
     170... 
     171AttributeError: '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') 
     176Traceback (most recent call last): 
     177... 
     178AttributeError: 'ManyRelatedManager' object has no attribute 'create' 
     179 
     180# Remove has similar complications, and is not provided either. 
     181>>> jim.group_set.remove(rock) 
     182Traceback (most recent call last): 
     183... 
     184AttributeError: '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 
     201Traceback (most recent call last): 
     202... 
     203AttributeError: 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"""}