diff options
| -rw-r--r-- | docs/api-guide/fields.md | 20 | ||||
| -rw-r--r-- | rest_framework/fields.py | 4 | ||||
| -rw-r--r-- | rest_framework/generics.py | 2 | ||||
| -rw-r--r-- | rest_framework/mixins.py | 27 | ||||
| -rw-r--r-- | rest_framework/tests/decorators.py | 14 | ||||
| -rw-r--r-- | rest_framework/tests/generics.py | 15 |
6 files changed, 79 insertions, 3 deletions
diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md index 009d2a79..5f3aba9a 100644 --- a/docs/api-guide/fields.md +++ b/docs/api-guide/fields.md @@ -6,14 +6,30 @@ > > — [The Zen of Python][cite] +**Note:** The serializer fields are declared in fields.py, but by convention you should import them using `from rest_framework import serializers` and refer to fields as `serializers.<FieldName>`. + +Serializer fields handle converting between primative values and internal datatypes. They also deal with validating input values, as well as retrieving and setting the values from their parent objects. + # Generic Fields ## Field +A generic, read-only field. You can use this field for any attribute that does not need to support write operations. + +## WritableField + +A field that supports both read and + ## ModelField +A generic field that can be tied to any arbitrary model field. The `ModelField` class delegates the task of serialization/deserialization to it's associated model field. This field can be used to create serializer fields for custom model fields, without having to create a new custom serializer field. + +**Signature:** `ModelField(model_field=<Django ModelField class>)` + # Typed Fields +These fields represent basic datatypes, and support both reading and writing values. + ## BooleanField ## CharField @@ -30,7 +46,7 @@ # Relational Fields -Relational fields are used to represent model relationships. +Relational fields are used to represent model relationships. They can be applied to `ForeignKey`, `ManyToManyField` and `OneToOneField` relationships, as well as to reverse relationships, and custom relationships such as `GenericForeignKey`. ## PrimaryKeyRelatedField @@ -40,4 +56,6 @@ Relational fields are used to represent model relationships. ## ManyHyperlinkedRelatedField +## HyperLinkedIdentityField + [cite]: http://www.python.org/dev/peps/pep-0020/ diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 5ce76817..336eac1e 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -341,6 +341,7 @@ class HyperlinkedRelatedField(RelatedField): def from_native(self, value): # Convert URL -> model instance pk + # TODO: Use values_list try: match = resolve(value) except: @@ -379,7 +380,8 @@ class HyperlinkedIdentityField(Field): A field that represents the model's identity using a hyperlink. """ def __init__(self, *args, **kwargs): - # TODO: Make this mandatory + # TODO: Make this mandatory, and have the HyperlinkedModelSerializer + # set it on-the-fly self.view_name = kwargs.pop('view_name', None) super(HyperlinkedIdentityField, self).__init__(*args, **kwargs) diff --git a/rest_framework/generics.py b/rest_framework/generics.py index 44f677cc..51874f28 100644 --- a/rest_framework/generics.py +++ b/rest_framework/generics.py @@ -79,6 +79,8 @@ class SingleObjectBaseView(SingleObjectMixin, BaseView): """ Base class for generic views onto a model instance. """ + pk_url_kwarg = 'pk' # Not provided in Django 1.3 + slug_url_kwarg = 'slug' # Not provided in Django 1.3 def get_object(self): """ diff --git a/rest_framework/mixins.py b/rest_framework/mixins.py index e3c7cf03..46821f64 100644 --- a/rest_framework/mixins.py +++ b/rest_framework/mixins.py @@ -71,13 +71,38 @@ class UpdateModelMixin(object): Should be mixed in with `SingleObjectBaseView`. """ def update(self, request, *args, **kwargs): - self.object = self.get_object() + try: + self.object = self.get_object() + except Http404: + self.object = None + serializer = self.get_serializer(data=request.DATA, instance=self.object) if serializer.is_valid(): + if self.object is None: + obj = serializer.object + # TODO: Make ModelSerializers return regular instances, + # not DeserializedObject + if hasattr(obj, 'object'): + obj = obj.object + self.update_urlconf_attributes(serializer.object.object) self.object = serializer.save() return Response(serializer.data) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + def update_urlconf_attributes(self, obj): + """ + When update (re)creates an object, we need to set any attributes that + are tied to the URLconf. + """ + pk = self.kwargs.get(self.pk_url_kwarg, None) + if pk: + setattr(obj, 'pk', pk) + + slug = self.kwargs.get(self.slug_url_kwarg, None) + if slug: + slug_field = self.get_slug_field() + setattr(obj, slug_field, slug) + class DestroyModelMixin(object): """ diff --git a/rest_framework/tests/decorators.py b/rest_framework/tests/decorators.py index a3217bd6..41864d71 100644 --- a/rest_framework/tests/decorators.py +++ b/rest_framework/tests/decorators.py @@ -49,6 +49,20 @@ class DecoratorTestCase(TestCase): response = view(request) self.assertEqual(response.status_code, 405) + def test_calling_put_method(self): + + @api_view(['GET', 'PUT']) + def view(request): + return Response({}) + + request = self.factory.put('/') + response = view(request) + self.assertEqual(response.status_code, 200) + + request = self.factory.post('/') + response = view(request) + self.assertEqual(response.status_code, 405) + def test_renderer_classes(self): @api_view(['GET']) diff --git a/rest_framework/tests/generics.py b/rest_framework/tests/generics.py index c0645d6e..2a6a0744 100644 --- a/rest_framework/tests/generics.py +++ b/rest_framework/tests/generics.py @@ -208,3 +208,18 @@ class TestInstanceView(TestCase): self.assertEquals(response.data, {'id': 1, 'text': 'foobar'}) updated = self.objects.get(id=1) self.assertEquals(updated.text, 'foobar') + + def test_put_to_deleted_instance(self): + """ + PUT requests to RetrieveUpdateDestroyAPIView should create an object + if it does not currently exist. + """ + self.objects.get(id=1).delete() + content = {'text': 'foobar'} + request = factory.put('/1', json.dumps(content), + content_type='application/json') + response = self.view(request, pk=1).render() + self.assertEquals(response.status_code, status.HTTP_200_OK) + self.assertEquals(response.data, {'id': 1, 'text': 'foobar'}) + updated = self.objects.get(id=1) + self.assertEquals(updated.text, 'foobar') |
