Django

Code

Ticket #6095: 6095-nfadmin.2.diff

File 6095-nfadmin.2.diff, 25.9 kB (added by floguy, 9 months ago)

Made sure that create is not allowed. This was an oversight in the original patch, but now there are tests which verify that create should not be allowed on a ManyRelatedManager?. Also removed new.instancemethod craziness because it was not necessary. All tests pass.

  • django/db/models/options.py

    old new  
    22from django.db.models.related import RelatedObject 
    33from django.db.models.fields.related import ManyToManyRel 
    44from django.db.models.fields import AutoField, FieldDoesNotExist 
    5 from django.db.models.loading import get_models, app_cache_ready 
     5from django.db.models.loading import get_models, get_model, app_cache_ready 
    66from django.db.models.query import orderlist2sql 
    77from django.utils.translation import activate, deactivate_all, get_language, string_concat 
    88from django.utils.encoding import force_unicode, smart_str 
     
    161161            follow = self.get_follow() 
    162162        return [f for f in self.get_all_related_objects() if follow.get(f.name, None)] 
    163163 
     164    def get_related_object(self, from_model): 
     165        "Gets the RelatedObject which links from from_model to this model." 
     166        if isinstance(from_model, str): 
     167            from_model = get_model(self.app_label, from_model) 
     168        for related_object in self.get_all_related_objects(): 
     169            if related_object.model == from_model: 
     170                return related_object 
     171        return None 
     172 
    164173    def get_data_holders(self, follow=None): 
    165174        if follow == None: 
    166175            follow = self.get_follow() 
  • django/db/models/fields/related.py

    old new  
    298298            manager.clear() 
    299299        manager.add(*value) 
    300300 
    301 def create_many_related_manager(superclass): 
     301def create_many_related_manager(superclass, through=False): 
    302302    """Creates a manager that subclasses 'superclass' (which is a Manager) 
    303303    and adds behavior for many-to-many related objects.""" 
    304304    class ManyRelatedManager(superclass): 
     
    312312            self.join_table = join_table 
    313313            self.source_col_name = source_col_name 
    314314            self.target_col_name = target_col_name 
     315            self.through = through 
    315316            self._pk_val = self.instance._get_pk_val() 
    316317            if self._pk_val is None: 
    317318                raise ValueError("%r instance needs to have a primary key value before a many-to-many relationship can be used." % model) 
     
    321322 
    322323        def add(self, *objs): 
    323324            self._add_items(self.source_col_name, self.target_col_name, *objs) 
    324  
    325325            # If this is a symmetrical m2m relation to self, add the mirror entry in the m2m table 
    326326            if self.symmetrical: 
    327327                self._add_items(self.target_col_name, self.source_col_name, *objs) 
    328328        add.alters_data = True 
    329  
     329     
    330330        def remove(self, *objs): 
    331331            self._remove_items(self.source_col_name, self.target_col_name, *objs) 
    332  
    333332            # If this is a symmetrical m2m relation to self, remove the mirror entry in the m2m table 
    334333            if self.symmetrical: 
    335334                self._remove_items(self.target_col_name, self.source_col_name, *objs) 
     
    344343        clear.alters_data = True 
    345344 
    346345        def create(self, **kwargs): 
     346            if through: 
     347                raise AttributeError, "'ManyRelatedManager' object has no attribute 'create'" 
    347348            new_obj = self.model(**kwargs) 
    348349            new_obj.save() 
    349350            self.add(new_obj) 
     
    411412                [self._pk_val]) 
    412413            transaction.commit_unless_managed() 
    413414 
     415    # If it's an intermediary model, detach the add, remove, and create methods 
     416    # from this ManyRelatedManager. 
     417    if through: 
     418        del ManyRelatedManager.add 
     419        del ManyRelatedManager.remove 
     420 
    414421    return ManyRelatedManager 
    415422 
    416423class ManyRelatedObjectsDescriptor(object): 
     
    431438        # model's default manager. 
    432439        rel_model = self.related.model 
    433440        superclass = rel_model._default_manager.__class__ 
    434         RelatedManager = create_many_related_manager(superclass
     441        RelatedManager = create_many_related_manager(superclass, self.related.field.rel.through
    435442 
    436443        qn = connection.ops.quote_name 
    437444        manager = RelatedManager( 
     
    450457        if instance is None: 
    451458            raise AttributeError, "Manager must be accessed via instance" 
    452459 
     460        through = getattr(self.related.field.rel, 'through', None) 
     461        if through: 
     462            raise AttributeError, "Cannot set values on a ManyToManyField which specifies a through model.  Use %s's Manager instead." % through 
     463         
    453464        manager = self.__get__(instance) 
    454465        manager.clear() 
    455466        manager.add(*value) 
     
    472483        # model's default manager. 
    473484        rel_model=self.field.rel.to 
    474485        superclass = rel_model._default_manager.__class__ 
    475         RelatedManager = create_many_related_manager(superclass
     486        RelatedManager = create_many_related_manager(superclass, self.field.rel.through
    476487 
    477488        qn = connection.ops.quote_name 
    478489        manager = RelatedManager( 
     
    491502        if instance is None: 
    492503            raise AttributeError, "Manager must be accessed via instance" 
    493504 
     505        through = getattr(self.field.rel, 'through', None) 
     506        if through: 
     507            raise AttributeError, "Cannot set values on a ManyToManyField which specifies a through model.  Use %s's Manager instead." % through 
     508 
    494509        manager = self.__get__(instance) 
    495510        manager.clear() 
    496511        manager.add(*value) 
     
    669684            num_in_admin=kwargs.pop('num_in_admin', 0), 
    670685            related_name=kwargs.pop('related_name', None), 
    671686            limit_choices_to=kwargs.pop('limit_choices_to', None), 
    672             symmetrical=kwargs.pop('symmetrical', True)) 
     687            symmetrical=kwargs.pop('symmetrical', True), 
     688            through=kwargs.pop('through', None)) 
    673689        self.db_table = kwargs.pop('db_table', None) 
     690        if kwargs['rel'].through: 
     691            self.creates_table = False 
     692            assert not self.db_table, "Cannot specify a db_table if an intermediary model is used." 
     693        else: 
     694            self.creates_table = True  
    674695        Field.__init__(self, **kwargs) 
    675696 
    676697        msg = ugettext_lazy('Hold down "Control", or "Command" on a Mac, to select more than one.') 
     
    685706 
    686707    def _get_m2m_db_table(self, opts): 
    687708        "Function that can be curried to provide the m2m table name for this relation" 
    688         if self.db_table: 
     709        if self.rel.through != None:  
     710            return get_model(opts.app_label, self.rel.through)._meta.db_table 
     711        elif self.db_table: 
    689712            return self.db_table 
    690713        else: 
    691714            return '%s_%s' % (opts.db_table, self.name) 
     
    693716    def _get_m2m_column_name(self, related): 
    694717        "Function that can be curried to provide the source column name for the m2m table" 
    695718        # If this is an m2m relation to self, avoid the inevitable name clash 
    696         if related.model == related.parent_model: 
     719        if self.rel.through != None: 
     720            field = related.model._meta.get_related_object(self.rel.through).field 
     721            attname, column = field.get_attname_column()  
     722            return column  
     723        elif related.model == related.parent_model: 
    697724            return 'from_' + related.model._meta.object_name.lower() + '_id' 
    698725        else: 
    699726            return related.model._meta.object_name.lower() + '_id' 
     
    701728    def _get_m2m_reverse_name(self, related): 
    702729        "Function that can be curried to provide the related column name for the m2m table" 
    703730        # If this is an m2m relation to self, avoid the inevitable name clash 
    704         if related.model == related.parent_model: 
     731        if self.rel.through != None: 
     732            field = related.parent_model._meta.get_related_object(self.rel.through).field 
     733            attname, column = field.get_attname_column()  
     734            return column 
     735        elif related.model == related.parent_model: 
    705736            return 'to_' + related.parent_model._meta.object_name.lower() + '_id' 
    706737        else: 
    707738            return related.parent_model._meta.object_name.lower() + '_id' 
     
    816847 
    817848class ManyToManyRel(object): 
    818849    def __init__(self, to, num_in_admin=0, related_name=None, 
    819         limit_choices_to=None, symmetrical=True): 
     850        limit_choices_to=None, symmetrical=True, through=None): 
    820851        self.to = to 
    821852        self.num_in_admin = num_in_admin 
    822853        self.related_name = related_name 
     
    826857        self.edit_inline = False 
    827858        self.symmetrical = symmetrical 
    828859        self.multiple = True 
     860        self.through = through  
  • django/core/management/validation.py

    old new  
    102102                        if r.get_accessor_name() == rel_query_name: 
    103103                            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)) 
    104104 
     105        seen_intermediary_signatures = [] 
     106 
    105107        for i, f in enumerate(opts.many_to_many): 
    106108            # Check to see if the related m2m field will clash with any 
    107109            # existing fields, m2m fields, m2m related objects or related objects 
     
    111113                # so skip the next section 
    112114                if isinstance(f.rel.to, (str, unicode)): 
    113115                    continue 
     116            if hasattr(f.rel, 'through') and f.rel.through != None: 
     117                intermediary_model = None 
     118                for model in models.get_models(): 
     119                    if model._meta.module_name == f.rel.through.lower(): 
     120                        intermediary_model = model 
     121                if intermediary_model == None: 
     122                    e.add(opts, "%s has a manually-defined m2m relationship through a model (%s) which does not exist." % (f.name, f.rel.through)) 
     123                else: 
     124                    signature = (f.rel.to, cls, intermediary_model) 
     125                    if signature in seen_intermediary_signatures: 
     126                        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)) 
     127                    else: 
     128                        seen_intermediary_signatures.append(signature) 
     129                    seen_related_fk, seen_this_fk = False, False 
     130                    for field in intermediary_model._meta.fields: 
     131                        if field.rel: 
     132                            if field.rel.to == f.rel.to: 
     133                                seen_related_fk = True 
     134                            elif field.rel.to == cls: 
     135                                seen_this_fk = True 
     136                    if not seen_related_fk or not seen_this_fk: 
     137                        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)) 
    114138 
    115139            rel_opts = f.rel.to._meta 
    116140            rel_name = RelatedObject(f.rel.to, cls, f).get_accessor_name() 
  • django/core/management/sql.py

    old new  
    354354    qn = connection.ops.quote_name 
    355355    inline_references = connection.features.inline_fk_references 
    356356    for f in opts.many_to_many: 
    357         if not isinstance(f.rel, generic.GenericRel)
     357        if not isinstance(f.rel, generic.GenericRel) and f.creates_table
    358358            tablespace = f.db_tablespace or opts.db_tablespace 
    359359            if tablespace and connection.features.supports_tablespaces and connection.features.autoindexes_primary_keys: 
    360360                tablespace_sql = ' ' + connection.ops.tablespace_sql(tablespace, inline=True) 
  • django/contrib/admin/options.py

    old new  
    172172                kwargs['widget'] = widgets.ForeignKeyRawIdWidget(db_field.rel) 
    173173            else: 
    174174                if isinstance(db_field, models.ManyToManyField): 
    175                     if db_field.name in self.raw_id_fields: 
     175                    # If it uses an intermediary model, don't show field in admin. 
     176                    if db_field.rel.through != None: 
     177                        return None 
     178                    elif db_field.name in self.raw_id_fields: 
    176179                        kwargs['widget'] = widgets.ManyToManyRawIdWidget(db_field.rel) 
    177180                        kwargs['help_text'] = '' 
    178181                    elif db_field.name in (self.filter_vertical + self.filter_horizontal): 
  • tests/modeltests/invalid_models/models.py

    old new  
    110110class MissingRelations(models.Model): 
    111111    rel1 = models.ForeignKey("Rel1") 
    112112    rel2 = models.ManyToManyField("Rel2") 
     113     
     114class MissingManualM2MModel(models.Model): 
     115    name = models.CharField(max_length=5) 
     116    missing_m2m = models.ManyToManyField(Model, through="MissingM2MModel") 
     117     
     118class Person(models.Model): 
     119    name = models.CharField(max_length=5) 
    113120 
     121class Group(models.Model): 
     122    name = models.CharField(max_length=5) 
     123    primary = models.ManyToManyField(Person, through="Membership", related_name="primary") 
     124    secondary = models.ManyToManyField(Person, through="Membership", related_name="secondary") 
     125 
     126class GroupTwo(models.Model): 
     127    name = models.CharField(max_length=5) 
     128    primary = models.ManyToManyField(Person, through="Membership") 
     129    secondary = models.ManyToManyField(Group, through="MembershipMissingFK") 
     130 
     131class Membership(models.Model): 
     132    person = models.ForeignKey(Person) 
     133    group = models.ForeignKey(Group) 
     134    not_default_or_null = models.CharField(max_length=5) 
     135 
     136class MembershipMissingFK(models.Model): 
     137    person = models.ForeignKey(Person) 
     138 
    114139model_errors = """invalid_models.fielderrors: "charfield": CharFields require a "max_length" attribute. 
    115140invalid_models.fielderrors: "decimalfield": DecimalFields require a "decimal_places" attribute. 
    116141invalid_models.fielderrors: "decimalfield": DecimalFields require a "max_digits" attribute. 
     
    195220invalid_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'. 
    196221invalid_models.missingrelations: 'rel2' has m2m relation with model Rel2, which has not been installed 
    197222invalid_models.missingrelations: 'rel1' has relation with model Rel1, which has not been installed 
     223invalid_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. 
     224invalid_models.missingmanualm2mmodel: missing_m2m has a manually-defined m2m relationship through a model (MissingM2MModel) which does not exist. 
     225invalid_models.grouptwo: primary has a manually-defined m2m relationship through a model (Membership) which does not have foreign keys to Person and GroupTwo 
     226invalid_models.grouptwo: secondary has a manually-defined m2m relationship through a model (MembershipMissingFK) which does not have foreign keys to Group and GroupTwo 
    198227""" 
  • tests/modeltests/m2m_manual/__init__.py

    old new  
  • tests/modeltests/m2m_manual/models.py

    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>>> 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 a through 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 a through 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"""} 
  • AUTHORS

    old new  
    137137    Afonso Fernández Nogueira <fonzzo.django@gmail.com> 
    138138    Matthew Flanagan <http://wadofstuff.blogspot.com> 
    139139    Eric Floehr <eric@intellovations.com> 
     140    Eric Florenzano <floguy@gmail.com> 
    140141    Vincent Foley <vfoleybourgon@yahoo.ca> 
    141142    Rudolph Froger <rfroger@estrate.nl> 
    142143    Jorge Gajon <gajon@gajon.org>