From e56934b9b903535ace50f46d892dab4225b67f61 Mon Sep 17 00:00:00 2001 From: Adrian Holovaty Date: Tue, 20 Feb 2007 02:42:07 +0000 Subject: [PATCH] Fixed #3257 -- Added newforms ModelChoiceField and ModelMultipleChoiceField, which are now used by form_for_model() and form_for_instance(). Thanks for the patch, Honza Kral, floguy@gmail.com and kilian.cavalotti git-svn-id: http://code.djangoproject.com/svn/django/trunk@4547 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/db/models/fields/related.py | 12 ++-- django/newforms/models.py | 38 +++++++++++- tests/modeltests/model_forms/models.py | 82 ++++++++++++++++++++++++++ 3 files changed, 124 insertions(+), 8 deletions(-) diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index b517747735..3e8f9adbd8 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -553,9 +553,9 @@ class ForeignKey(RelatedField, Field): setattr(cls, related.get_accessor_name(), ForeignRelatedObjectsDescriptor(related)) def formfield(self, **kwargs): - defaults = {'choices': self.get_choices_default(), 'required': not self.blank, 'label': capfirst(self.verbose_name), 'help_text': self.help_text} + defaults = {'queryset': self.rel.to._default_manager.all(), 'required': not self.blank, 'label': capfirst(self.verbose_name), 'help_text': self.help_text} defaults.update(kwargs) - return forms.ChoiceField(**defaults) + return forms.ModelChoiceField(**defaults) class OneToOneField(RelatedField, IntegerField): def __init__(self, to, to_field=None, **kwargs): @@ -619,9 +619,9 @@ class OneToOneField(RelatedField, IntegerField): cls._meta.one_to_one_field = self def formfield(self, **kwargs): - defaults = {'choices': self.get_choices_default(), 'required': not self.blank, 'label': capfirst(self.verbose_name), 'help_text': self.help_text} + defaults = {'queryset': self.rel.to._default_manager.all(), 'required': not self.blank, 'label': capfirst(self.verbose_name), 'help_text': self.help_text} defaults.update(kwargs) - return forms.ChoiceField(**kwargs) + return forms.ModelChoiceField(**kwargs) class ManyToManyField(RelatedField, Field): def __init__(self, to, **kwargs): @@ -742,9 +742,9 @@ class ManyToManyField(RelatedField, Field): # MultipleChoiceField takes a list of IDs. if kwargs.get('initial') is not None: kwargs['initial'] = [i._get_pk_val() for i in kwargs['initial']] - defaults = {'choices': self.get_choices_default(), 'required': not self.blank, 'label': capfirst(self.verbose_name), 'help_text': self.help_text} + defaults = {'queryset' : self.rel.to._default_manager.all(), 'required': not self.blank, 'label': capfirst(self.verbose_name), 'help_text': self.help_text} defaults.update(kwargs) - return forms.MultipleChoiceField(**defaults) + return forms.ModelMultipleChoiceField(**defaults) class ManyToOneRel(object): def __init__(self, to, field_name, num_in_admin=3, min_num_in_admin=None, diff --git a/django/newforms/models.py b/django/newforms/models.py index a938b6350e..48bf0f3d23 100644 --- a/django/newforms/models.py +++ b/django/newforms/models.py @@ -4,8 +4,11 @@ and database field objects. """ from forms import BaseForm, DeclarativeFieldsMetaclass, SortedDictFromList +from fields import ChoiceField, MultipleChoiceField +from widgets import Select, SelectMultiple -__all__ = ('save_instance', 'form_for_model', 'form_for_instance', 'form_for_fields') +__all__ = ('save_instance', 'form_for_model', 'form_for_instance', 'form_for_fields', + 'ModelChoiceField', 'ModelMultipleChoiceField') def model_save(self, commit=True): """ @@ -33,7 +36,7 @@ def save_instance(form, instance, commit=True): for f in opts.fields: if isinstance(f, models.AutoField): continue - setattr(instance, f.attname, clean_data[f.name]) + setattr(instance, f.name, clean_data[f.name]) if commit: instance.save() for f in opts.many_to_many: @@ -96,3 +99,34 @@ def form_for_fields(field_list): "Returns a Form class for the given list of Django database field instances." fields = SortedDictFromList([(f.name, f.formfield()) for f in field_list]) return type('FormForFields', (BaseForm,), {'base_fields': fields}) + +class ModelChoiceField(ChoiceField): + "A ChoiceField whose choices are a model QuerySet." + def __init__(self, queryset, empty_label=u"---------", **kwargs): + self.model = queryset.model + choices = [(obj._get_pk_val(), str(obj)) for obj in queryset] + if empty_label is not None: + choices = [(u"", empty_label)] + choices + ChoiceField.__init__(self, choices=choices, **kwargs) + + def clean(self, value): + value = ChoiceField.clean(self, value) + if not value: + return None + try: + value = self.model._default_manager.get(pk=value) + except self.model.DoesNotExist: + raise ValidationError(gettext(u'Select a valid choice. That choice is not one of the available choices.')) + return value + +class ModelMultipleChoiceField(MultipleChoiceField): + "A MultipleChoiceField whose choices are a model QuerySet." + def __init__(self, queryset, **kwargs): + self.model = queryset.model + MultipleChoiceField.__init__(self, choices=[(obj._get_pk_val(), str(obj)) for obj in queryset], **kwargs) + + def clean(self, value): + value = MultipleChoiceField.clean(self, value) + if not value: + return [] + return self.model._default_manager.filter(pk__in=value) diff --git a/tests/modeltests/model_forms/models.py b/tests/modeltests/model_forms/models.py index 657d506b33..c7ac44842a 100644 --- a/tests/modeltests/model_forms/models.py +++ b/tests/modeltests/model_forms/models.py @@ -281,4 +281,86 @@ existing Category instance. >>> Category.objects.get(id=3) + +# ModelChoiceField ############################################################ + +>>> from django.newforms import ModelChoiceField, ModelMultipleChoiceField + +>>> f = ModelChoiceField(Category.objects.all()) +>>> f.clean('') +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] +>>> f.clean(None) +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] +>>> f.clean(0) +Traceback (most recent call last): +... +ValidationError: [u'Select a valid choice. That choice is not one of the available choices.'] +>>> f.clean(3) + +>>> f.clean(2) + + +>>> f = ModelChoiceField(Category.objects.filter(pk=1), required=False) +>>> print f.clean('') +None +>>> f.clean('') +>>> f.clean('1') + +>>> f.clean('2') +Traceback (most recent call last): +... +ValidationError: [u'Select a valid choice. That choice is not one of the available choices.'] + +# ModelMultipleChoiceField #################################################### + +>>> f = ModelMultipleChoiceField(Category.objects.all()) +>>> f.clean(None) +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] +>>> f.clean([]) +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] +>>> f.clean([1]) +[] +>>> f.clean([2]) +[] +>>> f.clean(['1']) +[] +>>> f.clean(['1', '2']) +[, ] +>>> f.clean([1, '2']) +[, ] +>>> f.clean((1, '2')) +[, ] +>>> f.clean(['nonexistent']) +Traceback (most recent call last): +... +ValidationError: [u'Select a valid choice. nonexistent is not one of the available choices.'] +>>> f.clean('hello') +Traceback (most recent call last): +... +ValidationError: [u'Enter a list of values.'] +>>> f = ModelMultipleChoiceField(Category.objects.all(), required=False) +>>> f.clean([]) +[] +>>> f.clean(()) +[] +>>> f.clean(['4']) +Traceback (most recent call last): +... +ValidationError: [u'Select a valid choice. 4 is not one of the available choices.'] +>>> f.clean(['3', '4']) +Traceback (most recent call last): +... +ValidationError: [u'Select a valid choice. 4 is not one of the available choices.'] +>>> f.clean(['1', '5']) +Traceback (most recent call last): +... +ValidationError: [u'Select a valid choice. 5 is not one of the available choices.'] """}