Luke Plant avatar Luke Plant committed 55655c2

[1.2.X] Fixed #11707 - limit_choices_to on a ForeignKey can render duplicate options in formfield

Thanks to Chris Wesseling for the report and patch.

Backport of [15607] from trunk.

Comments (0)

Files changed (3)

django/db/models/fields/related.py

         db = kwargs.pop('using', None)
         defaults = {
             'form_class': forms.ModelChoiceField,
-            'queryset': self.rel.to._default_manager.using(db).complex_filter(self.rel.limit_choices_to),
+            'queryset': self.rel.to._default_manager.using(db).complex_filter(self.rel.limit_choices_to).distinct(),
             'to_field_name': self.rel.field_name,
         }
         defaults.update(kwargs)

tests/regressiontests/model_fields/models.py

     b = models.CharField(max_length=10)
     a = models.ForeignKey(Foo, default=get_foo)
 
+class Baz(models.Model):
+    a = models.CharField(max_length=5)
+    #Only Foos related to Bars starting with 'a'
+    foo = models.ForeignKey(Foo, limit_choices_to=models.Q(bar__b__startswith='a'))
+
 class Whiz(models.Model):
     CHOICES = (
         ('Group 1', (

tests/regressiontests/model_fields/tests.py

 import datetime
 import unittest
 from decimal import Decimal
+import re
 
 import django.test
 from django import forms
 from django.db import models
 from django.core.exceptions import ValidationError
 
-from models import Foo, Bar, Whiz, BigD, BigS, Image, BigInt, Post, NullBooleanModel, BooleanModel
+from models import Foo, Bar, Baz, Whiz, BigD, BigS, Image, BigInt, Post, NullBooleanModel, BooleanModel
 
 # If PIL available, do these tests.
 if Image:
         # This should not crash. That counts as a win for our purposes.
         Foo.objects.filter(d__gte=100000000000)
 
+class BazForm(forms.ModelForm):
+    class Meta:
+        model = Baz
+
 class ForeignKeyTests(django.test.TestCase):
     def test_callable_default(self):
         """Test the use of a lazy callable for ForeignKey.default"""
         b = Bar.objects.create(b="bcd")
         self.assertEqual(b.a, a)
 
+    def test_distinct_choice_limit(self):
+        """Doesn't make sense to offer the same ForeignKey multiple times in a form"""
+        a = Foo.objects.create(a='a', d=Decimal("-1"))
+        b = Foo.objects.create(a='b', d=Decimal("1"))
+        bar_a = Bar.objects.create(b='ah', a=a)
+        bar_b = Bar.objects.create(b='aha', a=a)
+        bar_b = Bar.objects.create(b='bla', a=b)
+        form = BazForm()
+        fk_field = str(form['foo'])
+        self.assertEqual(len(re.findall(r'value="2"', fk_field)), 0)
+        self.assertEqual(len(re.findall(r'value="1"', fk_field)), 1)
+
 class DateTimeFieldTests(unittest.TestCase):
     def test_datetimefield_to_python_usecs(self):
         """DateTimeField.to_python should support usecs"""
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.