aboutsummaryrefslogtreecommitdiffstats
path: root/rest_framework
diff options
context:
space:
mode:
Diffstat (limited to 'rest_framework')
-rw-r--r--rest_framework/__init__.py2
-rw-r--r--rest_framework/compat.py10
-rw-r--r--rest_framework/fields.py13
-rw-r--r--rest_framework/pagination.py8
-rw-r--r--rest_framework/routers.py21
-rw-r--r--rest_framework/templates/rest_framework/inline/checkbox_multiple.html2
-rw-r--r--rest_framework/templates/rest_framework/vertical/list_fieldset.html4
-rw-r--r--rest_framework/utils/field_mapping.py15
-rw-r--r--rest_framework/utils/model_meta.py57
9 files changed, 91 insertions, 41 deletions
diff --git a/rest_framework/__init__.py b/rest_framework/__init__.py
index 6808b74b..dec89b3e 100644
--- a/rest_framework/__init__.py
+++ b/rest_framework/__init__.py
@@ -11,7 +11,7 @@ __title__ = 'Django REST framework'
__version__ = '3.0.2'
__author__ = 'Tom Christie'
__license__ = 'BSD 2-Clause'
-__copyright__ = 'Copyright 2011-2014 Tom Christie'
+__copyright__ = 'Copyright 2011-2015 Tom Christie'
# Version synonym
VERSION = __version__
diff --git a/rest_framework/compat.py b/rest_framework/compat.py
index 3c8fb0da..7241da27 100644
--- a/rest_framework/compat.py
+++ b/rest_framework/compat.py
@@ -55,6 +55,16 @@ except ImportError:
from django.http import HttpResponse as HttpResponseBase
+# request only provides `resolver_match` from 1.5 onwards.
+def get_resolver_match(request):
+ try:
+ return request.resolver_match
+ except AttributeError:
+ # Django < 1.5
+ from django.core.urlresolvers import resolve
+ return resolve(request.path_info)
+
+
# django-filter is optional
try:
import django_filters
diff --git a/rest_framework/fields.py b/rest_framework/fields.py
index c40dc3fb..aab80982 100644
--- a/rest_framework/fields.py
+++ b/rest_framework/fields.py
@@ -273,7 +273,11 @@ class Field(object):
return empty
return self.default_empty_html
ret = dictionary[self.field_name]
- return self.default_empty_html if (ret == '') else ret
+ if ret == '' and self.allow_null:
+ # If the field is blank, and null is a valid value then
+ # determine if we should use null instead.
+ return '' if getattr(self, 'allow_blank', False) else None
+ return ret
return dictionary.get(self.field_name, empty)
def get_attribute(self, instance):
@@ -545,8 +549,6 @@ class CharField(Field):
'min_length': _('Ensure this field has at least {min_length} characters.')
}
initial = ''
- coerce_blank_to_null = False
- default_empty_html = ''
def __init__(self, **kwargs):
self.allow_blank = kwargs.pop('allow_blank', False)
@@ -560,11 +562,6 @@ class CharField(Field):
message = self.error_messages['min_length'].format(min_length=min_length)
self.validators.append(MinLengthValidator(min_length, message=message))
- if self.allow_null and (not self.allow_blank) and (self.default is empty):
- # HTML input cannot represent `None` values, so we need to
- # forcibly coerce empty HTML values to `None` if `allow_null=True`.
- self.default_empty_html = None
-
def run_validation(self, data=empty):
# Test for the empty string here so that it does not get validated,
# and so that subclasses do not need to handle it explicitly
diff --git a/rest_framework/pagination.py b/rest_framework/pagination.py
index fb451285..f31e5fa4 100644
--- a/rest_framework/pagination.py
+++ b/rest_framework/pagination.py
@@ -68,10 +68,16 @@ class BasePaginationSerializer(serializers.Serializer):
except AttributeError:
object_serializer = DefaultObjectSerializer
- self.fields[results_field] = serializers.ListSerializer(
+ try:
+ list_serializer_class = object_serializer.Meta.list_serializer_class
+ except AttributeError:
+ list_serializer_class = serializers.ListSerializer
+
+ self.fields[results_field] = list_serializer_class(
child=object_serializer(),
source='object_list'
)
+ self.fields[results_field].bind(field_name=results_field, parent=self)
class PaginationSerializer(BasePaginationSerializer):
diff --git a/rest_framework/routers.py b/rest_framework/routers.py
index 6e99f14d..61f3ccab 100644
--- a/rest_framework/routers.py
+++ b/rest_framework/routers.py
@@ -21,7 +21,7 @@ from django.conf.urls import patterns, url
from django.core.exceptions import ImproperlyConfigured
from django.core.urlresolvers import NoReverseMatch
from rest_framework import views
-from rest_framework.compat import OrderedDict
+from rest_framework.compat import get_resolver_match, OrderedDict
from rest_framework.response import Response
from rest_framework.reverse import reverse
from rest_framework.urlpatterns import format_suffix_patterns
@@ -176,23 +176,27 @@ class SimpleRouter(BaseRouter):
if isinstance(route, DynamicDetailRoute):
# Dynamic detail routes (@detail_route decorator)
for httpmethods, methodname in detail_routes:
+ method_kwargs = getattr(viewset, methodname).kwargs
+ url_path = method_kwargs.pop("url_path", None) or methodname
initkwargs = route.initkwargs.copy()
- initkwargs.update(getattr(viewset, methodname).kwargs)
+ initkwargs.update(method_kwargs)
ret.append(Route(
- url=replace_methodname(route.url, methodname),
+ url=replace_methodname(route.url, url_path),
mapping=dict((httpmethod, methodname) for httpmethod in httpmethods),
- name=replace_methodname(route.name, methodname),
+ name=replace_methodname(route.name, url_path),
initkwargs=initkwargs,
))
elif isinstance(route, DynamicListRoute):
# Dynamic list routes (@list_route decorator)
for httpmethods, methodname in list_routes:
+ method_kwargs = getattr(viewset, methodname).kwargs
+ url_path = method_kwargs.pop("url_path", None) or methodname
initkwargs = route.initkwargs.copy()
- initkwargs.update(getattr(viewset, methodname).kwargs)
+ initkwargs.update(method_kwargs)
ret.append(Route(
- url=replace_methodname(route.url, methodname),
+ url=replace_methodname(route.url, url_path),
mapping=dict((httpmethod, methodname) for httpmethod in httpmethods),
- name=replace_methodname(route.name, methodname),
+ name=replace_methodname(route.name, url_path),
initkwargs=initkwargs,
))
else:
@@ -288,7 +292,10 @@ class DefaultRouter(SimpleRouter):
def get(self, request, *args, **kwargs):
ret = OrderedDict()
+ namespace = get_resolver_match(request).namespace
for key, url_name in api_root_dict.items():
+ if namespace:
+ url_name = namespace + ':' + url_name
try:
ret[key] = reverse(
url_name,
diff --git a/rest_framework/templates/rest_framework/inline/checkbox_multiple.html b/rest_framework/templates/rest_framework/inline/checkbox_multiple.html
index 6caf6440..09349686 100644
--- a/rest_framework/templates/rest_framework/inline/checkbox_multiple.html
+++ b/rest_framework/templates/rest_framework/inline/checkbox_multiple.html
@@ -5,7 +5,7 @@
{% for key, text in field.choices.items %}
<div class="checkbox">
<label>
- <input type="checkbox" name="{{ rest_framework/field.name }}" value="{{ key }}" {% if key in field.value %}checked{% endif %}>
+ <input type="checkbox" name="{{ field.name }}" value="{{ key }}" {% if key in field.value %}checked{% endif %}>
{{ text }}
</label>
</div>
diff --git a/rest_framework/templates/rest_framework/vertical/list_fieldset.html b/rest_framework/templates/rest_framework/vertical/list_fieldset.html
index 1d86c7f2..82d7b5f4 100644
--- a/rest_framework/templates/rest_framework/vertical/list_fieldset.html
+++ b/rest_framework/templates/rest_framework/vertical/list_fieldset.html
@@ -1,8 +1,4 @@
<fieldset>
{% if field.label %}<legend {% if style.hide_label %}class="sr-only"{% endif %}>{{ field.label }}</legend>{% endif %}
-<!-- {% if field.label %}<legend {% if style.hide_label %}class="sr-only"{% endif %}>{{ field.label }}</legend>{% endif %}
- {% for field_item in field.value.field_items.values() %}
- {{ renderer.render_field(field_item, layout=layout) }}
- {% endfor %} -->
<p>Lists are not currently supported in HTML input.</p>
</fieldset>
diff --git a/rest_framework/utils/field_mapping.py b/rest_framework/utils/field_mapping.py
index fca97b4b..cba40d31 100644
--- a/rest_framework/utils/field_mapping.py
+++ b/rest_framework/utils/field_mapping.py
@@ -10,6 +10,11 @@ from rest_framework.validators import UniqueValidator
import inspect
+NUMERIC_FIELD_TYPES = (
+ models.IntegerField, models.FloatField, models.DecimalField
+)
+
+
class ClassLookupDict(object):
"""
Takes a dictionary with classes as keys.
@@ -80,7 +85,7 @@ def get_field_kwargs(field_name, model_field):
kwargs['decimal_places'] = decimal_places
if isinstance(model_field, models.TextField):
- kwargs['style'] = {'type': 'textarea'}
+ kwargs['style'] = {'base_template': 'textarea.html'}
if isinstance(model_field, models.AutoField) or not model_field.editable:
# If this field is read-only, then return early.
@@ -106,7 +111,7 @@ def get_field_kwargs(field_name, model_field):
# Ensure that max_length is passed explicitly as a keyword arg,
# rather than as a validator.
max_length = getattr(model_field, 'max_length', None)
- if max_length is not None:
+ if max_length is not None and isinstance(model_field, models.CharField):
kwargs['max_length'] = max_length
validator_kwarg = [
validator for validator in validator_kwarg
@@ -119,7 +124,7 @@ def get_field_kwargs(field_name, model_field):
validator.limit_value for validator in validator_kwarg
if isinstance(validator, validators.MinLengthValidator)
), None)
- if min_length is not None:
+ if min_length is not None and isinstance(model_field, models.CharField):
kwargs['min_length'] = min_length
validator_kwarg = [
validator for validator in validator_kwarg
@@ -132,7 +137,7 @@ def get_field_kwargs(field_name, model_field):
validator.limit_value for validator in validator_kwarg
if isinstance(validator, validators.MaxValueValidator)
), None)
- if max_value is not None:
+ if max_value is not None and isinstance(model_field, NUMERIC_FIELD_TYPES):
kwargs['max_value'] = max_value
validator_kwarg = [
validator for validator in validator_kwarg
@@ -145,7 +150,7 @@ def get_field_kwargs(field_name, model_field):
validator.limit_value for validator in validator_kwarg
if isinstance(validator, validators.MinValueValidator)
), None)
- if min_value is not None:
+ if min_value is not None and isinstance(model_field, NUMERIC_FIELD_TYPES):
kwargs['min_value'] = min_value
validator_kwarg = [
validator for validator in validator_kwarg
diff --git a/rest_framework/utils/model_meta.py b/rest_framework/utils/model_meta.py
index dfc387ca..b9fb6f67 100644
--- a/rest_framework/utils/model_meta.py
+++ b/rest_framework/utils/model_meta.py
@@ -35,7 +35,7 @@ def _resolve_model(obj):
Resolve supplied `obj` to a Django model class.
`obj` must be a Django model class itself, or a string
- representation of one. Useful in situtations like GH #1225 where
+ representation of one. Useful in situations like GH #1225 where
Django may not have resolved a string-based reference to a model in
another model's foreign key definition.
@@ -56,23 +56,44 @@ def _resolve_model(obj):
def get_field_info(model):
"""
- Given a model class, returns a `FieldInfo` instance containing metadata
- about the various field types on the model.
+ Given a model class, returns a `FieldInfo` instance, which is a
+ `namedtuple`, containing metadata about the various field types on the model
+ including information about their relationships.
"""
opts = model._meta.concrete_model._meta
- # Deal with the primary key.
+ pk = _get_pk(opts)
+ fields = _get_fields(opts)
+ forward_relations = _get_forward_relationships(opts)
+ reverse_relations = _get_reverse_relationships(opts)
+ fields_and_pk = _merge_fields_and_pk(pk, fields)
+ relationships = _merge_relationships(forward_relations, reverse_relations)
+
+ return FieldInfo(pk, fields, forward_relations, reverse_relations,
+ fields_and_pk, relationships)
+
+
+def _get_pk(opts):
pk = opts.pk
while pk.rel and pk.rel.parent_link:
- # If model is a child via multitable inheritance, use parent's pk.
+ # If model is a child via multi-table inheritance, use parent's pk.
pk = pk.rel.to._meta.pk
- # Deal with regular fields.
+ return pk
+
+
+def _get_fields(opts):
fields = OrderedDict()
for field in [field for field in opts.fields if field.serialize and not field.rel]:
fields[field.name] = field
- # Deal with forward relationships.
+ return fields
+
+
+def _get_forward_relationships(opts):
+ """
+ Returns an `OrderedDict` of field names to `RelationInfo`.
+ """
forward_relations = OrderedDict()
for field in [field for field in opts.fields if field.serialize and field.rel]:
forward_relations[field.name] = RelationInfo(
@@ -93,7 +114,13 @@ def get_field_info(model):
)
)
- # Deal with reverse relationships.
+ return forward_relations
+
+
+def _get_reverse_relationships(opts):
+ """
+ Returns an `OrderedDict` of field names to `RelationInfo`.
+ """
reverse_relations = OrderedDict()
for relation in opts.get_all_related_objects():
accessor_name = relation.get_accessor_name()
@@ -117,18 +144,20 @@ def get_field_info(model):
)
)
- # Shortcut that merges both regular fields and the pk,
- # for simplifying regular field lookup.
+ return reverse_relations
+
+
+def _merge_fields_and_pk(pk, fields):
fields_and_pk = OrderedDict()
fields_and_pk['pk'] = pk
fields_and_pk[pk.name] = pk
fields_and_pk.update(fields)
- # Shortcut that merges both forward and reverse relationships
+ return fields_and_pk
- relations = OrderedDict(
+
+def _merge_relationships(forward_relations, reverse_relations):
+ return OrderedDict(
list(forward_relations.items()) +
list(reverse_relations.items())
)
-
- return FieldInfo(pk, fields, forward_relations, reverse_relations, fields_and_pk, relations)