diff options
Diffstat (limited to 'rest_framework')
| -rw-r--r-- | rest_framework/__init__.py | 2 | ||||
| -rw-r--r-- | rest_framework/compat.py | 10 | ||||
| -rw-r--r-- | rest_framework/fields.py | 13 | ||||
| -rw-r--r-- | rest_framework/pagination.py | 8 | ||||
| -rw-r--r-- | rest_framework/routers.py | 21 | ||||
| -rw-r--r-- | rest_framework/templates/rest_framework/inline/checkbox_multiple.html | 2 | ||||
| -rw-r--r-- | rest_framework/templates/rest_framework/vertical/list_fieldset.html | 4 | ||||
| -rw-r--r-- | rest_framework/utils/field_mapping.py | 15 | ||||
| -rw-r--r-- | rest_framework/utils/model_meta.py | 57 |
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) |
