summaryrefslogtreecommitdiff
path: root/django/newforms
diff options
context:
space:
mode:
authorBoulder Sprinters <boulder-sprinters@djangoproject.com>2007-03-09 17:43:46 +0000
committerBoulder Sprinters <boulder-sprinters@djangoproject.com>2007-03-09 17:43:46 +0000
commit0b7dd14d1f87e2ecef7aacc39fe4189667ed4fdf (patch)
treeb77497f9de324d38a9c6341e54d2742633e20055 /django/newforms
parente17f75551491f5b864c1fc8a97c21d0b2bbf0bcd (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.py9
-rw-r--r--django/newforms/forms.py20
-rw-r--r--django/newforms/models.py100
-rw-r--r--django/newforms/util.py5
-rw-r--r--django/newforms/widgets.py8
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