Django

Code

Ticket #6095: 6095-qsrf.diff

File 6095-qsrf.diff, 26.3 kB (added by floguy, 9 months ago)

Updated the patch to apply to queryset-refactor. Currently two of my tests which pass on Trunk do not pass on qs-rf, and I'm not exactly sure why. Will have to wait to see more documentation on qs-rf before I can resolve.

  • a/AUTHORS

    old new  
    135135    Afonso Fernández Nogueira <fonzzo.django@gmail.com> 
    136136    Matthew Flanagan <http://wadofstuff.blogspot.com> 
    137137    Eric Floehr <eric@intellovations.com> 
     138    Eric Florenzano <floguy@gmail.com> 
    138139    Vincent Foley <vfoleybourgon@yahoo.ca> 
    139140    Rudolph Froger <rfroger@estrate.nl> 
    140141    Jorge Gajon <gajon@gajon.org> 
  • a/django/core/management/sql.py

    old new  
    352352    qn = connection.ops.quote_name 
    353353    inline_references = connection.features.inline_fk_references 
    354354    for f in opts.local_many_to_many: 
    355         if not isinstance(f.rel, generic.GenericRel)
     355        if not isinstance(f.rel, generic.GenericRel) and f.creates_table
    356356            tablespace = f.db_tablespace or opts.db_tablespace 
    357357            if tablespace and connection.features.supports_tablespaces and connection.features.autoindexes_primary_keys: 
    358358                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 
     
    115116                if isinstance(f.rel.to, (str, unicode)): 
    116117                    continue 
    117118 
     119            if hasattr(f.rel, 'through') and f.rel.through != None:  
     120                intermediary_model = None 
     121                for model in models.get_models(): 
     122                    if model._meta.module_name == f.rel.through.lower(): 
     123                        intermediary_model = model 
     124                if intermediary_model == None: 
     125                    e.add(opts, "%s has a manually-defined m2m relationship through a model (%s) which does not exist." % (f.name, f.rel.through)) 
     126                else: 
     127                    signature = (f.rel.to, cls, intermediary_model) 
     128                    if signature in seen_intermediary_signatures: 
     129                        e.add(opts, "%s has two manually-defined m2m relationships through the same model (%s), which is not possible.  Please use a field on your intermediary model instead." % (cls._meta.object_name, intermediary_model._meta.object_name)) 
     130                    else: 
     131                        seen_intermediary_signatures.append(signature) 
     132                    seen_related_fk, seen_this_fk = False, False 
     133                    for field in intermediary_model._meta.fields: 
     134                        if field.rel: 
     135                            if field.rel.to == f.rel.to: 
     136                                seen_related_fk = True  
     137                            elif field.rel.to == cls: 
     138                                seen_this_fk = True 
     139                    if not seen_related_fk or not seen_this_fk: 
     140                        e.add(opts, "%s has a manually-defined m2m relationship through a 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)) 
    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  
    1010from django import oldforms 
    1111from django import newforms as forms 
    1212from django.dispatch import dispatcher 
     13from new import instancemethod 
    1314 
    1415try: 
    1516    set 
     
    306307            manager.clear() 
    307308        manager.add(*value) 
    308309 
    309 def create_many_related_manager(superclass): 
     310def create_many_related_manager(superclass, through=False): 
    310311    """Creates a manager that subclasses 'superclass' (which is a Manager) 
    311312    and adds behavior for many-to-many related objects.""" 
    312313    class ManyRelatedManager(superclass): 
     
    320321            self.join_table = join_table 
    321322            self.source_col_name = source_col_name 
    322323            self.target_col_name = target_col_name 
     324            self.through = through 
    323325            self._pk_val = self.instance._get_pk_val() 
    324326            if self._pk_val is None: 
    325327                raise ValueError("%r instance needs to have a primary key value before a many-to-many relationship can be used." % model) 
     
    327329        def get_query_set(self): 
    328330            return superclass.get_query_set(self).filter(**(self.core_filters)) 
    329331 
    330         def add(self, *objs): 
    331             self._add_items(self.source_col_name, self.target_col_name, *objs) 
    332  
    333             # If this is a symmetrical m2m relation to self, add the mirror entry in the m2m table 
    334             if self.symmetrical: 
    335                 self._add_items(self.target_col_name, self.source_col_name, *objs) 
    336         add.alters_data = True 
    337  
    338         def remove(self, *objs): 
    339             self._remove_items(self.source_col_name, self.target_col_name, *objs) 
    340  
    341             # If this is a symmetrical m2m relation to self, remove the mirror entry in the m2m table 
    342             if self.symmetrical: 
    343                 self._remove_items(self.target_col_name, self.source_col_name, *objs) 
    344         remove.alters_data = True 
    345  
    346332        def clear(self): 
    347333            self._clear_items(self.source_col_name) 
    348334 
     
    419405                [self._pk_val]) 
    420406            transaction.commit_unless_managed() 
    421407 
     408    def add(self, *objs): 
     409        self._add_items(self.source_col_name, self.target_col_name, *objs) 
     410 
     411        # If this is a symmetrical m2m relation to self, add the mirror entry in the m2m table 
     412        if self.symmetrical: 
     413            self._add_items(self.target_col_name, self.source_col_name, *objs) 
     414    add.alters_data = True 
     415 
     416    def remove(self, *objs): 
     417        self._remove_items(self.source_col_name, self.target_col_name, *objs) 
     418 
     419        # If this is a symmetrical m2m relation to self, remove the mirror entry in the m2m table 
     420        if self.symmetrical: 
     421            self._remove_items(self.target_col_name, self.source_col_name, *objs) 
     422    remove.alters_data = True 
     423 
     424    if not through: 
     425        ManyRelatedManager.add = instancemethod(add, None, ManyRelatedManager) 
     426        ManyRelatedManager.remove = instancemethod(remove, None, ManyRelatedManager) 
     427 
    422428    return ManyRelatedManager 
    423429 
    424430class ManyRelatedObjectsDescriptor(object): 
     
    439445        # model's default manager. 
    440446        rel_model = self.related.model 
    441447        superclass = rel_model._default_manager.__class__ 
    442         RelatedManager = create_many_related_manager(superclass
     448        RelatedManager = create_many_related_manager(superclass, self.related.field.rel.through
    443449 
    444450        qn = connection.ops.quote_name 
    445451        manager = RelatedManager( 
     
    458464        if instance is None: 
    459465            raise AttributeError, "Manager must be accessed via instance" 
    460466 
     467        through = getattr(self.related.field.rel, 'through', None) 
     468        if through is not None: 
     469            raise AttributeError, "Cannot set values on a ManyToManyField which specifies a through model.  Use %s's Manager instead." % through 
     470 
    461471        manager = self.__get__(instance) 
    462472        manager.clear() 
    463473        manager.add(*value) 
     
    480490        # model's default manager. 
    481491        rel_model=self.field.rel.to 
    482492        superclass = rel_model._default_manager.__class__ 
    483         RelatedManager = create_many_related_manager(superclass
     493        RelatedManager = create_many_related_manager(superclass, self.field.rel.through
    484494 
    485495        qn = connection.ops.quote_name 
    486496        manager = RelatedManager( 
     
    499509        if instance is None: 
    500510            raise AttributeError, "Manager must be accessed via instance" 
    501511 
     512        through = getattr(self.field.rel, 'through', None) 
     513        if through is not None: 
     514            raise AttributeError, "Cannot set values on a ManyToManyField which specifies a through model.  Use %s's Manager instead." % through 
     515 
    502516        manager = self.__get__(instance) 
    503517        manager.clear() 
    504518        manager.add(*value) 
     
    548562 
    549563class ManyToManyRel(object): 
    550564    def __init__(self, to, num_in_admin=0, related_name=None, 
    551         filter_interface=None, limit_choices_to=None, raw_id_admin=False, symmetrical=True): 
     565        filter_interface=None, limit_choices_to=None, raw_id_admin=False, symmetrical=True, 
     566        through=None): 
    552567        self.to = to 
    553568        self.num_in_admin = num_in_admin 
    554569        self.related_name = related_name 
     
    560575        self.raw_id_admin = raw_id_admin 
    561576        self.symmetrical = symmetrical 
    562577        self.multiple = True 
     578        self.through = through 
    563579 
    564580        assert not (self.raw_id_admin and self.filter_interface), "ManyToManyRels may not use both raw_id_admin and filter_interface" 
    565581 
     
    696712            filter_interface=kwargs.pop('filter_interface', None), 
    697713            limit_choices_to=kwargs.pop('limit_choices_to', None), 
    698714            raw_id_admin=kwargs.pop('raw_id_admin', False), 
    699             symmetrical=kwargs.pop('symmetrical', True)) 
     715            symmetrical=kwargs.pop('symmetrical', True), 
     716            through=kwargs.pop('through', None)) 
    700717        self.db_table = kwargs.pop('db_table', None) 
     718        if kwargs['rel'].through: 
     719            self.creates_table = False 
     720            assert not self.db_table, "Cannot specify a db_table if an intermediary model is used." 
     721        else: 
     722            self.creates_table = True 
    701723        if kwargs["rel"].raw_id_admin: 
    702724            kwargs.setdefault("validator_list", []).append(self.isValidIDList) 
    703725        Field.__init__(self, **kwargs) 
     
    720742 
    721743    def _get_m2m_db_table(self, opts): 
    722744        "Function that can be curried to provide the m2m table name for this relation" 
    723         if self.db_table: 
     745        if self.rel.through != None: 
     746            return get_model(opts.app_label, self.rel.through)._meta.db_table 
     747        elif self.db_table: 
    724748            return self.db_table 
    725749        else: 
    726750            return '%s_%s' % (opts.db_table, self.name) 
     
    728752    def _get_m2m_column_name(self, related): 
    729753        "Function that can be curried to provide the source column name for the m2m table" 
    730754        # If this is an m2m relation to self, avoid the inevitable name clash 
    731         if related.model == related.parent_model: 
     755        if self.rel.through != None: 
     756            field = related.model._meta.get_related_object(self.rel.through).field 
     757            attname, column = field.get_attname_column() 
     758            return column 
     759        elif related.model == related.parent_model: 
    732760            return 'from_' + related.model._meta.object_name.lower() + '_id' 
    733761        else: 
    734762            return related.model._meta.object_name.lower() + '_id' 
     
    736764    def _get_m2m_reverse_name(self, related): 
    737765        "Function that can be curried to provide the related column name for the m2m table" 
    738766        # If this is an m2m relation to self, avoid the inevitable name clash 
    739         if related.model == related.parent_model: 
     767        if self.rel.through != None: 
     768            field = related.parent_model._meta.get_related_object(self.rel.through).field 
     769            attname, column = field.get_attname_column() 
     770            return column 
     771        elif related.model == related.parent_model: 
    740772            return 'to_' + related.parent_model._meta.object_name.lower() + '_id' 
    741773        else: 
    742774            return related.parent_model._meta.object_name.lower() + '_id' 
  • 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 
     
    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() 
  • a/tests/modeltests/invalid_models/models.py

    old new  
    112112    rel1 = models.ForeignKey("Rel1") 
    113113    rel2 = models.ManyToManyField("Rel2") 
    114114 
     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 
    115140model_errors = """invalid_models.fielderrors: "charfield": CharFields require a "max_length" attribute. 
    116141invalid_models.fielderrors: "decimalfield": DecimalFields require a "decimal_places" attribute. 
    117142invalid_models.fielderrors: "decimalfield": DecimalFields require a "max_digits" attribute. 
     
    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 relationships through the same model (Membership), which is not possible.  Please use a field on your intermediary model instead. 
     226invalid_models.missingmanualm2mmodel: missing_m2m has a manually-defined m2m relationship through a model (MissingM2MModel) which does not exist. 
     227invalid_models.grouptwo: primary has a manually-defined m2m relationship through a model (Membership) which does not have foreign keys to Person and GroupTwo 
     228invalid_models.grouptwo: secondary has a manually-defined m2m relationship through a model (MembershipMissingFK) which does not have foreign keys to Group and GroupTwo 
    200229""" 
  • /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 
     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# Remove has similar complications, and is not provided either. 
     112>>> rock.members.remove(jim) 
     113Traceback (most recent call last): 
     114... 
     115AttributeError: 'ManyRelatedManager' object has no attribute 'remove' 
     116 
     117# Here we back up the list of all members of Rock. 
     118>>> backup = list(rock.members.all()) 
     119 
     120# ...and we verify that it has worked. 
     121>>> backup 
     122[<Person: Jim>, <Person: Jane>] 
     123 
     124# The clear function should still work. 
     125>>> rock.members.clear() 
     126 
     127# Now there will be no members of Rock. 
     128>>> rock.members.all() 
     129[] 
     130 
     131# Assignment should not work with models specifying a through model for many of 
     132# the same reasons as adding. 
     133>>> rock.members = backup 
     134Traceback (most recent call last): 
     135... 
     136AttributeError: Cannot set values on a ManyToManyField which specifies a through model.  Use Membership's Manager instead. 
     137 
     138# Let's re-save those instances that we've cleared. 
     139>>> m1.save() 
     140>>> m2.save() 
     141 
     142# Verifying that those instances were re-saved successfully. 
     143>>> rock.members.all() 
     144[<Person: Jim>, <Person: Jane>] 
     145 
     146 
     147### Reverse Descriptors Tests ### 
     148 
     149# Due to complications with adding via an intermediary model, the add method is 
     150# not provided. 
     151>>> bob.group_set.add(rock) 
     152Traceback (most recent call last): 
     153... 
     154AttributeError: 'ManyRelatedManager' object has no attribute 'add' 
     155 
     156# Remove has similar complications, and is not provided either. 
     157>>> jim.group_set.remove(rock) 
     158Traceback (most recent call last): 
     159... 
     160AttributeError: 'ManyRelatedManager' object has no attribute 'remove' 
     161 
     162# Here we back up the list of all of Jim's groups. 
     163>>> backup = list(jim.group_set.all()) 
     164>>> backup 
     165[<Group: Rock>, <Group: Roll>] 
     166 
     167# The clear function should still work. 
     168>>> jim.group_set.clear() 
     169 
     170# Now Jim will be in no groups. 
     171>>> jim.group_set.all() 
     172[] 
     173 
     174# Assignment should not work with models specifying a through model for many of 
     175# the same reasons as adding. 
     176>>> jim.group_set = backup 
     177Traceback (most recent call last): 
     178... 
     179AttributeError: Cannot set values on a ManyToManyField which specifies a through model.  Use Membership's Manager instead. 
     180 
     181# Let's re-save those instances that we've cleared. 
     182>>> m1.save() 
     183>>> m4.save() 
     184 
     185# Verifying that those instances were re-saved successfully. 
     186>>> jim.group_set.all() 
     187[<Group: Rock>, <Group: Roll>] 
     188 
     189### Custom Tests ### 
     190 
     191# Let's see if we can query through our second relationship. 
     192>>> rock.custom_members.all() 
     193[] 
     194 
     195# We can query in the opposite direction as well. 
     196>>> bob.custom.all() 
     197[] 
     198 
     199# Let's create some membership objects in this custom relationship. 
     200>>> cm1 = CustomMembership.objects.create(person = bob, group = rock) 
     201>>> cm2 = CustomMembership.objects.create(person = jim, group = rock) 
     202 
     203# If we get the number of people in Rock, it should be both Bob and Jim. 
     204>>> rock.custom_members.all() 
     205[<Person: Bob>, <Person: Jim>] 
     206 
     207# Bob should only be in one custom group. 
     208>>> bob.custom.all() 
     209[<Group: Rock>] 
     210 
     211# Let's make sure our new descriptors don't conflict with the FK related_name. 
     212>>> bob.custom_person_related_name.all() 
     213[<CustomMembership: Bob is a member of Rock>] 
     214 
     215### QUERY TESTS ### 
     216 
     217# We can query for the related model by using its attribute name (members, in  
     218# this case). 
     219>>> Group.objects.filter(members__name='Bob') 
     220[<Group: Roll>] 
     221 
     222# To query through the intermediary model, we specify its model name. 
     223# In this case, membership. 
     224>>> Group.objects.filter(membership__invite_reason = "She was just awesome.") 
     225[<Group: Rock>] 
     226 
     227# If we want to query in the reverse direction by the related model, use its 
     228# model name (group, in this case). 
     229>>> Person.objects.filter(group__name="Rock") 
     230[<Person: Jim>, <Person: Jane>] 
     231 
     232# If the m2m field has specified a related_name, using that will work. 
     233>>> Person.objects.filter(custom__name="Rock") 
     234[<Person: Bob>, <Person: Jim>] 
     235 
     236# To query through the intermediary model in the reverse direction, we again 
     237# specify its model name (membership, in this case). 
     238>>> Person.objects.filter(membership__invite_reason = "She was just awesome.") 
     239[<Person: Jane>] 
     240 
     241# Let's see all of the groups that Jane joined after 1 Jan 2005: 
     242>>> Group.objects.filter(membership__date_joined__gt = datetime(2005, 1, 1),  
     243... membership__person = jane) 
     244[<Group: Rock>] 
     245 
     246# Queries also work in the reverse direction: Now let's see all of the people  
     247# that have joined Rock since 1 Jan 2005: 
     248>>> Person.objects.filter(membership__date_joined__gt = datetime(2005, 1, 1),  
     249... membership__group = rock) 
     250[<Person: Jim>, <Person: Jane>] 
     251 
     252# Conceivably, queries through membership could return correct, but non-unique 
     253# querysets.  To demonstrate this, we query for all people who have joined a  
     254# group after 2004: 
     255>>> Person.objects.filter(membership__date_joined__gt = datetime(2004, 1, 1)) 
     256[<Person: Jim>, <Person: Jim>, <Person: Jane>] 
     257 
     258# Jim showed up twice, because he joined two groups ('Rock', and 'Roll'): 
     259>>> [(m.person.name, m.group.name) for m in  
     260... Membership.objects.filter(date_joined__gt = datetime(2004, 1, 1))] 
     261[(u'Jim', u'Rock'), (u'Jane', u'Rock'), (u'Jim', u'Roll')] 
     262 
     263# QuerySet's distinct() method can correct this problem. 
     264>>> Person.objects.filter(membership__date_joined__gt = datetime(2004, 1, 1)).distinct() 
     265[<Person: Jim>, <Person: Jane>] 
     266"""}