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

File 6095-trunk-withdocs.rc1.diff, 29.8 KB (added by floguy, 16 years ago)

Updated to latest trunk, with some syntactic improvements and taking into account latest suggestions.

  • AUTHORS

    diff --git a/AUTHORS b/AUTHORS
    index b20092e..33bfed4 100644
    a b answer newbie questions, and generally made Django that much better:  
    144144    Afonso Fernández Nogueira <fonzzo.django@gmail.com>
    145145    Matthew Flanagan <http://wadofstuff.blogspot.com>
    146146    Eric Floehr <eric@intellovations.com>
     147    Eric Florenzano <floguy@gmail.com>
    147148    Vincent Foley <vfoleybourgon@yahoo.ca>
    148149    Rudolph Froger <rfroger@estrate.nl>
    149150    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):  
    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")
  • 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):  
    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)
  • 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):  
    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
    def get_validation_errors(outfile, app=None):  
    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 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()
  • 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):  
    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):
    def create_many_related_manager(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)
    def create_many_related_manager(superclass):  
    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)
    def create_many_related_manager(superclass):  
    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):
    class 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(
    class ManyRelatedObjectsDescriptor(object):  
    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)
    class ReverseManyRelatedObjectsDescriptor(object):  
    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(
    class ReverseManyRelatedObjectsDescriptor(object):  
    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)
    class OneToOneRel(ManyToOneRel):  
    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
    class ManyToManyRel(object):  
    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
    class ManyToManyField(RelatedField, Field):  
    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)
    class ManyToManyField(RelatedField, Field):  
    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)
    class ManyToManyField(RelatedField, Field):  
    768797    def _get_m2m_column_name(self, related):
    769798        "Function that can be curried to provide the source column name for the m2m table"
    770799        # 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:
    772804            return 'from_' + related.model._meta.object_name.lower() + '_id'
    773805        else:
    774806            return related.model._meta.object_name.lower() + '_id'
    class ManyToManyField(RelatedField, Field):  
    776808    def _get_m2m_reverse_name(self, related):
    777809        "Function that can be curried to provide the related column name for the m2m table"
    778810        # 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:
    780815            return 'to_' + related.parent_model._meta.object_name.lower() + '_id'
    781816        else:
    782817            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  
    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
    class Options(object):  
    374374            follow = self.get_follow()
    375375        return [f for f in self.get_all_related_objects() if follow.get(f.name, None)]
    376376
     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
    377386    def get_data_holders(self, follow=None):
    378387        if follow == None:
    379388            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:  
    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
  • 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):  
    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)
    114139
    115140model_errors = """invalid_models.fielderrors: "charfield": CharFields require a "max_length" attribute.
    116141invalid_models.fielderrors: "decimalfield": DecimalFields require a "decimal_places" attribute.
    invalid_models.selfclashm2m: Reverse query name for m2m field 'm2m_3' clashes wi  
    197222invalid_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'.
    198223invalid_models.missingrelations: 'rel2' has m2m relation with model Rel2, which has not been installed
    199224invalid_models.missingrelations: 'rel1' has relation with model Rel1, which has not been installed
     225invalid_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.
     226invalid_models.missingmanualm2mmodel: missing_m2m has a manually-defined m2m relation through model MissingM2MModel, which does not exist.
     227invalid_models.grouptwo: primary has a manually-defined m2m relation through model Membership, which does not have foreign keys to Person and GroupTwo
     228invalid_models.grouptwo: secondary has a manually-defined m2m relation through model MembershipMissingFK, which does not have foreign keys to Group and GroupTwo
    200229"""
  • 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
    - +  
     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
     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)
     107Traceback (most recent call last):
     108...
     109AttributeError: 'ManyRelatedManager' object has no attribute 'add'
     110
     111>>> rock.members.create(name = 'Anne')
     112Traceback (most recent call last):
     113...
     114AttributeError: 'ManyRelatedManager' object has no attribute 'create'
     115
     116# Remove has similar complications, and is not provided either.
     117>>> rock.members.remove(jim)
     118Traceback (most recent call last):
     119...
     120AttributeError: '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
     139Traceback (most recent call last):
     140...
     141AttributeError: 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)
     157Traceback (most recent call last):
     158...
     159AttributeError: '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')
     164Traceback (most recent call last):
     165...
     166AttributeError: 'ManyRelatedManager' object has no attribute 'create'
     167
     168# Remove has similar complications, and is not provided either.
     169>>> jim.group_set.remove(rock)
     170Traceback (most recent call last):
     171...
     172AttributeError: '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
     189Traceback (most recent call last):
     190...
     191AttributeError: 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
Back to Top