Ticket #14549: #14549-intermediary_m2m_disambiguation_fixed_tests.diff

File #14549-intermediary_m2m_disambiguation_fixed_tests.diff, 9.7 KB (added by German M. Bravo, 12 years ago)
  • django/db/models/fields/related.py

     
    998998        if kwargs['rel'].through is not None:
    999999            assert self.db_table is None, "Cannot specify a db_table if an intermediary model is used."
    10001000
     1001        self.source_fk_name = kwargs.pop('source_fk_name', None)
     1002        self.target_fk_name = kwargs.pop('target_fk_name', None)
     1003
    10011004        Field.__init__(self, **kwargs)
    10021005
    10031006        msg = _('Hold down "Control", or "Command" on a Mac, to select more than one.')
     
    10231026            return getattr(self, cache_attr)
    10241027        for f in self.rel.through._meta.fields:
    10251028            if hasattr(f,'rel') and f.rel and f.rel.to == related.model:
    1026                 setattr(self, cache_attr, getattr(f, attr))
    1027                 return getattr(self, cache_attr)
     1029                if not self.source_fk_name or self.source_fk_name == f.name:
     1030                    setattr(self, cache_attr, getattr(f, attr))
     1031                    return getattr(self, cache_attr)
    10281032
    10291033    def _get_m2m_reverse_attr(self, related, attr):
    10301034        "Function that can be curried to provide the related accessor or DB column name for the m2m table"
     
    10341038        found = False
    10351039        for f in self.rel.through._meta.fields:
    10361040            if hasattr(f,'rel') and f.rel and f.rel.to == related.parent_model:
    1037                 if related.model == related.parent_model:
     1041                if not self.target_fk_name and related.model == related.parent_model:
    10381042                    # If this is an m2m-intermediate to self,
    10391043                    # the first foreign key you find will be
    10401044                    # the source column. Keep searching for
     
    10441048                        break
    10451049                    else:
    10461050                        found = True
    1047                 else:
     1051                elif not self.target_fk_name or self.target_fk_name == f.name:
    10481052                    setattr(self, cache_attr, getattr(f, attr))
    10491053                    break
    10501054        return getattr(self, cache_attr)
  • django/core/management/validation.py

    def get_validation_errors(outfile, app=None):  
    173173                    rel_to = getattr(inter_field.rel, 'to', None)
    174174                    if from_model == to_model: # relation to self
    175175                        if rel_to == from_model:
    176                             seen_self += 1
     176                            if (not getattr(f, 'source_fk_name', None) or f.source_fk_name == inter_field.name or
     177                                not getattr(f, 'target_fk_name', None) or f.target_fk_name == inter_field.name):
     178                                seen_self += 1
    177179                        if seen_self > 2:
    178180                            e.add(opts, "Intermediary model %s has more than "
    179                                 "two foreign keys to %s, which is ambiguous "
    180                                 "and is not permitted." % (
     181                                "two foreign keys to %s. Add a source_fk_name "
     182                                "and a target_fk_name arguments to the definition for '%s'." % (
    181183                                    f.rel.through._meta.object_name,
    182                                     from_model._meta.object_name
     184                                    from_model._meta.object_name,
     185                                    f.name
    183186                                )
    184187                            )
    185188                    else:
    186                         if rel_to == from_model:
     189                        if rel_to == from_model and (not getattr(f, 'source_fk_name', None) or f.source_fk_name == inter_field.name):
    187190                            if seen_from:
    188191                                e.add(opts, "Intermediary model %s has more "
    189                                     "than one foreign key to %s, which is "
    190                                     "ambiguous and is not permitted." % (
     192                                    "than one foreign key to %s. Add a source_fk_name "
     193                                    "argument to the definition for '%s'." % (
    191194                                        f.rel.through._meta.object_name,
    192                                          from_model._meta.object_name
     195                                         from_model._meta.object_name,
     196                                         f.name
    193197                                     )
    194198                                 )
    195199                            else:
    196200                                seen_from = True
    197                         elif rel_to == to_model:
     201                        elif rel_to == to_model and (not getattr(f, 'target_fk_name', None) or f.target_fk_name == inter_field.name):
    198202                            if seen_to:
    199203                                e.add(opts, "Intermediary model %s has more "
    200                                     "than one foreign key to %s, which is "
    201                                     "ambiguous and is not permitted." % (
     204                                    "than one foreign key to %s. Add a target_fk_name "
     205                                    "argument to the definition for '%s'." % (
    202206                                        f.rel.through._meta.object_name,
    203                                         rel_to._meta.object_name
     207                                        rel_to._meta.object_name,
     208                                        f.name
    204209                                    )
    205210                                )
    206211                            else:
  • docs/topics/db/models.txt

     
    435435
    436436There are a few restrictions on the intermediate model:
    437437
    438     * Your intermediate model must contain one - and *only* one - foreign key
    439       to the target model (this would be ``Person`` in our example). If you
    440       have more than one foreign key, a validation error will be raised.
    441 
    442     * Your intermediate model must contain one - and *only* one - foreign key
    443       to the source model (this would be ``Group`` in our example). If you
    444       have more than one foreign key, a validation error will be raised.
     438    * To automatically detect the target foreign key, your intermediate model
     439      must contain one - and *only* one - foreign key to the target model (this
     440      would be ``Person`` in our example). If you have more than one foreign
     441      key, you will need to specify the field name by adding a
     442      :attr:`~django.db.models.ManyToManyField.target_fk_name`.
     443
     444    * To automatically detect the source foreign key, your intermediate model
     445      must contain one - and *only* one - foreign key to the source model (this
     446      would be ``Group`` in our example). If you have more than one foreign key,
     447      you will need to specify the field name by adding a
     448      :attr:`~django.db.models.ManyToManyField.source_fk_name`.
    445449
    446450    * The only exception to this is a model which has a many-to-many
    447451      relationship to itself, through an intermediary model. In this
    448452      case, two foreign keys to the same model are permitted, but they
    449453      will be treated as the two (different) sides of the many-to-many
    450       relation.
     454      relation. If more than two foreign keys exist, or if you want to
     455      explicitly specify which field is the target and which is the source,
     456      you will need to specify the field names by adding a
     457      :attr:`~django.db.models.ManyToManyField.target_fk_name` and a
     458      :attr:`~django.db.models.ManyToManyField.source_fk_name`.
    451459
    452460    * When defining a many-to-many relationship from a model to
    453461      itself, using an intermediary model, you *must* use
  • tests/modeltests/invalid_models/invalid_models/models.py

     
    326326invalid_models.grouptwo: 'secondary' is a manually-defined m2m relation through model MembershipMissingFK, which does not have foreign keys to Group and GroupTwo
    327327invalid_models.missingmanualm2mmodel: 'missing_m2m' specifies an m2m relation through model MissingM2MModel, which has not been installed
    328328invalid_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.
    329 invalid_models.group: Intermediary model RelationshipDoubleFK has more than one foreign key to Person, which is ambiguous and is not permitted.
     329invalid_models.group: Intermediary model RelationshipDoubleFK has more than one foreign key to Person. Add a target_fk_name argument to the definition for 'tertiary'.
    330330invalid_models.personselfrefm2m: Many-to-many fields with intermediate tables cannot be symmetrical.
    331 invalid_models.personselfrefm2m: Intermediary model RelationshipTripleFK has more than two foreign keys to PersonSelfRefM2M, which is ambiguous and is not permitted.
     331invalid_models.personselfrefm2m: Intermediary model RelationshipTripleFK has more than two foreign keys to PersonSelfRefM2M. Add a source_fk_name and a target_fk_name arguments to the definition for 'too_many_friends'.
    332332invalid_models.personselfrefm2mexplicit: Many-to-many fields with intermediate tables cannot be symmetrical.
    333333invalid_models.abstractrelationmodel: 'fk1' has a relation with model AbstractModel, which has either not been installed or is abstract.
    334334invalid_models.abstractrelationmodel: 'fk2' has an m2m relation with model AbstractModel, which has either not been installed or is abstract.
Back to Top