diff --git a/django/forms/fields.py b/django/forms/fields.py
index 621d380..d7b6ebb 100644
a
|
b
|
class NullBooleanField(BooleanField):
|
743 | 743 | return initial != data |
744 | 744 | |
745 | 745 | |
| 746 | class CallableChoiceIterator(object): |
| 747 | def __init__(self, callme, field): |
| 748 | self.callme = callme |
| 749 | self.field = field |
| 750 | |
| 751 | def __iter__(self): |
| 752 | for e in self.callme(self.field): |
| 753 | yield e |
| 754 | |
746 | 755 | class ChoiceField(Field): |
747 | 756 | widget = Select |
748 | 757 | default_error_messages = { |
… |
… |
class ChoiceField(Field):
|
757 | 766 | |
758 | 767 | def __deepcopy__(self, memo): |
759 | 768 | result = super(ChoiceField, self).__deepcopy__(memo) |
760 | | result._choices = copy.deepcopy(self._choices, memo) |
| 769 | result._set_choices(copy.deepcopy(self._choices, memo)) |
761 | 770 | return result |
762 | 771 | |
763 | 772 | def _get_choices(self): |
… |
… |
class ChoiceField(Field):
|
765 | 774 | |
766 | 775 | def _set_choices(self, value): |
767 | 776 | # Setting choices also sets the choices on the widget. |
768 | | # choices can be any iterable, but we call list() on it because |
769 | | # it will be consumed more than once. |
770 | | self._choices = self.widget.choices = list(value) |
| 777 | if callable(value): |
| 778 | value = CallableChoiceIterator(value, self) |
| 779 | else: |
| 780 | # choices can be any iterable, but we call list() on it because |
| 781 | # it will be consumed more than once. |
| 782 | value = list(value) |
| 783 | |
| 784 | self._choices = self.widget.choices = value |
771 | 785 | |
772 | 786 | choices = property(_get_choices, _set_choices) |
773 | 787 | |
diff --git a/django/forms/models.py b/django/forms/models.py
index d545a07..006d8b1 100644
a
|
b
|
from __future__ import absolute_import, unicode_literals
|
7 | 7 | |
8 | 8 | from django.core.exceptions import ValidationError, NON_FIELD_ERRORS, FieldError |
9 | 9 | from django.core.validators import EMPTY_VALUES |
10 | | from django.forms.fields import Field, ChoiceField |
| 10 | from django.forms.fields import Field, ChoiceField, CallableChoiceIterator |
11 | 11 | from django.forms.forms import BaseForm, get_declared_fields |
12 | 12 | from django.forms.formsets import BaseFormSet, formset_factory |
13 | 13 | from django.forms.util import ErrorList |
… |
… |
class InlineForeignKeyField(Field):
|
899 | 899 | |
900 | 900 | class ModelChoiceIterator(object): |
901 | 901 | def __init__(self, field): |
902 | | self.field = field |
903 | | self.queryset = field.queryset |
| 902 | self._delegate = CallableChoiceIterator(queryset_callable, field) |
904 | 903 | |
905 | 904 | def __iter__(self): |
906 | | if self.field.empty_label is not None: |
907 | | yield ("", self.field.empty_label) |
908 | | if self.field.cache_choices: |
909 | | if self.field.choice_cache is None: |
910 | | self.field.choice_cache = [ |
911 | | self.choice(obj) for obj in self.queryset.all() |
912 | | ] |
913 | | for choice in self.field.choice_cache: |
914 | | yield choice |
915 | | else: |
916 | | for obj in self.queryset.all(): |
917 | | yield self.choice(obj) |
| 905 | return self._delegate.__iter__() |
918 | 906 | |
919 | 907 | def __len__(self): |
920 | | return len(self.queryset) |
921 | | |
922 | | def choice(self, obj): |
923 | | return (self.field.prepare_value(obj), self.field.label_from_instance(obj)) |
| 908 | return len(self.field.queryset) |
| 909 | |
| 910 | |
| 911 | def queryset_callable(field): |
| 912 | def choice(obj): |
| 913 | return (field.prepare_value(obj), field.label_from_instance(obj)) |
| 914 | |
| 915 | if field.empty_label is not None: |
| 916 | yield ("", field.empty_label) |
| 917 | if field.cache_choices: |
| 918 | if field.choice_cache is None: |
| 919 | field.choice_cache = [ |
| 920 | choice(obj) for obj in field.queryset.all() |
| 921 | ] |
| 922 | for choice in field.choice_cache: |
| 923 | yield choice |
| 924 | else: |
| 925 | for obj in field.queryset.all(): |
| 926 | yield choice(obj) |
924 | 927 | |
925 | 928 | class ModelChoiceField(ChoiceField): |
926 | 929 | """A ChoiceField whose choices are a model QuerySet.""" |
diff --git a/tests/regressiontests/forms/tests/fields.py b/tests/regressiontests/forms/tests/fields.py
index 3fe2cd2..bdce0ba 100644
a
|
b
|
def fix_os_paths(x):
|
49 | 49 | else: |
50 | 50 | return x |
51 | 51 | |
52 | | |
53 | 52 | class FieldsTests(SimpleTestCase): |
54 | 53 | |
55 | 54 | def assertWidgetRendersTo(self, field, to): |
… |
… |
class FieldsTests(SimpleTestCase):
|
893 | 892 | f = TypedChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=int, required=False, empty_value=None) |
894 | 893 | self.assertEqual(None, f.clean('')) |
895 | 894 | |
| 895 | def test_choicefield_callable(self): |
| 896 | choices = lambda field: [('J', 'John'), ('P', 'Paul')] |
| 897 | f = ChoiceField(choices=choices) |
| 898 | self.assertEqual(u'J', f.clean('J')) |
| 899 | |
| 900 | def test_choicefield_callable_may_evaluate_to_different_values(self): |
| 901 | choices = [] |
| 902 | def choices_as_callable(field): |
| 903 | return choices |
| 904 | |
| 905 | class ChoiceFieldForm(Form): |
| 906 | choicefield = ChoiceField(choices=choices_as_callable) |
| 907 | |
| 908 | choices = [('J', 'John'), ('P', 'Paul')] |
| 909 | form = ChoiceFieldForm() |
| 910 | self.assertTrue("John" in form.as_p()) |
| 911 | |
| 912 | choices = [('M', 'Marie'),] |
| 913 | form = ChoiceFieldForm() |
| 914 | self.assertTrue("Marie" in form.as_p()) |
| 915 | |
896 | 916 | # NullBooleanField ############################################################ |
897 | 917 | |
898 | 918 | def test_nullbooleanfield_1(self): |