Ticket #11707: limit_ForeignKey.1.3.X.patch

File limit_ForeignKey.1.3.X.patch, 5.2 KB (added by Chris Wesseling, 13 years ago)

against 1.3.X branch

  • django/forms/models.py

    diff -r 1d6c8709466a django/forms/models.py
    a b  
    873873            yield (u"", self.field.empty_label)
    874874        if self.field.cache_choices:
    875875            if self.field.choice_cache is None:
    876                 self.field.choice_cache = [
    877                     self.choice(obj) for obj in self.queryset.all()
    878                 ]
     876                self.field.choice_cache = list(self.distinct_choices())
    879877            for choice in self.field.choice_cache:
    880878                yield choice
    881879        else:
    882             for obj in self.queryset.all():
     880            for choice in self.distinct_choices():
     881                yield choice
     882
     883    def distinct_choices(self):
     884        """Yields a choice for each distinct object in the queryset.
     885
     886        If you use limit_choices_to you could have more than one results for a
     887        single Instance. DISTINCT in the db won't work as expected so we do
     888        the check here."""
     889        seen_choices = set()
     890        for obj in self.queryset.all():
     891            if obj.pk not in seen_choices:
     892                seen_choices.add(obj.pk)
    883893                yield self.choice(obj)
    884894
    885895    def __len__(self):
     
    969979            return None
    970980        try:
    971981            key = self.to_field_name or 'pk'
    972             value = self.queryset.get(**{key: value})
     982            values = self.queryset.filter(**{key: value})
     983            # If you use limit_choices_to you could have more than one results
     984            # for a single Instance. DISTINCT in the db won't work as expected
     985            # so we do the check here.
     986            if not values:
     987                raise self.queryset.model.DoesNotExist
     988            if len(values) == 1:
     989                value = values[0]
     990            else:
     991                pks = set(value.pk for value in values)
     992                if len(pks) == 1:
     993                    value = values[0]
     994                else:
     995                    raise self.queryset.model.MultipleObjectsReturned
    973996        except (ValueError, self.queryset.model.DoesNotExist):
    974997            raise ValidationError(self.error_messages['invalid_choice'])
    975998        return value
  • tests/regressiontests/model_fields/models.py

    diff -r 1d6c8709466a tests/regressiontests/model_fields/models.py
    a b  
    2929    b = models.CharField(max_length=10)
    3030    a = models.ForeignKey(Foo, default=get_foo)
    3131
     32class Baz(models.Model):
     33    a = models.CharField(max_length=5)
     34    #Only Foos related to Bars starting with 'a'
     35    foo = models.ForeignKey(Foo, limit_choices_to=models.Q(bar__b__startswith='a'))
     36
    3237class Whiz(models.Model):
    3338    CHOICES = (
    3439        ('Group 1', (
  • tests/regressiontests/model_fields/tests.py

    diff -r 1d6c8709466a tests/regressiontests/model_fields/tests.py
    a b  
    11import datetime
    22from decimal import Decimal
     3import re
    34
    45from django import test
    56from django import forms
     
    89from django.db.models.fields.files import FieldFile
    910from django.utils import unittest
    1011
    11 from models import Foo, Bar, Whiz, BigD, BigS, Image, BigInt, Post, NullBooleanModel, BooleanModel, Document
     12from models import Foo, Bar, Baz, Whiz, BigD, BigS, Image, BigInt, Post, NullBooleanModel, BooleanModel, Document
    1213
    1314# If PIL available, do these tests.
    1415if Image:
     
    9596        # This should not crash. That counts as a win for our purposes.
    9697        Foo.objects.filter(d__gte=100000000000)
    9798
     99
     100class BazForm(forms.ModelForm):
     101    class Meta:
     102        model = Baz
     103
     104
    98105class ForeignKeyTests(test.TestCase):
    99106    def test_callable_default(self):
    100107        """Test the use of a lazy callable for ForeignKey.default"""
     
    102109        b = Bar.objects.create(b="bcd")
    103110        self.assertEqual(b.a, a)
    104111
     112    def test_distinct_choice_limit(self):
     113        """Doesn't make sense to offer the same ForeignKey multiple times in a form"""
     114        a = Foo.objects.create(a='a', d=Decimal("-1"))
     115        b = Foo.objects.create(a='b', d=Decimal("1"))
     116        Bar.objects.create(b='ah', a=a)
     117        Bar.objects.create(b='aha', a=a)
     118        Bar.objects.create(b='bla', a=b)
     119        form = BazForm()
     120        fk_field = str(form['foo'])
     121        self.assertEqual(len(re.findall(r'value="%s"' % b.pk, fk_field)), 0)
     122        self.assertEqual(len(re.findall(r'value="%s"' % a.pk, fk_field)), 1)
     123
     124    def test_distinct_choice_limit_save(self):
     125        """Doesn't make sense to offer the same ForeignKey multiple times in a form"""
     126        a = Foo.objects.create(a='a', d=Decimal("-1"))
     127        b = Foo.objects.create(a='b', d=Decimal("1"))
     128        Bar.objects.create(b='ah', a=a)
     129        Bar.objects.create(b='aha', a=a)
     130        Bar.objects.create(b='bla', a=b)
     131        form = BazForm({'foo': a.pk, 'a': 'a'})
     132        self.assertTrue(form.is_valid())
     133        obj = form.save()
     134        self.assertEqual(obj.foo.pk, a.pk)
     135
    105136class DateTimeFieldTests(unittest.TestCase):
    106137    def test_datetimefield_to_python_usecs(self):
    107138        """DateTimeField.to_python should support usecs"""
Back to Top