Author: russellm
Date: 2006-09-07 08:29:56 -0500 (Thu, 07 Sep 2006)
New Revision: 3734

Modified:
   django/trunk/django/core/management.py
   django/trunk/django/db/models/related.py
   django/trunk/tests/modeltests/invalid_models/models.py
Log:
Fixes #2653 -- Modified related field utility methods to return None as the 
related name for symmetrical m2m fields on self. Updated validators and unit 
tests to account for the new behavior.


Modified: django/trunk/django/core/management.py
===================================================================
--- django/trunk/django/core/management.py      2006-09-07 04:21:11 UTC (rev 
3733)
+++ django/trunk/django/core/management.py      2006-09-07 13:29:56 UTC (rev 
3734)
@@ -903,27 +903,32 @@
 
             rel_name = RelatedObject(f.rel.to, cls, f).get_accessor_name()
             rel_query_name = f.related_query_name()
-            for r in rel_opts.fields:
-                if r.name == rel_name:
-                    e.add(opts, "Accessor for m2m field '%s' clashes with 
field '%s.%s'. Add a related_name argument to the definition for '%s'." % 
(f.name, rel_opts.object_name, r.name, f.name))
-                if r.name == rel_query_name:
-                    e.add(opts, "Reverse query name for m2m field '%s' clashes 
with field '%s.%s'. Add a related_name argument to the definition for '%s'." % 
(f.name, rel_opts.object_name, r.name, f.name))
-            for r in rel_opts.many_to_many:
-                if r.name == rel_name:
-                    e.add(opts, "Accessor for m2m field '%s' clashes with m2m 
field '%s.%s'. Add a related_name argument to the definition for '%s'." % 
(f.name, rel_opts.object_name, r.name, f.name))
-                if r.name == rel_query_name:
-                    e.add(opts, "Reverse query name for m2m field '%s' clashes 
with m2m field '%s.%s'. Add a related_name argument to the definition for 
'%s'." % (f.name, rel_opts.object_name, r.name, f.name))
-            for r in rel_opts.get_all_related_many_to_many_objects():
-                if r.field is not f:
+            # If rel_name is none, there is no reverse accessor.
+            # (This only occurs for symmetrical m2m relations to self). 
+            # If this is the case, there are no clashes to check for this 
field, as
+            # there are no reverse descriptors for this field.
+            if rel_name is not None:
+                for r in rel_opts.fields:
+                    if r.name == rel_name:
+                        e.add(opts, "Accessor for m2m field '%s' clashes with 
field '%s.%s'. Add a related_name argument to the definition for '%s'." % 
(f.name, rel_opts.object_name, r.name, f.name))
+                    if r.name == rel_query_name:
+                        e.add(opts, "Reverse query name for m2m field '%s' 
clashes with field '%s.%s'. Add a related_name argument to the definition for 
'%s'." % (f.name, rel_opts.object_name, r.name, f.name))
+                for r in rel_opts.many_to_many:
+                    if r.name == rel_name:
+                        e.add(opts, "Accessor for m2m field '%s' clashes with 
m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % 
(f.name, rel_opts.object_name, r.name, f.name))
+                    if r.name == rel_query_name:
+                        e.add(opts, "Reverse query name for m2m field '%s' 
clashes with m2m field '%s.%s'. Add a related_name argument to the definition 
for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
+                for r in rel_opts.get_all_related_many_to_many_objects():
+                    if r.field is not f:
+                        if r.get_accessor_name() == rel_name:
+                            e.add(opts, "Accessor for m2m field '%s' clashes 
with related m2m 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))
+                        if r.get_accessor_name() == rel_query_name:
+                            e.add(opts, "Reverse query name for m2m field '%s' 
clashes with related m2m 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))
+                for r in rel_opts.get_all_related_objects():
                     if r.get_accessor_name() == rel_name:
-                        e.add(opts, "Accessor for m2m field '%s' clashes with 
related m2m 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))
+                        e.add(opts, "Accessor for m2m 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))
                     if r.get_accessor_name() == rel_query_name:
-                        e.add(opts, "Reverse query name for m2m field '%s' 
clashes with related m2m 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))
-            for r in rel_opts.get_all_related_objects():
-                if r.get_accessor_name() == rel_name:
-                    e.add(opts, "Accessor for m2m 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))
-                if r.get_accessor_name() == rel_query_name:
-                    e.add(opts, "Reverse query name for m2m 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))
+                        e.add(opts, "Reverse query name for m2m 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))
 
         # Check admin attribute.
         if opts.admin is not None:

Modified: django/trunk/django/db/models/related.py
===================================================================
--- django/trunk/django/db/models/related.py    2006-09-07 04:21:11 UTC (rev 
3733)
+++ django/trunk/django/db/models/related.py    2006-09-07 13:29:56 UTC (rev 
3734)
@@ -131,6 +131,9 @@
         # many-to-many objects. It uses the lower-cased object_name + "_set",
         # but this can be overridden with the "related_name" option.
         if self.field.rel.multiple:
+            # If this is a symmetrical m2m relation on self, there is no 
reverse accessor.
+            if getattr(self.field.rel, 'symmetrical', False) and self.model == 
self.parent_model:
+                return None
             return self.field.rel.related_name or 
(self.opts.object_name.lower() + '_set')
         else:
             return self.field.rel.related_name or 
(self.opts.object_name.lower())

Modified: django/trunk/tests/modeltests/invalid_models/models.py
===================================================================
--- django/trunk/tests/modeltests/invalid_models/models.py      2006-09-07 
04:21:11 UTC (rev 3733)
+++ django/trunk/tests/modeltests/invalid_models/models.py      2006-09-07 
13:29:56 UTC (rev 3734)
@@ -68,16 +68,35 @@
     foreign_1 = models.ForeignKey("SelfClashForeign", related_name='id')
     foreign_2 = models.ForeignKey("SelfClashForeign", related_name='src_safe')
 
+class ValidM2M(models.Model):
+    src_safe = models.CharField(maxlength=10)
+    validm2m = models.CharField(maxlength=10)
+
+    # M2M fields are symmetrical by default. Symmetrical M2M fields
+    # on self don't require a related accessor, so many potential
+    # clashes are avoided.
+    validm2m_set = models.ManyToManyField("ValidM2M")
+    
+    m2m_1 = models.ManyToManyField("ValidM2M", related_name='id')
+    m2m_2 = models.ManyToManyField("ValidM2M", related_name='src_safe')
+
+    m2m_3 = models.ManyToManyField('self')
+    m2m_4 = models.ManyToManyField('self')
+
 class SelfClashM2M(models.Model):
     src_safe = models.CharField(maxlength=10)
     selfclashm2m = models.CharField(maxlength=10)
 
-    selfclashm2m_set = models.ManyToManyField("SelfClashM2M")
-    m2m_1 = models.ManyToManyField("SelfClashM2M", related_name='id')
-    m2m_2 = models.ManyToManyField("SelfClashM2M", related_name='src_safe')
+    # Non-symmetrical M2M fields _do_ have related accessors, so 
+    # there is potential for clashes.
+    selfclashm2m_set = models.ManyToManyField("SelfClashM2M", 
symmetrical=False)
+    
+    m2m_1 = models.ManyToManyField("SelfClashM2M", related_name='id', 
symmetrical=False)
+    m2m_2 = models.ManyToManyField("SelfClashM2M", related_name='src_safe', 
symmetrical=False)
 
+    m2m_3 = models.ManyToManyField('self', symmetrical=False)
+    m2m_4 = models.ManyToManyField('self', symmetrical=False)
 
-
 model_errors = """invalid_models.fielderrors: "charfield": CharFields require 
a "maxlength" attribute.
 invalid_models.fielderrors: "floatfield": FloatFields require a 
"decimal_places" attribute.
 invalid_models.fielderrors: "floatfield": FloatFields require a "max_digits" 
attribute.
@@ -147,9 +166,17 @@
 invalid_models.selfclashforeign: Reverse query name for field 'foreign_2' 
clashes with field 'SelfClashForeign.src_safe'. Add a related_name argument to 
the definition for 'foreign_2'.
 invalid_models.selfclashm2m: Accessor for m2m field 'selfclashm2m_set' clashes 
with m2m field 'SelfClashM2M.selfclashm2m_set'. Add a related_name argument to 
the definition for 'selfclashm2m_set'.
 invalid_models.selfclashm2m: Reverse query name for m2m field 
'selfclashm2m_set' clashes with field 'SelfClashM2M.selfclashm2m'. Add a 
related_name argument to the definition for 'selfclashm2m_set'.
+invalid_models.selfclashm2m: Accessor for m2m field 'selfclashm2m_set' clashes 
with related m2m field 'SelfClashM2M.selfclashm2m_set'. Add a related_name 
argument to the definition for 'selfclashm2m_set'.
 invalid_models.selfclashm2m: Accessor for m2m field 'm2m_1' clashes with field 
'SelfClashM2M.id'. Add a related_name argument to the definition for 'm2m_1'.
 invalid_models.selfclashm2m: Accessor for m2m field 'm2m_2' clashes with field 
'SelfClashM2M.src_safe'. Add a related_name argument to the definition for 
'm2m_2'.
 invalid_models.selfclashm2m: Reverse query name for m2m field 'm2m_1' clashes 
with field 'SelfClashM2M.id'. Add a related_name argument to the definition for 
'm2m_1'.
 invalid_models.selfclashm2m: Reverse query name for m2m field 'm2m_2' clashes 
with field 'SelfClashM2M.src_safe'. Add a related_name argument to the 
definition for 'm2m_2'.
+invalid_models.selfclashm2m: Accessor for m2m field 'm2m_3' clashes with m2m 
field 'SelfClashM2M.selfclashm2m_set'. Add a related_name argument to the 
definition for 'm2m_3'.
+invalid_models.selfclashm2m: Accessor for m2m field 'm2m_3' clashes with 
related m2m field 'SelfClashM2M.selfclashm2m_set'. Add a related_name argument 
to the definition for 'm2m_3'.
+invalid_models.selfclashm2m: Accessor for m2m field 'm2m_3' clashes with 
related m2m field 'SelfClashM2M.selfclashm2m_set'. Add a related_name argument 
to the definition for 'm2m_3'.
+invalid_models.selfclashm2m: Accessor for m2m field 'm2m_4' clashes with m2m 
field 'SelfClashM2M.selfclashm2m_set'. Add a related_name argument to the 
definition for 'm2m_4'.
+invalid_models.selfclashm2m: Accessor for m2m field 'm2m_4' clashes with 
related m2m field 'SelfClashM2M.selfclashm2m_set'. Add a related_name argument 
to the definition for 'm2m_4'.
+invalid_models.selfclashm2m: Accessor for m2m field 'm2m_4' clashes with 
related m2m field 'SelfClashM2M.selfclashm2m_set'. Add a related_name argument 
to the definition for 'm2m_4'.
+invalid_models.selfclashm2m: Reverse query name for m2m field 'm2m_3' clashes 
with field 'SelfClashM2M.selfclashm2m'. Add a related_name argument to the 
definition for 'm2m_3'.
+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'.
 """
-


--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to [EMAIL PROTECTED]
For more options, visit this group at 
http://groups.google.com/group/django-updates
-~----------~----~----~----~------~----~------~--~---

Reply via email to