aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/api-guide/fields.md34
-rw-r--r--rest_framework/fields.py83
-rw-r--r--rest_framework/renderers.py24
3 files changed, 122 insertions, 19 deletions
diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md
index b3cf186c..189ed76f 100644
--- a/docs/api-guide/fields.md
+++ b/docs/api-guide/fields.md
@@ -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`.
---
diff --git a/rest_framework/fields.py b/rest_framework/fields.py
index 162eed26..f610d6aa 100644
--- a/rest_framework/fields.py
+++ b/rest_framework/fields.py
@@ -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'
diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py
index 23fd961b..b2dbffd2 100644
--- a/rest_framework/renderers.py
+++ b/rest_framework/renderers.py
@@ -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