Merge field changes: .default and .widget

This commit is contained in:
Tom Christie 2012-10-22 15:24:09 +01:00
commit 95a670de41
3 changed files with 122 additions and 19 deletions

View File

@ -73,34 +73,52 @@ These fields represent basic datatypes, and support both reading and writing val
## BooleanField
A Boolean representation, corresponds to `django.db.models.fields.BooleanField`.
A Boolean representation.
Corresponds to `django.db.models.fields.BooleanField`.
## CharField
A text representation, optionally validates the text to be shorter than `max_length` and longer than `min_length`, corresponds to `django.db.models.fields.CharField`
A text representation, optionally validates the text to be shorter than `max_length` and longer than `min_length`.
Corresponds to `django.db.models.fields.CharField`
or `django.db.models.fields.TextField`.
**Signature:** `CharField([max_length=<Integer>[, min_length=<Integer>]])`
**Signature:** `CharField(max_length=None, min_length=None)`
## ChoiceField
A field that can accept on of a limited set of choices.
## EmailField
A text representation, validates the text to be a valid e-mail address. Corresponds to `django.db.models.fields.EmailField`
A text representation, validates the text to be a valid e-mail address.
Corresponds to `django.db.models.fields.EmailField`
## DateField
A date representation. Corresponds to `django.db.models.fields.DateField`
A date representation.
Corresponds to `django.db.models.fields.DateField`
## DateTimeField
A date and time representation. Corresponds to `django.db.models.fields.DateTimeField`
A date and time representation.
Corresponds to `django.db.models.fields.DateTimeField`
## IntegerField
An integer representation. Corresponds to `django.db.models.fields.IntegerField`, `django.db.models.fields.SmallIntegerField`, `django.db.models.fields.PositiveIntegerField` and `django.db.models.fields.PositiveSmallIntegerField`
An integer representation.
Corresponds to `django.db.models.fields.IntegerField`, `django.db.models.fields.SmallIntegerField`, `django.db.models.fields.PositiveIntegerField` and `django.db.models.fields.PositiveSmallIntegerField`
## FloatField
A floating point representation. Corresponds to `django.db.models.fields.FloatField`.
A floating point representation.
Corresponds to `django.db.models.fields.FloatField`.
---

View File

@ -7,6 +7,7 @@ from django.core import validators
from django.core.exceptions import ObjectDoesNotExist, ValidationError
from django.core.urlresolvers import resolve
from django.conf import settings
from django.forms import widgets
from django.utils.encoding import is_protected_type, smart_unicode
from django.utils.translation import ugettext_lazy as _
from rest_framework.reverse import reverse
@ -107,10 +108,15 @@ class WritableField(Field):
'required': _('This field is required.'),
'invalid': _('Invalid value.'),
}
widget = widgets.TextInput
default = None
def __init__(self, source=None, readonly=False, required=None,
validators=[], error_messages=None):
validators=[], error_messages=None, widget=None,
default=None):
super(WritableField, self).__init__(source=source)
self.readonly = readonly
if required is None:
self.required = not(readonly)
@ -125,6 +131,13 @@ class WritableField(Field):
self.error_messages = messages
self.validators = self.default_validators + validators
self.default = default or self.default
# Widgets are ony used for HTML forms.
widget = widget or self.widget
if isinstance(widget, type):
widget = widget()
self.widget = widget
def validate(self, value):
if value in validators.EMPTY_VALUES and self.required:
@ -159,9 +172,12 @@ class WritableField(Field):
try:
native = data[field_name]
except KeyError:
if self.required:
raise ValidationError(self.error_messages['required'])
return
if self.default is not None:
native = self.default
else:
if self.required:
raise ValidationError(self.error_messages['required'])
return
value = self.from_native(native)
if self.source == '*':
@ -399,20 +415,23 @@ class HyperlinkedIdentityField(Field):
class BooleanField(WritableField):
type_name = 'BooleanField'
widget = widgets.CheckboxInput
default_error_messages = {
'invalid': _(u"'%s' value must be either True or False."),
}
empty = False
# Note: we set default to `False` in order to fill in missing value not
# supplied by html form. TODO: Fix so that only html form input gets
# this behavior.
default = False
def from_native(self, value):
if value in (True, False):
# if value is 1 or 0 than it's equal to True or False, but we want
# to return a true bool for semantic reasons.
return bool(value)
if value in ('t', 'True', '1'):
return True
if value in ('f', 'False', '0'):
return False
raise ValidationError(self.error_messages['invalid'] % value)
return bool(value)
class CharField(WritableField):
@ -432,6 +451,52 @@ class CharField(WritableField):
return smart_unicode(value)
class ChoiceField(WritableField):
type_name = 'ChoiceField'
widget = widgets.Select
default_error_messages = {
'invalid_choice': _('Select a valid choice. %(value)s is not one of the available choices.'),
}
def __init__(self, choices=(), *args, **kwargs):
super(ChoiceField, self).__init__(*args, **kwargs)
self.choices = choices
def _get_choices(self):
return self._choices
def _set_choices(self, value):
# Setting choices also sets the choices on the widget.
# 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)
def validate(self, value):
"""
Validates that the input is in self.choices.
"""
super(ChoiceField, self).validate(value)
if value and not self.valid_value(value):
raise ValidationError(self.error_messages['invalid_choice'] % {'value': value})
def valid_value(self, value):
"""
Check to see if the provided value is a valid choice.
"""
for k, v in self.choices:
if isinstance(v, (list, tuple)):
# This is an optgroup, so look inside the group for options
for k2, v2 in v:
if value == smart_unicode(k2):
return True
else:
if value == smart_unicode(k):
return True
return False
class EmailField(CharField):
type_name = 'EmailField'

View File

@ -6,6 +6,7 @@ on the response, such as JSON encoded data or HTML output.
REST framework also provides an HTML renderer the renders the browseable API.
"""
import copy
import string
from django import forms
from django.http.multipartparser import parse_header
@ -279,13 +280,32 @@ class BrowsableAPIRenderer(BaseRenderer):
continue
kwargs = {}
kwargs['required'] = v.required
if getattr(v, 'queryset', None):
kwargs['queryset'] = getattr(v, 'queryset', None)
kwargs['queryset'] = v.queryset
if getattr(v, 'widget', None):
widget = copy.deepcopy(v.widget)
# If choices have friendly readable names,
# then add in the identities too
if getattr(widget, 'choices', None):
choices = widget.choices
if any([ident != desc for (ident, desc) in choices]):
choices = [(ident, "%s (%s)" % (desc, ident))
for (ident, desc) in choices]
widget.choices = choices
kwargs['widget'] = widget
if getattr(v, 'default', None) is not None:
kwargs['initial'] = v.default
kwargs['label'] = k
try:
fields[k] = field_mapping[v.__class__](**kwargs)
except KeyError:
fields[k] = forms.CharField()
fields[k] = forms.CharField(**kwargs)
OnTheFlyForm = type("OnTheFlyForm", (forms.Form,), fields)
if obj and not view.request.method == 'DELETE': # Don't fill in the form when the object is deleted