aboutsummaryrefslogtreecommitdiffstats
path: root/rest_framework/serializers.py
diff options
context:
space:
mode:
authorTom Christie2014-10-02 16:24:24 +0100
committerTom Christie2014-10-02 16:24:24 +0100
commitdf7b6fcf58417fd95e49655eb140b387899b1ceb (patch)
treea4a7d932bb3ef7c8e326b0248662fd31edcc2658 /rest_framework/serializers.py
parentffc6aa3abcb0f823b43b63db1666913565e6f934 (diff)
downloaddjango-rest-framework-df7b6fcf58417fd95e49655eb140b387899b1ceb.tar.bz2
First pass on incorperating the form rendering into the browsable API
Diffstat (limited to 'rest_framework/serializers.py')
-rw-r--r--rest_framework/serializers.py129
1 files changed, 92 insertions, 37 deletions
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index 5da81247..0f24ed40 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -14,7 +14,6 @@ from django.core.exceptions import ImproperlyConfigured, ValidationError
from django.db import models
from django.utils import six
from django.utils.datastructures import SortedDict
-from collections import namedtuple
from rest_framework.fields import empty, set_value, Field, SkipField
from rest_framework.settings import api_settings
from rest_framework.utils import html, model_meta, representation
@@ -38,8 +37,8 @@ from rest_framework.relations import * # NOQA
from rest_framework.fields import * # NOQA
-FieldResult = namedtuple('FieldResult', ['field', 'value', 'error'])
-
+# BaseSerializer
+# --------------
class BaseSerializer(Field):
"""
@@ -113,11 +112,6 @@ class BaseSerializer(Field):
if not hasattr(self, '_data'):
if self.instance is not None:
self._data = self.to_representation(self.instance)
- elif self._initial_data is not None:
- self._data = dict([
- (field_name, field.get_value(self._initial_data))
- for field_name, field in self.fields.items()
- ])
else:
self._data = self.get_initial()
return self._data
@@ -137,34 +131,48 @@ class BaseSerializer(Field):
return self._validated_data
-class SerializerMetaclass(type):
+# Serializer & ListSerializer classes
+# -----------------------------------
+
+class ReturnDict(SortedDict):
"""
- This metaclass sets a dictionary named `base_fields` on the class.
+ Return object from `serialier.data` for the `Serializer` class.
+ Includes a backlink to the serializer instance for renderers
+ to use if they need richer field information.
+ """
+ def __init__(self, *args, **kwargs):
+ self.serializer = kwargs.pop('serializer')
+ super(ReturnDict, self).__init__(*args, **kwargs)
- Any instances of `Field` included as attributes on either the class
- or on any of its superclasses will be include in the
- `base_fields` dictionary.
+
+class ReturnList(list):
+ """
+ Return object from `serialier.data` for the `SerializerList` class.
+ Includes a backlink to the serializer instance for renderers
+ to use if they need richer field information.
"""
+ def __init__(self, *args, **kwargs):
+ self.serializer = kwargs.pop('serializer')
+ super(ReturnList, self).__init__(*args, **kwargs)
- @classmethod
- def _get_declared_fields(cls, bases, attrs):
- fields = [(field_name, attrs.pop(field_name))
- for field_name, obj in list(attrs.items())
- if isinstance(obj, Field)]
- fields.sort(key=lambda x: x[1]._creation_counter)
- # If this class is subclassing another Serializer, add that Serializer's
- # fields. Note that we loop over the bases in *reverse*. This is necessary
- # in order to maintain the correct order of fields.
- for base in bases[::-1]:
- if hasattr(base, '_declared_fields'):
- fields = list(base._declared_fields.items()) + fields
+class BoundField(object):
+ """
+ A field object that also includes `.value` and `.error` properties.
+ Returned when iterating over a serializer instance,
+ providing an API similar to Django forms and form fields.
+ """
+ def __init__(self, field, value, errors):
+ self._field = field
+ self.value = value
+ self.errors = errors
- return SortedDict(fields)
+ def __getattr__(self, attr_name):
+ return getattr(self._field, attr_name)
- def __new__(cls, name, bases, attrs):
- attrs['_declared_fields'] = cls._get_declared_fields(bases, attrs)
- return super(SerializerMetaclass, cls).__new__(cls, name, bases, attrs)
+ @property
+ def _proxy_class(self):
+ return self._field.__class__
class BindingDict(object):
@@ -196,6 +204,36 @@ class BindingDict(object):
return self.fields.values()
+class SerializerMetaclass(type):
+ """
+ This metaclass sets a dictionary named `base_fields` on the class.
+
+ Any instances of `Field` included as attributes on either the class
+ or on any of its superclasses will be include in the
+ `base_fields` dictionary.
+ """
+
+ @classmethod
+ def _get_declared_fields(cls, bases, attrs):
+ fields = [(field_name, attrs.pop(field_name))
+ for field_name, obj in list(attrs.items())
+ if isinstance(obj, Field)]
+ fields.sort(key=lambda x: x[1]._creation_counter)
+
+ # If this class is subclassing another Serializer, add that Serializer's
+ # fields. Note that we loop over the bases in *reverse*. This is necessary
+ # in order to maintain the correct order of fields.
+ for base in bases[::-1]:
+ if hasattr(base, '_declared_fields'):
+ fields = list(base._declared_fields.items()) + fields
+
+ return SortedDict(fields)
+
+ def __new__(cls, name, bases, attrs):
+ attrs['_declared_fields'] = cls._get_declared_fields(bases, attrs)
+ return super(SerializerMetaclass, cls).__new__(cls, name, bases, attrs)
+
+
@six.add_metaclass(SerializerMetaclass)
class Serializer(BaseSerializer):
def __init__(self, *args, **kwargs):
@@ -212,10 +250,18 @@ class Serializer(BaseSerializer):
return copy.deepcopy(self._declared_fields)
def get_initial(self):
- return dict([
+ if self._initial_data is not None:
+ return ReturnDict([
+ (field_name, field.get_value(self._initial_data))
+ for field_name, field in self.fields.items()
+ ], serializer=self)
+ #return self.to_representation(self._initial_data)
+
+ return ReturnDict([
(field.field_name, field.get_initial())
for field in self.fields.values()
- ])
+ if not field.write_only
+ ], serializer=self)
def get_value(self, dictionary):
# We override the default field access in order to support
@@ -288,7 +334,7 @@ class Serializer(BaseSerializer):
"""
Object instance -> Dict of primitive datatypes.
"""
- ret = SortedDict()
+ ret = ReturnDict(serializer=self)
fields = [field for field in self.fields.values() if not field.write_only]
for field in fields:
@@ -302,11 +348,9 @@ class Serializer(BaseSerializer):
def __iter__(self):
errors = self.errors if hasattr(self, '_errors') else {}
for field in self.fields.values():
- if field.read_only:
- continue
value = self.data.get(field.field_name) if self.data else None
error = errors.get(field.field_name)
- yield FieldResult(field, value, error)
+ yield BoundField(field, value, error)
def __repr__(self):
return representation.serializer_repr(self, indent=1)
@@ -317,7 +361,7 @@ class Serializer(BaseSerializer):
class ListSerializer(BaseSerializer):
child = None
- initial = []
+ many = True
def __init__(self, *args, **kwargs):
self.child = kwargs.pop('child', copy.deepcopy(self.child))
@@ -326,6 +370,11 @@ class ListSerializer(BaseSerializer):
super(ListSerializer, self).__init__(*args, **kwargs)
self.child.bind(field_name='', parent=self)
+ def get_initial(self):
+ if self._initial_data is not None:
+ return self.to_representation(self._initial_data)
+ return ReturnList(serializer=self)
+
def get_value(self, dictionary):
# We override the default field access in order to support
# lists in HTML forms.
@@ -345,7 +394,10 @@ class ListSerializer(BaseSerializer):
"""
List of object instances -> List of dicts of primitive datatypes.
"""
- return [self.child.to_representation(item) for item in data]
+ return ReturnList(
+ [self.child.to_representation(item) for item in data],
+ serializer=self
+ )
def create(self, attrs_list):
return [self.child.create(attrs) for attrs in attrs_list]
@@ -354,6 +406,9 @@ class ListSerializer(BaseSerializer):
return representation.list_repr(self, indent=1)
+# ModelSerializer & HyperlinkedModelSerializer
+# --------------------------------------------
+
class ModelSerializer(Serializer):
_field_mapping = ClassLookupDict({
models.AutoField: IntegerField,