diff options
| author | Tom Christie | 2011-06-15 14:09:01 +0100 |
|---|---|---|
| committer | Tom Christie | 2011-06-15 14:09:01 +0100 |
| commit | 7dcb851c7f1e4bc01105fc5856494c196827fe2e (patch) | |
| tree | 7c0e02a8e504b66e7a33af69f8cd60ed1ad25df3 /djangorestframework | |
| parent | 412b5fc2d54def2f2601b860b80afaa52d595e58 (diff) | |
| download | django-rest-framework-7dcb851c7f1e4bc01105fc5856494c196827fe2e.tar.bz2 | |
Allow related models to be fully serialized
Diffstat (limited to 'djangorestframework')
| -rw-r--r-- | djangorestframework/mixins.py | 12 | ||||
| -rw-r--r-- | djangorestframework/renderers.py | 2 | ||||
| -rw-r--r-- | djangorestframework/resources.py | 126 | ||||
| -rw-r--r-- | djangorestframework/serializer.py | 122 | ||||
| -rw-r--r-- | djangorestframework/tests/serializer.py | 30 | ||||
| -rw-r--r-- | djangorestframework/tests/validators.py | 12 |
6 files changed, 130 insertions, 174 deletions
diff --git a/djangorestframework/mixins.py b/djangorestframework/mixins.py index 75fd3646..910d06ae 100644 --- a/djangorestframework/mixins.py +++ b/djangorestframework/mixins.py @@ -466,7 +466,7 @@ class InstanceMixin(object): # We do a little dance when we store the view callable... # we need to store it wrapped in a 1-tuple, so that inspect will treat it # as a function when we later look it up (rather than turning it into a method). - # This makes sure our URL reversing works ok. + # This makes sure our URL reversing works ok. resource.view_callable = (view,) return view @@ -479,6 +479,7 @@ class ReadModelMixin(object): """ def get(self, request, *args, **kwargs): model = self.resource.model + try: if args: # If we have any none kwargs then assume the last represents the primrary key @@ -498,6 +499,7 @@ class CreateModelMixin(object): """ def post(self, request, *args, **kwargs): model = self.resource.model + # translated 'related_field' kwargs into 'related_field_id' for related_name in [field.name for field in model._meta.fields if isinstance(field, RelatedField)]: if kwargs.has_key(related_name): @@ -522,6 +524,7 @@ class UpdateModelMixin(object): """ def put(self, request, *args, **kwargs): model = self.resource.model + # TODO: update on the url of a non-existing resource url doesn't work correctly at the moment - will end up with a new url try: if args: @@ -547,6 +550,7 @@ class DeleteModelMixin(object): """ def delete(self, request, *args, **kwargs): model = self.resource.model + try: if args: # If we have any none kwargs then assume the last represents the primrary key @@ -581,10 +585,12 @@ class ListModelMixin(object): queryset = None def get(self, request, *args, **kwargs): - queryset = self.queryset if self.queryset else self.resource.model.objects.all() + model = self.resource.model + + queryset = self.queryset if self.queryset else model.objects.all() if hasattr(self, 'resource'): - ordering = getattr(self.resource.Meta, 'ordering', None) + ordering = getattr(self.resource, 'ordering', None) else: ordering = None diff --git a/djangorestframework/renderers.py b/djangorestframework/renderers.py index 9834ba5e..7aa8777c 100644 --- a/djangorestframework/renderers.py +++ b/djangorestframework/renderers.py @@ -181,7 +181,7 @@ class DocumentingTemplateRenderer(BaseRenderer): # Get the form instance if we have one bound to the input form_instance = None - if method == view.method.lower(): + if method == getattr(view, 'method', view.request.method).lower(): form_instance = getattr(view, 'bound_form_instance', None) if not form_instance and hasattr(view, 'get_bound_form'): diff --git a/djangorestframework/resources.py b/djangorestframework/resources.py index 7a4f31d5..08f9e0ae 100644 --- a/djangorestframework/resources.py +++ b/djangorestframework/resources.py @@ -20,12 +20,11 @@ class BaseResource(Serializer): """ Base class for all Resource classes, which simply defines the interface they provide. """ - class Meta: - fields = None - include = None - exclude = None + fields = None + include = None + exclude = None - def __init__(self, view, depth=None, stack=[], **kwargs): + def __init__(self, view=None, depth=None, stack=[], **kwargs): super(BaseResource, self).__init__(depth, stack, **kwargs) self.view = view @@ -49,20 +48,19 @@ class Resource(BaseResource): Objects that a resource can act on include plain Python object instances, Django Models, and Django QuerySets. """ - class Meta: - # The model attribute refers to the Django Model which this Resource maps to. - # (The Model's class, rather than an instance of the Model) - model = None - - # By default the set of returned fields will be the set of: - # - # 0. All the fields on the model, excluding 'id'. - # 1. All the properties on the model. - # 2. The absolute_url of the model, if a get_absolute_url method exists for the model. - # - # If you wish to override this behaviour, - # you should explicitly set the fields attribute on your class. - fields = None + # The model attribute refers to the Django Model which this Resource maps to. + # (The Model's class, rather than an instance of the Model) + model = None + + # By default the set of returned fields will be the set of: + # + # 0. All the fields on the model, excluding 'id'. + # 1. All the properties on the model. + # 2. The absolute_url of the model, if a get_absolute_url method exists for the model. + # + # If you wish to override this behaviour, + # you should explicitly set the fields attribute on your class. + fields = None class FormResource(Resource): @@ -74,12 +72,11 @@ class FormResource(Resource): view, which may be used by some renderers. """ - class Meta: - """ - The :class:`Form` class that should be used for request validation. - This can be overridden by a :attr:`form` attribute on the :class:`views.View`. - """ - form = None + """ + The :class:`Form` class that should be used for request validation. + This can be overridden by a :attr:`form` attribute on the :class:`views.View`. + """ + form = None def validate_request(self, data, files=None): @@ -189,7 +186,7 @@ class FormResource(Resource): """ # A form on the view overrides a form on the resource. - form = getattr(self.view, 'form', None) or self.Meta.form + form = getattr(self.view, 'form', None) or self.form # Use the requested method or determine the request method if method is None and hasattr(self.view, 'request') and hasattr(self.view, 'method'): @@ -235,44 +232,43 @@ class ModelResource(FormResource): # Auto-register new ModelResource classes into _model_to_resource #__metaclass__ = _RegisterModelResource - class Meta: - """ - The form class that should be used for request validation. - If set to :const:`None` then the default model form validation will be used. + """ + The form class that should be used for request validation. + If set to :const:`None` then the default model form validation will be used. + + This can be overridden by a :attr:`form` attribute on the :class:`views.View`. + """ + form = None + + """ + The model class which this resource maps to. + + This can be overridden by a :attr:`model` attribute on the :class:`views.View`. + """ + model = None + + """ + The list of fields to use on the output. - This can be overridden by a :attr:`form` attribute on the :class:`views.View`. - """ - form = None + May be any of: - """ - The model class which this resource maps to. + The name of a model field. + The name of an attribute on the model. + The name of an attribute on the resource. + The name of a method on the model, with a signature like ``func(self)``. + The name of a method on the resource, with a signature like ``func(self, instance)``. + """ + fields = None - This can be overridden by a :attr:`model` attribute on the :class:`views.View`. - """ - model = None + """ + The list of fields to exclude. This is only used if :attr:`fields` is not set. + """ + exclude = ('id', 'pk') - """ - The list of fields to use on the output. - - May be any of: - - The name of a model field. - The name of an attribute on the model. - The name of an attribute on the resource. - The name of a method on the model, with a signature like ``func(self)``. - The name of a method on the resource, with a signature like ``func(self, instance)``. - """ - fields = None - - """ - The list of fields to exclude. This is only used if :attr:`fields` is not set. - """ - exclude = ('id', 'pk') - - """ - The list of extra fields to include. This is only used if :attr:`fields` is not set. - """ - include = ('url',) + """ + The list of extra fields to include. This is only used if :attr:`fields` is not set. + """ + include = ('url',) def __init__(self, view): @@ -283,7 +279,7 @@ class ModelResource(FormResource): """ super(ModelResource, self).__init__(view) - self.model = getattr(view, 'model', None) or self.Meta.model + self.model = getattr(view, 'model', None) or self.model def validate_request(self, data, files=None): @@ -369,7 +365,7 @@ class ModelResource(FormResource): if isinstance(attr, models.Model): instance_attrs[param] = attr.pk else: - instance_attrs[param] = attr + instance_attrs[param] = attr try: return reverse(self.view_callable[0], kwargs=instance_attrs) @@ -399,7 +395,7 @@ class ModelResource(FormResource): isinstance(getattr(self.model, attr, None), property) and not attr.startswith('_')) - if self.Meta.fields: - return property_fields & set(as_tuple(self.Meta.fields)) + if self.fields: + return property_fields & set(as_tuple(self.fields)) - return property_fields.union(set(as_tuple(self.Meta.include))) - set(as_tuple(self.Meta.exclude)) + return property_fields.union(set(as_tuple(self.include))) - set(as_tuple(self.exclude)) diff --git a/djangorestframework/serializer.py b/djangorestframework/serializer.py index 47c631bc..8d73d623 100644 --- a/djangorestframework/serializer.py +++ b/djangorestframework/serializer.py @@ -46,30 +46,14 @@ class _SkipField(Exception): pass -class _BuildInnerMeta(type): +class _RegisterSerializer(type): """ - Some magic so that an inner Meta class gets inheriting behavior. + Metaclass to register serializers. """ def __new__(cls, name, bases, attrs): - # Get a list of all the inner Metas, from the bases upwards. - inner_metas = [getattr(base, 'Meta', object) for base in bases] - inner_metas.append(attrs.get('Meta', object)) - - # Build up the attributes on the inner Meta. - meta_attrs = {} - [meta_attrs.update(inner_meta.__dict__) for inner_meta in inner_metas] - - # Drop private attributes. - meta_attrs = dict([ (key, val) for (key, val) in meta_attrs.items() - if not key.startswith('_') ]) - - # Lovely, now we can create our inner Meta class. - attrs['Meta'] = type('Meta', (object,), meta_attrs) - # Build the class and register it. - ret = super(_BuildInnerMeta, cls).__new__(cls, name, bases, attrs) + ret = super(_RegisterSerializer, cls).__new__(cls, name, bases, attrs) _serializers[name] = ret - return ret @@ -78,8 +62,8 @@ class Serializer(object): Converts python objects into plain old native types suitable for serialization. In particular it handles models and querysets. - The output format is specified by setting a number of attributes on the - inner `Meta` class. + The output format is specified by setting a number of attributes + on the class. You may also override any of the serialization methods, to provide for more flexible behavior. @@ -87,75 +71,61 @@ class Serializer(object): Valid output types include anything that may be directly rendered into json, xml etc... """ - __metaclass__ = _BuildInnerMeta + __metaclass__ = _RegisterSerializer - class Meta: - """ - Information on how to serialize the data. - """ - - fields = () - """ - Specify the fields to be serialized on a model or dict. - Overrides `include` and `exclude`. - """ + fields = () + """ + Specify the fields to be serialized on a model or dict. + Overrides `include` and `exclude`. + """ - include = () - """ - Fields to add to the default set to be serialized on a model/dict. - """ + include = () + """ + Fields to add to the default set to be serialized on a model/dict. + """ - exclude = () - """ - Fields to remove from the default set to be serialized on a model/dict. - """ + exclude = () + """ + Fields to remove from the default set to be serialized on a model/dict. + """ - rename = {} - """ - A dict of key->name to use for the field keys. - """ + rename = {} + """ + A dict of key->name to use for the field keys. + """ - related_serializer = None - """ - The default serializer class to use for any related models. - """ + related_serializer = None + """ + The default serializer class to use for any related models. + """ - depth = None - """ - The maximum depth to serialize to, or `None`. - """ + depth = None + """ + The maximum depth to serialize to, or `None`. + """ def __init__(self, depth=None, stack=[], **kwargs): - """ - Allow `Meta` items to be set on init. - """ - self.depth = depth + self.depth = depth or self.depth self.stack = stack - - if self.depth is None: - self.depth = self.Meta.depth - - for (key, val) in kwargs.items(): - setattr(self.Meta, key, val) def get_fields(self, obj): """ Return the set of field names/keys to use for a model instance/dict. """ - fields = self.Meta.fields + fields = self.fields - # If Meta.fields is not set, we use the default fields and modify - # them with Meta.include and Meta.exclude + # If `fields` is not set, we use the default fields and modify + # them with `include` and `exclude` if not fields: default = self.get_default_fields(obj) - include = self.Meta.include or () - exclude = self.Meta.exclude or () + include = self.include or () + exclude = self.exclude or () fields = set(default + list(include)) - set(exclude) else: - fields = _fields_to_list(self.Meta.fields) + fields = _fields_to_list(self.fields) return fields @@ -163,7 +133,7 @@ class Serializer(object): def get_default_fields(self, obj): """ Return the default list of field names/keys for a model instance/dict. - These are used if `Meta.fields` is not given. + These are used if `fields` is not given. """ if isinstance(obj, models.Model): opts = obj._meta @@ -173,15 +143,14 @@ class Serializer(object): def get_related_serializer(self, key): - info = _fields_to_dict(self.Meta.fields).get(key, None) + info = _fields_to_dict(self.fields).get(key, None) # If an element in `fields` is a 2-tuple of (str, tuple) # then the second element of the tuple is the fields to # set on the related serializer if isinstance(info, (list, tuple)): class OnTheFlySerializer(Serializer): - class Meta: - fields = info + fields = info return OnTheFlySerializer # If an element in `fields` is a 2-tuple of (str, Serializer) @@ -200,15 +169,15 @@ class Serializer(object): return _serializers[info] # Otherwise use `related_serializer` or fall back to `Serializer` - return getattr(self.Meta, 'related_serializer') or Serializer + return getattr(self, 'related_serializer') or Serializer def serialize_key(self, key): """ Keys serialize to their string value, - unless they exist in the `Meta.rename` dict. + unless they exist in the `rename` dict. """ - return getattr(self.Meta.rename, key, key) + return getattr(self.rename, key, key) def serialize_val(self, key, obj): @@ -251,8 +220,7 @@ class Serializer(object): def serialize_model(self, instance): """ - Given a model instance or dict, serialize it to a dict, using - the given behavior given on Meta. + Given a model instance or dict, serialize it to a dict.. """ data = {} diff --git a/djangorestframework/tests/serializer.py b/djangorestframework/tests/serializer.py index cf00b104..783e941e 100644 --- a/djangorestframework/tests/serializer.py +++ b/djangorestframework/tests/serializer.py @@ -69,12 +69,10 @@ class TestFieldNesting(TestCase): Test tuple nesting on `fields` attr """ class SerializerM2(Serializer): - class Meta: - fields = (('field', ('field1',)),) + fields = (('field', ('field1',)),) class SerializerM3(Serializer): - class Meta: - fields = (('field', ('field2',)),) + fields = (('field', ('field2',)),) self.assertEqual(SerializerM2().serialize(self.m2), {'field': {'field1': u'foo'}}) self.assertEqual(SerializerM3().serialize(self.m3), {'field': {'field2': u'bar'}}) @@ -85,20 +83,16 @@ class TestFieldNesting(TestCase): Test related model serialization """ class NestedM2(Serializer): - class Meta: - fields = ('field1', ) + fields = ('field1', ) class NestedM3(Serializer): - class Meta: - fields = ('field2', ) + fields = ('field2', ) class SerializerM2(Serializer): - class Meta: - fields = [('field', NestedM2)] + fields = [('field', NestedM2)] class SerializerM3(Serializer): - class Meta: - fields = [('field', NestedM3)] + fields = [('field', NestedM3)] self.assertEqual(SerializerM2().serialize(self.m2), {'field': {'field1': u'foo'}}) self.assertEqual(SerializerM3().serialize(self.m3), {'field': {'field2': u'bar'}}) @@ -108,20 +102,16 @@ class TestFieldNesting(TestCase): Test related model serialization """ class SerializerM2(Serializer): - class Meta: - fields = [('field', 'NestedM2')] + fields = [('field', 'NestedM2')] class SerializerM3(Serializer): - class Meta: - fields = [('field', 'NestedM3')] + fields = [('field', 'NestedM3')] class NestedM2(Serializer): - class Meta: - fields = ('field1', ) + fields = ('field1', ) class NestedM3(Serializer): - class Meta: - fields = ('field2', ) + fields = ('field2', ) self.assertEqual(SerializerM2().serialize(self.m2), {'field': {'field1': u'foo'}}) self.assertEqual(SerializerM3().serialize(self.m3), {'field': {'field2': u'bar'}}) diff --git a/djangorestframework/tests/validators.py b/djangorestframework/tests/validators.py index 45b75c4f..a1e5d2d7 100644 --- a/djangorestframework/tests/validators.py +++ b/djangorestframework/tests/validators.py @@ -75,8 +75,7 @@ class TestNonFieldErrors(TestCase): return self.cleaned_data #pragma: no cover class MockResource(FormResource): - class Meta: - form = MockForm + form = MockForm class MockView(View): pass @@ -100,12 +99,10 @@ class TestFormValidation(TestCase): qwerty = forms.CharField(required=True) class MockFormResource(FormResource): - class Meta: - form = MockForm + form = MockForm class MockModelResource(ModelResource): - class Meta: - form = MockForm + form = MockForm class MockFormView(View): resource = MockFormResource @@ -278,8 +275,7 @@ class TestModelFormValidator(TestCase): return 'read only' class MockResource(ModelResource): - class Meta: - model = MockModel + model = MockModel class MockView(View): resource = MockResource |
