diff options
| author | Boulder Sprinters <boulder-sprinters@djangoproject.com> | 2007-03-09 17:43:46 +0000 |
|---|---|---|
| committer | Boulder Sprinters <boulder-sprinters@djangoproject.com> | 2007-03-09 17:43:46 +0000 |
| commit | 0b7dd14d1f87e2ecef7aacc39fe4189667ed4fdf (patch) | |
| tree | b77497f9de324d38a9c6341e54d2742633e20055 /django/newforms | |
| parent | e17f75551491f5b864c1fc8a97c21d0b2bbf0bcd (diff) | |
boulder-oracle-sprint: Merged to trunk [4692].
git-svn-id: http://code.djangoproject.com/svn/django/branches/boulder-oracle-sprint@4695 bcc190cf-cafb-0310-a4f2-bffc1f526a37
Diffstat (limited to 'django/newforms')
| -rw-r--r-- | django/newforms/fields.py | 9 | ||||
| -rw-r--r-- | django/newforms/forms.py | 20 | ||||
| -rw-r--r-- | django/newforms/models.py | 100 | ||||
| -rw-r--r-- | django/newforms/util.py | 5 | ||||
| -rw-r--r-- | django/newforms/widgets.py | 8 |
5 files changed, 130 insertions, 12 deletions
diff --git a/django/newforms/fields.py b/django/newforms/fields.py index e9e1fb7746..8e3da03470 100644 --- a/django/newforms/fields.py +++ b/django/newforms/fields.py @@ -51,7 +51,7 @@ class Field(object): if label is not None: label = smart_unicode(label) self.required, self.label, self.initial = required, label, initial - self.help_text = help_text + self.help_text = smart_unicode(help_text or '') widget = widget or self.widget if isinstance(widget, type): widget = widget() @@ -339,8 +339,9 @@ class ChoiceField(Field): def _set_choices(self, value): # Setting choices also sets the choices on the widget. - self._choices = value - self.widget.choices = value + # choices can be any iterable, but we call list() on it because + # it will be consumed more than once. + self._choices = self.widget.choices = list(value) choices = property(_get_choices, _set_choices) @@ -356,7 +357,7 @@ class ChoiceField(Field): return value valid_values = set([str(k) for k, v in self.choices]) if value not in valid_values: - raise ValidationError(gettext(u'Select a valid choice. %s is not one of the available choices.') % value) + raise ValidationError(gettext(u'Select a valid choice. That choice is not one of the available choices.')) return value class MultipleChoiceField(ChoiceField): diff --git a/django/newforms/forms.py b/django/newforms/forms.py index 7e29941bef..2b3aa97428 100644 --- a/django/newforms/forms.py +++ b/django/newforms/forms.py @@ -7,6 +7,7 @@ from django.utils.html import escape from fields import Field from widgets import TextInput, Textarea, HiddenInput, MultipleHiddenInput from util import flatatt, StrAndUnicode, ErrorDict, ErrorList, ValidationError +import copy __all__ = ('BaseForm', 'Form') @@ -27,13 +28,24 @@ class SortedDictFromList(SortedDict): dict.__init__(self, dict(data)) def copy(self): - return SortedDictFromList(self.items()) + return SortedDictFromList([(k, copy.copy(v)) for k, v in self.items()]) class DeclarativeFieldsMetaclass(type): - "Metaclass that converts Field attributes to a dictionary called 'base_fields'." + """ + Metaclass that converts Field attributes to a dictionary called + 'base_fields', taking into account parent class 'base_fields' as well. + """ def __new__(cls, name, bases, attrs): fields = [(field_name, attrs.pop(field_name)) for field_name, obj in attrs.items() if isinstance(obj, Field)] fields.sort(lambda x, y: cmp(x[1].creation_counter, y[1].creation_counter)) + + # If this class is subclassing another Form, add that Form's fields. + # Note that we loop over the bases in *reverse*. This is necessary in + # order to preserve the correct order of fields. + for base in bases[::-1]: + if hasattr(base, 'base_fields'): + fields = base.base_fields.items() + fields + attrs['base_fields'] = SortedDictFromList(fields) return type.__new__(cls, name, bases, attrs) @@ -49,6 +61,7 @@ class BaseForm(StrAndUnicode): self.prefix = prefix self.initial = initial or {} self.__errors = None # Stores the errors after clean() has been called. + # The base_fields class attribute is the *class-wide* definition of # fields. Because a particular *instance* of the class might want to # alter self.fields, we create self.fields here by copying base_fields. @@ -100,7 +113,7 @@ class BaseForm(StrAndUnicode): output, hidden_fields = [], [] for name, field in self.fields.items(): bf = BoundField(self, field, name) - bf_errors = bf.errors # Cache in local variable. + bf_errors = ErrorList([escape(error) for error in bf.errors]) # Escape and cache in local variable. if bf.is_hidden: if bf_errors: top_errors.extend(['(Hidden field %s) %s' % (name, e) for e in bf_errors]) @@ -205,6 +218,7 @@ class BoundField(StrAndUnicode): self.label = pretty_name(name) else: self.label = self.field.label + self.help_text = field.help_text or '' def __unicode__(self): "Renders this field as an HTML widget." diff --git a/django/newforms/models.py b/django/newforms/models.py index a938b6350e..616c7141e7 100644 --- a/django/newforms/models.py +++ b/django/newforms/models.py @@ -3,9 +3,14 @@ Helper functions for creating Form classes from Django models and database field objects. """ +from django.utils.translation import gettext +from util import ValidationError from forms import BaseForm, DeclarativeFieldsMetaclass, SortedDictFromList +from fields import Field, ChoiceField +from widgets import Select, SelectMultiple, MultipleHiddenInput -__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): """ @@ -31,9 +36,9 @@ def save_instance(form, instance, commit=True): raise ValueError("The %s could not be changed because the data didn't validate." % opts.object_name) clean_data = form.clean_data for f in opts.fields: - if isinstance(f, models.AutoField): + if not f.editable or 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: @@ -63,6 +68,8 @@ def form_for_model(model, form=BaseForm, formfield_callback=lambda f: f.formfiel opts = model._meta field_list = [] for f in opts.fields + opts.many_to_many: + if not f.editable: + continue formfield = formfield_callback(f) if formfield: field_list.append((f.name, formfield)) @@ -84,6 +91,8 @@ def form_for_instance(instance, form=BaseForm, formfield_callback=lambda f, **kw opts = model._meta field_list = [] for f in opts.fields + opts.many_to_many: + if not f.editable: + continue current_value = f.value_from_object(instance) formfield = formfield_callback(f, initial=current_value) if formfield: @@ -94,5 +103,88 @@ def form_for_instance(instance, form=BaseForm, formfield_callback=lambda f, **kw 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]) + fields = SortedDictFromList([(f.name, f.formfield()) for f in field_list if f.editable]) return type('FormForFields', (BaseForm,), {'base_fields': fields}) + +class QuerySetIterator(object): + def __init__(self, queryset, empty_label, cache_choices): + self.queryset, self.empty_label, self.cache_choices = queryset, empty_label, cache_choices + + def __iter__(self): + if self.empty_label is not None: + yield (u"", self.empty_label) + for obj in self.queryset: + yield (obj._get_pk_val(), str(obj)) + # Clear the QuerySet cache if required. + if not self.cache_choices: + self.queryset._result_cache = None + +class ModelChoiceField(ChoiceField): + "A ChoiceField whose choices are a model QuerySet." + # This class is a subclass of ChoiceField for purity, but it doesn't + # actually use any of ChoiceField's implementation. + def __init__(self, queryset, empty_label=u"---------", cache_choices=False, + required=True, widget=Select, label=None, initial=None, help_text=None): + self.queryset = queryset + self.empty_label = empty_label + self.cache_choices = cache_choices + # Call Field instead of ChoiceField __init__() because we don't need + # ChoiceField.__init__(). + Field.__init__(self, required, widget, label, initial, help_text) + self.widget.choices = self.choices + + def _get_choices(self): + # If self._choices is set, then somebody must have manually set + # the property self.choices. In this case, just return self._choices. + if hasattr(self, '_choices'): + return self._choices + # Otherwise, execute the QuerySet in self.queryset to determine the + # choices dynamically. Return a fresh QuerySetIterator that has not + # been consumed. Note that we're instantiating a new QuerySetIterator + # *each* time _get_choices() is called (and, thus, each time + # self.choices is accessed) so that we can ensure the QuerySet has not + # been consumed. + return QuerySetIterator(self.queryset, self.empty_label, self.cache_choices) + + def _set_choices(self, value): + # This method is copied from ChoiceField._set_choices(). It's necessary + # because property() doesn't allow a subclass to overwrite only + # _get_choices without implementing _set_choices. + self._choices = self.widget.choices = list(value) + + choices = property(_get_choices, _set_choices) + + def clean(self, value): + Field.clean(self, value) + if value in ('', None): + return None + try: + value = self.queryset.model._default_manager.get(pk=value) + except self.queryset.model.DoesNotExist: + raise ValidationError(gettext(u'Select a valid choice. That choice is not one of the available choices.')) + return value + +class ModelMultipleChoiceField(ModelChoiceField): + "A MultipleChoiceField whose choices are a model QuerySet." + hidden_widget = MultipleHiddenInput + def __init__(self, queryset, cache_choices=False, required=True, + widget=SelectMultiple, label=None, initial=None, help_text=None): + super(ModelMultipleChoiceField, self).__init__(queryset, None, cache_choices, + required, widget, label, initial, help_text) + + def clean(self, value): + if self.required and not value: + raise ValidationError(gettext(u'This field is required.')) + elif not self.required and not value: + return [] + if not isinstance(value, (list, tuple)): + raise ValidationError(gettext(u'Enter a list of values.')) + final_values = [] + for val in value: + try: + obj = self.queryset.model._default_manager.get(pk=val) + except self.queryset.model.DoesNotExist: + raise ValidationError(gettext(u'Select a valid choice. %s is not one of the available choices.') % val) + else: + final_values.append(obj) + return final_values diff --git a/django/newforms/util.py b/django/newforms/util.py index 2fec9e4e2e..51a8efdde2 100644 --- a/django/newforms/util.py +++ b/django/newforms/util.py @@ -7,7 +7,10 @@ flatatt = lambda attrs: u''.join([u' %s="%s"' % (k, escape(v)) for k, v in attrs def smart_unicode(s): if not isinstance(s, basestring): - s = unicode(str(s)) + if hasattr(s, '__unicode__'): + s = unicode(s) + else: + s = unicode(str(s), settings.DEFAULT_CHARSET) elif not isinstance(s, unicode): s = unicode(s, settings.DEFAULT_CHARSET) return s diff --git a/django/newforms/widgets.py b/django/newforms/widgets.py index 585e12cc18..18bba31897 100644 --- a/django/newforms/widgets.py +++ b/django/newforms/widgets.py @@ -81,6 +81,14 @@ class TextInput(Input): class PasswordInput(Input): input_type = 'password' + def __init__(self, attrs=None, render_value=True): + self.attrs = attrs or {} + self.render_value = render_value + + def render(self, name, value, attrs=None): + if not self.render_value: value=None + return super(PasswordInput, self).render(name, value, attrs) + class HiddenInput(Input): input_type = 'hidden' is_hidden = True |
