Ticket #11707: limit_ForeignKey.1.2.X.patch

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

backported to 1.2.X and refactored to reduce complexity

  • django/forms/models.py

    diff -r 77515d829c8b django/forms/models.py
    a b  
    882882            yield (u"", self.field.empty_label)
    883883        if self.field.cache_choices:
    884884            if self.field.choice_cache is None:
    885                 self.field.choice_cache = [
    886                     self.choice(obj) for obj in self.queryset.all()
    887                 ]
     885                self.field.choice_cache = list(self.distinct_choices())
    888886            for choice in self.field.choice_cache:
    889887                yield choice
    890888        else:
    891             for obj in self.queryset.all():
     889            for choice in self.distinct_choices():
     890                yield choice
     891
     892    def distinct_choices(self):
     893        """Yields a choice for each distinct object in the queryset.
     894
     895        If you use limit_choices_to you could have more than one results for a
     896        single Instance. DISTINCT in the db won't work as expected so we do
     897        the check here."""
     898        seen_choices = set()
     899        for obj in self.queryset.all():
     900            if obj.pk not in seen_choices:
     901                seen_choices.add(obj.pk)
    892902                yield self.choice(obj)
    893903
    894904    def __len__(self):
     
    978988            return None
    979989        try:
    980990            key = self.to_field_name or 'pk'
    981             value = self.queryset.get(**{key: value})
     991            values = self.queryset.filter(**{key: value})
     992            # If you use limit_choices_to you could have more than one results
     993            # for a single Instance. DISTINCT in the db won't work as expected
     994            # so we do the check here.
     995            if not values:
     996                raise self.queryset.model.DoesNotExist
     997            if len(values) == 1:
     998                value = values[0]
     999            else:
     1000                pks = set(value.pk for value in values)
     1001                if len(pks) == 1:
     1002                    value = values[0]
     1003                else:
     1004                    raise self.queryset.model.MultipleObjectsReturned
    9821005        except (ValueError, self.queryset.model.DoesNotExist):
    9831006            raise ValidationError(self.error_messages['invalid_choice'])
    9841007        return value
  • tests/regressiontests/model_fields/models.py

    diff -r 77515d829c8b 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 77515d829c8b tests/regressiontests/model_fields/tests.py
    a b  
    11import datetime
    22import unittest
    33from decimal import Decimal
     4import re
    45
    56import django.test
    67from django import forms
    78from django.db import models
    89from django.core.exceptions import ValidationError
    910
    10 from models import Foo, Bar, Whiz, BigD, BigS, Image, BigInt, Post, NullBooleanModel, BooleanModel
     11from models import Foo, Bar, Baz, Whiz, BigD, BigS, Image, BigInt, Post, NullBooleanModel, BooleanModel
    1112
    1213# If PIL available, do these tests.
    1314if Image:
     
    9495        # This should not crash. That counts as a win for our purposes.
    9596        Foo.objects.filter(d__gte=100000000000)
    9697
     98class BazForm(forms.ModelForm):
     99    class Meta:
     100        model = Baz
     101
    97102class ForeignKeyTests(django.test.TestCase):
    98103    def test_callable_default(self):
    99104        """Test the use of a lazy callable for ForeignKey.default"""
     
    101106        b = Bar.objects.create(b="bcd")
    102107        self.assertEqual(b.a, a)
    103108
     109    def test_distinct_choice_limit(self):
     110        """Doesn't make sense to offer the same ForeignKey multiple times in a form"""
     111        a = Foo.objects.create(a='a', d=Decimal("-1"))
     112        b = Foo.objects.create(a='b', d=Decimal("1"))
     113        Bar.objects.create(b='ah', a=a)
     114        Bar.objects.create(b='aha', a=a)
     115        Bar.objects.create(b='bla', a=b)
     116        form = BazForm()
     117        fk_field = str(form['foo'])
     118        self.assertEqual(len(re.findall(r'value="%s"' % b.pk, fk_field)), 0)
     119        self.assertEqual(len(re.findall(r'value="%s"' % a.pk, fk_field)), 1)
     120
     121    def test_distinct_choice_limit_save(self):
     122        """Doesn't make sense to offer the same ForeignKey multiple times in a form"""
     123        a = Foo.objects.create(a='a', d=Decimal("-1"))
     124        b = Foo.objects.create(a='b', d=Decimal("1"))
     125        Bar.objects.create(b='ah', a=a)
     126        Bar.objects.create(b='aha', a=a)
     127        Bar.objects.create(b='bla', a=b)
     128        form = BazForm({'foo': a.pk, 'a': 'a'})
     129        self.assertTrue(form.is_valid())
     130        obj = form.save()
     131        self.assertEqual(obj.foo.pk, a.pk)
     132
    104133class DateTimeFieldTests(unittest.TestCase):
    105134    def test_datetimefield_to_python_usecs(self):
    106135        """DateTimeField.to_python should support usecs"""
Back to Top