aboutsummaryrefslogtreecommitdiffstats
path: root/rest_framework
diff options
context:
space:
mode:
authorXavier Ordoquy2014-03-03 11:41:07 +0100
committerXavier Ordoquy2014-03-03 11:41:07 +0100
commit3d7cb72e0a770595d8934b731f9c462b839f941a (patch)
tree809035a13a8de89d020ffefea38f779bfa4a7ed3 /rest_framework
parentb2f0f4fcf49d457aefc21960f62fcb8f2cf6770d (diff)
parentee9864e0dce10018261c131a76eb7c668703d76c (diff)
downloaddjango-rest-framework-3d7cb72e0a770595d8934b731f9c462b839f941a.tar.bz2
Merge remote-tracking branch 'reference/master' into feature/django_1_7
Diffstat (limited to 'rest_framework')
-rw-r--r--rest_framework/mixins.py37
-rw-r--r--rest_framework/relations.py7
-rw-r--r--rest_framework/renderers.py11
-rw-r--r--rest_framework/templates/rest_framework/base.html2
-rw-r--r--rest_framework/tests/serializers.py8
-rw-r--r--rest_framework/tests/test_nullable_fields.py30
-rw-r--r--rest_framework/tests/test_renderers.py12
-rw-r--r--rest_framework/tests/views.py8
-rw-r--r--rest_framework/throttling.py2
-rw-r--r--rest_framework/views.py2
10 files changed, 93 insertions, 26 deletions
diff --git a/rest_framework/mixins.py b/rest_framework/mixins.py
index 5fbcf700..e1a24dc7 100644
--- a/rest_framework/mixins.py
+++ b/rest_framework/mixins.py
@@ -116,30 +116,27 @@ class UpdateModelMixin(object):
partial = kwargs.pop('partial', False)
self.object = self.get_object_or_none()
- if self.object is None:
- created = True
- save_kwargs = {'force_insert': True}
- success_status_code = status.HTTP_201_CREATED
- else:
- created = False
- save_kwargs = {'force_update': True}
- success_status_code = status.HTTP_200_OK
-
serializer = self.get_serializer(self.object, data=request.DATA,
files=request.FILES, partial=partial)
- if serializer.is_valid():
- try:
- self.pre_save(serializer.object)
- except ValidationError as err:
- # full_clean on model instance may be called in pre_save, so we
- # have to handle eventual errors.
- return Response(err.message_dict, status=status.HTTP_400_BAD_REQUEST)
- self.object = serializer.save(**save_kwargs)
- self.post_save(self.object, created=created)
- return Response(serializer.data, status=success_status_code)
+ if not serializer.is_valid():
+ return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
- return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
+ try:
+ self.pre_save(serializer.object)
+ except ValidationError as err:
+ # full_clean on model instance may be called in pre_save,
+ # so we have to handle eventual errors.
+ return Response(err.message_dict, status=status.HTTP_400_BAD_REQUEST)
+
+ if self.object is None:
+ self.object = serializer.save(force_insert=True)
+ self.post_save(self.object, created=True)
+ return Response(serializer.data, status=status.HTTP_201_CREATED)
+
+ self.object = serializer.save(force_update=True)
+ self.post_save(self.object, created=False)
+ return Response(serializer.data, status=status.HTTP_200_OK)
def partial_update(self, request, *args, **kwargs):
kwargs['partial'] = True
diff --git a/rest_framework/relations.py b/rest_framework/relations.py
index 02185c2f..163a8984 100644
--- a/rest_framework/relations.py
+++ b/rest_framework/relations.py
@@ -33,6 +33,7 @@ class RelatedField(WritableField):
many_widget = widgets.SelectMultiple
form_field_class = forms.ChoiceField
many_form_field_class = forms.MultipleChoiceField
+ null_values = (None, '', 'None')
cache_choices = False
empty_label = None
@@ -168,9 +169,9 @@ class RelatedField(WritableField):
return
value = [] if self.many else None
- if value in (None, '') and self.required:
- raise ValidationError(self.error_messages['required'])
- elif value in (None, ''):
+ if value in self.null_values:
+ if self.required:
+ raise ValidationError(self.error_messages['required'])
into[(self.source or field_name)] = None
elif self.many:
into[(self.source or field_name)] = [self.from_native(item) for item in value]
diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py
index e8afc26d..7cf1c051 100644
--- a/rest_framework/renderers.py
+++ b/rest_framework/renderers.py
@@ -427,7 +427,7 @@ class BrowsableAPIRenderer(BaseRenderer):
files = request.FILES
except ParseError:
data = None
- files = None
+ files = None
else:
data = None
files = None
@@ -544,6 +544,14 @@ class BrowsableAPIRenderer(BaseRenderer):
raw_data_patch_form = self.get_raw_data_form(view, 'PATCH', request)
raw_data_put_or_patch_form = raw_data_put_form or raw_data_patch_form
+ response_headers = dict(response.items())
+ renderer_content_type = ''
+ if renderer:
+ renderer_content_type = '%s' % renderer.media_type
+ if renderer.charset:
+ renderer_content_type += ' ;%s' % renderer.charset
+ response_headers['Content-Type'] = renderer_content_type
+
context = {
'content': self.get_content(renderer, data, accepted_media_type, renderer_context),
'view': view,
@@ -555,6 +563,7 @@ class BrowsableAPIRenderer(BaseRenderer):
'breadcrumblist': self.get_breadcrumbs(request),
'allowed_methods': view.allowed_methods,
'available_formats': [renderer.format for renderer in view.renderer_classes],
+ 'response_headers': response_headers,
'put_form': self.get_rendered_html_form(view, 'PUT', request),
'post_form': self.get_rendered_html_form(view, 'POST', request),
diff --git a/rest_framework/templates/rest_framework/base.html b/rest_framework/templates/rest_framework/base.html
index d19d5a2b..7067ee2f 100644
--- a/rest_framework/templates/rest_framework/base.html
+++ b/rest_framework/templates/rest_framework/base.html
@@ -118,7 +118,7 @@
</div>
<div class="response-info">
<pre class="prettyprint"><div class="meta nocode"><b>HTTP {{ response.status_code }} {{ response.status_text }}</b>{% autoescape off %}
-{% for key, val in response.items %}<b>{{ key }}:</b> <span class="lit">{{ val|break_long_headers|urlize_quoted_links }}</span>
+{% for key, val in response_headers.items %}<b>{{ key }}:</b> <span class="lit">{{ val|break_long_headers|urlize_quoted_links }}</span>
{% endfor %}
</div>{{ content|urlize_quoted_links }}</pre>{% endautoescape %}
</div>
diff --git a/rest_framework/tests/serializers.py b/rest_framework/tests/serializers.py
new file mode 100644
index 00000000..cc943c7d
--- /dev/null
+++ b/rest_framework/tests/serializers.py
@@ -0,0 +1,8 @@
+from rest_framework import serializers
+
+from rest_framework.tests.models import NullableForeignKeySource
+
+
+class NullableFKSourceSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = NullableForeignKeySource
diff --git a/rest_framework/tests/test_nullable_fields.py b/rest_framework/tests/test_nullable_fields.py
new file mode 100644
index 00000000..6ee55c00
--- /dev/null
+++ b/rest_framework/tests/test_nullable_fields.py
@@ -0,0 +1,30 @@
+from django.core.urlresolvers import reverse
+
+from rest_framework.compat import patterns, url
+from rest_framework.test import APITestCase
+from rest_framework.tests.models import NullableForeignKeySource
+from rest_framework.tests.serializers import NullableFKSourceSerializer
+from rest_framework.tests.views import NullableFKSourceDetail
+
+
+urlpatterns = patterns(
+ '',
+ url(r'^objects/(?P<pk>\d+)/$', NullableFKSourceDetail.as_view(), name='object-detail'),
+)
+
+
+class NullableForeignKeyTests(APITestCase):
+ """
+ DRF should be able to handle nullable foreign keys when a test
+ Client POST/PUT request is made with its own serialized object.
+ """
+ urls = 'rest_framework.tests.test_nullable_fields'
+
+ def test_updating_object_with_null_fk(self):
+ obj = NullableForeignKeySource(name='example', target=None)
+ obj.save()
+ serialized_data = NullableFKSourceSerializer(obj).data
+
+ response = self.client.put(reverse('object-detail', args=[obj.pk]), serialized_data)
+
+ self.assertEqual(response.data, serialized_data)
diff --git a/rest_framework/tests/test_renderers.py b/rest_framework/tests/test_renderers.py
index cddd00e7..c7bf772e 100644
--- a/rest_framework/tests/test_renderers.py
+++ b/rest_framework/tests/test_renderers.py
@@ -256,6 +256,18 @@ class RendererEndToEndTests(TestCase):
self.assertEqual(resp.get('Content-Type', None), None)
self.assertEqual(resp.status_code, status.HTTP_204_NO_CONTENT)
+ def test_contains_headers_of_api_response(self):
+ """
+ Issue #1437
+
+ Test we display the headers of the API response and not those from the
+ HTML response
+ """
+ resp = self.client.get('/html1')
+ self.assertContains(resp, '>GET, HEAD, OPTIONS<')
+ self.assertContains(resp, '>application/json<')
+ self.assertNotContains(resp, '>text/html; charset=utf-8<')
+
_flat_repr = '{"foo": ["bar", "baz"]}'
_indented_repr = '{\n "foo": [\n "bar",\n "baz"\n ]\n}'
diff --git a/rest_framework/tests/views.py b/rest_framework/tests/views.py
new file mode 100644
index 00000000..3917b74a
--- /dev/null
+++ b/rest_framework/tests/views.py
@@ -0,0 +1,8 @@
+from rest_framework import generics
+from rest_framework.tests.models import NullableForeignKeySource
+from rest_framework.tests.serializers import NullableFKSourceSerializer
+
+
+class NullableFKSourceDetail(generics.RetrieveUpdateDestroyAPIView):
+ model = NullableForeignKeySource
+ model_serializer_class = NullableFKSourceSerializer
diff --git a/rest_framework/throttling.py b/rest_framework/throttling.py
index a946d837..efa9fb94 100644
--- a/rest_framework/throttling.py
+++ b/rest_framework/throttling.py
@@ -136,6 +136,8 @@ class SimpleRateThrottle(BaseThrottle):
remaining_duration = self.duration
available_requests = self.num_requests - len(self.history) + 1
+ if available_requests <= 0:
+ return None
return remaining_duration / float(available_requests)
diff --git a/rest_framework/views.py b/rest_framework/views.py
index 02a6e25a..2cf9b220 100644
--- a/rest_framework/views.py
+++ b/rest_framework/views.py
@@ -131,7 +131,7 @@ class APIView(View):
"""
If request is not permitted, determine what kind of exception to raise.
"""
- if not self.request.successful_authenticator:
+ if not request.successful_authenticator:
raise exceptions.NotAuthenticated()
raise exceptions.PermissionDenied()