diff --git a/django/core/management/validation.py b/django/core/management/validation.py index 46cdfc5c46..9b65b8ed6c 100644 --- a/django/core/management/validation.py +++ b/django/core/management/validation.py @@ -179,19 +179,20 @@ def get_validation_errors(outfile, app=None): ) else: seen_intermediary_signatures.append(signature) - seen_related_fk, seen_this_fk = False, False - for field in f.rel.through._meta.fields: - if field.rel: - if not seen_related_fk and field.rel.to == f.rel.to: - seen_related_fk = True - elif field.rel.to == cls: - seen_this_fk = True - if not seen_related_fk or not seen_this_fk: - 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._meta.object_name, - f.rel.to._meta.object_name, cls._meta.object_name) - ) + if not f.rel.through._meta.auto_created: + seen_related_fk, seen_this_fk = False, False + for field in f.rel.through._meta.fields: + if field.rel: + if not seen_related_fk and field.rel.to == f.rel.to: + seen_related_fk = True + elif field.rel.to == cls: + seen_this_fk = True + if not seen_related_fk or not seen_this_fk: + e.add(opts, "'%s' is a manually-defined m2m relation " + "through model %s, which does not have foreign keys " + "to %s and %s" % (f.name, f.rel.through._meta.object_name, + f.rel.to._meta.object_name, cls._meta.object_name) + ) elif isinstance(f.rel.through, basestring): e.add(opts, "'%s' specifies an m2m relation through model %s, " "which has not been installed" % (f.name, f.rel.through) diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index 5de6fb1067..a9d178a16c 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -511,7 +511,7 @@ def create_many_related_manager(superclass, rel=False): def _add_items(self, source_field_name, target_field_name, *objs): # join_table: name of the m2m link table # source_field_name: the PK fieldname in join_table for the source object - # target_col_name: the PK fieldname in join_table for the target object + # target_field_name: the PK fieldname in join_table for the target object # *objs - objects to add. Either object instances, or primary keys of object instances. # If there aren't any objects, there is nothing to do. @@ -914,7 +914,7 @@ def create_many_to_many_intermediary_model(field, klass): to_model = field.rel.to managed = klass._meta.managed or to_model._meta.managed name = '%s_%s' % (klass._meta.object_name, field.name) - if field.rel.to == RECURSIVE_RELATIONSHIP_CONSTANT or field.rel.to == klass._meta.object_name: + if field.rel.to == RECURSIVE_RELATIONSHIP_CONSTANT or to == klass._meta.object_name: from_ = 'from_%s' % to.lower() to = 'to_%s' % to.lower() else: @@ -973,7 +973,7 @@ class ManyToManyField(RelatedField, Field): connection.ops.max_name_length()) def _get_m2m_attr(self, related, attr): - "Function that can be curried to provide the source column name for the m2m table" + "Function that can be curried to provide the source accessor or DB column name for the m2m table" cache_attr = '_m2m_%s_cache' % attr if hasattr(self, cache_attr): return getattr(self, cache_attr) @@ -983,7 +983,7 @@ class ManyToManyField(RelatedField, Field): return getattr(self, cache_attr) def _get_m2m_reverse_attr(self, related, attr): - "Function that can be curried to provide the related column name for the m2m table" + "Function that can be curried to provide the related accessor or DB column name for the m2m table" cache_attr = '_m2m_reverse_%s_cache' % attr if hasattr(self, cache_attr): return getattr(self, cache_attr) diff --git a/tests/modeltests/invalid_models/models.py b/tests/modeltests/invalid_models/models.py index af199635e6..4957f768f7 100644 --- a/tests/modeltests/invalid_models/models.py +++ b/tests/modeltests/invalid_models/models.py @@ -268,8 +268,8 @@ invalid_models.selfclashm2m: Reverse query name for m2m field 'm2m_3' clashes wi invalid_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'. invalid_models.missingrelations: 'rel1' has a relation with model Rel1, which has either not been installed or is abstract. invalid_models.missingrelations: 'rel2' has an m2m relation with model Rel2, which has either not been installed or is abstract. -invalid_models.grouptwo: 'primary' has a manually-defined m2m relation through model Membership, which does not have foreign keys to Person and GroupTwo -invalid_models.grouptwo: 'secondary' has a manually-defined m2m relation through model MembershipMissingFK, which does not have foreign keys to Group and GroupTwo +invalid_models.grouptwo: 'primary' is a manually-defined m2m relation through model Membership, which does not have foreign keys to Person and GroupTwo +invalid_models.grouptwo: 'secondary' is a manually-defined m2m relation through model MembershipMissingFK, which does not have foreign keys to Group and GroupTwo invalid_models.missingmanualm2mmodel: 'missing_m2m' specifies an m2m relation through model MissingM2MModel, which has not been installed invalid_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. invalid_models.group: Intermediary model RelationshipDoubleFK has more than one foreign key to Person, which is ambiguous and is not permitted. diff --git a/tests/regressiontests/m2m_regress/models.py b/tests/regressiontests/m2m_regress/models.py index 913e719902..7f6da3341b 100644 --- a/tests/regressiontests/m2m_regress/models.py +++ b/tests/regressiontests/m2m_regress/models.py @@ -1,4 +1,5 @@ from django.db import models +from django.contrib.auth import models as auth # No related name is needed here, since symmetrical relations are not # explicitly reversible. @@ -41,6 +42,14 @@ class Worksheet(models.Model): id = models.CharField(primary_key=True, max_length=100) lines = models.ManyToManyField(Line, blank=True, null=True) +# Regression for #11226 -- A model with the same name that another one to +# which it has a m2m relation. This shouldn't cause a name clash between +# the automatically created m2m intermediary table FK field names when +# running syncdb +class User(models.Model): + name = models.CharField(max_length=30) + friends = models.ManyToManyField(auth.User) + __test__ = {"regressions": """ # Multiple m2m references to the same model or a different model must be # distinguished when accessing the relations through an instance attribute.