Django

Code

Changeset 8136

Show
Ignore:
Timestamp:
07/29/08 07:41:08 (2 months ago)
Author:
russellm
Message:

Fixed #6095 -- Added the ability to specify the model to use to manage a ManyToManyField?. Thanks to Eric Florenzano for his excellent work on this patch.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • django/trunk/AUTHORS

    r8127 r8136  
    155155    Matthew Flanagan <http://wadofstuff.blogspot.com> 
    156156    Eric Floehr <eric@intellovations.com> 
     157    Eric Florenzano <floguy@gmail.com> 
    157158    Vincent Foley <vfoleybourgon@yahoo.ca> 
    158159    Rudolph Froger <rfroger@estrate.nl> 
  • django/trunk/django/contrib/admin/options.py

    r8057 r8136  
    162162            else: 
    163163                if isinstance(db_field, models.ManyToManyField): 
    164                     if db_field.name in self.raw_id_fields: 
     164                    # If it uses an intermediary model, don't show field in admin.  
     165                    if db_field.rel.through is not None: 
     166                        return None 
     167                    elif db_field.name in self.raw_id_fields: 
    165168                        kwargs['widget'] = widgets.ManyToManyRawIdWidget(db_field.rel) 
    166169                        kwargs['help_text'] = '' 
  • django/trunk/django/contrib/contenttypes/generic.py

    r7477 r8136  
    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") 
  • django/trunk/django/core/management/sql.py

    r8133 r8136  
    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:  
  • django/trunk/django/core/management/validation.py

    r7967 r8136  
    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 = []  
    105106        for i, f in enumerate(opts.local_many_to_many): 
    106107            # Check to see if the related m2m field will clash with any 
     
    113114                if isinstance(f.rel.to, (str, unicode)): 
    114115                    continue 
    115  
     116            if getattr(f.rel, 'through', None) is not None: 
     117                if hasattr(f.rel, 'through_model'): 
     118                    from_model, to_model = cls, f.rel.to 
     119                    if from_model == to_model and f.rel.symmetrical: 
     120                        e.add(opts, "Many-to-many fields with intermediate tables cannot be symmetrical.") 
     121                    seen_from, seen_to, seen_self = False, False, 0 
     122                    for inter_field in f.rel.through_model._meta.fields: 
     123                        rel_to = getattr(inter_field.rel, 'to', None) 
     124                        if from_model == to_model: # relation to self 
     125                            if rel_to == from_model: 
     126                                seen_self += 1 
     127                            if seen_self > 2: 
     128                                e.add(opts, "Intermediary model %s has more than two foreign keys to %s, which is ambiguous and is not permitted." % (f.rel.through_model._meta.object_name, from_model._meta.object_name)) 
     129                        else: 
     130                            if rel_to == from_model: 
     131                                if seen_from: 
     132                                    e.add(opts, "Intermediary model %s has more than one foreign key to %s, which is ambiguous and is not permitted." % (f.rel.through_model._meta.object_name, rel_from._meta.object_name)) 
     133                                else: 
     134                                    seen_from = True 
     135                            elif rel_to == to_model: 
     136                                if seen_to: 
     137                                    e.add(opts, "Intermediary model %s has more than one foreign key to %s, which is ambiguous and is not permitted." % (f.rel.through_model._meta.object_name, rel_to._meta.object_name)) 
     138                                else: 
     139                                    seen_to = True 
     140                    if f.rel.through_model not in models.get_models(): 
     141                        e.add(opts, "'%s' specifies an m2m relation through model %s, which has not been installed." % (f.name, f.rel.through)) 
     142                    signature = (f.rel.to, cls, f.rel.through_model) 
     143                    if signature in seen_intermediary_signatures: 
     144                        e.add(opts, "The model %s has two manually-defined m2m relations through the model %s, which is not permitted. Please consider using an extra field on your intermediary model instead." % (cls._meta.object_name, f.rel.through_model._meta.object_name)) 
     145                    else: 
     146                        seen_intermediary_signatures.append(signature) 
     147                    seen_related_fk, seen_this_fk = False, False 
     148                    for field in f.rel.through_model._meta.fields: 
     149                        if field.rel: 
     150                            if not seen_related_fk and field.rel.to == f.rel.to: 
     151                                seen_related_fk = True 
     152                            elif field.rel.to == cls: 
     153                                seen_this_fk = True 
     154                    if not seen_related_fk or not seen_this_fk: 
     155                        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)) 
     156                else: 
     157                    e.add(opts, "'%s' specifies an m2m relation through model %s, which has not been installed" % (f.name, f.rel.through)) 
     158             
    116159            rel_opts = f.rel.to._meta 
    117160            rel_name = RelatedObject(f.rel.to, cls, f).get_accessor_name() 
  • django/trunk/django/db/models/fields/related.py

    r8132 r8136  
    2424pending_lookups = {} 
    2525 
    26 def add_lazy_relation(cls, field, relation): 
     26def add_lazy_relation(cls, field, relation, operation): 
    2727    """ 
    2828    Adds a lookup on ``cls`` when a related field is defined using a string, 
     
    4646    lazy relationships -- then the relation won't be set up until the 
    4747    class_prepared signal fires at the end of model initialization. 
     48     
     49    operation is the work that must be performed once the relation can be resolved. 
    4850    """ 
    4951    # Check for recursive relations 
     
    6769    model = get_model(app_label, model_name, False) 
    6870    if model: 
    69         field.rel.to = model 
    70         field.do_related_class(model, cls) 
     71        operation(field, model, cls) 
    7172    else: 
    7273        key = (app_label, model_name) 
    73         value = (cls, field
     74        value = (cls, field, operation
    7475        pending_lookups.setdefault(key, []).append(value) 
    7576 
     
    7980    """ 
    8081    key = (sender._meta.app_label, sender.__name__) 
    81     for cls, field in pending_lookups.pop(key, []): 
    82         field.rel.to = sender 
    83         field.do_related_class(sender, cls) 
     82    for cls, field, operation in pending_lookups.pop(key, []): 
     83        operation(field, sender, cls) 
    8484 
    8585dispatcher.connect(do_pending_lookups, signal=signals.class_prepared) 
     
    109109        other = self.rel.to 
    110110        if isinstance(other, basestring): 
    111             add_lazy_relation(cls, self, other) 
     111            def resolve_related_class(field, model, cls): 
     112                field.rel.to = model 
     113                field.do_related_class(model, cls) 
     114            add_lazy_relation(cls, self, other, resolve_related_class) 
    112115        else: 
    113116            self.do_related_class(other, cls) 
     
    341344        manager.add(*value) 
    342345 
    343 def create_many_related_manager(superclass): 
     346def create_many_related_manager(superclass, through=False): 
    344347    """Creates a manager that subclasses 'superclass' (which is a Manager) 
    345348    and adds behavior for many-to-many related objects.""" 
     
    355358            self.source_col_name = source_col_name 
    356359            self.target_col_name = target_col_name 
     360            self.through = through 
    357361            self._pk_val = self.instance._get_pk_val() 
    358362            if self._pk_val is None: 
     
    362366            return superclass.get_query_set(self).filter(**(self.core_filters)) 
    363367 
    364         def add(self, *objs): 
    365             self._add_items(self.source_col_name, self.target_col_name, *objs) 
    366  
    367             # If this is a symmetrical m2m relation to self, add the mirror entry in the m2m table 
    368             if self.symmetrical: 
    369                 self._add_items(self.target_col_name, self.source_col_name, *objs) 
    370         add.alters_data = True 
    371  
    372         def remove(self, *objs): 
    373             self._remove_items(self.source_col_name, self.target_col_name, *objs) 
    374  
    375             # If this is a symmetrical m2m relation to self, remove the mirror entry in the m2m table 
    376             if self.symmetrical: 
    377                 self._remove_items(self.target_col_name, self.source_col_name, *objs) 
    378         remove.alters_data = True 
     368        # If the ManyToMany relation has an intermediary model,  
     369        # the add and remove methods do not exist. 
     370        if through is None: 
     371            def add(self, *objs): 
     372                self._add_items(self.source_col_name, self.target_col_name, *objs) 
     373 
     374                # If this is a symmetrical m2m relation to self, add the mirror entry in the m2m table 
     375                if self.symmetrical: 
     376                    self._add_items(self.target_col_name, self.source_col_name, *objs) 
     377            add.alters_data = True 
     378 
     379            def remove(self, *objs): 
     380                self._remove_items(self.source_col_name, self.target_col_name, *objs) 
     381 
     382                # If this is a symmetrical m2m relation to self, remove the mirror entry in the m2m table 
     383                if self.symmetrical: 
     384                    self._remove_items(self.target_col_name, self.source_col_name, *objs) 
     385            remove.alters_data = True 
    379386 
    380387        def clear(self): 
     
    387394 
    388395        def create(self, **kwargs): 
     396            # This check needs to be done here, since we can't later remove this 
     397            # from the method lookup table, as we do with add and remove. 
     398            if through is not None: 
     399                raise AttributeError, "Cannot use create() on a ManyToManyField which specifies an intermediary model. Use %s's Manager instead." % through 
    389400            new_obj = self.model(**kwargs) 
    390401            new_obj.save() 
     
    474485        rel_model = self.related.model 
    475486        superclass = rel_model._default_manager.__class__ 
    476         RelatedManager = create_many_related_manager(superclass
     487        RelatedManager = create_many_related_manager(superclass, self.related.field.rel.through
    477488 
    478489        qn = connection.ops.quote_name 
     
    493504            raise AttributeError, "Manager must be accessed via instance" 
    494505 
     506        through = getattr(self.related.field.rel, 'through', None) 
     507        if through is not None: 
     508            raise AttributeError, "Cannot set values on a ManyToManyField which specifies an intermediary model. Use %s's Manager instead." % through 
     509 
    495510        manager = self.__get__(instance) 
    496511        manager.clear() 
     
    515530        rel_model=self.field.rel.to 
    516531        superclass = rel_model._default_manager.__class__ 
    517         RelatedManager = create_many_related_manager(superclass
     532        RelatedManager = create_many_related_manager(superclass, self.field.rel.through
    518533 
    519534        qn = connection.ops.quote_name 
     
    533548        if instance is None: 
    534549            raise AttributeError, "Manager must be accessed via instance" 
     550 
     551        through = getattr(self.field.rel, 'through', None) 
     552        if through is not None: 
     553            raise AttributeError, "Cannot set values on a ManyToManyField which specifies an intermediary model.  Use %s's Manager instead." % through 
    535554 
    536555        manager = self.__get__(instance) 
     
    585604class ManyToManyRel(object): 
    586605    def __init__(self, to, num_in_admin=0, related_name=None, 
    587         limit_choices_to=None, symmetrical=True): 
     606        limit_choices_to=None, symmetrical=True, through=None): 
    588607        self.to = to 
    589608        self.num_in_admin = num_in_admin 
     
    595614        self.symmetrical = symmetrical 
    596615        self.multiple = True 
     616        self.through = through 
    597617 
    598618class ForeignKey(RelatedField, Field): 
     
    724744            related_name=kwargs.pop('related_name', None), 
    725745            limit_choices_to=kwargs.pop('limit_choices_to', None), 
    726             symmetrical=kwargs.pop('symmetrical', True)) 
     746            symmetrical=kwargs.pop('symmetrical', True), 
     747            through=kwargs.pop('through', None)) 
     748             
    727749        self.db_table = kwargs.pop('db_table', None) 
     750        if kwargs['rel'].through is not None: 
     751            self.creates_table = False 
     752            assert self.db_table is None, "Cannot specify a db_table if an intermediary model is used." 
     753        else: 
     754            self.creates_table = True 
     755 
    728756        Field.__init__(self, **kwargs) 
    729757 
     
    740768    def _get_m2m_db_table(self, opts): 
    741769        "Function that can be curried to provide the m2m table name for this relation" 
    742         if self.db_table: 
     770        if self.rel.through is not None: 
     771            return self.rel.through_model._meta.db_table 
     772        elif self.db_table: 
    743773            return self.db_table 
    744774        else: 
     
    747777    def _get_m2m_column_name(self, related): 
    748778        "Function that can be curried to provide the source column name for the m2m table" 
    749         # If this is an m2m relation to self, avoid the inevitable name clash 
    750         if related.model == related.parent_model: 
    751             return 'from_' + related.model._meta.object_name.lower() + '_id' 
    752         else: 
    753             return related.model._meta.object_name.lower() + '_id' 
     779        try: 
     780            return self._m2m_column_name_cache 
     781        except: 
     782            if self.rel.through is not None: 
     783                for f in self.rel.through_model._meta.fields: 
     784                    if hasattr(f,'rel') and f.rel and f.rel.to == related.model: 
     785                        self._m2m_column_name_cache = f.column 
     786                        break 
     787            # If this is an m2m relation to self, avoid the inevitable name clash 
     788            elif related.model == related.parent_model: 
     789                self._m2m_column_name_cache = 'from_' + related.model._meta.object_name.lower() + '_id' 
     790            else: 
     791                self._m2m_column_name_cache = related.model._meta.object_name.lower() + '_id' 
     792                 
     793            # Return the newly cached value 
     794            return self._m2m_column_name_cache 
    754795 
    755796    def _get_m2m_reverse_name(self, related): 
    756797        "Function that can be curried to provide the related column name for the m2m table" 
    757         # If this is an m2m relation to self, avoid the inevitable name clash 
    758         if related.model == related.parent_model: 
    759             return 'to_' + related.parent_model._meta.object_name.lower() + '_id' 
    760         else: 
    761             return related.parent_model._meta.object_name.lower() + '_id' 
     798        try: 
     799            return self._m2m_reverse_name_cache 
     800        except: 
     801            if self.rel.through is not None: 
     802                found = False 
     803                for f in self.rel.through_model._meta.fields: 
     804                    if hasattr(f,'rel') and f.rel and f.rel.to == related.parent_model: 
     805                        if related.model == related.parent_model: 
     806                            # If this is an m2m-intermediate to self,  
     807                            # the first foreign key you find will be  
     808                            # the source column. Keep searching for 
     809                            # the second foreign key. 
     810                            if found: 
     811                                self._m2m_reverse_name_cache = f.column 
     812                                break 
     813                            else: 
     814                                found = True 
     815                        else: 
     816                            self._m2m_reverse_name_cache = f.column 
     817                            break 
     818            # If this is an m2m relation to self, avoid the inevitable name clash 
     819            elif related.model == related.parent_model: 
     820                self._m2m_reverse_name_cache = 'to_' + related.parent_model._meta.object_name.lower() + '_id' 
     821            else: 
     822                self._m2m_reverse_name_cache = related.parent_model._meta.object_name.lower() + '_id' 
     823 
     824            # Return the newly cached value 
     825            return self._m2m_reverse_name_cache 
    762826 
    763827    def isValidIDList(self, field_data, all_data): 
     
    793857 
    794858    def contribute_to_class(self, cls, name): 
    795         super(ManyToManyField, self).contribute_to_class(cls, name) 
     859        super(ManyToManyField, self).contribute_to_class(cls, name)         
    796860        # Add the descriptor for the m2m relation 
    797861        setattr(cls, self.name, ReverseManyRelatedObjectsDescriptor(self)) 
     
    799863        # Set up the accessor for the m2m table name for the relation 
    800864        self.m2m_db_table = curry(self._get_m2m_db_table, cls._meta) 
    801  
     865         
     866        # Populate some necessary rel arguments so that cross-app relations 
     867        # work correctly. 
     868        if isinstance(self.rel.through, basestring): 
     869            def resolve_through_model(field, model, cls): 
     870                field.rel.through_model = model 
     871            add_lazy_relation(cls, self, self.rel.through, resolve_through_model) 
     872        elif self.rel.through: 
     873            self.rel.through_model = self.rel.through 
     874            self.rel.through = self.rel.through._meta.object_name 
     875             
    802876        if isinstance(self.rel.to, basestring): 
    803877            target = self.rel.to 
  • django/trunk/docs/admin.txt

    r8111 r8136  
    618618        ] 
    619619 
     620Working with Many-to-Many Intermediary Models 
     621---------------------------------------------- 
     622 
     623By default, admin widgets for many-to-many relations will be displayed inline 
     624on whichever model contains the actual reference to the `ManyToManyField`.   
     625However, when you specify an intermediary model using the ``through`` 
     626argument to a ``ManyToManyField``, the admin will not display a widget by  
     627default. This is because each instance of that intermediary model requires 
     628more information than could be displayed in a single widget, and the layout 
     629required for multiple widgets will vary depending on the intermediate model. 
     630 
     631However, we still want to be able to edit that information inline. Fortunately, 
     632this is easy to do with inline admin models. Suppose we have the following 
     633models:: 
     634 
     635    class Person(models.Model): 
     636        name = models.CharField(max_length=128) 
     637     
     638    class Group(models.Model): 
     639        name = models.CharField(max_length=128) 
     640        members = models.ManyToManyField(Person, through='Membership') 
     641 
     642    class Membership(models.Model): 
     643        person = models.ForeignKey(Person) 
     644        group = models.ForeignKey(Group) 
     645        date_joined = models.DateField() 
     646        invite_reason = models.CharField(max_length=64) 
     647 
     648The first step in displaying this intermediate model in the admin is to 
     649define an inline model for the Membership table:: 
     650 
     651    class MembershipInline(admin.TabularInline): 
     652        model = Membership 
     653        extra = 1 
     654 
     655This simple example uses the defaults inline form for the Membership model, 
     656and shows 1 extra line. This could be customized using any of the options 
     657available to inline models. 
     658 
     659Now create admin views for the ``Person`` and ``Group`` models:: 
     660 
     661    class PersonAdmin(admin.ModelAdmin): 
     662        inlines = (MembershipInline,) 
     663 
     664    class GroupAdmin(admin.ModelAdmin): 
     665        inlines = (MembershipInline,) 
     666 
     667Finally, register your ``Person`` and ``Group`` models with the admin site:: 
     668     
     669    admin.site.register(Person, PersonAdmin) 
     670    admin.site.register(Group, GroupAdmin) 
     671 
     672Now your admin site is set up to edit ``Membership`` objects inline from either 
     673the ``Person`` or the ``Group`` detail pages. 
     674 
    620675``AdminSite`` objects 
    621676===================== 
  • django/trunk/docs/model-api.txt

    r8122 r8136  
    656656example:: 
    657657 
    658        help_text="Please use the following format: <em>YYYY-MM-DD</em>." 
     658    help_text="Please use the following format: <em>YYYY-MM-DD</em>." 
    659659 
    660660Alternatively you can use plain text and 
     
    945945    =======================  ============================================================ 
    946946 
     947Extra fields on many-to-many relationships 
     948~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
     949 
     950**New in Django development version**  
     951 
     952When you're only dealing with simple many-to-many relationships such as 
     953mixing and matching pizzas and toppings, a standard ``ManyToManyField`` 
     954is all you need. However, sometimes you may need to associate data with the 
     955relationship between two models.  
     956 
     957For example, consider the case of an application tracking the musical groups 
     958which musicians belong to. There is a many-to-many relationship between a person 
     959and the groups of which they are a member, so you could use a ManyToManyField 
     960to represent this relationship. However, there is a lot of detail about the 
     961membership that you might want to collect, such as the date at which the person 
     962joined the group. 
     963 
     964For these situations, Django allows you to specify the model that will be used 
     965to govern the many-to-many relationship. You can then put extra fields on the 
     966intermediate model. The intermediate model is associated with the 
     967``ManyToManyField`` using the ``through`` argument to point to the model 
     968that will act as an intermediary. For our musician example, the code would look 
     969something like this:: 
     970 
     971    class Person(models.Model): 
     972        name = models.CharField(max_length=128) 
     973 
     974        def __unicode__(self): 
     975            return self.name 
     976 
     977    class Group(models.Model): 
     978        name = models.CharField(max_length=128) 
     979        members = models.ManyToManyField(Person, through='Membership') 
     980 
     981        def __unicode__(self): 
     982            return self.name 
     983 
     984    class Membership(models.Model): 
     985        person = models.ForeignKey(Person) 
     986        group = models.ForeignKey(Group) 
     987        date_joined = models.DateField() 
     988        invite_reason = models.CharField(max_length=64) 
     989 
     990When you set up the intermediary model, you explicitly specify foreign  
     991keys to the models that are involved in the ManyToMany relation. This 
     992explicit declaration defines how the two models are related. 
     993 
     994There are a few restrictions on the intermediate model: 
     995 
     996    * Your intermediate model must contain one - and *only* one - foreign key 
     997      on the target model (this would be ``Person`` in our example). If you 
     998      have more than one foreign key, a validation error will be raised. 
     999   
     1000    * Your intermediate model must contain one - and *only* one - foreign key  
     1001      on the source model (this would be ``Group`` in our example). If you 
     1002      have more than one foreign key, a validation error will be raised. 
     1003       
     1004    * If the many-to-many relation is a relation on itself, the relationship 
     1005      must be non-symmetric. 
     1006 
     1007Now that you have set up your ``ManyToManyField`` to use your intermediary  
     1008model (Membership, in this case), you're ready to start creating some 
     1009many-to-many relationships. You do this by creating instances of the 
     1010intermediate model:: 
     1011     
     1012    >>> ringo = Person.objects.create(name="Ringo Starr") 
     1013    >>> paul = Person.objects.create(name="Paul McCartney") 
     1014    >>> beatles = Group.objects.create(name="The Beatles") 
     1015    >>> m1 = Membership(person=ringo, group=beatles, 
     1016    ...     date_joined=date(1962, 8, 16),  
     1017    ...     invite_reason= "Needed a new drummer.") 
     1018    >>> m1.save() 
     1019    >>> beatles.members.all() 
     1020    [<Person: Ringo Starr>] 
     1021    >>> ringo.group_set.all() 
     1022    [<Group: The Beatles>] 
     1023    >>> m2 = Membership.objects.create(person=paul, group=beatles, 
     1024    ...     date_joined=date(1960, 8, 1),  
     1025    ...     invite_reason= "Wanted to form a band.") 
     1026    >>> beatles.members.all() 
     1027    [<Person: Ringo Starr>, <Person: Paul McCartney>] 
     1028 
     1029Unlike normal many-to-many fields, you *can't* use ``add``, ``create``, 
     1030or assignment (i.e., ``beatles.members = [...]``) to create relationships:: 
     1031 
     1032    # THIS WILL NOT WORK 
     1033    >>> beatles.members.add(john) 
     1034    # NEITHER WILL THIS 
     1035    >>> beatles.members.create(name="George Harrison") 
     1036    # AND NEITHER WILL THIS 
     1037    >>> beatles.members = [john, paul, ringo, george] 
     1038     
     1039Why? You can't just create a relationship between a Person and a Group - you 
     1040need to specify all the detail for the relationship required by the 
     1041Membership table. The simple ``add``, ``create`` and assignment calls 
     1042don't provide a way to specify this extra detail. As a result, they are 
     1043disabled for many-to-many relationships that use an intermediate model. 
     1044The only way to create a many-to-many relationship with an intermediate table 
     1045is to create instances of the intermediate model. 
     1046 
     1047The ``remove`` method is disabled for similar reasons. However, the 
     1048``clear()`` method can be used to remove all many-to-many relationships 
     1049for an instance:: 
     1050 
     1051    # Beatles have broken up 
     1052    >>> beatles.members.clear() 
     1053 
     1054Once you have established the many-to-many relationships by creating instances 
     1055of your intermediate model, you can issue queries. Just as with normal  
     1056many-to-many relationships, you can query using the attributes of the  
     1057many-to-many-related model:: 
     1058 
     1059    # Find all the groups with a member whose name starts with 'Paul' 
     1060    >>> Groups.objects.filter(person__name__startswith='Paul') 
     1061    [<Group: The Beatles>] 
     1062 
     1063As you are using an intermediate table, you can also query on the attributes  
     1064of the intermediate model:: 
     1065 
     1066    # Find all the members of the Beatles that joined after 1 Jan 1961 
     1067    >>> Person.objects.filter( 
     1068    ...     group__name='The Beatles', 
     1069    ...     membership__date_joined__gt=date(1961,1,1)) 
     1070    [<Person: Ringo Starr] 
     1071     
    9471072One-to-one relationships 
    9481073~~~~~~~~~~~~~~~~~~~~~~~~ 
     
    11461271with a single set of fields:: 
    11471272 
    1148        unique_together = ("driver", "restaurant") 
     1273    unique_together = ("driver", "restaurant") 
    11491274 
    11501275``verbose_name`` 
  • django/trunk/tests/modeltests/invalid_models/models.py

    r7967 r8136  
    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) 
     120 
     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    tertiary = models.ManyToManyField(Person, through="RelationshipDoubleFK", related_name="tertiary") 
     126 
     127class GroupTwo(models.Model): 
     128    name = models.CharField(max_length=5) 
     129    primary = models.ManyToManyField(Person, through="Membership") 
     130    secondary = models.ManyToManyField(Group, through="MembershipMissingFK") 
     131 
     132class Membership(models.Model): 
     133    person = models.ForeignKey(Person) 
     134    group = models.ForeignKey(Group) 
     135    not_default_or_null = models.CharField(max_length=5) 
     136 
     137class MembershipMissingFK(models.Model): 
     138    person = models.ForeignKey(Person) 
     139 
     140class PersonSelfRefM2M(models.Model): 
     141    name = models.CharField(max_length=5) 
     142    friends = models.ManyToManyField('self', through="Relationship") 
     143    too_many_friends = models.ManyToManyField('self', through="RelationshipTripleFK") 
     144 
     145class PersonSelfRefM2MExplicit(models.Model): 
     146    name = models.CharField(max_length=5) 
     147    friends = models.ManyToManyField('self', through="ExplicitRelationship", symmetrical=True) 
     148 
     149class Relationship(models.Model): 
     150    first = models.ForeignKey(PersonSelfRefM2M, related_name="rel_from_set") 
     151    second = models.ForeignKey(PersonSelfRefM2M, related_name="rel_to_set") 
     152    date_added = models.DateTimeField() 
     153 
     154class ExplicitRelationship(models.Model): 
     155    first = models.ForeignKey(PersonSelfRefM2MExplicit, related_name="rel_from_set") 
     156    second = models.ForeignKey(PersonSelfRefM2MExplicit, related_name="rel_to_set") 
     157    date_added = models.DateTimeField() 
     158 
     159class RelationshipTripleFK(models.Model): 
     160    first = models.ForeignKey(PersonSelfRefM2M, related_name="rel_from_set_2") 
     161    second = models.ForeignKey(PersonSelfRefM2M, related_name="rel_to_set_2") 
     162    third = models.ForeignKey(PersonSelfRefM2M, related_name="too_many_by_far") 
     163    date_added = models.DateTimeField() 
     164 
     165class RelationshipDoubleFK(models.Model): 
     166    first = models.ForeignKey(Person, related_name="first_related_name") 
     167    second = models.ForeignKey(Person, related_name="second_related_name") 
     168    third = models.ForeignKey(Group, related_name="rel_to_set") 
     169    date_added = models.DateTimeField() 
    113170 
    114171model_errors = """invalid_models.fielderrors: "charfield": CharFields require a "max_length" attribute. 
     
    196253invalid_models.missingrelations: 'rel2' has m2m relation with model Rel2, which has not been installed 
    197254invalid_models.missingrelations: 'rel1' has relation with model Rel1, which has not been installed 
     255invalid_models.grouptwo: 'primary' has a manually-defined m2m relation through model Membership, which does not have foreign keys to Person and GroupTwo 
     256invalid_models.grouptwo: 'secondary' has a manually-defined m2m relation through model MembershipMissingFK, which does not have foreign keys to Group and GroupTwo 
     257invalid_models.missingmanualm2mmodel: 'missing_m2m' specifies an m2m relation through model MissingM2MModel, which has not been installed 
     258invalid_models.group: The model Group has two manually-defined m2m relations through the model Membership, which is not permitted. Please consider using an extra field on your intermediary model instead. 
     259invalid_models.group: Intermediary model RelationshipDoubleFK has more than one foreign key to Person, which is ambiguous and is not permitted. 
     260invalid_models.personselfrefm2m: Many-to-many fields with intermediate tables cannot be symmetrical. 
     261invalid_models.personselfrefm2m: Intermediary model RelationshipTripleFK has more than two foreign keys to PersonSelfRefM2M, which is ambiguous and is not permitted. 
     262invalid_models.personselfrefm2mexplicit: Many-to-many fields with intermediate tables cannot be symmetrical. 
    198263"""