From d6ccdfc53d266e1f6b329069722c106775120181 Mon Sep 17 00:00:00 2001
From: Carlton Gibson
Date: Wed, 22 Oct 2014 16:43:37 +0200
Subject: Added Note on tox & .pyc files. Fixes #1957
---
CONTRIBUTING.md | 9 +++++++++
docs/topics/contributing.md | 9 +++++++++
2 files changed, 18 insertions(+)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index a6dd05a0..6abc6553 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -71,6 +71,15 @@ You can also use the excellent [`tox`][tox] testing tool to run the tests agains
tox
+---
+
+**Note**: If you run `tox` followed by `./runtests.py` you _may_ encounter a "bad marshall data" error as `pytest` attempts to read `.pyc` files from an incompatible Python version. To clear the offending `.pyc` files run:
+
+ $ find . -name '*.pyc' -delete
+
+---
+
+
## Pull requests
It's a good idea to make pull requests early on. A pull request represents the start of a discussion, and doesn't necessarily need to be the final, finished submission.
diff --git a/docs/topics/contributing.md b/docs/topics/contributing.md
index 4fafb1b1..8be753f2 100644
--- a/docs/topics/contributing.md
+++ b/docs/topics/contributing.md
@@ -105,6 +105,15 @@ You can also use the excellent [tox][tox] testing tool to run the tests against
tox
+---
+
+**Note**: If you run `tox` followed by `./runtests.py` you _may_ encounter a "bad marshall data" error as `pytest` attempts to read `.pyc` files from an incompatible Python version. To clear the offending `.pyc` files run:
+
+ $ find . -name '*.pyc' -delete
+
+---
+
+
## Pull requests
It's a good idea to make pull requests early on. A pull request represents the start of a discussion, and doesn't necessarily need to be the final, finished submission.
--
cgit v1.2.3
From ba52c0c62762b9976fffa72dde7ce922176e481d Mon Sep 17 00:00:00 2001
From: Carlton Gibson
Date: Wed, 22 Oct 2014 17:08:44 +0200
Subject: Revert "Added Note on tox & .pyc files. Fixes #1957"
This reverts commit d6ccdfc53d266e1f6b329069722c106775120181.
---
CONTRIBUTING.md | 9 ---------
docs/topics/contributing.md | 9 ---------
2 files changed, 18 deletions(-)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 6abc6553..a6dd05a0 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -71,15 +71,6 @@ You can also use the excellent [`tox`][tox] testing tool to run the tests agains
tox
----
-
-**Note**: If you run `tox` followed by `./runtests.py` you _may_ encounter a "bad marshall data" error as `pytest` attempts to read `.pyc` files from an incompatible Python version. To clear the offending `.pyc` files run:
-
- $ find . -name '*.pyc' -delete
-
----
-
-
## Pull requests
It's a good idea to make pull requests early on. A pull request represents the start of a discussion, and doesn't necessarily need to be the final, finished submission.
diff --git a/docs/topics/contributing.md b/docs/topics/contributing.md
index 8be753f2..4fafb1b1 100644
--- a/docs/topics/contributing.md
+++ b/docs/topics/contributing.md
@@ -105,15 +105,6 @@ You can also use the excellent [tox][tox] testing tool to run the tests against
tox
----
-
-**Note**: If you run `tox` followed by `./runtests.py` you _may_ encounter a "bad marshall data" error as `pytest` attempts to read `.pyc` files from an incompatible Python version. To clear the offending `.pyc` files run:
-
- $ find . -name '*.pyc' -delete
-
----
-
-
## Pull requests
It's a good idea to make pull requests early on. A pull request represents the start of a discussion, and doesn't necessarily need to be the final, finished submission.
--
cgit v1.2.3
From dbb435ca3165eba990d3dc2ce72c8e108e123f2f Mon Sep 17 00:00:00 2001
From: Kevin Chang
Date: Wed, 12 Nov 2014 12:46:09 -0800
Subject: Added missing default style for FileField
---
rest_framework/renderers.py | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py
index b56f99db..37d3c47c 100644
--- a/rest_framework/renderers.py
+++ b/rest_framework/renderers.py
@@ -374,6 +374,10 @@ class HTMLFormRenderer(BaseRenderer):
'base_template': 'input.html',
'input_type': 'time'
},
+ serializers.FileField: {
+ 'base_template': 'input.html',
+ 'input_type': 'file'
+ },
serializers.BooleanField: {
'base_template': 'checkbox.html'
},
--
cgit v1.2.3
From ad060aa360fa2ed33bd83cbb419d7b996a428726 Mon Sep 17 00:00:00 2001
From: Gregor Müllegger
Date: Sat, 15 Nov 2014 15:23:58 +0100
Subject: More helpful error message when default `.create` fails. Closes
#2013.
---
rest_framework/serializers.py | 14 +++++++++++++-
tests/test_model_serializer.py | 28 ++++++++++++++++++++++++++++
2 files changed, 41 insertions(+), 1 deletion(-)
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index e7e93f38..8dafea4d 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -34,6 +34,7 @@ from rest_framework.validators import (
)
import copy
import inspect
+import sys
import warnings
# Note: We do the following so that users of the framework can use this style:
@@ -593,7 +594,18 @@ class ModelSerializer(Serializer):
if relation_info.to_many and (field_name in validated_attrs):
many_to_many[field_name] = validated_attrs.pop(field_name)
- instance = ModelClass.objects.create(**validated_attrs)
+ try:
+ instance = ModelClass.objects.create(**validated_attrs)
+ except TypeError as exc:
+ msg = (
+ 'The mentioned argument might be a field on the serializer '
+ 'that is not part of the model. You need to override the '
+ 'create() method in your ModelSerializer subclass to support '
+ 'this.')
+ six.reraise(
+ type(exc),
+ type(exc)(str(exc) + '. ' + msg),
+ sys.exc_info()[2])
# Save many-to-many relationships after the instance is created.
if many_to_many:
diff --git a/tests/test_model_serializer.py b/tests/test_model_serializer.py
index 3aec0da0..90767dac 100644
--- a/tests/test_model_serializer.py
+++ b/tests/test_model_serializer.py
@@ -10,6 +10,7 @@ from django.core.validators import MaxValueValidator, MinValueValidator, MinLeng
from django.db import models
from django.test import TestCase
from rest_framework import serializers
+import pytest
def dedent(blocktext):
@@ -26,6 +27,10 @@ class CustomField(models.Field):
pass
+class OneFieldModel(models.Model):
+ char_field = models.CharField(max_length=100)
+
+
class RegularFieldsModel(models.Model):
"""
A model class for testing regular flat fields.
@@ -68,6 +73,29 @@ class FieldOptionsModel(models.Model):
choices_field = models.CharField(max_length=100, choices=COLOR_CHOICES)
+class TestModelSerializer(TestCase):
+ def test_create_method(self):
+ class TestSerializer(serializers.ModelSerializer):
+ non_model_field = serializers.CharField()
+
+ class Meta:
+ model = OneFieldModel
+ fields = ('char_field', 'non_model_field')
+
+ serializer = TestSerializer(data={
+ 'char_field': 'foo',
+ 'non_model_field': 'bar',
+ })
+ serializer.is_valid()
+ with pytest.raises(TypeError):
+ serializer.save()
+
+ try:
+ serializer.save()
+ except TypeError as exc:
+ assert 'ModelSerializer' in str(exc)
+
+
class TestRegularFieldMappings(TestCase):
def test_regular_fields(self):
"""
--
cgit v1.2.3
From 08c727add37790b5a556db3fff762f4a27a5c660 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Fri, 28 Nov 2014 15:55:02 +0000
Subject: @api_view defaults to allowing GET
---
docs/api-guide/views.md | 15 +++++++++++----
rest_framework/decorators.py | 4 +++-
2 files changed, 14 insertions(+), 5 deletions(-)
diff --git a/docs/api-guide/views.md b/docs/api-guide/views.md
index 31c62682..291fe737 100644
--- a/docs/api-guide/views.md
+++ b/docs/api-guide/views.md
@@ -127,19 +127,26 @@ REST framework also allows you to work with regular function based views. It pr
## @api_view()
-**Signature:** `@api_view(http_method_names)`
+**Signature:** `@api_view(http_method_names=['GET'])`
-The core of this functionality is the `api_view` decorator, which takes a list of HTTP methods that your view should respond to. For example, this is how you would write a very simple view that just manually returns some data:
+The core of this functionality is the `api_view` decorator, which takes a list of HTTP methods that your view should respond to. For example, this is how you would write a very simple view that just manually returns some data:
from rest_framework.decorators import api_view
- @api_view(['GET'])
+ @api_view()
def hello_world(request):
return Response({"message": "Hello, world!"})
-
This view will use the default renderers, parsers, authentication classes etc specified in the [settings].
+By default only `GET` methods will be accepted. Other methods will respond with "405 Method Not Allowed". To alter this behavior, specify which methods the view allows, like so:
+
+ @api_view(['GET', 'POST'])
+ def hello_world(request):
+ if request.method == 'POST':
+ return Response({"message": "Got some data!", "data": request.data})
+ return Response({"message": "Hello, world!"})
+
## API policy decorators
To override the default settings, REST framework provides a set of additional decorators which can be added to your views. These must come *after* (below) the `@api_view` decorator. For example, to create a view that uses a [throttle][throttling] to ensure it can only be called once per day by a particular user, use the `@throttle_classes` decorator, passing a list of throttle classes:
diff --git a/rest_framework/decorators.py b/rest_framework/decorators.py
index d28d6e22..325435b3 100644
--- a/rest_framework/decorators.py
+++ b/rest_framework/decorators.py
@@ -12,12 +12,14 @@ from rest_framework.views import APIView
import types
-def api_view(http_method_names):
+def api_view(http_method_names=None):
"""
Decorator that converts a function-based view into an APIView subclass.
Takes a list of allowed methods for the view as an argument.
"""
+ if http_method_names is None:
+ http_method_names = ['GET']
def decorator(func):
--
cgit v1.2.3
From caf1de3b88c6644b55065abea255f64a81e800c0 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Fri, 28 Nov 2014 16:06:44 +0000
Subject: Minor tweaks to contributing docs
---
docs/topics/third-party-resources.md | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/docs/topics/third-party-resources.md b/docs/topics/third-party-resources.md
index ffb490af..efa0b91f 100644
--- a/docs/topics/third-party-resources.md
+++ b/docs/topics/third-party-resources.md
@@ -4,11 +4,11 @@
Third Party Packages allow developers to share code that extends the functionality of Django REST framework, in order to support additional use-cases.
-We **support**, **encourage** and **strongly favour** the creation of Third Party Packages to encapsulate new behaviour rather than adding additional functionality directly to Django REST Framework.
+We **support**, **encourage** and **strongly favor** the creation of Third Party Packages to encapsulate new behavior rather than adding additional functionality directly to Django REST Framework.
-We aim to make creating Third Party Packages as easy as possible, whilst keeping the **simplicity** of the core API and ensuring that **maintenance** of the main project remains under control. If a Third Party Package proves popular it is relatively easy to move it into the main project; removing features is much more problematic.
+We aim to make creating third party packages as easy as possible, whilst keeping a **simple** and **well maintained** core API. By promoting third party packages we ensure that the responsibility for a package remains with its author. If a package proves suitably popular it can always be considered for inclusion into the core REST framework.
-If you have an idea for a new feature please consider how it may be packaged as a Third Party Package. We're always happy to dicuss ideas on the [Mailing List][discussion-group].
+If you have an idea for a new feature please consider how it may be packaged as a Third Party Package. We're always happy to discuss ideas on the [Mailing List][discussion-group].
## How to create a Third Party Package
--
cgit v1.2.3
From 2f03483f966c5402734b5db2f7006c788bbe04f7 Mon Sep 17 00:00:00 2001
From: Tymur Maryokhin
Date: Sat, 29 Nov 2014 19:45:26 +0100
Subject: Removed unused models
---
tests/models.py | 99 +--------------------------------------------------------
1 file changed, 1 insertion(+), 98 deletions(-)
diff --git a/tests/models.py b/tests/models.py
index 06ec5a22..a0e0b3cc 100644
--- a/tests/models.py
+++ b/tests/models.py
@@ -3,35 +3,16 @@ from django.db import models
from django.utils.translation import ugettext_lazy as _
-def foobar():
- return 'foobar'
-
-
-class CustomField(models.CharField):
-
- def __init__(self, *args, **kwargs):
- kwargs['max_length'] = 12
- super(CustomField, self).__init__(*args, **kwargs)
-
-
class RESTFrameworkModel(models.Model):
"""
Base for test models that sets app_label, so they play nicely.
"""
+
class Meta:
app_label = 'tests'
abstract = True
-class HasPositiveIntegerAsChoice(RESTFrameworkModel):
- some_choices = ((1, 'A'), (2, 'B'), (3, 'C'))
- some_integer = models.PositiveIntegerField(choices=some_choices)
-
-
-class Anchor(RESTFrameworkModel):
- text = models.CharField(max_length=100, default='anchor')
-
-
class BasicModel(RESTFrameworkModel):
text = models.CharField(max_length=100, verbose_name=_("Text comes here"), help_text=_("Text description."))
@@ -41,24 +22,6 @@ class SlugBasedModel(RESTFrameworkModel):
slug = models.SlugField(max_length=32)
-class DefaultValueModel(RESTFrameworkModel):
- text = models.CharField(default='foobar', max_length=100)
- extra = models.CharField(blank=True, null=True, max_length=100)
-
-
-class CallableDefaultValueModel(RESTFrameworkModel):
- text = models.CharField(default=foobar, max_length=100)
-
-
-class ManyToManyModel(RESTFrameworkModel):
- rel = models.ManyToManyField(Anchor, help_text='Some help text.')
-
-
-class ReadOnlyManyToManyModel(RESTFrameworkModel):
- text = models.CharField(max_length=100, default='anchor')
- rel = models.ManyToManyField(Anchor)
-
-
class BaseFilterableItem(RESTFrameworkModel):
text = models.CharField(max_length=100)
@@ -72,72 +35,12 @@ class FilterableItem(BaseFilterableItem):
# Model for regression test for #285
-
class Comment(RESTFrameworkModel):
email = models.EmailField()
content = models.CharField(max_length=200)
created = models.DateTimeField(auto_now_add=True)
-class ActionItem(RESTFrameworkModel):
- title = models.CharField(max_length=200)
- started = models.NullBooleanField(default=False)
- done = models.BooleanField(default=False)
- info = CustomField(default='---', max_length=12)
-
-
-# Models for reverse relations
-class Person(RESTFrameworkModel):
- name = models.CharField(max_length=10)
- age = models.IntegerField(null=True, blank=True)
-
- @property
- def info(self):
- return {
- 'name': self.name,
- 'age': self.age,
- }
-
-
-class BlogPost(RESTFrameworkModel):
- title = models.CharField(max_length=100)
- writer = models.ForeignKey(Person, null=True, blank=True)
-
- def get_first_comment(self):
- return self.blogpostcomment_set.all()[0]
-
-
-class BlogPostComment(RESTFrameworkModel):
- text = models.TextField()
- blog_post = models.ForeignKey(BlogPost)
-
-
-class Album(RESTFrameworkModel):
- title = models.CharField(max_length=100, unique=True)
- ref = models.CharField(max_length=10, unique=True, null=True, blank=True)
-
-
-class Photo(RESTFrameworkModel):
- description = models.TextField()
- album = models.ForeignKey(Album)
-
-
-# Model for issue #324
-class BlankFieldModel(RESTFrameworkModel):
- title = models.CharField(max_length=100, blank=True, null=False,
- default="title")
-
-
-# Model for issue #380
-class OptionalRelationModel(RESTFrameworkModel):
- other = models.ForeignKey('OptionalRelationModel', blank=True, null=True)
-
-
-# Model for RegexField
-class Book(RESTFrameworkModel):
- isbn = models.CharField(max_length=13)
-
-
# Models for relations tests
# ManyToMany
class ManyToManyTarget(RESTFrameworkModel):
--
cgit v1.2.3
From dd9d40d8c01f54f1542ba728d89b8b2da584dc1f Mon Sep 17 00:00:00 2001
From: Tymur Maryokhin
Date: Sat, 29 Nov 2014 20:04:50 +0100
Subject: Moved non-conflicting models
---
tests/models.py | 12 ------------
tests/test_generics.py | 34 +++++++++++++++++++++++++---------
2 files changed, 25 insertions(+), 21 deletions(-)
diff --git a/tests/models.py b/tests/models.py
index a0e0b3cc..456b0a0b 100644
--- a/tests/models.py
+++ b/tests/models.py
@@ -17,11 +17,6 @@ class BasicModel(RESTFrameworkModel):
text = models.CharField(max_length=100, verbose_name=_("Text comes here"), help_text=_("Text description."))
-class SlugBasedModel(RESTFrameworkModel):
- text = models.CharField(max_length=100)
- slug = models.SlugField(max_length=32)
-
-
class BaseFilterableItem(RESTFrameworkModel):
text = models.CharField(max_length=100)
@@ -34,13 +29,6 @@ class FilterableItem(BaseFilterableItem):
date = models.DateField()
-# Model for regression test for #285
-class Comment(RESTFrameworkModel):
- email = models.EmailField()
- content = models.CharField(max_length=200)
- created = models.DateTimeField(auto_now_add=True)
-
-
# Models for relations tests
# ManyToMany
class ManyToManyTarget(RESTFrameworkModel):
diff --git a/tests/test_generics.py b/tests/test_generics.py
index 2690fb47..b78584f0 100644
--- a/tests/test_generics.py
+++ b/tests/test_generics.py
@@ -6,12 +6,26 @@ from django.test import TestCase
from django.utils import six
from rest_framework import generics, renderers, serializers, status
from rest_framework.test import APIRequestFactory
-from tests.models import BasicModel, Comment, SlugBasedModel
+from tests.models import BasicModel, RESTFrameworkModel
from tests.models import ForeignKeySource, ForeignKeyTarget
factory = APIRequestFactory()
+# Models
+class SlugBasedModel(RESTFrameworkModel):
+ text = models.CharField(max_length=100)
+ slug = models.SlugField(max_length=32)
+
+
+# Model for regression test for #285
+class Comment(RESTFrameworkModel):
+ email = models.EmailField()
+ content = models.CharField(max_length=200)
+ created = models.DateTimeField(auto_now_add=True)
+
+
+# Serializers
class BasicSerializer(serializers.ModelSerializer):
class Meta:
model = BasicModel
@@ -22,6 +36,15 @@ class ForeignKeySerializer(serializers.ModelSerializer):
model = ForeignKeySource
+class SlugSerializer(serializers.ModelSerializer):
+ slug = serializers.ReadOnlyField()
+
+ class Meta:
+ model = SlugBasedModel
+ fields = ('text', 'slug')
+
+
+# Views
class RootView(generics.ListCreateAPIView):
queryset = BasicModel.objects.all()
serializer_class = BasicSerializer
@@ -37,14 +60,6 @@ class FKInstanceView(generics.RetrieveUpdateDestroyAPIView):
serializer_class = ForeignKeySerializer
-class SlugSerializer(serializers.ModelSerializer):
- slug = serializers.ReadOnlyField()
-
- class Meta:
- model = SlugBasedModel
- fields = ('text', 'slug')
-
-
class SlugBasedInstanceView(InstanceView):
"""
A model with a slug-field.
@@ -54,6 +69,7 @@ class SlugBasedInstanceView(InstanceView):
lookup_field = 'slug'
+# Tests
class TestRootView(TestCase):
def setUp(self):
"""
--
cgit v1.2.3
From e2ea98e8ab3192fa8d252d33cc03929fcf6ed02f Mon Sep 17 00:00:00 2001
From: Tymur Maryokhin
Date: Sat, 29 Nov 2014 20:23:55 +0100
Subject: Fixed typos
---
README.md | 8 ++++----
docs/index.md | 4 ++--
docs/topics/2.3-announcement.md | 6 +++---
docs/topics/release-notes.md | 4 ++--
docs/tutorial/6-viewsets-and-routers.md | 4 ++--
docs/tutorial/quickstart.md | 2 +-
tests/test_generics.py | 22 +++++++++++-----------
7 files changed, 25 insertions(+), 25 deletions(-)
diff --git a/README.md b/README.md
index c86bb65f..31fe1e52 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@
[![build-status-image]][travis]
[![pypi-version]][pypi]
-**Awesome web-browseable Web APIs.**
+**Awesome web-browsable Web APIs.**
Full documentation for the project is available at [http://www.django-rest-framework.org][docs].
@@ -19,7 +19,7 @@ Django REST framework is a powerful and flexible toolkit for building Web APIs.
Some reasons you might want to use REST framework:
-* The [Web browseable API][sandbox] is a huge useability win for your developers.
+* The [Web browsable API][sandbox] is a huge usability win for your developers.
* [Authentication policies][authentication] including [OAuth1a][oauth1-section] and [OAuth2][oauth2-section] out of the box.
* [Serialization][serializers] that supports both [ORM][modelserializer-section] and [non-ORM][serializer-section] data sources.
* Customizable all the way down - just use [regular function-based views][functionview-section] if you don't need the [more][generic-views] [powerful][viewsets] [features][routers].
@@ -27,7 +27,7 @@ Some reasons you might want to use REST framework:
There is a live example API for testing purposes, [available here][sandbox].
-**Below**: *Screenshot from the browseable API*
+**Below**: *Screenshot from the browsable API*
![Screenshot][image]
@@ -86,7 +86,7 @@ router.register(r'users', UserViewSet)
# Wire up our API using automatic URL routing.
-# Additionally, we include login URLs for the browseable API.
+# Additionally, we include login URLs for the browsable API.
urlpatterns = [
url(r'^', include(router.urls)),
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))
diff --git a/docs/index.md b/docs/index.md
index b5257c73..feada2a9 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -31,7 +31,7 @@ Django REST framework is a powerful and flexible toolkit that makes it easy to b
Some reasons you might want to use REST framework:
-* The [Web browseable API][sandbox] is a huge usability win for your developers.
+* The [Web browsable API][sandbox] is a huge usability win for your developers.
* [Authentication policies][authentication] including [OAuth1a][oauth1-section] and [OAuth2][oauth2-section] out of the box.
* [Serialization][serializers] that supports both [ORM][modelserializer-section] and [non-ORM][serializer-section] data sources.
* Customizable all the way down - just use [regular function-based views][functionview-section] if you don't need the [more][generic-views] [powerful][viewsets] [features][routers].
@@ -132,7 +132,7 @@ Here's our project's root `urls.py` module:
router.register(r'users', UserViewSet)
# Wire up our API using automatic URL routing.
- # Additionally, we include login URLs for the browseable API.
+ # Additionally, we include login URLs for the browsable API.
urlpatterns = [
url(r'^', include(router.urls)),
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))
diff --git a/docs/topics/2.3-announcement.md b/docs/topics/2.3-announcement.md
index 9c9f3e9f..66e46865 100644
--- a/docs/topics/2.3-announcement.md
+++ b/docs/topics/2.3-announcement.md
@@ -35,7 +35,7 @@ As an example of just how simple REST framework APIs can now be, here's an API w
# Wire up our API using automatic URL routing.
- # Additionally, we include login URLs for the browseable API.
+ # Additionally, we include login URLs for the browsable API.
urlpatterns = [
url(r'^', include(router.urls)),
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))
@@ -207,9 +207,9 @@ The old-style signature will continue to function but will raise a `PendingDepre
## View names and descriptions
-The mechanics of how the names and descriptions used in the browseable API are generated has been modified and cleaned up somewhat.
+The mechanics of how the names and descriptions used in the browsable API are generated has been modified and cleaned up somewhat.
-If you've been customizing this behavior, for example perhaps to use `rst` markup for the browseable API, then you'll need to take a look at the implementation to see what updates you need to make.
+If you've been customizing this behavior, for example perhaps to use `rst` markup for the browsable API, then you'll need to take a look at the implementation to see what updates you need to make.
Note that the relevant methods have always been private APIs, and the docstrings called them out as intended to be deprecated.
diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md
index 53187589..19dfbb98 100644
--- a/docs/topics/release-notes.md
+++ b/docs/topics/release-notes.md
@@ -121,7 +121,7 @@ You can determine your currently installed version using `pip freeze`:
* Add `UnicodeYAMLRenderer` that extends `YAMLRenderer` with unicode.
* Fix `parse_header` argument convertion.
* Fix mediatype detection under Python 3.
-* Web browseable API now offers blank option on dropdown when the field is not required.
+* Web browsable API now offers blank option on dropdown when the field is not required.
* `APIException` representation improved for logging purposes.
* Allow source="*" within nested serializers.
* Better support for custom oauth2 provider backends.
@@ -200,7 +200,7 @@ You can determine your currently installed version using `pip freeze`:
* Added `MAX_PAGINATE_BY` setting and `max_paginate_by` generic view attribute.
* Added `cache` attribute to throttles to allow overriding of default cache.
* 'Raw data' tab in browsable API now contains pre-populated data.
-* 'Raw data' and 'HTML form' tab preference in browseable API now saved between page views.
+* 'Raw data' and 'HTML form' tab preference in browsable API now saved between page views.
* Bugfix: `required=True` argument fixed for boolean serializer fields.
* Bugfix: `client.force_authenticate(None)` should also clear session info if it exists.
* Bugfix: Client sending empty string instead of file now clears `FileField`.
diff --git a/docs/tutorial/6-viewsets-and-routers.md b/docs/tutorial/6-viewsets-and-routers.md
index 3fad509a..816e9da6 100644
--- a/docs/tutorial/6-viewsets-and-routers.md
+++ b/docs/tutorial/6-viewsets-and-routers.md
@@ -112,7 +112,7 @@ Here's our re-wired `urls.py` file.
router.register(r'users', views.UserViewSet)
# The API URLs are now determined automatically by the router.
- # Additionally, we include the login URLs for the browseable API.
+ # Additionally, we include the login URLs for the browsable API.
urlpatterns = [
url(r'^', include(router.urls)),
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))
@@ -130,7 +130,7 @@ That doesn't mean it's always the right approach to take. There's a similar set
## Reviewing our work
-With an incredibly small amount of code, we've now got a complete pastebin Web API, which is fully web browseable, and comes complete with authentication, per-object permissions, and multiple renderer formats.
+With an incredibly small amount of code, we've now got a complete pastebin Web API, which is fully web browsable, and comes complete with authentication, per-object permissions, and multiple renderer formats.
We've walked through each step of the design process, and seen how if we need to customize anything we can gradually work our way down to simply using regular Django views.
diff --git a/docs/tutorial/quickstart.md b/docs/tutorial/quickstart.md
index 1c398c1f..3e1ce0a9 100644
--- a/docs/tutorial/quickstart.md
+++ b/docs/tutorial/quickstart.md
@@ -100,7 +100,7 @@ Okay, now let's wire up the API URLs. On to `tutorial/urls.py`...
router.register(r'groups', views.GroupViewSet)
# Wire up our API using automatic URL routing.
- # Additionally, we include login URLs for the browseable API.
+ # Additionally, we include login URLs for the browsable API.
urlpatterns = [
url(r'^', include(router.urls)),
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))
diff --git a/tests/test_generics.py b/tests/test_generics.py
index b78584f0..94023c30 100644
--- a/tests/test_generics.py
+++ b/tests/test_generics.py
@@ -143,13 +143,13 @@ class TestRootView(TestCase):
self.assertEqual(created.text, 'foobar')
-EXPECTED_QUERYS_FOR_PUT = 3 if django.VERSION < (1, 6) else 2
+EXPECTED_QUERIES_FOR_PUT = 3 if django.VERSION < (1, 6) else 2
class TestInstanceView(TestCase):
def setUp(self):
"""
- Create 3 BasicModel intances.
+ Create 3 BasicModel instances.
"""
items = ['foo', 'bar', 'baz', 'filtered out']
for item in items:
@@ -189,7 +189,7 @@ class TestInstanceView(TestCase):
"""
data = {'text': 'foobar'}
request = factory.put('/1', data, format='json')
- with self.assertNumQueries(EXPECTED_QUERYS_FOR_PUT):
+ with self.assertNumQueries(EXPECTED_QUERIES_FOR_PUT):
response = self.view(request, pk='1').render()
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(dict(response.data), {'id': 1, 'text': 'foobar'})
@@ -203,7 +203,7 @@ class TestInstanceView(TestCase):
data = {'text': 'foobar'}
request = factory.patch('/1', data, format='json')
- with self.assertNumQueries(EXPECTED_QUERYS_FOR_PUT):
+ with self.assertNumQueries(EXPECTED_QUERIES_FOR_PUT):
response = self.view(request, pk=1).render()
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, {'id': 1, 'text': 'foobar'})
@@ -238,7 +238,7 @@ class TestInstanceView(TestCase):
"""
data = {'id': 999, 'text': 'foobar'}
request = factory.put('/1', data, format='json')
- with self.assertNumQueries(EXPECTED_QUERYS_FOR_PUT):
+ with self.assertNumQueries(EXPECTED_QUERIES_FOR_PUT):
response = self.view(request, pk=1).render()
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, {'id': 1, 'text': 'foobar'})
@@ -304,9 +304,10 @@ class TestOverriddenGetObject(TestCase):
Test cases for a RetrieveUpdateDestroyAPIView that does NOT use the
queryset/model mechanism but instead overrides get_object()
"""
+
def setUp(self):
"""
- Create 3 BasicModel intances.
+ Create 3 BasicModel instances.
"""
items = ['foo', 'bar', 'baz']
for item in items:
@@ -379,11 +380,11 @@ class ClassB(models.Model):
class ClassA(models.Model):
name = models.CharField(max_length=255)
- childs = models.ManyToManyField(ClassB, blank=True, null=True)
+ children = models.ManyToManyField(ClassB, blank=True, null=True)
class ClassASerializer(serializers.ModelSerializer):
- childs = serializers.PrimaryKeyRelatedField(
+ children = serializers.PrimaryKeyRelatedField(
many=True, queryset=ClassB.objects.all()
)
@@ -396,8 +397,8 @@ class ExampleView(generics.ListCreateAPIView):
queryset = ClassA.objects.all()
-class TestM2MBrowseableAPI(TestCase):
- def test_m2m_in_browseable_api(self):
+class TestM2MBrowsableAPI(TestCase):
+ def test_m2m_in_browsable_api(self):
"""
Test for particularly ugly regression with m2m in browsable API
"""
@@ -440,7 +441,6 @@ class DynamicSerializerView(generics.ListCreateAPIView):
class TestFilterBackendAppliedToViews(TestCase):
-
def setUp(self):
"""
Create 3 BasicModel instances to filter on.
--
cgit v1.2.3
From 72c4ec4e189796e506655e275cd9c77abe98e1b9 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Mon, 1 Dec 2014 08:54:12 +0000
Subject: Add 3.0 announcement
---
docs/index.md | 4 ++++
mkdocs.yml | 1 +
2 files changed, 5 insertions(+)
diff --git a/docs/index.md b/docs/index.md
index b5257c73..10b45584 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -11,6 +11,8 @@
**Note**: This is the documentation for the **version 3.0** of REST framework. Documentation for [version 2.4](http://tomchristie.github.io/rest-framework-2-docs/) is also available.
+For more details see the [3.0 release notes](3.0-announcement).
+
---
@@ -201,6 +203,7 @@ General guides to using REST framework.
* [2.2 Announcement][2.2-announcement]
* [2.3 Announcement][2.3-announcement]
* [2.4 Announcement][2.4-announcement]
+* [3.0 Announcement][3.0-announcement]
* [Kickstarter Announcement][kickstarter-announcement]
* [Release Notes][release-notes]
* [Credits][credits]
@@ -319,6 +322,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[2.2-announcement]: topics/2.2-announcement.md
[2.3-announcement]: topics/2.3-announcement.md
[2.4-announcement]: topics/2.4-announcement.md
+[3.0-announcement]: topics/3.0-announcement.md
[kickstarter-announcement]: topics/kickstarter-announcement.md
[release-notes]: topics/release-notes.md
[credits]: topics/credits.md
diff --git a/mkdocs.yml b/mkdocs.yml
index cc31b1be..9513f04f 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -50,6 +50,7 @@ pages:
- ['topics/2.2-announcement.md', 'Topics', '2.2 Announcement']
- ['topics/2.3-announcement.md', 'Topics', '2.3 Announcement']
- ['topics/2.4-announcement.md', 'Topics', '2.4 Announcement']
+ - ['topics/3.0-announcement.md', 'Topics', '3.0 Announcement']
- ['topics/kickstarter-announcement.md', 'Topics', 'Kickstarter Announcement']
- ['topics/release-notes.md', 'Topics', 'Release Notes']
- ['topics/credits.md', 'Topics', 'Credits']
--
cgit v1.2.3
From b9503cd603613e4ae72b7718ba70a00b1537b289 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Mon, 1 Dec 2014 10:48:45 +0000
Subject: Support Django's core ValidationError for backwards compat. Refs
#2145.
---
docs/index.md | 2 +-
docs/topics/3.0-announcement.md | 42 +++++++++++++++++++++++++++++++++++------
rest_framework/serializers.py | 11 +++++++++++
3 files changed, 48 insertions(+), 7 deletions(-)
diff --git a/docs/index.md b/docs/index.md
index 10b45584..7631be1e 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -11,7 +11,7 @@
**Note**: This is the documentation for the **version 3.0** of REST framework. Documentation for [version 2.4](http://tomchristie.github.io/rest-framework-2-docs/) is also available.
-For more details see the [3.0 release notes](3.0-announcement).
+For more details see the [3.0 release notes][3.0-announcement].
---
diff --git a/docs/topics/3.0-announcement.md b/docs/topics/3.0-announcement.md
index b32fe510..72fee019 100644
--- a/docs/topics/3.0-announcement.md
+++ b/docs/topics/3.0-announcement.md
@@ -6,9 +6,11 @@ This release is incremental in nature. There *are* some breaking API changes, an
The difference in quality of the REST framework API and implementation should make writing, maintaining and debugging your application far easier.
-3.0 is the first of three releases that have been funded by our recent [Kickstarter campaign](kickstarter.com/projects/tomchristie/django-rest-framework-3).
+3.0 is the first of three releases that have been funded by our recent [Kickstarter campaign][kickstarter].
-As ever, a huge thank you to our many [wonderful sponsors](sponsors). If you're looking for a Django gig, and want to work with smart community-minded folks, you should probably check out that list and see who's hiring.
+As ever, a huge thank you to our many [wonderful sponsors][sponsors]. If you're looking for a Django gig, and want to work with smart community-minded folks, you should probably check out that list and see who's hiring.
+
+---
## New features
@@ -51,6 +53,8 @@ Instead of passing the files argument separately:
The usage of `request.QUERY_PARAMS` is now pending deprecation in favor of the lowercased `request.query_params`.
+---
+
## Serializers
#### Single-step object creation.
@@ -149,7 +153,7 @@ Previously `serializers.ValidationError` error was simply a synonym for `django.
The reason behind this is that Django's `ValidationError` class is intended for use with HTML forms and its API makes using it slightly awkward with nested validation errors that can occur in serializers.
-For most users this change shouldn't require any updates to your codebase, but it is worth ensuring that whenever raising validation errors you are always using the `serializers.ValidationError` exception class, and not Django's built-in exception.
+For most users this change shouldn't require any updates to your codebase, but it is worth ensuring that whenever raising validation errors you should prefer using the `serializers.ValidationError` exception class, and not Django's built-in exception.
We strongly recommend that you use the namespaced import style of `import serializers` and not `from serializers import ValidationError` in order to avoid any potential confusion.
@@ -218,7 +222,18 @@ If you absolutely need to preserve `transform_` behavior, for exampl
This change also means that we no longer use the `.full_clean()` method on model instances, but instead perform all validation explicitly on the serializer. This gives a cleaner separation, and ensures that there's no automatic validation behavior on `ModelSerializer` classes that can't also be easily replicated on regular `Serializer` classes.
-It's important to note that this change also means that the model `.clean()` method will not be called as part of serializer validation, as it would be if using a `ModelForm`. Use the serializer `.validate()` method to perform a final validation step on incoming data where required.
+For the most part this change should be transparent. Field validation and uniqueness checks will still be run as normal, but the implementation is a little different.
+
+The one difference that you do need to note is that the `.clean()` method will not be called as part of serializer validation, as it would be if using a `ModelForm`. Use the serializer `.validate()` method to perform a final validation step on incoming data where required.
+
+There may be some cases where you really do need to keep validation logic in the model `.clean()` method, and cannot instead separate it into the serializer `.validate()`. You can do so by explicitly instantiating a model instance in the `.validate()` method.
+
+ def validate(self, attrs):
+ instance = ExampleModel(**attrs)
+ instance.clean()
+ return attrs
+
+Again, you really should look at properly separating the validation logic out of the model method if possible, but the above might be useful in some backwards compatibility cases, or for an easy migration path.
#### Writable nested serialization.
@@ -524,6 +539,8 @@ The following class is an example of a generic serializer that can handle coerci
# Force anything else to its string representation.
output[attribute_name] = str(attribute)
+---
+
## Serializer fields
#### The `Field` and `ReadOnly` field classes.
@@ -721,6 +738,8 @@ REST framework also now includes explicit validator classes for validating the `
These classes are documented in the [Validators](../api-guide/validators.md) section of the documentation.
+---
+
## Generic views
#### Simplification of view logic.
@@ -769,12 +788,16 @@ The generic views now raise `ValidationFailed` exception for invalid data. This
This change means that you can now easily customize the style of error responses across your entire API, without having to modify any of the generic views.
+---
+
## The metadata API
Behavior for dealing with `OPTIONS` requests was previously built directly into the class based views. This has now been properly separated out into a Metadata API that allows the same pluggable style as other API policies in REST framework.
This makes it far easier to use a different style for `OPTIONS` responses throughout your API, and makes it possible to create third-party metadata policies.
+---
+
## Serializers as HTML forms
REST framework 3.0 includes templated HTML form rendering for serializers.
@@ -806,6 +829,8 @@ Similarly, to use a radio button control instead of the default `select` control
This API should be considered provisional, and there may be minor alterations with the incoming 3.1 release.
+---
+
## API style
There are some improvements in the default style we use in our API responses.
@@ -899,12 +924,16 @@ Or modify it on an individual serializer field, using the `coerce_to_string` key
The default JSON renderer will return float objects for un-coerced `Decimal` instances. This allows you to easily switch between string or float representations for decimals depending on your API design needs.
-## Miscellaneous notes.
+---
+
+## Miscellaneous notes
* The serializer `ChoiceField` does not currently display nested choices, as was the case in 2.4. This will be address as part of 3.1.
* Due to the new templated form rendering, the 'widget' option is no longer valid. This means there's no easy way of using third party "autocomplete" widgets for rendering select inputs that contain a large number of choices. You'll either need to use a regular select or a plain text input. We may consider addressing this in 3.1 or 3.2 if there's sufficient demand.
-## What's coming next.
+---
+
+## What's coming next
3.0 is an incremental release, and there are several upcoming features that will build on the baseline improvements that it makes.
@@ -919,5 +948,6 @@ The 3.2 release is planned to introduce an alternative admin-style interface to
You can follow development on the GitHub site, where we use [milestones to indicate planning timescales](https://github.com/tomchristie/django-rest-framework/milestones).
+[kickstarter]: http://kickstarter.com/projects/tomchristie/django-rest-framework-3
[sponsors]: http://www.django-rest-framework.org/topics/kickstarter-announcement/#sponsors
[mixins.py]: https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/mixins.py
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index f7aa3a7d..de0d026d 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -11,6 +11,7 @@ python primitives.
response content is handled by parsers and renderers.
"""
from django.core.exceptions import ImproperlyConfigured
+from django.core.exceptions import ValidationError as DjangoValidationError
from django.db import models
from django.db.models.fields import FieldDoesNotExist
from django.utils import six
@@ -330,6 +331,14 @@ class Serializer(BaseSerializer):
raise ValidationError({
api_settings.NON_FIELD_ERRORS_KEY: [exc.detail]
})
+ except DjangoValidationError as exc:
+ # Normally you should raise `serializers.ValidationError`
+ # inside your codebase, but we handle Django's validation
+ # exception class as well for simpler compat.
+ # Eg. Calling Model.clean() explictily inside Serializer.validate()
+ raise ValidationError({
+ api_settings.NON_FIELD_ERRORS_KEY: list(exc.messages)
+ })
return value
@@ -353,6 +362,8 @@ class Serializer(BaseSerializer):
validated_value = validate_method(validated_value)
except ValidationError as exc:
errors[field.field_name] = exc.detail
+ except DjangoValidationError as exc:
+ errors[field.field_name] = list(exc.messages)
except SkipField:
pass
else:
--
cgit v1.2.3
From 22c5b863bc395a6df84ec2536849540d3e92da7a Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Mon, 1 Dec 2014 11:37:38 +0000
Subject: More descriptive docstring on ModelSerializer
---
rest_framework/serializers.py | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index de0d026d..3783b285 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -565,6 +565,14 @@ class ModelSerializer(Serializer):
* A set of default fields are automatically populated.
* A set of default validators are automatically populated.
* Default `.create()` and `.update()` implementations are provided.
+
+ The process of automatically determining a set of serializer fields
+ based on the model fields is reasonably complex, but you almost certainly
+ don't need to dig into the implemention.
+
+ If the `ModelSerializer` class *doesn't* generate the set of fields that
+ you need you should either declare the extra/differing fields explicitly on
+ the serializer class, or simply use a `Serializer` class.
"""
_field_mapping = ClassLookupDict({
models.AutoField: IntegerField,
--
cgit v1.2.3
From 270c7acdd75e9dd0b7a600e5648b0ec364c9a510 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Mon, 1 Dec 2014 11:59:04 +0000
Subject: Minor validtors tweak
---
docs/topics/3.0-announcement.md | 9 +++++----
rest_framework/serializers.py | 31 ++++++++++++++++++++++++++++---
2 files changed, 33 insertions(+), 7 deletions(-)
diff --git a/docs/topics/3.0-announcement.md b/docs/topics/3.0-announcement.md
index 72fee019..281e33bd 100644
--- a/docs/topics/3.0-announcement.md
+++ b/docs/topics/3.0-announcement.md
@@ -727,10 +727,11 @@ The `UniqueTogetherValidator` should be applied to a serializer, and takes a `qu
position = serializers.IntegerField()
name = serializers.CharField(max_length=100)
- default_validators = [UniqueTogetherValidator(
- queryset=RaceResult.objects.all(),
- fields=('category', 'position')
- )]
+ class Meta:
+ validators = [UniqueTogetherValidator(
+ queryset=RaceResult.objects.all(),
+ fields=('category', 'position')
+ )]
#### The `UniqueForDateValidator` classes.
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index 3783b285..0d0a4d9a 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -601,6 +601,26 @@ class ModelSerializer(Serializer):
_related_class = PrimaryKeyRelatedField
def create(self, validated_attrs):
+ """
+ We have a bit of extra checking around this in order to provide
+ descriptive messages when something goes wrong, but this method is
+ essentially just:
+
+ return ExampleModel.objects.create(**validated_attrs)
+
+ If there are many to many fields present on the instance then they
+ cannot be set until the model is instantiated, in which case the
+ implementation is like so:
+
+ example_relationship = validated_attrs.pop('example_relationship')
+ instance = ExampleModel.objects.create(**validated_attrs)
+ instance.example_relationship = example_relationship
+ return instance
+
+ The default implementation also does not handle nested relationships.
+ If you want to support writable nested relationships you'll need
+ to write an explicit `.create()` method.
+ """
# Check that the user isn't trying to handle a writable nested field.
# If we don't do this explicitly they'd likely get a confusing
# error at the point of calling `Model.objects.create()`.
@@ -651,14 +671,19 @@ class ModelSerializer(Serializer):
return instance
def get_validators(self):
+ # If the validators have been declared explicitly then use that.
+ validators = getattr(getattr(self, 'Meta', None), 'validators', None)
+ if validators is not None:
+ return validators
+
+ # Determine the default set of validators.
+ validators = []
+ model_class = self.Meta.model
field_names = set([
field.source for field in self.fields.values()
if (field.source != '*') and ('.' not in field.source)
])
- validators = getattr(getattr(self, 'Meta', None), 'validators', [])
- model_class = self.Meta.model
-
# Note that we make sure to check `unique_together` both on the
# base model class, but also on any parent classes.
for parent_class in [model_class] + list(model_class._meta.parents.keys()):
--
cgit v1.2.3
From 1caa14dd1425812fd47f58e859d694a81915caf3 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Mon, 1 Dec 2014 12:22:21 +0000
Subject: Minor 3.0 announcment tweaks
---
docs/topics/3.0-announcement.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/topics/3.0-announcement.md b/docs/topics/3.0-announcement.md
index 281e33bd..4aafb3d8 100644
--- a/docs/topics/3.0-announcement.md
+++ b/docs/topics/3.0-announcement.md
@@ -1,8 +1,8 @@
-# REST framework 3.0
+# Django REST framework 3.0
The 3.0 release of Django REST framework is the result of almost four years of iteration and refinement. It comprehensively addresses some of the previous remaining design issues in serializers, fields and the generic views.
-This release is incremental in nature. There *are* some breaking API changes, and upgrading *will* require you to read the release notes carefully, but the migration path should otherwise be relatively straightforward.
+**This release is incremental in nature. There *are* some breaking API changes, and upgrading *will* require you to read the release notes carefully, but the migration path should otherwise be relatively straightforward.**
The difference in quality of the REST framework API and implementation should make writing, maintaining and debugging your application far easier.
--
cgit v1.2.3
From 34b5db62e560e73516fb569eaf9b71ea5562958f Mon Sep 17 00:00:00 2001
From: phalt
Date: Mon, 1 Dec 2014 13:39:53 +0000
Subject: Use httpie for tutorials
---
docs/tutorial/1-serialization.md | 48 +++++++++++++++++---
docs/tutorial/2-requests-and-responses.md | 55 ++++++++++++++++++-----
docs/tutorial/4-authentication-and-permissions.md | 20 ++++++---
docs/tutorial/quickstart.md | 29 ++++++++++++
4 files changed, 129 insertions(+), 23 deletions(-)
diff --git a/docs/tutorial/1-serialization.md b/docs/tutorial/1-serialization.md
index a3c19858..3ef65116 100644
--- a/docs/tutorial/1-serialization.md
+++ b/docs/tutorial/1-serialization.md
@@ -332,17 +332,51 @@ Quit out of the shell...
In another terminal window, we can test the server.
-We can get a list of all of the snippets.
+We could use `curl`, but let's use a nicer tool called [httpie][httpie] to test our server. It has much nicer formatting and makes our output easier to read. This is especially useful when testing.
- curl http://127.0.0.1:8000/snippets/
+You can install httpie on all operating systems using pip:
- [{"id": 1, "title": "", "code": "foo = \"bar\"\n", "linenos": false, "language": "python", "style": "friendly"}, {"id": 2, "title": "", "code": "print \"hello, world\"\n", "linenos": false, "language": "python", "style": "friendly"}]
+ pip install httpie
-Or we can get a particular snippet by referencing its id.
+It can also be installed through [Homebrew][brew] on Mac:
- curl http://127.0.0.1:8000/snippets/2/
+ brew install httpie
- {"id": 2, "title": "", "code": "print \"hello, world\"\n", "linenos": false, "language": "python", "style": "friendly"}
+Finally, we can get a list of all of the snippets:
+
+ http http://127.0.0.1:8000/snippets/ --body
+
+ [
+ {
+ "id": 1,
+ "title": "",
+ "code": "foo = \"bar\"\n",
+ "linenos": false,
+ "language": "python",
+ "style": "friendly"
+ },
+ {
+ "id": 2,
+ "title": "",
+ "code": "print \"hello, world\"\n",
+ "linenos": false,
+ "language": "python",
+ "style": "friendly"
+ }
+ ]
+
+Or we can get a particular snippet by referencing its id:
+
+ http http://127.0.0.1:8000/snippets/2/ --body
+
+ {
+ "id": 2,
+ "title": "",
+ "code": "print \"hello, world\"\n",
+ "linenos": false,
+ "language": "python",
+ "style": "friendly"
+ }
Similarly, you can have the same json displayed by visiting these URLs in a web browser.
@@ -359,3 +393,5 @@ We'll see how we can start to improve things in [part 2 of the tutorial][tut-2].
[sandbox]: http://restframework.herokuapp.com/
[virtualenv]: http://www.virtualenv.org/en/latest/index.html
[tut-2]: 2-requests-and-responses.md
+[httpie]: https://github.com/jakubroztocil/httpie#installation
+[brew]: http://brew.sh
diff --git a/docs/tutorial/2-requests-and-responses.md b/docs/tutorial/2-requests-and-responses.md
index f377c712..dcaf7c0c 100644
--- a/docs/tutorial/2-requests-and-responses.md
+++ b/docs/tutorial/2-requests-and-responses.md
@@ -127,31 +127,62 @@ Go ahead and test the API from the command line, as we did in [tutorial part 1][
We can get a list of all of the snippets, as before.
- curl http://127.0.0.1:8000/snippets/
-
- [{"id": 1, "title": "", "code": "foo = \"bar\"\n", "linenos": false, "language": "python", "style": "friendly"}, {"id": 2, "title": "", "code": "print \"hello, world\"\n", "linenos": false, "language": "python", "style": "friendly"}]
+ http http://127.0.0.1:8000/snippets/ --body
+
+ [
+ {
+ "id": 1,
+ "title": "",
+ "code": "foo = \"bar\"\n",
+ "linenos": false,
+ "language": "python",
+ "style": "friendly"
+ },
+ {
+ "id": 2,
+ "title": "",
+ "code": "print \"hello, world\"\n",
+ "linenos": false,
+ "language": "python",
+ "style": "friendly"
+ }
+ ]
We can control the format of the response that we get back, either by using the `Accept` header:
- curl http://127.0.0.1:8000/snippets/ -H 'Accept: application/json' # Request JSON
- curl http://127.0.0.1:8000/snippets/ -H 'Accept: text/html' # Request HTML
+ http http://127.0.0.1:8000/snippets/ Accept:application/json # Request JSON
+ http http://127.0.0.1:8000/snippets/ Accept:text/html # Request HTML
Or by appending a format suffix:
- curl http://127.0.0.1:8000/snippets/.json # JSON suffix
- curl http://127.0.0.1:8000/snippets/.api # Browsable API suffix
+ http http://127.0.0.1:8000/snippets/.json # JSON suffix
+ http http://127.0.0.1:8000/snippets/.api # Browsable API suffix
Similarly, we can control the format of the request that we send, using the `Content-Type` header.
# POST using form data
- curl -X POST http://127.0.0.1:8000/snippets/ -d "code=print 123"
+ http --form POST http://127.0.0.1:8000/snippets/ code="print 123"
- {"id": 3, "title": "", "code": "print 123", "linenos": false, "language": "python", "style": "friendly"}
+ {
+ "id": 3,
+ "title": "",
+ "code": "print 123",
+ "linenos": false,
+ "language": "python",
+ "style": "friendly"
+ }
# POST using JSON
- curl -X POST http://127.0.0.1:8000/snippets/ -d '{"code": "print 456"}' -H "Content-Type: application/json"
-
- {"id": 4, "title": "", "code": "print 456", "linenos": true, "language": "python", "style": "friendly"}
+ http --json POST http://127.0.0.1:8000/snippets/ code="print 456"
+
+ {
+ "id": 4,
+ "title": "",
+ "code": "print 456",
+ "linenos": true,
+ "language": "python",
+ "style": "friendly"
+ }
Now go and open the API in a web browser, by visiting [http://127.0.0.1:8000/snippets/][devserver].
diff --git a/docs/tutorial/4-authentication-and-permissions.md b/docs/tutorial/4-authentication-and-permissions.md
index 4e4edeea..15d93a62 100644
--- a/docs/tutorial/4-authentication-and-permissions.md
+++ b/docs/tutorial/4-authentication-and-permissions.md
@@ -198,15 +198,25 @@ If we're interacting with the API programmatically we need to explicitly provide
If we try to create a snippet without authenticating, we'll get an error:
- curl -i -X POST http://127.0.0.1:8000/snippets/ -d "code=print 123"
+ http POST http://127.0.0.1:8000/snippets/ code="print 123"
- {"detail": "Authentication credentials were not provided."}
+ {
+ "detail": "Authentication credentials were not provided."
+ }
We can make a successful request by including the username and password of one of the users we created earlier.
- curl -X POST http://127.0.0.1:8000/snippets/ -d "code=print 789" -u tom:password
-
- {"id": 5, "owner": "tom", "title": "foo", "code": "print 789", "linenos": false, "language": "python", "style": "friendly"}
+ http POST -a tom:password http://127.0.0.1:8000/snippets/ code="print 789"
+
+ {
+ "id": 5,
+ "owner": "tom",
+ "title": "foo",
+ "code": "print 789",
+ "linenos": false,
+ "language": "python",
+ "style": "friendly"
+ }
## Summary
diff --git a/docs/tutorial/quickstart.md b/docs/tutorial/quickstart.md
index 1c398c1f..41e864cc 100644
--- a/docs/tutorial/quickstart.md
+++ b/docs/tutorial/quickstart.md
@@ -24,6 +24,10 @@ Create a new Django project named `tutorial`, then start a new app called `quick
django-admin.py startapp quickstart
cd ..
+Optionally, install [httpie][httpie] for tastier HTTP requests:
+
+ pip install httpie
+
Now sync your database for the first time:
python manage.py migrate
@@ -159,6 +163,30 @@ We can now access our API, both from the command-line, using tools like `curl`..
]
}
+Or with [httpie][httpie], a tastier version of `curl`...
+
+ bash: http -a username:password http://127.0.0.1:8000/users/ --body
+ {
+ "count": 2,
+ "next": null,
+ "previous": null,
+ "results": [
+ {
+ "email": "admin@example.com",
+ "groups": [],
+ "url": "http://localhost:8000/users/1/",
+ "username": "paul"
+ },
+ {
+ "email": "tom@example.com",
+ "groups": [ ],
+ "url": "http://127.0.0.1:8000/users/2/",
+ "username": "tom"
+ }
+ ]
+ }
+
+
Or directly through the browser...
![Quick start image][image]
@@ -173,3 +201,4 @@ If you want to get a more in depth understanding of how REST framework fits toge
[image]: ../img/quickstart.png
[tutorial]: 1-serialization.md
[guide]: ../#api-guide
+[httpie]: https://github.com/jakubroztocil/httpie#installation
--
cgit v1.2.3
From 555c450497e96bf8fed82fc76e70adf907c5c409 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Mon, 1 Dec 2014 14:46:47 +0000
Subject: Add missing 'validators.py' link. Closes #2166.
---
docs/api-guide/validators.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/api-guide/validators.md b/docs/api-guide/validators.md
index f087e191..8f5a8929 100644
--- a/docs/api-guide/validators.md
+++ b/docs/api-guide/validators.md
@@ -1,4 +1,4 @@
-
+source: validators.py
---
--
cgit v1.2.3
From 080fa4f5f863609f5647ce1424f13b01e9f427ad Mon Sep 17 00:00:00 2001
From: José Padilla
Date: Mon, 1 Dec 2014 11:26:50 -0400
Subject: Change allow_none to allow_null
---
docs/topics/3.0-announcement.md | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/docs/topics/3.0-announcement.md b/docs/topics/3.0-announcement.md
index 4aafb3d8..8791ad08 100644
--- a/docs/topics/3.0-announcement.md
+++ b/docs/topics/3.0-announcement.md
@@ -557,22 +557,22 @@ We now use the following:
* `Field` is the base class for all fields. It does not include any default implementation for either serializing or deserializing data.
* `ReadOnlyField` is a concrete implementation for read-only fields that simply returns the attribute value without modification.
-#### The `required`, `allow_none`, `allow_blank` and `default` arguments.
+#### The `required`, `allow_null`, `allow_blank` and `default` arguments.
REST framework now has more explicit and clear control over validating empty values for fields.
Previously the meaning of the `required=False` keyword argument was underspecified. In practice its use meant that a field could either be not included in the input, or it could be included, but be `None` or the empty string.
-We now have a better separation, with separate `required`, `allow_none` and `allow_blank` arguments.
+We now have a better separation, with separate `required`, `allow_null` and `allow_blank` arguments.
The following set of arguments are used to control validation of empty values:
* `required=False`: The value does not need to be present in the input, and will not be passed to `.create()` or `.update()` if it is not seen.
* `default=`: The value does not need to be present in the input, and a default value will be passed to `.create()` or `.update()` if it is not seen.
-* `allow_none=True`: `None` is a valid input.
+* `allow_null=True`: `None` is a valid input.
* `allow_blank=True`: `''` is valid input. For `CharField` and subclasses only.
-Typically you'll want to use `required=False` if the corresponding model field has a default value, and additionally set either `allow_none=True` or `allow_blank=True` if required.
+Typically you'll want to use `required=False` if the corresponding model field has a default value, and additionally set either `allow_null=True` or `allow_blank=True` if required.
The `default` argument is also available and always implies that the field is not required to be in the input. It is unnecessary to use the `required` argument when a default is specified, and doing so will result in an error.
--
cgit v1.2.3
From d1fe61ce94af1e942f8d1026fb84b1909c230779 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Mon, 1 Dec 2014 17:05:11 +0000
Subject: Fix requirements. Closes #2170.
---
requirements.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/requirements.txt b/requirements.txt
index 8a698230..f284644a 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1 +1 @@
-Django>=1.4.2
+Django>=1.4.11
--
cgit v1.2.3
From bc0c25df3020772124e1767895c7e7cb60d974c1 Mon Sep 17 00:00:00 2001
From: José Padilla
Date: Mon, 1 Dec 2014 17:31:11 -0400
Subject: Consolidate Django and test requirements
---
CONTRIBUTING.md | 17 ++++++++---------
docs/topics/contributing.md | 1 -
docs/topics/third-party-resources.md | 2 +-
requirements-test.txt | 15 ---------------
requirements.txt | 17 +++++++++++++++++
5 files changed, 26 insertions(+), 26 deletions(-)
delete mode 100644 requirements-test.txt
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 69802995..96e55161 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -14,7 +14,7 @@ If you use REST framework, we'd love you to be vocal about your experiences with
Other really great ways you can help move the community forward include helping answer questions on the [discussion group][google-group], or setting up an [email alert on StackOverflow][so-filter] so that you get notified of any new questions with the `django-rest-framework` tag.
-When answering questions make sure to help future contributors find their way around by hyperlinking wherever possible to related threads and tickets, and include backlinks from those items if relevant.
+When answering questions make sure to help future contributors find their way around by hyperlinking wherever possible to related threads and tickets, and include backlinks from those items if relevant.
## Code of conduct
@@ -38,7 +38,7 @@ Some tips on good issue reporting:
## Triaging issues
-Getting involved in triaging incoming issues is a good way to start contributing. Every single ticket that comes into the ticket tracker needs to be reviewed in order to determine what the next steps should be. Anyone can help out with this, you just need to be willing to
+Getting involved in triaging incoming issues is a good way to start contributing. Every single ticket that comes into the ticket tracker needs to be reviewed in order to determine what the next steps should be. Anyone can help out with this, you just need to be willing to
* Read through the ticket - does it make sense, is it missing any context that would help explain it better?
* Is the ticket reported in the correct place, would it be better suited as a discussion on the discussion group?
@@ -62,7 +62,6 @@ To run the tests, clone the repository, and then:
virtualenv env
env/bin/activate
pip install -r requirements.txt
- pip install -r requirements-test.txt
# Run the tests
./runtests.py
@@ -130,8 +129,8 @@ There are a couple of conventions you should follow when working on the document
Headers should use the hash style. For example:
### Some important topic
-
-The underline style should not be used. **Don't do this:**
+
+The underline style should not be used. **Don't do this:**
Some important topic
====================
@@ -141,9 +140,9 @@ The underline style should not be used. **Don't do this:**
Links should always use the reference style, with the referenced hyperlinks kept at the end of the document.
Here is a link to [some other thing][other-thing].
-
+
More text...
-
+
[other-thing]: http://example.com/other/thing
This style helps keep the documentation source consistent and readable.
@@ -159,9 +158,9 @@ Linking in this style means you'll be able to click the hyperlink in your markdo
If you want to draw attention to a note or warning, use a pair of enclosing lines, like so:
---
-
+
**Note:** A useful documentation note.
-
+
---
# Third party packages
diff --git a/docs/topics/contributing.md b/docs/topics/contributing.md
index 99f4fc3c..c9626ebf 100644
--- a/docs/topics/contributing.md
+++ b/docs/topics/contributing.md
@@ -62,7 +62,6 @@ To run the tests, clone the repository, and then:
virtualenv env
source env/bin/activate
pip install -r requirements.txt
- pip install -r requirements-test.txt
# Run the tests
./runtests.py
diff --git a/docs/topics/third-party-resources.md b/docs/topics/third-party-resources.md
index efa0b91f..0358d614 100644
--- a/docs/topics/third-party-resources.md
+++ b/docs/topics/third-party-resources.md
@@ -93,7 +93,7 @@ The cookiecutter template includes a `runtests.py` which uses the `pytest` packa
Before running, you'll need to install a couple test requirements.
- $ pip install -r requirements-test.txt
+ $ pip install -r requirements.txt
Once requirements installed, you can run `runtests.py`.
diff --git a/requirements-test.txt b/requirements-test.txt
deleted file mode 100644
index 06c8849a..00000000
--- a/requirements-test.txt
+++ /dev/null
@@ -1,15 +0,0 @@
-# Test requirements
-pytest-django==2.6
-pytest==2.5.2
-pytest-cov==1.6
-flake8==2.2.2
-
-# Optional packages
-markdown>=2.1.0
-PyYAML>=3.10
-defusedxml>=0.3
-django-guardian==1.2.4
-django-filter>=0.5.4
-django-oauth-plus>=2.2.1
-oauth2>=1.5.211
-django-oauth2-provider>=0.2.4
diff --git a/requirements.txt b/requirements.txt
index f284644a..f282d3ba 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1 +1,18 @@
+# Minimum Django version
Django>=1.4.11
+
+# Test requirements
+pytest-django==2.6
+pytest==2.5.2
+pytest-cov==1.6
+flake8==2.2.2
+
+# Optional packages
+markdown>=2.1.0
+PyYAML>=3.10
+defusedxml>=0.3
+django-guardian==1.2.4
+django-filter>=0.5.4
+django-oauth-plus>=2.2.1
+oauth2>=1.5.211
+django-oauth2-provider>=0.2.4
--
cgit v1.2.3
From f4fc4670ca491eabd5bcdfcef382d8373dd5e380 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Tue, 2 Dec 2014 08:53:36 +0000
Subject: Promote 'many_init' to public API. Closes #2152.
---
docs/api-guide/serializers.md | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/docs/api-guide/serializers.md b/docs/api-guide/serializers.md
index 0ee80d53..4c78473e 100644
--- a/docs/api-guide/serializers.md
+++ b/docs/api-guide/serializers.md
@@ -689,6 +689,21 @@ Here's an example of how you might choose to implement multiple updates:
It is possible that a third party package may be included alongside the 3.1 release that provides some automatic support for multiple update operations, similar to the `allow_add_remove` behavior that was present in REST framework 2.
+#### Customizing ListSerializer initialization
+
+When a serializer with `many=True` is instantiated, we need to determine which arguments and keyword arguments should be passed to the `.__init__()` method for both the child `Serializer` class, and for the parent `ListSerializer` class.
+
+The default implementation is to pass all arguments to both classes, except for `validators`, and any custom keyword arguments, both of which are assumed to be intended for the child serializer class.
+
+Occasionally you might need to explicitly specify how the child and parent classes should be instantiated when `many=True` is passed. You can do so by using the `many_init` class method.
+
+ @classmethod
+ def many_init(cls, *args, **kwargs):
+ # Instantiate the child serializer.
+ kwargs['child'] = cls()
+ # Instantiate the parent list serializer.
+ return CustomListSerializer(*args, **kwargs)
+
---
# BaseSerializer
--
cgit v1.2.3
From 53f52765fc90472a05cbeb34760b45f735a7332c Mon Sep 17 00:00:00 2001
From: BrickXu
Date: Tue, 2 Dec 2014 12:55:34 +0800
Subject: Not allow to pass an empty actions to viewset.as_view(). Refs issue
#2171
---
rest_framework/viewsets.py | 6 ++++++
tests/test_viewsets.py | 35 +++++++++++++++++++++++++++++++++++
2 files changed, 41 insertions(+)
create mode 100644 tests/test_viewsets.py
diff --git a/rest_framework/viewsets.py b/rest_framework/viewsets.py
index 84b4bd8d..70d14695 100644
--- a/rest_framework/viewsets.py
+++ b/rest_framework/viewsets.py
@@ -48,6 +48,12 @@ class ViewSetMixin(object):
# eg. 'List' or 'Instance'.
cls.suffix = None
+ # actions must not be empty
+ if not actions:
+ raise TypeError("The `actions` argument must be provided when "
+ "calling `.as_view()` on a ViewSet. For example "
+ "`.as_view({'get': 'list'})`")
+
# sanitize keyword arguments
for key in initkwargs:
if key in cls.http_method_names:
diff --git a/tests/test_viewsets.py b/tests/test_viewsets.py
new file mode 100644
index 00000000..4d18a955
--- /dev/null
+++ b/tests/test_viewsets.py
@@ -0,0 +1,35 @@
+from django.test import TestCase
+from rest_framework import status
+from rest_framework.response import Response
+from rest_framework.test import APIRequestFactory
+from rest_framework.viewsets import GenericViewSet
+
+
+factory = APIRequestFactory()
+
+
+class BasicViewSet(GenericViewSet):
+ def list(self, request, *args, **kwargs):
+ return Response({'ACTION': 'LIST'})
+
+
+class InitializeViewSetsTestCase(TestCase):
+ def test_initialize_view_set_with_actions(self):
+ request = factory.get('/', '', content_type='application/json')
+ my_view = BasicViewSet.as_view(actions={
+ 'get': 'list',
+ })
+
+ response = my_view(request)
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(response.data, {'ACTION': 'LIST'})
+
+ def test_initialize_view_set_with_empty_actions(self):
+ try:
+ BasicViewSet.as_view()
+ except TypeError as e:
+ self.assertEqual(str(e), "The `actions` argument must be provided "
+ "when calling `.as_view()` on a ViewSet. "
+ "For example `.as_view({'get': 'list'})`")
+ else:
+ self.fail("actions must not be empty.")
--
cgit v1.2.3
From 6ac79b822325784ad145ff0ad064127750c4f7e0 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Tue, 2 Dec 2014 09:19:59 +0000
Subject: Document Field.fail(). Closes #2147.
---
docs/api-guide/fields.md | 49 +++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 48 insertions(+), 1 deletion(-)
diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md
index 391a52e5..aa5cc84e 100644
--- a/docs/api-guide/fields.md
+++ b/docs/api-guide/fields.md
@@ -453,7 +453,7 @@ If you want to create a custom field, you'll need to subclass `Field` and then o
The `.to_representation()` method is called to convert the initial datatype into a primitive, serializable datatype.
-The `to_internal_value()` method is called to restore a primitive datatype into its internal python representation.
+The `to_internal_value()` method is called to restore a primitive datatype into its internal python representation. This method should raise a `serializer.ValidationError` if the data is invalid.
Note that the `WritableField` class that was present in version 2.x no longer exists. You should subclass `Field` and override `to_internal_value()` if the field supports data input.
@@ -498,6 +498,53 @@ As an example, let's create a field that can be used represent the class name of
"""
return obj.__class__.__name__
+#### Raising validation errors
+
+Our `ColorField` class above currently does not perform any data validation.
+To indicate invalid data, we should raise a `serializers.ValidationError`, like so:
+
+ def to_internal_value(self, data):
+ if not isinstance(data, six.text_type):
+ msg = 'Incorrect type. Expected a string, but got %s'
+ raise ValidationError(msg % type(data).__name__)
+
+ if not re.match(r'^rgb\([0-9]+,[0-9]+,[0-9]+\)$', data):
+ raise ValidationError('Incorrect format. Expected `rgb(#,#,#)`.')
+
+ data = data.strip('rgb(').rstrip(')')
+ red, green, blue = [int(col) for col in data.split(',')]
+
+ if any([col > 255 or col < 0 for col in (red, green, blue)]):
+ raise ValidationError('Value out of range. Must be between 0 and 255.')
+
+ return Color(red, green, blue)
+
+The `.fail()` method is a shortcut for raising `ValidationError` that takes a message string from the `error_messages` dictionary. For example:
+
+ default_error_messages = {
+ 'incorrect_type': 'Incorrect type. Expected a string, but got {input_type}',
+ 'incorrect_format': 'Incorrect format. Expected `rgb(#,#,#)`.',
+ 'out_of_range': 'Value out of range. Must be between 0 and 255.'
+ }
+
+ def to_internal_value(self, data):
+ if not isinstance(data, six.text_type):
+ msg = 'Incorrect type. Expected a string, but got %s'
+ self.fail('incorrect_type', input_type=type(data).__name__)
+
+ if not re.match(r'^rgb\([0-9]+,[0-9]+,[0-9]+\)$', data):
+ self.fail('incorrect_format')
+
+ data = data.strip('rgb(').rstrip(')')
+ red, green, blue = [int(col) for col in data.split(',')]
+
+ if any([col > 255 or col < 0 for col in (red, green, blue)]):
+ self.fail('out_of_range')
+
+ return Color(red, green, blue)
+
+This style keeps you error messages more cleanly separated from your code, and should be preferred.
+
# Third party packages
The following third party packages are also available.
--
cgit v1.2.3
From 79e18a2a06178e8c00dfafc1cfd062f2528ec2c1 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Tue, 2 Dec 2014 09:27:40 +0000
Subject: Raise assertion error if calling .save() on a serializer with errors.
Closes #2098.
---
rest_framework/serializers.py | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index 0d0a4d9a..a4140c0f 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -127,6 +127,14 @@ class BaseSerializer(Field):
(self.__class__.__module__, self.__class__.__name__)
)
+ assert hasattr(self, '_errors'), (
+ 'You must call `.is_valid()` before calling `.save()`.'
+ )
+
+ assert not self.errors, (
+ 'You cannot call `.save()` on a serializer with invalid data.'
+ )
+
validated_data = dict(
list(self.validated_data.items()) +
list(kwargs.items())
--
cgit v1.2.3
From 515060a6ab71e1ef22f4a1e03cb23dbad28a7b23 Mon Sep 17 00:00:00 2001
From: phalt
Date: Tue, 2 Dec 2014 10:16:41 +0000
Subject: Only show pip install for httpie
---
docs/tutorial/1-serialization.md | 6 +-----
1 file changed, 1 insertion(+), 5 deletions(-)
diff --git a/docs/tutorial/1-serialization.md b/docs/tutorial/1-serialization.md
index 3ef65116..dc4fddf9 100644
--- a/docs/tutorial/1-serialization.md
+++ b/docs/tutorial/1-serialization.md
@@ -332,16 +332,12 @@ Quit out of the shell...
In another terminal window, we can test the server.
-We could use `curl`, but let's use a nicer tool called [httpie][httpie] to test our server. It has much nicer formatting and makes our output easier to read. This is especially useful when testing.
+We can test our API using using `curl` or [httpie][httpie]. Httpie is a user friendly http client that's written in Python. Let's install that.
You can install httpie on all operating systems using pip:
pip install httpie
-It can also be installed through [Homebrew][brew] on Mac:
-
- brew install httpie
-
Finally, we can get a list of all of the snippets:
http http://127.0.0.1:8000/snippets/ --body
--
cgit v1.2.3
From 76ac641fbd6c9d7dff5da3c551c3fd1ef7dedd2e Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Tue, 2 Dec 2014 13:04:49 +0000
Subject: Minor tweaks for helpful message on Model.objects.create() failure.
---
rest_framework/serializers.py | 23 ++++++++++++++---------
tests/test_model_serializer.py | 10 +++-------
2 files changed, 17 insertions(+), 16 deletions(-)
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index 143d205d..d417ca80 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -35,7 +35,6 @@ from rest_framework.validators import (
)
import copy
import inspect
-import sys
import warnings
# Note: We do the following so that users of the framework can use this style:
@@ -658,14 +657,20 @@ class ModelSerializer(Serializer):
instance = ModelClass.objects.create(**validated_attrs)
except TypeError as exc:
msg = (
- 'The mentioned argument might be a field on the serializer '
- 'that is not part of the model. You need to override the '
- 'create() method in your ModelSerializer subclass to support '
- 'this.')
- six.reraise(
- type(exc),
- type(exc)(str(exc) + '. ' + msg),
- sys.exc_info()[2])
+ 'Got a `TypeError` when calling `%s.objects.create()`. '
+ 'This may be because you have a writable field on the '
+ 'serializer class that is not a valid argument to '
+ '`%s.objects.create()`. You may need to make the field '
+ 'read-only, or override the %s.create() method to handle '
+ 'this correctly.\nOriginal exception text was: %s.' %
+ (
+ ModelClass.__name__,
+ ModelClass.__name__,
+ self.__class__.__name__,
+ exc
+ )
+ )
+ raise TypeError(msg)
# Save many-to-many relationships after the instance is created.
if many_to_many:
diff --git a/tests/test_model_serializer.py b/tests/test_model_serializer.py
index 90767dac..1bcd58e0 100644
--- a/tests/test_model_serializer.py
+++ b/tests/test_model_serializer.py
@@ -10,7 +10,6 @@ from django.core.validators import MaxValueValidator, MinValueValidator, MinLeng
from django.db import models
from django.test import TestCase
from rest_framework import serializers
-import pytest
def dedent(blocktext):
@@ -87,13 +86,10 @@ class TestModelSerializer(TestCase):
'non_model_field': 'bar',
})
serializer.is_valid()
- with pytest.raises(TypeError):
+ with self.assertRaises(TypeError) as excinfo:
serializer.save()
-
- try:
- serializer.save()
- except TypeError as exc:
- assert 'ModelSerializer' in str(exc)
+ msginitial = 'Got a `TypeError` when calling `OneFieldModel.objects.create()`.'
+ assert str(excinfo.exception).startswith(msginitial)
class TestRegularFieldMappings(TestCase):
--
cgit v1.2.3
From e30e3f6dfc8cdb47c1048bbe497599d250d7bf75 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Tue, 2 Dec 2014 13:14:11 +0000
Subject: Update README with 3.0 info.
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 31fe1e52..95d05c9b 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,7 @@ Full documentation for the project is available at [http://www.django-rest-frame
---
-**Note**: The incoming 3.0 version has now been merged to the `master` branch on GitHub. For the source of the currently available PyPI version, please see the `2.4.4` tag.
+**Note**: We have now released Django REST framework 3.0. For older codebases you may want to refer to the version 2.4.4 [source code](https://github.com/tomchristie/django-rest-framework/tree/version-2.4.x), and [documentation](http://tomchristie.github.io/rest-framework-2-docs/).
---
--
cgit v1.2.3
From 0359e9250d34e18aef2db6216f24c130a4f51fce Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Tue, 2 Dec 2014 13:52:46 +0000
Subject: FileUploadParser. Raising StopFutureHandlers removes any handlers not
yet run for the active set. Closes #2109.
---
rest_framework/parsers.py | 13 +++++++------
tests/test_parsers.py | 32 ++++++++++++++++++++++++++++++--
2 files changed, 37 insertions(+), 8 deletions(-)
diff --git a/rest_framework/parsers.py b/rest_framework/parsers.py
index ccb82f03..d229abec 100644
--- a/rest_framework/parsers.py
+++ b/rest_framework/parsers.py
@@ -256,23 +256,24 @@ class FileUploadParser(BaseParser):
chunks = ChunkIter(stream, chunk_size)
counters = [0] * len(upload_handlers)
- for handler in upload_handlers:
+ for index, handler in enumerate(upload_handlers):
try:
handler.new_file(None, filename, content_type,
content_length, encoding)
except StopFutureHandlers:
+ upload_handlers = upload_handlers[:index + 1]
break
for chunk in chunks:
- for i, handler in enumerate(upload_handlers):
+ for index, handler in enumerate(upload_handlers):
chunk_length = len(chunk)
- chunk = handler.receive_data_chunk(chunk, counters[i])
- counters[i] += chunk_length
+ chunk = handler.receive_data_chunk(chunk, counters[index])
+ counters[index] += chunk_length
if chunk is None:
break
- for i, handler in enumerate(upload_handlers):
- file_obj = handler.file_complete(counters[i])
+ for index, handler in enumerate(upload_handlers):
+ file_obj = handler.file_complete(counters[index])
if file_obj:
return DataAndFiles(None, {'file': file_obj})
raise ParseError("FileUpload parse error - "
diff --git a/tests/test_parsers.py b/tests/test_parsers.py
index 3f2672df..88eccef3 100644
--- a/tests/test_parsers.py
+++ b/tests/test_parsers.py
@@ -1,12 +1,13 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
-from rest_framework.compat import StringIO
from django import forms
from django.core.files.uploadhandler import MemoryFileUploadHandler
from django.test import TestCase
from django.utils import unittest
from rest_framework.compat import etree
+from rest_framework.compat import StringIO
+from rest_framework.exceptions import ParseError
from rest_framework.parsers import FormParser, FileUploadParser
from rest_framework.parsers import XMLParser
import datetime
@@ -104,13 +105,40 @@ class TestFileUploadParser(TestCase):
self.parser_context = {'request': request, 'kwargs': {}}
def test_parse(self):
- """ Make sure the `QueryDict` works OK """
+ """
+ Parse raw file upload.
+ """
parser = FileUploadParser()
self.stream.seek(0)
data_and_files = parser.parse(self.stream, None, self.parser_context)
file_obj = data_and_files.files['file']
self.assertEqual(file_obj._size, 14)
+ def test_parse_missing_filename(self):
+ """
+ Parse raw file upload when filename is missing.
+ """
+ parser = FileUploadParser()
+ self.stream.seek(0)
+ self.parser_context['request'].META['HTTP_CONTENT_DISPOSITION'] = ''
+ with self.assertRaises(ParseError):
+ parser.parse(self.stream, None, self.parser_context)
+
+ def test_parse_missing_filename_multiple_upload_handlers(self):
+ """
+ Parse raw file upload with multiple handlers when filename is missing.
+ Regression test for #2109.
+ """
+ parser = FileUploadParser()
+ self.stream.seek(0)
+ self.parser_context['request'].upload_handlers = (
+ MemoryFileUploadHandler(),
+ MemoryFileUploadHandler()
+ )
+ self.parser_context['request'].META['HTTP_CONTENT_DISPOSITION'] = ''
+ with self.assertRaises(ParseError):
+ parser.parse(self.stream, None, self.parser_context)
+
def test_get_filename(self):
parser = FileUploadParser()
filename = parser.get_filename(self.stream, None, self.parser_context)
--
cgit v1.2.3
From 84cff98fbf72355cb5e8359aa1c9b5568c289cbf Mon Sep 17 00:00:00 2001
From: David Ray
Date: Tue, 2 Dec 2014 09:46:43 -0500
Subject: fix typo
---
docs/api-guide/serializers.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/api-guide/serializers.md b/docs/api-guide/serializers.md
index 4c78473e..a011bb52 100644
--- a/docs/api-guide/serializers.md
+++ b/docs/api-guide/serializers.md
@@ -96,7 +96,7 @@ If we want to be able to return complete object instances based on the validated
If your object instances correspond to Django models you'll also want to ensure that these methods save the object to the database. For example, if `Comment` was a Django model, the methods might look like this:
def create(self, validated_data):
- return Comment.objcts.create(**validated_data)
+ return Comment.objects.create(**validated_data)
def update(self, instance, validated_data):
instance.email = validated_data.get('email', instance.email)
--
cgit v1.2.3
From 33096a1de6c20581caab36bc1af0e686d47483e7 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Tue, 2 Dec 2014 15:15:21 +0000
Subject: BindingDict inherits from collections.MutableMapping. Closes #2135.
---
rest_framework/utils/serializer_helpers.py | 14 ++++++--------
1 file changed, 6 insertions(+), 8 deletions(-)
diff --git a/rest_framework/utils/serializer_helpers.py b/rest_framework/utils/serializer_helpers.py
index 92d19857..277cf649 100644
--- a/rest_framework/utils/serializer_helpers.py
+++ b/rest_framework/utils/serializer_helpers.py
@@ -1,3 +1,4 @@
+import collections
from rest_framework.compat import OrderedDict
@@ -70,7 +71,7 @@ class NestedBoundField(BoundField):
return BoundField(field, value, error, prefix=self.name + '.')
-class BindingDict(object):
+class BindingDict(collections.MutableMapping):
"""
This dict-like object is used to store fields on a serializer.
@@ -92,11 +93,8 @@ class BindingDict(object):
def __delitem__(self, key):
del self.fields[key]
- def items(self):
- return self.fields.items()
-
- def keys(self):
- return self.fields.keys()
+ def __iter__(self):
+ return iter(self.fields)
- def values(self):
- return self.fields.values()
+ def __len__(self):
+ return len(self.fields)
--
cgit v1.2.3
From 5ad22aea605f888e06186d907674669c46a611ef Mon Sep 17 00:00:00 2001
From: Matías Lang
Date: Tue, 2 Dec 2014 12:23:25 -0300
Subject: Updated serializers documentation
There was an error in the docs: the field extra_field_kwargs of the serializer's Meta doesn't work. The field must be extra_kwargs instead.---
docs/api-guide/serializers.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/api-guide/serializers.md b/docs/api-guide/serializers.md
index a011bb52..1779c863 100644
--- a/docs/api-guide/serializers.md
+++ b/docs/api-guide/serializers.md
@@ -567,13 +567,13 @@ There needs to be a way of determining which views should be used for hyperlinki
By default hyperlinks are expected to correspond to a view name that matches the style `'{model_name}-detail'`, and looks up the instance by a `pk` keyword argument.
-You can override a URL field view name and lookup field by using either, or both of, the `view_name` and `lookup_field` options in the `extra_field_kwargs` setting, like so:
+You can override a URL field view name and lookup field by using either, or both of, the `view_name` and `lookup_field` options in the `extra_kwargs` setting, like so:
class AccountSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Account
fields = ('account_url', 'account_name', 'users', 'created')
- extra_field_kwargs = {
+ extra_kwargs = {
'url': {'view_name': 'accounts', 'lookup_field': 'account_name'}
'users': {'lookup_field': 'username'}
}
--
cgit v1.2.3
From a17d5d2b0bff535dc1d7dcbd36947648e7a0511f Mon Sep 17 00:00:00 2001
From: phalt
Date: Tue, 2 Dec 2014 16:11:43 +0000
Subject: remove unsused link
---
docs/tutorial/1-serialization.md | 1 -
1 file changed, 1 deletion(-)
diff --git a/docs/tutorial/1-serialization.md b/docs/tutorial/1-serialization.md
index dc4fddf9..ba2a0c32 100644
--- a/docs/tutorial/1-serialization.md
+++ b/docs/tutorial/1-serialization.md
@@ -390,4 +390,3 @@ We'll see how we can start to improve things in [part 2 of the tutorial][tut-2].
[virtualenv]: http://www.virtualenv.org/en/latest/index.html
[tut-2]: 2-requests-and-responses.md
[httpie]: https://github.com/jakubroztocil/httpie#installation
-[brew]: http://brew.sh
--
cgit v1.2.3
From 9397a653a4cfd6725616ea8e1b23093d3b0d59b5 Mon Sep 17 00:00:00 2001
From: eiriksm
Date: Wed, 3 Dec 2014 09:22:42 +0100
Subject: Use svg version of travis build status badge
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 95d05c9b..b8957545 100644
--- a/README.md
+++ b/README.md
@@ -180,7 +180,7 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-[build-status-image]: https://secure.travis-ci.org/tomchristie/django-rest-framework.png?branch=master
+[build-status-image]: https://secure.travis-ci.org/tomchristie/django-rest-framework.svg?branch=master
[travis]: http://travis-ci.org/tomchristie/django-rest-framework?branch=master
[pypi-version]: https://pypip.in/version/djangorestframework/badge.svg
[pypi]: https://pypi.python.org/pypi/djangorestframework
--
cgit v1.2.3
From 55528addf97c1d3ee24bb3332fb6dcb5496de158 Mon Sep 17 00:00:00 2001
From: Mark Hughes
Date: Wed, 3 Dec 2014 12:30:15 +0000
Subject: Added test to show potential issue with UniqueValidator being added
to the underlying Django model.
---
tests/test_validators.py | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/tests/test_validators.py b/tests/test_validators.py
index 1df0641c..9226cc7a 100644
--- a/tests/test_validators.py
+++ b/tests/test_validators.py
@@ -20,6 +20,15 @@ class UniquenessSerializer(serializers.ModelSerializer):
model = UniquenessModel
+class AnotherUniquenessModel(models.Model):
+ code = models.IntegerField(unique=True)
+
+
+class AnotherUniquenessSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = AnotherUniquenessModel
+
+
class TestUniquenessValidation(TestCase):
def setUp(self):
self.instance = UniquenessModel.objects.create(username='existing')
@@ -51,6 +60,17 @@ class TestUniquenessValidation(TestCase):
assert serializer.is_valid()
assert serializer.validated_data == {'username': 'existing'}
+ def test_doesnt_pollute_model(self):
+ instance = AnotherUniquenessModel.objects.create(code='100')
+ serializer = AnotherUniquenessSerializer(instance)
+ self.assertEqual(
+ AnotherUniquenessModel._meta.get_field('code').validators, [])
+
+ # Accessing data shouldn't effect validators on the model
+ serializer.data
+ self.assertEqual(
+ AnotherUniquenessModel._meta.get_field('code').validators, [])
+
# Tests for `UniqueTogetherValidator`
# -----------------------------------
--
cgit v1.2.3
From 74a9ece3dcf76372c26aaf3bdd6c48bbfbf45f99 Mon Sep 17 00:00:00 2001
From: eiriksm
Date: Wed, 3 Dec 2014 13:37:56 +0100
Subject: Update build status icon on github pages page.
---
docs/index.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/index.md b/docs/index.md
index e0ba2332..52e42fc9 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -4,7 +4,7 @@
-
+
---
--
cgit v1.2.3
From 23fa6e54ce978055f7d4af5f5f99bc6f419f990b Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Wed, 3 Dec 2014 22:33:34 +0000
Subject: Escape \u2028 and \u2029 in JSON output.
Closes #2169.
---
rest_framework/renderers.py | 5 +++++
tests/test_renderers.py | 9 +++++++++
2 files changed, 14 insertions(+)
diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py
index e87d16d0..64ad5a06 100644
--- a/rest_framework/renderers.py
+++ b/rest_framework/renderers.py
@@ -102,6 +102,11 @@ class JSONRenderer(BaseRenderer):
# and may (or may not) be unicode.
# On python 3.x json.dumps() returns unicode strings.
if isinstance(ret, six.text_type):
+ # We always fully escape \u2028 and \u2029 to ensure we output JSON
+ # that is a strict javascript subset. If bytes were returned
+ # by json.dumps() then we don't have these characters in any case.
+ # See: http://timelessrepo.com/json-isnt-a-javascript-subset
+ ret = ret.replace('\u2028', '\\u2028').replace('\u2029', '\\u2029')
return bytes(ret.encode('utf-8'))
return ret
diff --git a/tests/test_renderers.py b/tests/test_renderers.py
index 416d7f22..61dd7c7a 100644
--- a/tests/test_renderers.py
+++ b/tests/test_renderers.py
@@ -384,6 +384,15 @@ class UnicodeJSONRendererTests(TestCase):
content = renderer.render(obj, 'application/json')
self.assertEqual(content, '{"countries":["United Kingdom","France","España"]}'.encode('utf-8'))
+ def test_u2028_u2029(self):
+ # The \u2028 and \u2029 characters should be escaped,
+ # even when the non-escaping unicode representation is used.
+ # Regression test for #2169
+ obj = {'should_escape': '\u2028\u2029'}
+ renderer = JSONRenderer()
+ content = renderer.render(obj, 'application/json')
+ self.assertEqual(content, '{"should_escape":"\\u2028\\u2029"}'.encode('utf-8'))
+
class AsciiJSONRendererTests(TestCase):
"""
--
cgit v1.2.3
From f2dd05a6e661525908fe5ec99b52b5274b04a198 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Wed, 3 Dec 2014 22:43:40 +0000
Subject: Improved nested update test in update().
Closes #2194.
---
rest_framework/serializers.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index d417ca80..b1175b5b 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -681,8 +681,8 @@ class ModelSerializer(Serializer):
def update(self, instance, validated_attrs):
assert not any(
- isinstance(field, BaseSerializer) and not field.read_only
- for field in self.fields.values()
+ isinstance(field, BaseSerializer) and (key in validated_attrs)
+ for key, field in self.fields.values()
), (
'The `.update()` method does not suport nested writable fields '
'by default. Write an explicit `.update()` method for serializer '
--
cgit v1.2.3
From e1d98f77563abf49c4b19dcfb95f263515ae4087 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Wed, 3 Dec 2014 22:45:44 +0000
Subject: Improve nested update and create testing.
---
rest_framework/serializers.py | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index b1175b5b..c7f04b40 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -633,8 +633,8 @@ class ModelSerializer(Serializer):
# If we don't do this explicitly they'd likely get a confusing
# error at the point of calling `Model.objects.create()`.
assert not any(
- isinstance(field, BaseSerializer) and not field.read_only
- for field in self.fields.values()
+ isinstance(field, BaseSerializer) and (key in validated_attrs)
+ for key, field in self.fields.items()
), (
'The `.create()` method does not suport nested writable fields '
'by default. Write an explicit `.create()` method for serializer '
@@ -682,7 +682,7 @@ class ModelSerializer(Serializer):
def update(self, instance, validated_attrs):
assert not any(
isinstance(field, BaseSerializer) and (key in validated_attrs)
- for key, field in self.fields.values()
+ for key, field in self.fields.items()
), (
'The `.update()` method does not suport nested writable fields '
'by default. Write an explicit `.update()` method for serializer '
--
cgit v1.2.3
From ab25d706c78627dfd582fe9d142ada510c4d6d90 Mon Sep 17 00:00:00 2001
From: Martin Tschammer
Date: Wed, 3 Dec 2014 23:52:35 +0100
Subject: Renamed validated_attrs to validated_data to be more in line with
other similar code.
---
docs/tutorial/1-serialization.md | 16 ++++++++--------
rest_framework/serializers.py | 20 ++++++++++----------
2 files changed, 18 insertions(+), 18 deletions(-)
diff --git a/docs/tutorial/1-serialization.md b/docs/tutorial/1-serialization.md
index a3c19858..52c75d2c 100644
--- a/docs/tutorial/1-serialization.md
+++ b/docs/tutorial/1-serialization.md
@@ -110,21 +110,21 @@ The first thing we need to get started on our Web API is to provide a way of ser
style = serializers.ChoiceField(choices=STYLE_CHOICES,
default='friendly')
- def create(self, validated_attrs):
+ def create(self, validated_data):
"""
Create and return a new `Snippet` instance, given the validated data.
"""
- return Snippet.objects.create(**validated_attrs)
+ return Snippet.objects.create(**validated_data)
- def update(self, instance, validated_attrs):
+ def update(self, instance, validated_data):
"""
Update and return an existing `Snippet` instance, given the validated data.
"""
- instance.title = validated_attrs.get('title', instance.title)
- instance.code = validated_attrs.get('code', instance.code)
- instance.linenos = validated_attrs.get('linenos', instance.linenos)
- instance.language = validated_attrs.get('language', instance.language)
- instance.style = validated_attrs.get('style', instance.style)
+ instance.title = validated_data.get('title', instance.title)
+ instance.code = validated_data.get('code', instance.code)
+ instance.linenos = validated_data.get('linenos', instance.linenos)
+ instance.language = validated_data.get('language', instance.language)
+ instance.style = validated_data.get('style', instance.style)
instance.save()
return instance
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index d417ca80..a289b021 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -608,20 +608,20 @@ class ModelSerializer(Serializer):
})
_related_class = PrimaryKeyRelatedField
- def create(self, validated_attrs):
+ def create(self, validated_data):
"""
We have a bit of extra checking around this in order to provide
descriptive messages when something goes wrong, but this method is
essentially just:
- return ExampleModel.objects.create(**validated_attrs)
+ return ExampleModel.objects.create(**validated_data)
If there are many to many fields present on the instance then they
cannot be set until the model is instantiated, in which case the
implementation is like so:
- example_relationship = validated_attrs.pop('example_relationship')
- instance = ExampleModel.objects.create(**validated_attrs)
+ example_relationship = validated_data.pop('example_relationship')
+ instance = ExampleModel.objects.create(**validated_data)
instance.example_relationship = example_relationship
return instance
@@ -644,17 +644,17 @@ class ModelSerializer(Serializer):
ModelClass = self.Meta.model
- # Remove many-to-many relationships from validated_attrs.
+ # Remove many-to-many relationships from validated_data.
# They are not valid arguments to the default `.create()` method,
# as they require that the instance has already been saved.
info = model_meta.get_field_info(ModelClass)
many_to_many = {}
for field_name, relation_info in info.relations.items():
- if relation_info.to_many and (field_name in validated_attrs):
- many_to_many[field_name] = validated_attrs.pop(field_name)
+ if relation_info.to_many and (field_name in validated_data):
+ many_to_many[field_name] = validated_data.pop(field_name)
try:
- instance = ModelClass.objects.create(**validated_attrs)
+ instance = ModelClass.objects.create(**validated_data)
except TypeError as exc:
msg = (
'Got a `TypeError` when calling `%s.objects.create()`. '
@@ -679,7 +679,7 @@ class ModelSerializer(Serializer):
return instance
- def update(self, instance, validated_attrs):
+ def update(self, instance, validated_data):
assert not any(
isinstance(field, BaseSerializer) and not field.read_only
for field in self.fields.values()
@@ -690,7 +690,7 @@ class ModelSerializer(Serializer):
(self.__class__.__module__, self.__class__.__name__)
)
- for attr, value in validated_attrs.items():
+ for attr, value in validated_data.items():
setattr(instance, attr, value)
instance.save()
return instance
--
cgit v1.2.3
From f8fdfe5a9379bd72066f7ac3a05198271a74088a Mon Sep 17 00:00:00 2001
From: Tymur Maryokhin
Date: Thu, 4 Dec 2014 00:03:08 +0100
Subject: Updated Django versions to match requirements
---
tox.ini | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/tox.ini b/tox.ini
index d5cb9ef9..933ee560 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,6 +1,6 @@
[tox]
envlist =
- py27-flake8,
+ py27-{flake8,docs},
{py26,py27}-django14,
{py26,py27,py32,py33,py34}-django{15,16},
{py27,py32,py33,py34}-django{17,master}
@@ -10,8 +10,8 @@ commands = ./runtests.py --fast
setenv =
PYTHONDONTWRITEBYTECODE=1
deps =
- django14: Django==1.4.16
- django15: Django==1.5.11
+ django14: Django==1.4.11
+ django15: Django==1.5.5
django16: Django==1.6.8
django17: Django==1.7.1
djangomaster: https://github.com/django/django/zipball/master
--
cgit v1.2.3
From 09e59f268619927dc22f15fed97c3ceac05ea306 Mon Sep 17 00:00:00 2001
From: Tymur Maryokhin
Date: Thu, 4 Dec 2014 02:50:25 +0100
Subject: Removed custom python_2_unicode_compatible. Closes #2183
---
rest_framework/authtoken/models.py | 5 ++++-
rest_framework/compat.py | 42 +++++++++++---------------------------
rest_framework/utils/mediatypes.py | 5 ++---
tests/test_description.py | 5 ++---
tests/test_relations_generic.py | 2 +-
5 files changed, 21 insertions(+), 38 deletions(-)
diff --git a/rest_framework/authtoken/models.py b/rest_framework/authtoken/models.py
index db21d44c..a1a9315f 100644
--- a/rest_framework/authtoken/models.py
+++ b/rest_framework/authtoken/models.py
@@ -1,7 +1,9 @@
import binascii
import os
+
from django.conf import settings
from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
# Prior to Django 1.5, the AUTH_USER_MODEL setting does not exist.
@@ -11,6 +13,7 @@ from django.db import models
AUTH_USER_MODEL = getattr(settings, 'AUTH_USER_MODEL', 'auth.User')
+@python_2_unicode_compatible
class Token(models.Model):
"""
The default authorization token model.
@@ -35,5 +38,5 @@ class Token(models.Model):
def generate_key(self):
return binascii.hexlify(os.urandom(20)).decode()
- def __unicode__(self):
+ def __str__(self):
return self.key
diff --git a/rest_framework/compat.py b/rest_framework/compat.py
index 5bd85e74..eefaf22e 100644
--- a/rest_framework/compat.py
+++ b/rest_framework/compat.py
@@ -6,11 +6,12 @@ versions of django/python, and compatibility wrappers around optional packages.
# flake8: noqa
from __future__ import unicode_literals
+import inspect
+
from django.core.exceptions import ImproperlyConfigured
from django.conf import settings
from django.utils import six
import django
-import inspect
# Handle django.utils.encoding rename in 1.5 onwards.
@@ -49,7 +50,6 @@ try:
except ImportError:
django_filters = None
-
if django.VERSION >= (1, 6):
def clean_manytomany_helptext(text):
return text
@@ -123,7 +123,6 @@ else:
return [m.upper() for m in self.http_method_names if hasattr(self, m)]
-
# MinValueValidator, MaxValueValidator et al. only accept `message` in 1.8+
if django.VERSION >= (1, 8):
from django.core.validators import MinValueValidator, MaxValueValidator
@@ -187,6 +186,7 @@ if 'patch' not in View.http_method_names:
# RequestFactory only provides `generic` from 1.5 onwards
from django.test.client import RequestFactory as DjangoRequestFactory
from django.test.client import FakePayload
+
try:
# In 1.5 the test client uses force_bytes
from django.utils.encoding import force_bytes as force_bytes_or_smart_bytes
@@ -194,21 +194,22 @@ except ImportError:
# In 1.4 the test client just uses smart_str
from django.utils.encoding import smart_str as force_bytes_or_smart_bytes
+
class RequestFactory(DjangoRequestFactory):
def generic(self, method, path,
data='', content_type='application/octet-stream', **extra):
parsed = urlparse.urlparse(path)
data = force_bytes_or_smart_bytes(data, settings.DEFAULT_CHARSET)
r = {
- 'PATH_INFO': self._get_path(parsed),
- 'QUERY_STRING': force_text(parsed[4]),
+ 'PATH_INFO': self._get_path(parsed),
+ 'QUERY_STRING': force_text(parsed[4]),
'REQUEST_METHOD': six.text_type(method),
}
if data:
r.update({
'CONTENT_LENGTH': len(data),
- 'CONTENT_TYPE': six.text_type(content_type),
- 'wsgi.input': FakePayload(data),
+ 'CONTENT_TYPE': six.text_type(content_type),
+ 'wsgi.input': FakePayload(data),
})
elif django.VERSION <= (1, 4):
# For 1.3 we need an empty WSGI payload
@@ -287,10 +288,12 @@ try:
import provider as oauth2_provider
from provider import scope as oauth2_provider_scope
from provider import constants as oauth2_constants
+
if oauth2_provider.__version__ in ('0.2.3', '0.2.4'):
# 0.2.3 and 0.2.4 are supported version that do not support
# timezone aware datetimes
import datetime
+
provider_now = datetime.datetime.now
else:
# Any other supported version does use timezone aware datetimes
@@ -301,7 +304,7 @@ except ImportError:
oauth2_constants = None
provider_now = None
-# `seperators` argument to `json.dumps()` differs between 2.x and 3.x
+# `separators` argument to `json.dumps()` differs between 2.x and 3.x
# See: http://bugs.python.org/issue22767
if six.PY3:
SHORT_SEPARATORS = (',', ':')
@@ -316,30 +319,9 @@ from django.utils.functional import Promise
if six.PY3:
def is_non_str_iterable(obj):
- if (isinstance(obj, str) or
- (isinstance(obj, Promise) and obj._delegate_text)):
+ if isinstance(obj, str) or (isinstance(obj, Promise) and obj._delegate_text):
return False
return hasattr(obj, '__iter__')
else:
def is_non_str_iterable(obj):
return hasattr(obj, '__iter__')
-
-
-try:
- from django.utils.encoding import python_2_unicode_compatible
-except ImportError:
- def python_2_unicode_compatible(klass):
- """
- A decorator that defines __unicode__ and __str__ methods under Python 2.
- Under Python 3 it does nothing.
-
- To support Python 2 and 3 with a single code base, define a __str__ method
- returning text and apply this decorator to the class.
- """
- if '__str__' not in klass.__dict__:
- raise ValueError("@python_2_unicode_compatible cannot be applied "
- "to %s because it doesn't define __str__()." %
- klass.__name__)
- klass.__unicode__ = klass.__str__
- klass.__str__ = lambda self: self.__unicode__().encode('utf-8')
- return klass
diff --git a/rest_framework/utils/mediatypes.py b/rest_framework/utils/mediatypes.py
index 87b3cc6a..de2931c2 100644
--- a/rest_framework/utils/mediatypes.py
+++ b/rest_framework/utils/mediatypes.py
@@ -5,6 +5,7 @@ See http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7
"""
from __future__ import unicode_literals
from django.http.multipartparser import parse_header
+from django.utils.encoding import python_2_unicode_compatible
from rest_framework import HTTP_HEADER_ENCODING
@@ -43,6 +44,7 @@ def order_by_precedence(media_type_lst):
return [media_types for media_types in ret if media_types]
+@python_2_unicode_compatible
class _MediaType(object):
def __init__(self, media_type_str):
if media_type_str is None:
@@ -79,9 +81,6 @@ class _MediaType(object):
return 3
def __str__(self):
- return self.__unicode__().encode('utf-8')
-
- def __unicode__(self):
ret = "%s/%s" % (self.main_type, self.sub_type)
for key, val in self.params.items():
ret += "; %s=%s" % (key, val)
diff --git a/tests/test_description.py b/tests/test_description.py
index 0675d209..6cd871ed 100644
--- a/tests/test_description.py
+++ b/tests/test_description.py
@@ -2,6 +2,7 @@
from __future__ import unicode_literals
from django.test import TestCase
+from django.utils.encoding import python_2_unicode_compatible
from rest_framework.compat import apply_markdown, smart_text
from rest_framework.views import APIView
from .description import ViewWithNonASCIICharactersInDocstring
@@ -107,6 +108,7 @@ class TestViewNamesAndDescriptions(TestCase):
"""
# use a mock object instead of gettext_lazy to ensure that we can't end
# up with a test case string in our l10n catalog
+ @python_2_unicode_compatible
class MockLazyStr(object):
def __init__(self, string):
self.s = string
@@ -114,9 +116,6 @@ class TestViewNamesAndDescriptions(TestCase):
def __str__(self):
return self.s
- def __unicode__(self):
- return self.s
-
class MockView(APIView):
__doc__ = MockLazyStr("a gettext string")
diff --git a/tests/test_relations_generic.py b/tests/test_relations_generic.py
index 380ad91d..b600b333 100644
--- a/tests/test_relations_generic.py
+++ b/tests/test_relations_generic.py
@@ -3,8 +3,8 @@ from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.generic import GenericRelation, GenericForeignKey
from django.db import models
from django.test import TestCase
+from django.utils.encoding import python_2_unicode_compatible
from rest_framework import serializers
-from rest_framework.compat import python_2_unicode_compatible
@python_2_unicode_compatible
--
cgit v1.2.3
From d54c67d79d0f5661ef33d860efe80a4272e22b3e Mon Sep 17 00:00:00 2001
From: Tymur Maryokhin
Date: Thu, 4 Dec 2014 03:11:42 +0100
Subject: Removed custom StringIO, force_text, smart_text compat
---
rest_framework/compat.py | 34 +++------------------------
rest_framework/exceptions.py | 2 +-
rest_framework/fields.py | 4 ++--
rest_framework/metadata.py | 7 +++---
rest_framework/parsers.py | 3 ++-
rest_framework/relations.py | 3 ++-
rest_framework/renderers.py | 6 ++---
rest_framework/request.py | 2 +-
rest_framework/templatetags/rest_framework.py | 4 ++--
rest_framework/utils/encoders.py | 3 ++-
rest_framework/utils/representation.py | 2 +-
rest_framework/views.py | 3 ++-
tests/test_description.py | 4 ++--
tests/test_parsers.py | 2 +-
tests/test_renderers.py | 4 +++-
15 files changed, 31 insertions(+), 52 deletions(-)
diff --git a/rest_framework/compat.py b/rest_framework/compat.py
index eefaf22e..91723b45 100644
--- a/rest_framework/compat.py
+++ b/rest_framework/compat.py
@@ -9,31 +9,19 @@ from __future__ import unicode_literals
import inspect
from django.core.exceptions import ImproperlyConfigured
+from django.utils.encoding import force_text
from django.conf import settings
from django.utils import six
import django
-# Handle django.utils.encoding rename in 1.5 onwards.
-# smart_unicode -> smart_text
-# force_unicode -> force_text
-try:
- from django.utils.encoding import smart_text
-except ImportError:
- from django.utils.encoding import smart_unicode as smart_text
-try:
- from django.utils.encoding import force_text
-except ImportError:
- from django.utils.encoding import force_unicode as force_text
-
-
# OrderedDict only available in Python 2.7.
# This will always be the case in Django 1.7 and above, as these versions
# no longer support Python 2.6.
# For Django <= 1.6 and Python 2.6 fall back to OrderedDict.
try:
from collections import OrderedDict
-except:
+except ImportError:
from django.utils.datastructures import SortedDict as OrderedDict
@@ -72,21 +60,13 @@ if 'guardian' in settings.INSTALLED_APPS:
pass
-# cStringIO only if it's available, otherwise StringIO
-try:
- import cStringIO.StringIO as StringIO
-except ImportError:
- StringIO = six.StringIO
-
-BytesIO = six.BytesIO
-
-
# urlparse compat import (Required because it changed in python 3.x)
try:
from urllib import parse as urlparse
except ImportError:
import urlparse
+
# UserDict moves in Python 3
try:
from UserDict import UserDict
@@ -104,14 +84,6 @@ def get_model_name(model_cls):
return model_cls._meta.module_name
-def get_concrete_model(model_cls):
- try:
- return model_cls._meta.concrete_model
- except AttributeError:
- # 1.3 does not include concrete model
- return model_cls
-
-
# View._allowed_methods only present from 1.5 onwards
if django.VERSION >= (1, 5):
from django.views.generic import View
diff --git a/rest_framework/exceptions.py b/rest_framework/exceptions.py
index 906de3b0..be41d08d 100644
--- a/rest_framework/exceptions.py
+++ b/rest_framework/exceptions.py
@@ -5,11 +5,11 @@ In addition Django's built in 403 and 404 exceptions are handled.
(`django.http.Http404` and `django.core.exceptions.PermissionDenied`)
"""
from __future__ import unicode_literals
+from django.utils.encoding import force_text
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ungettext_lazy
from rest_framework import status
-from rest_framework.compat import force_text
import math
diff --git a/rest_framework/fields.py b/rest_framework/fields.py
index ca9c479f..37adbe16 100644
--- a/rest_framework/fields.py
+++ b/rest_framework/fields.py
@@ -5,11 +5,11 @@ from django.core.validators import RegexValidator
from django.forms import ImageField as DjangoImageField
from django.utils import six, timezone
from django.utils.dateparse import parse_date, parse_datetime, parse_time
-from django.utils.encoding import is_protected_type
+from django.utils.encoding import is_protected_type, smart_text
from django.utils.translation import ugettext_lazy as _
from rest_framework import ISO_8601
from rest_framework.compat import (
- smart_text, EmailValidator, MinValueValidator, MaxValueValidator,
+ EmailValidator, MinValueValidator, MaxValueValidator,
MinLengthValidator, MaxLengthValidator, URLValidator, OrderedDict
)
from rest_framework.exceptions import ValidationError
diff --git a/rest_framework/metadata.py b/rest_framework/metadata.py
index de829d00..3b058fab 100644
--- a/rest_framework/metadata.py
+++ b/rest_framework/metadata.py
@@ -1,17 +1,18 @@
"""
-The metadata API is used to allow cusomization of how `OPTIONS` requests
+The metadata API is used to allow customization of how `OPTIONS` requests
are handled. We currently provide a single default implementation that returns
some fairly ad-hoc information about the view.
-Future implementations might use JSON schema or other definations in order
+Future implementations might use JSON schema or other definitions in order
to return this information in a more standardized way.
"""
from __future__ import unicode_literals
from django.core.exceptions import PermissionDenied
from django.http import Http404
+from django.utils.encoding import force_text
from rest_framework import exceptions, serializers
-from rest_framework.compat import force_text, OrderedDict
+from rest_framework.compat import OrderedDict
from rest_framework.request import clone_request
from rest_framework.utils.field_mapping import ClassLookupDict
diff --git a/rest_framework/parsers.py b/rest_framework/parsers.py
index d229abec..7b948362 100644
--- a/rest_framework/parsers.py
+++ b/rest_framework/parsers.py
@@ -12,7 +12,8 @@ from django.http import QueryDict
from django.http.multipartparser import MultiPartParser as DjangoMultiPartParser
from django.http.multipartparser import MultiPartParserError, parse_header, ChunkIter
from django.utils import six
-from rest_framework.compat import etree, yaml, force_text, urlparse
+from django.utils.encoding import force_text
+from rest_framework.compat import etree, yaml, urlparse
from rest_framework.exceptions import ParseError
from rest_framework import renderers
import json
diff --git a/rest_framework/relations.py b/rest_framework/relations.py
index d1ea497a..b889acc8 100644
--- a/rest_framework/relations.py
+++ b/rest_framework/relations.py
@@ -1,4 +1,5 @@
-from rest_framework.compat import smart_text, urlparse
+from django.utils.encoding import smart_text
+from rest_framework.compat import urlparse
from rest_framework.fields import get_attribute, empty, Field
from rest_framework.reverse import reverse
from rest_framework.utils import html
diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py
index e87d16d0..ee34ffae 100644
--- a/rest_framework/renderers.py
+++ b/rest_framework/renderers.py
@@ -16,11 +16,11 @@ from django.http.multipartparser import parse_header
from django.template import Context, RequestContext, loader, Template
from django.test.client import encode_multipart
from django.utils import six
+from django.utils.encoding import smart_text
from django.utils.xmlutils import SimplerXMLGenerator
+from django.utils.six.moves import StringIO
from rest_framework import exceptions, serializers, status, VERSION
-from rest_framework.compat import (
- SHORT_SEPARATORS, LONG_SEPARATORS, StringIO, smart_text, yaml
-)
+from rest_framework.compat import SHORT_SEPARATORS, LONG_SEPARATORS, yaml
from rest_framework.exceptions import ParseError
from rest_framework.settings import api_settings
from rest_framework.request import is_form_media_type, override_method
diff --git a/rest_framework/request.py b/rest_framework/request.py
index d7e74674..20e049ed 100644
--- a/rest_framework/request.py
+++ b/rest_framework/request.py
@@ -14,9 +14,9 @@ from django.http import QueryDict
from django.http.multipartparser import parse_header
from django.utils.datastructures import MultiValueDict
from django.utils.datastructures import MergeDict as DjangoMergeDict
+from django.utils.six import BytesIO
from rest_framework import HTTP_HEADER_ENCODING
from rest_framework import exceptions
-from rest_framework.compat import BytesIO
from rest_framework.settings import api_settings
import warnings
diff --git a/rest_framework/templatetags/rest_framework.py b/rest_framework/templatetags/rest_framework.py
index f1825a24..f8686439 100644
--- a/rest_framework/templatetags/rest_framework.py
+++ b/rest_framework/templatetags/rest_framework.py
@@ -3,11 +3,11 @@ from django import template
from django.core.urlresolvers import reverse, NoReverseMatch
from django.http import QueryDict
from django.utils import six
-from django.utils.encoding import iri_to_uri
+from django.utils.encoding import iri_to_uri, force_text
from django.utils.html import escape
from django.utils.safestring import SafeData, mark_safe
from django.utils.html import smart_urlquote
-from rest_framework.compat import urlparse, force_text
+from rest_framework.compat import urlparse
from rest_framework.renderers import HTMLFormRenderer
import re
diff --git a/rest_framework/utils/encoders.py b/rest_framework/utils/encoders.py
index 4d6bb3a3..adc83e57 100644
--- a/rest_framework/utils/encoders.py
+++ b/rest_framework/utils/encoders.py
@@ -4,8 +4,9 @@ Helper classes for parsers.
from __future__ import unicode_literals
from django.db.models.query import QuerySet
from django.utils import six, timezone
+from django.utils.encoding import force_text
from django.utils.functional import Promise
-from rest_framework.compat import force_text, OrderedDict
+from rest_framework.compat import OrderedDict
import datetime
import decimal
import types
diff --git a/rest_framework/utils/representation.py b/rest_framework/utils/representation.py
index 2a7c4675..3f17a8b9 100644
--- a/rest_framework/utils/representation.py
+++ b/rest_framework/utils/representation.py
@@ -3,8 +3,8 @@ Helper functions for creating user-friendly representations
of serializer classes and serializer fields.
"""
from django.db import models
+from django.utils.encoding import force_text
from django.utils.functional import Promise
-from rest_framework.compat import force_text
import re
diff --git a/rest_framework/views.py b/rest_framework/views.py
index 292431c8..bc870417 100644
--- a/rest_framework/views.py
+++ b/rest_framework/views.py
@@ -5,9 +5,10 @@ from __future__ import unicode_literals
from django.core.exceptions import PermissionDenied
from django.http import Http404
+from django.utils.encoding import smart_text
from django.views.decorators.csrf import csrf_exempt
from rest_framework import status, exceptions
-from rest_framework.compat import smart_text, HttpResponseBase, View
+from rest_framework.compat import HttpResponseBase, View
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.settings import api_settings
diff --git a/tests/test_description.py b/tests/test_description.py
index 6cd871ed..78ce2350 100644
--- a/tests/test_description.py
+++ b/tests/test_description.py
@@ -2,8 +2,8 @@
from __future__ import unicode_literals
from django.test import TestCase
-from django.utils.encoding import python_2_unicode_compatible
-from rest_framework.compat import apply_markdown, smart_text
+from django.utils.encoding import python_2_unicode_compatible, smart_text
+from rest_framework.compat import apply_markdown
from rest_framework.views import APIView
from .description import ViewWithNonASCIICharactersInDocstring
from .description import UTF8_TEST_DOCSTRING
diff --git a/tests/test_parsers.py b/tests/test_parsers.py
index 88eccef3..d28d8bd4 100644
--- a/tests/test_parsers.py
+++ b/tests/test_parsers.py
@@ -5,8 +5,8 @@ from django import forms
from django.core.files.uploadhandler import MemoryFileUploadHandler
from django.test import TestCase
from django.utils import unittest
+from django.utils.six.moves import StringIO
from rest_framework.compat import etree
-from rest_framework.compat import StringIO
from rest_framework.exceptions import ParseError
from rest_framework.parsers import FormParser, FileUploadParser
from rest_framework.parsers import XMLParser
diff --git a/tests/test_renderers.py b/tests/test_renderers.py
index 416d7f22..22eb5459 100644
--- a/tests/test_renderers.py
+++ b/tests/test_renderers.py
@@ -7,9 +7,11 @@ from django.core.cache import cache
from django.db import models
from django.test import TestCase
from django.utils import six, unittest
+from django.utils.six import BytesIO
+from django.utils.six.moves import StringIO
from django.utils.translation import ugettext_lazy as _
from rest_framework import status, permissions
-from rest_framework.compat import yaml, etree, StringIO, BytesIO
+from rest_framework.compat import yaml, etree
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.renderers import BaseRenderer, JSONRenderer, YAMLRenderer, \
--
cgit v1.2.3
From 71e1a3942e7945fe4d8da4c44b4ba2100a2c67de Mon Sep 17 00:00:00 2001
From: Carlton Gibson
Date: Thu, 4 Dec 2014 12:15:14 +0100
Subject: Initial link from Serializers to Validators
---
docs/api-guide/serializers.md | 27 ++++++++++++++++++++-------
1 file changed, 20 insertions(+), 7 deletions(-)
diff --git a/docs/api-guide/serializers.md b/docs/api-guide/serializers.md
index 1779c863..69fc1857 100644
--- a/docs/api-guide/serializers.md
+++ b/docs/api-guide/serializers.md
@@ -104,7 +104,7 @@ If your object instances correspond to Django models you'll also want to ensure
instance.created = validated_data.get('created', instance.created)
instance.save()
return instance
-
+
Now when deserializing data, we can call `.save()` to return an object instance, based on the validated data.
comment = serializer.save()
@@ -113,7 +113,7 @@ Calling `.save()` will either create a new instance, or update an existing insta
# .save() will create a new instance.
serializer = CommentSerializer(data=data)
-
+
# .save() will update the existing `comment` instance.
serializer = CommentSerializer(comment, data=data)
@@ -140,7 +140,7 @@ For example:
class ContactForm(serializers.Serializer):
email = serializers.EmailField()
message = serializers.CharField()
-
+
def save(self):
email = self.validated_data['email']
message = self.validated_data['message']
@@ -230,7 +230,7 @@ Serializer classes can also include reusable validators that are applied to the
name = serializers.CharField()
room_number = serializers.IntegerField(choices=[101, 102, 103, 201])
date = serializers.DateField()
-
+
class Meta:
# Each room only has one event per day.
validators = UniqueTogetherValidator(
@@ -448,7 +448,7 @@ To do so, open the Django shell, using `python manage.py shell`, then import the
id = IntegerField(label='ID', read_only=True)
name = CharField(allow_blank=True, max_length=100, required=False)
owner = PrimaryKeyRelatedField(queryset=User.objects.all())
-
+
## Specifying which fields should be included
If you only want a subset of the default fields to be used in a model serializer, you can do so using `fields` or `exclude` options, just as you would with a `ModelForm`.
@@ -505,6 +505,19 @@ This option should be a list or tuple of field names, and is declared as follows
Model fields which have `editable=False` set, and `AutoField` fields will be set to read-only by default, and do not need to be added to the `read_only_fields` option.
+---
+
+**Note**: There is a special-case where a read-only field is part of a `unique_together` constraint at the model level. Here you **must** specify the field explicitly and provide a valid default value.
+
+A common example of this is a read-only relation to currently authenticated `User` which is `unique_together` with another identifier. In this case you would declare the user field like so:
+
+ user = serializers.PrimaryKeyRelatedField(read_only=True, default=serializers.CurrentUserDefault())
+
+Please review the [Validators Documentation](/api-guide/validators/) for details on the [UniqueTogetherValidator](/api-guide/validators/#uniquetogethervalidator) and [CurrentUserDefault](/api-guide/validators/#currentuserdefault) classes.
+
+---
+
+
## Specifying additional keyword arguments for fields.
There is also a shortcut allowing you to specify arbitrary additional keyword arguments on fields, using the `extra_kwargs` option. Similarly to `read_only_fields` this means you do not need to explicitly declare the field on the serializer.
@@ -516,7 +529,7 @@ This option is a dictionary, mapping field names to a dictionary of keyword argu
model = User
fields = ('email', 'username', 'password')
extra_kwargs = {'password': {'write_only': True}}
-
+
def create(self, validated_data):
user = User(
email=validated_data['email'],
@@ -656,7 +669,7 @@ To support multiple updates you'll need to do so explicitly. When writing your m
* How do you determine which instance should be updated for each item in the list of data?
* How should insertions be handled? Are they invalid, or do they create new objects?
* How should removals be handled? Do they imply object deletion, or removing a relationship? Should they be silently ignored, or are they invalid?
-* How should ordering be handled? Does changing the position of two items imply any state change or is it ignored?
+* How should ordering be handled? Does changing the position of two items imply any state change or is it ignored?
Here's an example of how you might choose to implement multiple updates:
--
cgit v1.2.3
From fcbae5d99f93a28c9aac340bf2d4d2a3930e1a6a Mon Sep 17 00:00:00 2001
From: phalt
Date: Thu, 4 Dec 2014 11:20:33 +0000
Subject: updates based on suggestions
---
docs/tutorial/1-serialization.md | 9 ++++++---
docs/tutorial/2-requests-and-responses.md | 4 +++-
docs/tutorial/quickstart.md | 11 +++++------
3 files changed, 14 insertions(+), 10 deletions(-)
diff --git a/docs/tutorial/1-serialization.md b/docs/tutorial/1-serialization.md
index ba2a0c32..5b1ae6e8 100644
--- a/docs/tutorial/1-serialization.md
+++ b/docs/tutorial/1-serialization.md
@@ -332,9 +332,9 @@ Quit out of the shell...
In another terminal window, we can test the server.
-We can test our API using using `curl` or [httpie][httpie]. Httpie is a user friendly http client that's written in Python. Let's install that.
+We can test our API using using [curl][curl] or [httpie][httpie]. Httpie is a user friendly http client that's written in Python. Let's install that.
-You can install httpie on all operating systems using pip:
+You can install httpie using pip:
pip install httpie
@@ -363,8 +363,10 @@ Finally, we can get a list of all of the snippets:
Or we can get a particular snippet by referencing its id:
- http http://127.0.0.1:8000/snippets/2/ --body
+ http http://127.0.0.1:8000/snippets/2/
+ HTTP/1.1 200 OK
+ ...
{
"id": 2,
"title": "",
@@ -390,3 +392,4 @@ We'll see how we can start to improve things in [part 2 of the tutorial][tut-2].
[virtualenv]: http://www.virtualenv.org/en/latest/index.html
[tut-2]: 2-requests-and-responses.md
[httpie]: https://github.com/jakubroztocil/httpie#installation
+[curl]: http://curl.haxx.se
diff --git a/docs/tutorial/2-requests-and-responses.md b/docs/tutorial/2-requests-and-responses.md
index dcaf7c0c..08746cd7 100644
--- a/docs/tutorial/2-requests-and-responses.md
+++ b/docs/tutorial/2-requests-and-responses.md
@@ -127,8 +127,10 @@ Go ahead and test the API from the command line, as we did in [tutorial part 1][
We can get a list of all of the snippets, as before.
- http http://127.0.0.1:8000/snippets/ --body
+ http http://127.0.0.1:8000/snippets/
+ HTTP/1.1 200 OK
+ ...
[
{
"id": 1,
diff --git a/docs/tutorial/quickstart.md b/docs/tutorial/quickstart.md
index 41e864cc..43220ce8 100644
--- a/docs/tutorial/quickstart.md
+++ b/docs/tutorial/quickstart.md
@@ -24,10 +24,6 @@ Create a new Django project named `tutorial`, then start a new app called `quick
django-admin.py startapp quickstart
cd ..
-Optionally, install [httpie][httpie] for tastier HTTP requests:
-
- pip install httpie
-
Now sync your database for the first time:
python manage.py migrate
@@ -163,9 +159,12 @@ We can now access our API, both from the command-line, using tools like `curl`..
]
}
-Or with [httpie][httpie], a tastier version of `curl`...
+Or using the [httpie][httpie], command line tool...
+
+ bash: http -a username:password http://127.0.0.1:8000/users/
- bash: http -a username:password http://127.0.0.1:8000/users/ --body
+ HTTP/1.1 200 OK
+ ...
{
"count": 2,
"next": null,
--
cgit v1.2.3
From 6ee361332b09f148f86149a7d9a6220bd61966e8 Mon Sep 17 00:00:00 2001
From: Carlton Gibson
Date: Thu, 4 Dec 2014 14:15:01 +0100
Subject: Add missing definite article
---
docs/api-guide/serializers.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/api-guide/serializers.md b/docs/api-guide/serializers.md
index 69fc1857..79db275f 100644
--- a/docs/api-guide/serializers.md
+++ b/docs/api-guide/serializers.md
@@ -509,7 +509,7 @@ Model fields which have `editable=False` set, and `AutoField` fields will be set
**Note**: There is a special-case where a read-only field is part of a `unique_together` constraint at the model level. Here you **must** specify the field explicitly and provide a valid default value.
-A common example of this is a read-only relation to currently authenticated `User` which is `unique_together` with another identifier. In this case you would declare the user field like so:
+A common example of this is a read-only relation to the currently authenticated `User` which is `unique_together` with another identifier. In this case you would declare the user field like so:
user = serializers.PrimaryKeyRelatedField(read_only=True, default=serializers.CurrentUserDefault())
--
cgit v1.2.3
From 95af92ca01435c32f4374d26eb448f964d77c81a Mon Sep 17 00:00:00 2001
From: Tymur Maryokhin
Date: Thu, 4 Dec 2014 15:47:42 +0100
Subject: Removed custom urlparse compat
---
rest_framework/compat.py | 8 +-------
rest_framework/parsers.py | 3 ++-
rest_framework/relations.py | 2 +-
rest_framework/templatetags/rest_framework.py | 2 +-
4 files changed, 5 insertions(+), 10 deletions(-)
diff --git a/rest_framework/compat.py b/rest_framework/compat.py
index 91723b45..c58aa4ce 100644
--- a/rest_framework/compat.py
+++ b/rest_framework/compat.py
@@ -10,6 +10,7 @@ import inspect
from django.core.exceptions import ImproperlyConfigured
from django.utils.encoding import force_text
+from django.utils.six.moves.urllib import parse as urlparse
from django.conf import settings
from django.utils import six
import django
@@ -60,13 +61,6 @@ if 'guardian' in settings.INSTALLED_APPS:
pass
-# urlparse compat import (Required because it changed in python 3.x)
-try:
- from urllib import parse as urlparse
-except ImportError:
- import urlparse
-
-
# UserDict moves in Python 3
try:
from UserDict import UserDict
diff --git a/rest_framework/parsers.py b/rest_framework/parsers.py
index 7b948362..3e3395c0 100644
--- a/rest_framework/parsers.py
+++ b/rest_framework/parsers.py
@@ -12,8 +12,9 @@ from django.http import QueryDict
from django.http.multipartparser import MultiPartParser as DjangoMultiPartParser
from django.http.multipartparser import MultiPartParserError, parse_header, ChunkIter
from django.utils import six
+from django.utils.six.moves.urllib import parse as urlparse
from django.utils.encoding import force_text
-from rest_framework.compat import etree, yaml, urlparse
+from rest_framework.compat import etree, yaml
from rest_framework.exceptions import ParseError
from rest_framework import renderers
import json
diff --git a/rest_framework/relations.py b/rest_framework/relations.py
index b889acc8..d0cd3154 100644
--- a/rest_framework/relations.py
+++ b/rest_framework/relations.py
@@ -1,5 +1,4 @@
from django.utils.encoding import smart_text
-from rest_framework.compat import urlparse
from rest_framework.fields import get_attribute, empty, Field
from rest_framework.reverse import reverse
from rest_framework.utils import html
@@ -7,6 +6,7 @@ from django.core.exceptions import ObjectDoesNotExist, ImproperlyConfigured
from django.core.urlresolvers import resolve, get_script_prefix, NoReverseMatch, Resolver404
from django.db.models.query import QuerySet
from django.utils import six
+from django.utils.six.moves.urllib import parse as urlparse
from django.utils.translation import ugettext_lazy as _
diff --git a/rest_framework/templatetags/rest_framework.py b/rest_framework/templatetags/rest_framework.py
index f8686439..69e03af4 100644
--- a/rest_framework/templatetags/rest_framework.py
+++ b/rest_framework/templatetags/rest_framework.py
@@ -3,11 +3,11 @@ from django import template
from django.core.urlresolvers import reverse, NoReverseMatch
from django.http import QueryDict
from django.utils import six
+from django.utils.six.moves.urllib import parse as urlparse
from django.utils.encoding import iri_to_uri, force_text
from django.utils.html import escape
from django.utils.safestring import SafeData, mark_safe
from django.utils.html import smart_urlquote
-from rest_framework.compat import urlparse
from rest_framework.renderers import HTMLFormRenderer
import re
--
cgit v1.2.3
From 001884a1927f6ceaa60dcc2a4df3422f94455311 Mon Sep 17 00:00:00 2001
From: Tymur Maryokhin
Date: Thu, 4 Dec 2014 16:06:43 +0100
Subject: Removed unused compat code
---
rest_framework/compat.py | 27 ---------------------------
1 file changed, 27 deletions(-)
diff --git a/rest_framework/compat.py b/rest_framework/compat.py
index c58aa4ce..71520b92 100644
--- a/rest_framework/compat.py
+++ b/rest_framework/compat.py
@@ -61,15 +61,6 @@ if 'guardian' in settings.INSTALLED_APPS:
pass
-# UserDict moves in Python 3
-try:
- from UserDict import UserDict
- from UserDict import DictMixin
-except ImportError:
- from collections import UserDict
- from collections import MutableMapping as DictMixin
-
-
def get_model_name(model_cls):
try:
return model_cls._meta.model_name
@@ -177,11 +168,6 @@ class RequestFactory(DjangoRequestFactory):
'CONTENT_TYPE': six.text_type(content_type),
'wsgi.input': FakePayload(data),
})
- elif django.VERSION <= (1, 4):
- # For 1.3 we need an empty WSGI payload
- r.update({
- 'wsgi.input': FakePayload('')
- })
r.update(extra)
return self.request(**r)
@@ -278,16 +264,3 @@ if six.PY3:
else:
SHORT_SEPARATORS = (b',', b':')
LONG_SEPARATORS = (b', ', b': ')
-
-
-# Handle lazy strings across Py2/Py3
-from django.utils.functional import Promise
-
-if six.PY3:
- def is_non_str_iterable(obj):
- if isinstance(obj, str) or (isinstance(obj, Promise) and obj._delegate_text):
- return False
- return hasattr(obj, '__iter__')
-else:
- def is_non_str_iterable(obj):
- return hasattr(obj, '__iter__')
--
cgit v1.2.3
From 38e05e66c932fc2967cefbd88225bcdc2b0313a7 Mon Sep 17 00:00:00 2001
From: Xavier Ordoquy
Date: Thu, 4 Dec 2014 23:22:00 +0100
Subject: print() function works from Python 2.6 to 3.X
---
docs/tutorial/1-serialization.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/tutorial/1-serialization.md b/docs/tutorial/1-serialization.md
index 52c75d2c..b704996d 100644
--- a/docs/tutorial/1-serialization.md
+++ b/docs/tutorial/1-serialization.md
@@ -210,7 +210,7 @@ One nice property that serializers have is that you can inspect all the fields i
>>> from snippets.serializers import SnippetSerializer
>>> serializer = SnippetSerializer()
- >>> print repr(serializer) # In python 3 use `print(repr(serializer))`
+ >>> print(repr(serializer))
SnippetSerializer():
id = IntegerField(label='ID', read_only=True)
title = CharField(allow_blank=True, max_length=100, required=False)
--
cgit v1.2.3
From 9d078be59ca5067d098263b1892740b44f7c41ee Mon Sep 17 00:00:00 2001
From: Xavier Ordoquy
Date: Thu, 4 Dec 2014 23:34:55 +0100
Subject: Fix the tutorial against the v3.0
---
docs/tutorial/1-serialization.md | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/docs/tutorial/1-serialization.md b/docs/tutorial/1-serialization.md
index b704996d..eb0a00c0 100644
--- a/docs/tutorial/1-serialization.md
+++ b/docs/tutorial/1-serialization.md
@@ -101,7 +101,7 @@ The first thing we need to get started on our Web API is to provide a way of ser
class SnippetSerializer(serializers.Serializer):
pk = serializers.IntegerField(read_only=True)
- title = serializers.CharField(required=False,
+ title = serializers.CharField(required=False, allow_blank=True
max_length=100)
code = serializers.CharField(style={'type': 'textarea'})
linenos = serializers.BooleanField(required=False)
@@ -181,7 +181,7 @@ Deserialization is similar. First we parse a stream into Python native datatype
serializer = SnippetSerializer(data=data)
serializer.is_valid()
# True
- serializer.object
+ serializer.save()
#
Notice how similar the API is to working with forms. The similarity should become even more apparent when we start writing views that use our serializer.
@@ -301,7 +301,7 @@ We'll also need a view which corresponds to an individual snippet, and can be us
Finally we need to wire these views up. Create the `snippets/urls.py` file:
- from django.conf.urls import patterns, url
+ from django.conf.urls import url
from snippets import views
urlpatterns = [
--
cgit v1.2.3
From d9930181ee157f51e2fcea33a3af5ea397647324 Mon Sep 17 00:00:00 2001
From: Tymur Maryokhin
Date: Fri, 5 Dec 2014 00:29:28 +0100
Subject: Removed unused imports, pep8 fixes, typo fixes
---
CONTRIBUTING.md | 4 ++--
rest_framework/authentication.py | 2 +-
rest_framework/permissions.py | 2 +-
rest_framework/relations.py | 2 +-
rest_framework/renderers.py | 4 +++-
rest_framework/serializers.py | 33 +++++++++++++++------------------
rest_framework/settings.py | 2 +-
rest_framework/validators.py | 2 +-
rest_framework/viewsets.py | 6 +++---
runtests.py | 5 +++++
tests/test_multitable_inheritance.py | 2 +-
tests/test_request.py | 2 +-
tests/test_validators.py | 2 +-
13 files changed, 36 insertions(+), 32 deletions(-)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 96e55161..b963a499 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -33,7 +33,7 @@ Some tips on good issue reporting:
* When describing issues try to phrase your ticket in terms of the *behavior* you think needs changing rather than the *code* you think need changing.
* Search the issue list first for related items, and make sure you're running the latest version of REST framework before reporting an issue.
* If reporting a bug, then try to include a pull request with a failing test case. This will help us quickly identify if there is a valid issue, and make sure that it gets fixed more quickly if there is one.
-* Feature requests will often be closed with a recommendation that they be implemented outside of the core REST framework library. Keeping new feature requests implemented as third party libraries allows us to keep down the maintainence overhead of REST framework, so that the focus can be on continued stability, bugfixes, and great documentation.
+* Feature requests will often be closed with a recommendation that they be implemented outside of the core REST framework library. Keeping new feature requests implemented as third party libraries allows us to keep down the maintenance overhead of REST framework, so that the focus can be on continued stability, bugfixes, and great documentation.
* Closing an issue doesn't necessarily mean the end of a discussion. If you believe your issue has been closed incorrectly, explain why and we'll consider if it needs to be reopened.
## Triaging issues
@@ -82,7 +82,7 @@ GitHub's documentation for working on pull requests is [available here][pull-req
Always run the tests before submitting pull requests, and ideally run `tox` in order to check that your modifications are compatible with both Python 2 and Python 3, and that they run properly on all supported versions of Django.
-Once you've made a pull request take a look at the travis build status in the GitHub interface and make sure the tests are runnning as you'd expect.
+Once you've made a pull request take a look at the travis build status in the GitHub interface and make sure the tests are running as you'd expect.
![Travis status][travis-status]
diff --git a/rest_framework/authentication.py b/rest_framework/authentication.py
index 36d74dd9..4832ad33 100644
--- a/rest_framework/authentication.py
+++ b/rest_framework/authentication.py
@@ -267,7 +267,7 @@ class OAuthAuthentication(BaseAuthentication):
def authenticate_header(self, request):
"""
If permission is denied, return a '401 Unauthorized' response,
- with an appropraite 'WWW-Authenticate' header.
+ with an appropriate 'WWW-Authenticate' header.
"""
return 'OAuth realm="%s"' % self.www_authenticate_realm
diff --git a/rest_framework/permissions.py b/rest_framework/permissions.py
index 29f60d6d..3f6f5961 100644
--- a/rest_framework/permissions.py
+++ b/rest_framework/permissions.py
@@ -184,7 +184,7 @@ class DjangoObjectPermissions(DjangoModelPermissions):
if not user.has_perms(perms, obj):
# If the user does not have permissions we need to determine if
# they have read permissions to see 403, or not, and simply see
- # a 404 reponse.
+ # a 404 response.
if request.method in ('GET', 'OPTIONS', 'HEAD'):
# Read permissions already checked and failed, no need
diff --git a/rest_framework/relations.py b/rest_framework/relations.py
index d0cd3154..178a8e2b 100644
--- a/rest_framework/relations.py
+++ b/rest_framework/relations.py
@@ -142,7 +142,7 @@ class PrimaryKeyRelatedField(RelatedField):
def get_iterable(self, instance, source_attrs):
# For consistency with `get_attribute` we're using `serializable_value()`
# here. Typically there won't be any difference, but some custom field
- # types might return a non-primative value for the pk otherwise.
+ # types might return a non-primitive value for the pk otherwise.
#
# We could try to get smart with `values_list('pk', flat=True)`, which
# would be better in some case, but would actually end up with *more*
diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py
index 4ffd46e3..46126d91 100644
--- a/rest_framework/renderers.py
+++ b/rest_framework/renderers.py
@@ -282,7 +282,9 @@ class TemplateHTMLRenderer(BaseRenderer):
return view.get_template_names()
elif hasattr(view, 'template_name'):
return [view.template_name]
- raise ImproperlyConfigured('Returned a template response with no `template_name` attribute set on either the view or response')
+ raise ImproperlyConfigured(
+ 'Returned a template response with no `template_name` attribute set on either the view or response'
+ )
def get_exception_template(self, response):
template_names = [name % {'status_code': response.status_code}
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index af8aeb48..e1851ddd 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -10,17 +10,13 @@ python primitives.
2. The process of marshalling between python primitives and request and
response content is handled by parsers and renderers.
"""
-from django.core.exceptions import ImproperlyConfigured
-from django.core.exceptions import ValidationError as DjangoValidationError
+import warnings
+
from django.db import models
from django.db.models.fields import FieldDoesNotExist
-from django.utils import six
from django.utils.translation import ugettext_lazy as _
-from rest_framework.compat import OrderedDict
-from rest_framework.exceptions import ValidationError
-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
+
+from rest_framework.utils import model_meta
from rest_framework.utils.field_mapping import (
get_url_kwargs, get_field_kwargs,
get_relation_kwargs, get_nested_relation_kwargs,
@@ -33,9 +29,7 @@ from rest_framework.validators import (
UniqueForDateValidator, UniqueForMonthValidator, UniqueForYearValidator,
UniqueTogetherValidator
)
-import copy
-import inspect
-import warnings
+
# Note: We do the following so that users of the framework can use this style:
#
@@ -65,6 +59,7 @@ class BaseSerializer(Field):
The BaseSerializer class provides a minimal class which may be used
for writing custom serializer implementations.
"""
+
def __init__(self, instance=None, data=None, **kwargs):
self.instance = instance
self._initial_data = data
@@ -245,7 +240,7 @@ class Serializer(BaseSerializer):
"""
A dictionary of {field_name: field_instance}.
"""
- # `fields` is evalutated lazily. We do this to ensure that we don't
+ # `fields` is evaluated lazily. We do this to ensure that we don't
# have issues importing modules that use ModelSerializers as fields,
# even if Django's app-loading stage has not yet run.
if not hasattr(self, '_fields'):
@@ -343,7 +338,7 @@ class Serializer(BaseSerializer):
# Normally you should raise `serializers.ValidationError`
# inside your codebase, but we handle Django's validation
# exception class as well for simpler compat.
- # Eg. Calling Model.clean() explictily inside Serializer.validate()
+ # Eg. Calling Model.clean() explicitly inside Serializer.validate()
raise ValidationError({
api_settings.NON_FIELD_ERRORS_KEY: list(exc.messages)
})
@@ -576,7 +571,7 @@ class ModelSerializer(Serializer):
The process of automatically determining a set of serializer fields
based on the model fields is reasonably complex, but you almost certainly
- don't need to dig into the implemention.
+ don't need to dig into the implementation.
If the `ModelSerializer` class *doesn't* generate the set of fields that
you need you should either declare the extra/differing fields explicitly on
@@ -636,7 +631,7 @@ class ModelSerializer(Serializer):
isinstance(field, BaseSerializer) and (key in validated_attrs)
for key, field in self.fields.items()
), (
- 'The `.create()` method does not suport nested writable fields '
+ 'The `.create()` method does not support nested writable fields '
'by default. Write an explicit `.create()` method for serializer '
'`%s.%s`, or set `read_only=True` on nested serializer fields.' %
(self.__class__.__module__, self.__class__.__name__)
@@ -684,7 +679,7 @@ class ModelSerializer(Serializer):
isinstance(field, BaseSerializer) and (key in validated_attrs)
for key, field in self.fields.items()
), (
- 'The `.update()` method does not suport nested writable fields '
+ 'The `.update()` method does not support nested writable fields '
'by default. Write an explicit `.update()` method for serializer '
'`%s.%s`, or set `read_only=True` on nested serializer fields.' %
(self.__class__.__module__, self.__class__.__name__)
@@ -824,7 +819,7 @@ class ModelSerializer(Serializer):
# applied, we can add the extra 'required=...' or 'default=...'
# arguments that are appropriate to these fields, or add a `HiddenField` for it.
for unique_constraint_name in unique_constraint_names:
- # Get the model field that is refered too.
+ # Get the model field that is referred too.
unique_constraint_field = model._meta.get_field(unique_constraint_name)
if getattr(unique_constraint_field, 'auto_now_add', None):
@@ -907,7 +902,7 @@ class ModelSerializer(Serializer):
)
# Check that any fields declared on the class are
- # also explicity included in `Meta.fields`.
+ # also explicitly included in `Meta.fields`.
missing_fields = set(declared_fields.keys()) - set(fields)
if missing_fields:
missing_field = list(missing_fields)[0]
@@ -1001,6 +996,7 @@ class ModelSerializer(Serializer):
class Meta:
model = relation_info.related
depth = nested_depth
+
return NestedSerializer
@@ -1027,4 +1023,5 @@ class HyperlinkedModelSerializer(ModelSerializer):
class Meta:
model = relation_info.related
depth = nested_depth
+
return NestedSerializer
diff --git a/rest_framework/settings.py b/rest_framework/settings.py
index 1e8c27fc..79da23ca 100644
--- a/rest_framework/settings.py
+++ b/rest_framework/settings.py
@@ -47,7 +47,7 @@ DEFAULTS = {
'DEFAULT_CONTENT_NEGOTIATION_CLASS': 'rest_framework.negotiation.DefaultContentNegotiation',
'DEFAULT_METADATA_CLASS': 'rest_framework.metadata.SimpleMetadata',
- # Genric view behavior
+ # Generic view behavior
'DEFAULT_MODEL_SERIALIZER_CLASS': 'rest_framework.serializers.ModelSerializer',
'DEFAULT_PAGINATION_SERIALIZER_CLASS': 'rest_framework.pagination.PaginationSerializer',
'DEFAULT_FILTER_BACKENDS': (),
diff --git a/rest_framework/validators.py b/rest_framework/validators.py
index 7ca4e6a9..63eb7b22 100644
--- a/rest_framework/validators.py
+++ b/rest_framework/validators.py
@@ -4,7 +4,7 @@ the using Django's `.full_clean()`.
This gives us better separation of concerns, allows us to use single-step
object creation, and makes it possible to switch between using the implicit
-`ModelSerializer` class and an equivelent explicit `Serializer` class.
+`ModelSerializer` class and an equivalent explicit `Serializer` class.
"""
from django.utils.translation import ugettext_lazy as _
from rest_framework.exceptions import ValidationError
diff --git a/rest_framework/viewsets.py b/rest_framework/viewsets.py
index 70d14695..88c763da 100644
--- a/rest_framework/viewsets.py
+++ b/rest_framework/viewsets.py
@@ -44,7 +44,7 @@ class ViewSetMixin(object):
instantiated view, we need to totally reimplement `.as_view`,
and slightly modify the view function that is created and returned.
"""
- # The suffix initkwarg is reserved for identifing the viewset type
+ # The suffix initkwarg is reserved for identifying the viewset type
# eg. 'List' or 'Instance'.
cls.suffix = None
@@ -98,12 +98,12 @@ class ViewSetMixin(object):
view.suffix = initkwargs.get('suffix', None)
return csrf_exempt(view)
- def initialize_request(self, request, *args, **kargs):
+ def initialize_request(self, request, *args, **kwargs):
"""
Set the `.action` attribute on the view,
depending on the request method.
"""
- request = super(ViewSetMixin, self).initialize_request(request, *args, **kargs)
+ request = super(ViewSetMixin, self).initialize_request(request, *args, **kwargs)
self.action = self.action_map.get(request.method.lower())
return request
diff --git a/runtests.py b/runtests.py
index 4da05ac3..abf15a62 100755
--- a/runtests.py
+++ b/runtests.py
@@ -17,24 +17,29 @@ FLAKE8_ARGS = ['rest_framework', 'tests', '--ignore=E501']
sys.path.append(os.path.dirname(__file__))
+
def exit_on_failure(ret, message=None):
if ret:
sys.exit(ret)
+
def flake8_main(args):
print('Running flake8 code linting')
ret = subprocess.call(['flake8'] + args)
print('flake8 failed' if ret else 'flake8 passed')
return ret
+
def split_class_and_function(string):
class_string, function_string = string.split('.', 1)
return "%s and %s" % (class_string, function_string)
+
def is_function(string):
# `True` if it looks like a test function is included in the string.
return string.startswith('test_') or '.test_' in string
+
def is_class(string):
# `True` if first character is uppercase - assume it's a class name.
return string[0] == string[0].upper()
diff --git a/tests/test_multitable_inheritance.py b/tests/test_multitable_inheritance.py
index ce1bf3ea..e1b40cc7 100644
--- a/tests/test_multitable_inheritance.py
+++ b/tests/test_multitable_inheritance.py
@@ -31,7 +31,7 @@ class AssociatedModelSerializer(serializers.ModelSerializer):
# Tests
-class IneritedModelSerializationTests(TestCase):
+class InheritedModelSerializationTests(TestCase):
def test_multitable_inherited_model_fields_as_expected(self):
"""
diff --git a/tests/test_request.py b/tests/test_request.py
index 44afd243..7cf8c327 100644
--- a/tests/test_request.py
+++ b/tests/test_request.py
@@ -187,7 +187,7 @@ class MockView(APIView):
if request.POST.get('example') is not None:
return Response(status=status.HTTP_200_OK)
- return Response(status=status.INTERNAL_SERVER_ERROR)
+ return Response(status=status.HTTP_500_INTERNAL_SERVER_ERROR)
urlpatterns = patterns(
'',
diff --git a/tests/test_validators.py b/tests/test_validators.py
index 1df0641c..5d92b284 100644
--- a/tests/test_validators.py
+++ b/tests/test_validators.py
@@ -148,7 +148,7 @@ class TestUniquenessTogetherValidation(TestCase):
def test_ignore_excluded_fields(self):
"""
When model fields are not included in a serializer, then uniqueness
- validtors should not be added for that field.
+ validators should not be added for that field.
"""
class ExcludedFieldSerializer(serializers.ModelSerializer):
class Meta:
--
cgit v1.2.3
From d68c61450440a522b08b64fdd21028cc739e6ead Mon Sep 17 00:00:00 2001
From: BrickXu
Date: Fri, 5 Dec 2014 14:50:53 +0800
Subject: Add validation for fields & exclude type.
---
rest_framework/serializers.py | 6 ++++
tests/test_serializer_metaclass.py | 62 ++++++++++++++++++++++++++++++++++++++
2 files changed, 68 insertions(+)
create mode 100644 tests/test_serializer_metaclass.py
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index af8aeb48..96cd51e3 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -759,6 +759,12 @@ class ModelSerializer(Serializer):
depth = getattr(self.Meta, 'depth', 0)
extra_kwargs = getattr(self.Meta, 'extra_kwargs', {})
+ if fields and not isinstance(fields, (list, tuple)):
+ raise TypeError('`fields` must be a list or tuple')
+
+ if exclude and not isinstance(exclude, (list, tuple)):
+ raise TypeError('`exclude` must be a list or tuple')
+
assert not (fields and exclude), "Cannot set both 'fields' and 'exclude'."
extra_kwargs = self._include_additional_options(extra_kwargs)
diff --git a/tests/test_serializer_metaclass.py b/tests/test_serializer_metaclass.py
new file mode 100644
index 00000000..bd2fbed7
--- /dev/null
+++ b/tests/test_serializer_metaclass.py
@@ -0,0 +1,62 @@
+from django.test import TestCase
+from rest_framework import serializers
+from .models import BasicModel
+
+
+class TestSerializerMetaClass(TestCase):
+ def setUp(self):
+ class FieldsSerializer(serializers.ModelSerializer):
+ text = serializers.CharField()
+
+ class Meta:
+ model = BasicModel
+ fields = ('text')
+
+ class ExcludeSerializer(serializers.ModelSerializer):
+ text = serializers.CharField()
+
+ class Meta:
+ model = BasicModel
+ exclude = ('text')
+
+ class FieldsAndExcludeSerializer(serializers.ModelSerializer):
+ text = serializers.CharField()
+
+ class Meta:
+ model = BasicModel
+ fields = ('text',)
+ exclude = ('text',)
+
+ self.fields_serializer = FieldsSerializer
+ self.exclude_serializer = ExcludeSerializer
+ self.faeSerializer = FieldsAndExcludeSerializer
+
+ def test_meta_class_fields(self):
+ object = BasicModel(text="Hello World.")
+ serializer = self.fields_serializer(instance=object)
+
+ with self.assertRaises(TypeError) as result:
+ serializer.data
+
+ exception = result.exception
+ self.assertEqual(str(exception), "`fields` must be a list or tuple")
+
+ def test_meta_class_exclude(self):
+ object = BasicModel(text="Hello World.")
+ serializer = self.exclude_serializer(instance=object)
+
+ with self.assertRaises(TypeError) as result:
+ serializer.data
+
+ exception = result.exception
+ self.assertEqual(str(exception), "`exclude` must be a list or tuple")
+
+ def test_meta_class_fields_and_exclude(self):
+ object = BasicModel(text="Hello World.")
+ serializer = self.faeSerializer(instance=object)
+
+ with self.assertRaises(AssertionError) as result:
+ serializer.data
+
+ exception = result.exception
+ self.assertEqual(str(exception), "Cannot set both 'fields' and 'exclude'.")
--
cgit v1.2.3
From e2b39088345e564a06ce332b740215600c29e481 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Fri, 5 Dec 2014 09:44:01 +0000
Subject: Fix quickstart tutorial
---
docs/tutorial/quickstart.md | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/docs/tutorial/quickstart.md b/docs/tutorial/quickstart.md
index 3e1ce0a9..d0703381 100644
--- a/docs/tutorial/quickstart.md
+++ b/docs/tutorial/quickstart.md
@@ -19,10 +19,9 @@ Create a new Django project named `tutorial`, then start a new app called `quick
pip install djangorestframework
# Set up a new project with a single application
- django-admin.py startproject tutorial
+ django-admin.py startproject tutorial .
cd tutorial
django-admin.py startapp quickstart
- cd ..
Now sync your database for the first time:
--
cgit v1.2.3
From 1b8c06aefe33f178610d2c4195a72637757698e8 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Fri, 5 Dec 2014 12:46:08 +0000
Subject: Style tweaks in examples
---
docs/tutorial/1-serialization.md | 18 +++++-------------
1 file changed, 5 insertions(+), 13 deletions(-)
diff --git a/docs/tutorial/1-serialization.md b/docs/tutorial/1-serialization.md
index eb0a00c0..fc1b87f9 100644
--- a/docs/tutorial/1-serialization.md
+++ b/docs/tutorial/1-serialization.md
@@ -16,7 +16,6 @@ The tutorial is fairly in-depth, so you should probably get a cookie and a cup o
Before we do anything else we'll create a new virtual environment, using [virtualenv]. This will make sure our package configuration is kept nicely isolated from any other projects we're working on.
- :::bash
virtualenv env
source env/bin/activate
@@ -75,12 +74,8 @@ For the purposes of this tutorial we're going to start by creating a simple `Sni
title = models.CharField(max_length=100, blank=True, default='')
code = models.TextField()
linenos = models.BooleanField(default=False)
- language = models.CharField(choices=LANGUAGE_CHOICES,
- default='python',
- max_length=100)
- style = models.CharField(choices=STYLE_CHOICES,
- default='friendly',
- max_length=100)
+ language = models.CharField(choices=LANGUAGE_CHOICES, default='python', max_length=100)
+ style = models.CharField(choices=STYLE_CHOICES, default='friendly', max_length=100)
class Meta:
ordering = ('created',)
@@ -101,14 +96,11 @@ The first thing we need to get started on our Web API is to provide a way of ser
class SnippetSerializer(serializers.Serializer):
pk = serializers.IntegerField(read_only=True)
- title = serializers.CharField(required=False, allow_blank=True
- max_length=100)
+ title = serializers.CharField(required=False, allow_blank=True, max_length=100)
code = serializers.CharField(style={'type': 'textarea'})
linenos = serializers.BooleanField(required=False)
- language = serializers.ChoiceField(choices=LANGUAGE_CHOICES,
- default='python')
- style = serializers.ChoiceField(choices=STYLE_CHOICES,
- default='friendly')
+ language = serializers.ChoiceField(choices=LANGUAGE_CHOICES, default='python')
+ style = serializers.ChoiceField(choices=STYLE_CHOICES, default='friendly')
def create(self, validated_data):
"""
--
cgit v1.2.3
From b7b0fd3e146648ba2dc03621edd979abaebcb3b3 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Fri, 5 Dec 2014 12:48:12 +0000
Subject: Added .validated_data usage. Closes #2214.
---
docs/tutorial/1-serialization.md | 2 ++
1 file changed, 2 insertions(+)
diff --git a/docs/tutorial/1-serialization.md b/docs/tutorial/1-serialization.md
index fc1b87f9..3621f01b 100644
--- a/docs/tutorial/1-serialization.md
+++ b/docs/tutorial/1-serialization.md
@@ -173,6 +173,8 @@ Deserialization is similar. First we parse a stream into Python native datatype
serializer = SnippetSerializer(data=data)
serializer.is_valid()
# True
+ serializer.validated_data
+ # OrderedDict([('title', ''), ('code', 'print "hello, world"\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')])
serializer.save()
#
--
cgit v1.2.3
From 9fb1b396db751234a531dabacb6758ac2645776c Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Fri, 5 Dec 2014 13:07:31 +0000
Subject: user in example should have been instance. Closees #2191.
---
docs/api-guide/serializers.md | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/docs/api-guide/serializers.md b/docs/api-guide/serializers.md
index 1779c863..ab44839f 100644
--- a/docs/api-guide/serializers.md
+++ b/docs/api-guide/serializers.md
@@ -326,9 +326,9 @@ Here's an example for an `update()` method on our previous `UserSerializer` clas
# would need to be handled.
profile = instance.profile
- user.username = validated_data.get('username', instance.username)
- user.email = validated_data.get('email', instance.email)
- user.save()
+ instance.username = validated_data.get('username', instance.username)
+ instance.email = validated_data.get('email', instance.email)
+ instance.save()
profile.is_premium_member = profile_data.get(
'is_premium_member',
@@ -340,7 +340,7 @@ Here's an example for an `update()` method on our previous `UserSerializer` clas
)
profile.save()
- return user
+ return instance
Because the behavior of nested creates and updates can be ambiguous, and may require complex dependancies between related models, REST framework 3 requires you to always write these methods explicitly. The default `ModelSerializer` `.create()` and `.update()` methods do not include support for writable nested representations.
--
cgit v1.2.3
From 59b2ad542580eb93243c4403ded4c2b4dc8518c2 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Fri, 5 Dec 2014 13:23:14 +0000
Subject: Minor docs tweaks
---
docs/api-guide/serializers.md | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/docs/api-guide/serializers.md b/docs/api-guide/serializers.md
index 1f97614d..5fe6b4c2 100644
--- a/docs/api-guide/serializers.md
+++ b/docs/api-guide/serializers.md
@@ -507,9 +507,11 @@ Model fields which have `editable=False` set, and `AutoField` fields will be set
---
-**Note**: There is a special-case where a read-only field is part of a `unique_together` constraint at the model level. Here you **must** specify the field explicitly and provide a valid default value.
+**Note**: There is a special-case where a read-only field is part of a `unique_together` constraint at the model level. In this case the field is required by the serializer class in order to validate the constraint, but should also not be editable by the user.
-A common example of this is a read-only relation to the currently authenticated `User` which is `unique_together` with another identifier. In this case you would declare the user field like so:
+The right way to deal with this is to specify the field explicitly on the serializer, providing both the `read_only=True` and `default=…` keyword arguments.
+
+One example of this is a read-only relation to the currently authenticated `User` which is `unique_together` with another identifier. In this case you would declare the user field like so:
user = serializers.PrimaryKeyRelatedField(read_only=True, default=serializers.CurrentUserDefault())
--
cgit v1.2.3
From ca74fa989dd5a3236894736c838fe0a21c312e2a Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Fri, 5 Dec 2014 13:50:28 +0000
Subject: Better serializer errors for nested writes. Closes #2202
---
rest_framework/serializers.py | 81 ++++++++++++++++++++++++++++++++-----------
1 file changed, 60 insertions(+), 21 deletions(-)
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index e1851ddd..68d0b8cc 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -561,6 +561,64 @@ class ListSerializer(BaseSerializer):
# ModelSerializer & HyperlinkedModelSerializer
# --------------------------------------------
+def raise_errors_on_nested_writes(method_name, serializer):
+ """
+ Give explicit errors when users attempt to pass writable nested data.
+
+ If we don't do this explicitly they'd get a less helpful error when
+ calling `.save()` on the serializer.
+
+ We don't *automatically* support these sorts of nested writes brecause
+ there are too many ambiguities to define a default behavior.
+
+ Eg. Suppose we have a `UserSerializer` with a nested profile. How should
+ we handle the case of an update, where the `profile` realtionship does
+ not exist? Any of the following might be valid:
+
+ * Raise an application error.
+ * Silently ignore the nested part of the update.
+ * Automatically create a profile instance.
+ """
+
+ # Ensure we don't have a writable nested field. For example:
+ #
+ # class UserSerializer(ModelSerializer):
+ # ...
+ # profile = ProfileSerializer()
+ assert not any(
+ isinstance(field, BaseSerializer) and (key in validated_attrs)
+ for key, field in serializer.fields.items()
+ ), (
+ 'The `.{method_name}()` method does not support nested writable '
+ 'fields by default. Write an explicit `.{method_name}()` method for '
+ 'serializer `{module}.{class_name}`, or set `read_only=True` on '
+ 'nested serializer fields.'.format(
+ method_name=method_name,
+ module=serializer.__class__.__module__,
+ class_name=serializer.__class__.__name__
+ )
+ )
+
+ # Ensure we don't have a writable dotted-source field. For example:
+ #
+ # class UserSerializer(ModelSerializer):
+ # ...
+ # address = serializer.CharField('profile.address')
+ assert not any(
+ '.' in field.source and (key in validated_attrs)
+ for key, field in serializer.fields.items()
+ ), (
+ 'The `.{method_name}()` method does not support writable dotted-source '
+ 'fields by default. Write an explicit `.{method_name}()` method for '
+ 'serializer `{module}.{class_name}`, or set `read_only=True` on '
+ 'dotted-source serializer fields.'.format(
+ method_name=method_name,
+ module=serializer.__class__.__module__,
+ class_name=serializer.__class__.__name__
+ )
+ )
+
+
class ModelSerializer(Serializer):
"""
A `ModelSerializer` is just a regular `Serializer`, except that:
@@ -624,18 +682,7 @@ class ModelSerializer(Serializer):
If you want to support writable nested relationships you'll need
to write an explicit `.create()` method.
"""
- # Check that the user isn't trying to handle a writable nested field.
- # If we don't do this explicitly they'd likely get a confusing
- # error at the point of calling `Model.objects.create()`.
- assert not any(
- isinstance(field, BaseSerializer) and (key in validated_attrs)
- for key, field in self.fields.items()
- ), (
- 'The `.create()` method does not support nested writable fields '
- 'by default. Write an explicit `.create()` method for serializer '
- '`%s.%s`, or set `read_only=True` on nested serializer fields.' %
- (self.__class__.__module__, self.__class__.__name__)
- )
+ raise_errors_on_nested_writes('create', self)
ModelClass = self.Meta.model
@@ -675,15 +722,7 @@ class ModelSerializer(Serializer):
return instance
def update(self, instance, validated_data):
- assert not any(
- isinstance(field, BaseSerializer) and (key in validated_attrs)
- for key, field in self.fields.items()
- ), (
- 'The `.update()` method does not support nested writable fields '
- 'by default. Write an explicit `.update()` method for serializer '
- '`%s.%s`, or set `read_only=True` on nested serializer fields.' %
- (self.__class__.__module__, self.__class__.__name__)
- )
+ raise_errors_on_nested_writes('update', self)
for attr, value in validated_data.items():
setattr(instance, attr, value)
--
cgit v1.2.3
From 88900a0844f1b0cd996235ae0f99105563ae6473 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Fri, 5 Dec 2014 13:58:39 +0000
Subject: Minor tweaks
---
rest_framework/serializers.py | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index 68d0b8cc..c022cad3 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -589,8 +589,8 @@ def raise_errors_on_nested_writes(method_name, serializer):
isinstance(field, BaseSerializer) and (key in validated_attrs)
for key, field in serializer.fields.items()
), (
- 'The `.{method_name}()` method does not support nested writable '
- 'fields by default. Write an explicit `.{method_name}()` method for '
+ 'The `.{method_name}()` method does not support writable nested'
+ 'fields by default.\nWrite an explicit `.{method_name}()` method for '
'serializer `{module}.{class_name}`, or set `read_only=True` on '
'nested serializer fields.'.format(
method_name=method_name,
@@ -609,7 +609,7 @@ def raise_errors_on_nested_writes(method_name, serializer):
for key, field in serializer.fields.items()
), (
'The `.{method_name}()` method does not support writable dotted-source '
- 'fields by default. Write an explicit `.{method_name}()` method for '
+ 'fields by default.\nWrite an explicit `.{method_name}()` method for '
'serializer `{module}.{class_name}`, or set `read_only=True` on '
'dotted-source serializer fields.'.format(
method_name=method_name,
@@ -727,6 +727,7 @@ class ModelSerializer(Serializer):
for attr, value in validated_data.items():
setattr(instance, attr, value)
instance.save()
+
return instance
def get_validators(self):
--
cgit v1.2.3
From 544967f36ed6e29819018428d48da00de74958b9 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Fri, 5 Dec 2014 14:15:58 +0000
Subject: Test tweaks
---
rest_framework/serializers.py | 10 ++++--
tests/test_model_serializer.py | 50 ++++++++++++++++++++++++++++++
tests/test_serializer_metaclass.py | 62 --------------------------------------
3 files changed, 58 insertions(+), 64 deletions(-)
delete mode 100644 tests/test_serializer_metaclass.py
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index 8784b303..d8e544d4 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -795,10 +795,16 @@ class ModelSerializer(Serializer):
extra_kwargs = getattr(self.Meta, 'extra_kwargs', {})
if fields and not isinstance(fields, (list, tuple)):
- raise TypeError('`fields` must be a list or tuple')
+ raise TypeError(
+ 'The `fields` option must be a list or tuple. Got %s.' %
+ type(fields).__name__
+ )
if exclude and not isinstance(exclude, (list, tuple)):
- raise TypeError('`exclude` must be a list or tuple')
+ raise TypeError(
+ 'The `exclude` option must be a list or tuple. Got %s.' %
+ type(exclude).__name__
+ )
assert not (fields and exclude), "Cannot set both 'fields' and 'exclude'."
diff --git a/tests/test_model_serializer.py b/tests/test_model_serializer.py
index 1bcd58e0..da79164a 100644
--- a/tests/test_model_serializer.py
+++ b/tests/test_model_serializer.py
@@ -559,3 +559,53 @@ class TestBulkCreate(TestCase):
# Serializer returns correct data.
assert serializer.data == data
+
+
+class TestMetaClassModel(models.Model):
+ text = models.CharField(max_length=100)
+
+
+class TestSerializerMetaClass(TestCase):
+ def test_meta_class_fields_option(self):
+ class ExampleSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = TestMetaClassModel
+ fields = 'text'
+
+ with self.assertRaises(TypeError) as result:
+ ExampleSerializer().fields
+
+ exception = result.exception
+ assert str(exception).startswith(
+ "The `fields` option must be a list or tuple"
+ )
+
+ def test_meta_class_exclude_option(self):
+ class ExampleSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = TestMetaClassModel
+ exclude = 'text'
+
+ with self.assertRaises(TypeError) as result:
+ ExampleSerializer().fields
+
+ exception = result.exception
+ assert str(exception).startswith(
+ "The `exclude` option must be a list or tuple"
+ )
+
+ def test_meta_class_fields_and_exclude_options(self):
+ class ExampleSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = TestMetaClassModel
+ fields = ('text',)
+ exclude = ('text',)
+
+ with self.assertRaises(AssertionError) as result:
+ ExampleSerializer().fields
+
+ exception = result.exception
+ self.assertEqual(
+ str(exception),
+ "Cannot set both 'fields' and 'exclude'."
+ )
diff --git a/tests/test_serializer_metaclass.py b/tests/test_serializer_metaclass.py
deleted file mode 100644
index bd2fbed7..00000000
--- a/tests/test_serializer_metaclass.py
+++ /dev/null
@@ -1,62 +0,0 @@
-from django.test import TestCase
-from rest_framework import serializers
-from .models import BasicModel
-
-
-class TestSerializerMetaClass(TestCase):
- def setUp(self):
- class FieldsSerializer(serializers.ModelSerializer):
- text = serializers.CharField()
-
- class Meta:
- model = BasicModel
- fields = ('text')
-
- class ExcludeSerializer(serializers.ModelSerializer):
- text = serializers.CharField()
-
- class Meta:
- model = BasicModel
- exclude = ('text')
-
- class FieldsAndExcludeSerializer(serializers.ModelSerializer):
- text = serializers.CharField()
-
- class Meta:
- model = BasicModel
- fields = ('text',)
- exclude = ('text',)
-
- self.fields_serializer = FieldsSerializer
- self.exclude_serializer = ExcludeSerializer
- self.faeSerializer = FieldsAndExcludeSerializer
-
- def test_meta_class_fields(self):
- object = BasicModel(text="Hello World.")
- serializer = self.fields_serializer(instance=object)
-
- with self.assertRaises(TypeError) as result:
- serializer.data
-
- exception = result.exception
- self.assertEqual(str(exception), "`fields` must be a list or tuple")
-
- def test_meta_class_exclude(self):
- object = BasicModel(text="Hello World.")
- serializer = self.exclude_serializer(instance=object)
-
- with self.assertRaises(TypeError) as result:
- serializer.data
-
- exception = result.exception
- self.assertEqual(str(exception), "`exclude` must be a list or tuple")
-
- def test_meta_class_fields_and_exclude(self):
- object = BasicModel(text="Hello World.")
- serializer = self.faeSerializer(instance=object)
-
- with self.assertRaises(AssertionError) as result:
- serializer.data
-
- exception = result.exception
- self.assertEqual(str(exception), "Cannot set both 'fields' and 'exclude'.")
--
cgit v1.2.3
From e4820d611bb17e33bf466c2a6dedcce7548d8d21 Mon Sep 17 00:00:00 2001
From: Xavier Ordoquy
Date: Sat, 6 Dec 2014 10:53:24 +0100
Subject: Fix the new Django default db name PrimaryKeyRelatedField now needs a
queryset argument. urls now don't use urlpatterns.
---
docs/tutorial/4-authentication-and-permissions.md | 4 ++--
docs/tutorial/5-relationships-and-hyperlinked-apis.md | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/docs/tutorial/4-authentication-and-permissions.md b/docs/tutorial/4-authentication-and-permissions.md
index 4e4edeea..43c1953e 100644
--- a/docs/tutorial/4-authentication-and-permissions.md
+++ b/docs/tutorial/4-authentication-and-permissions.md
@@ -43,7 +43,7 @@ And now we can add a `.save()` method to our model class:
When that's all done we'll need to update our database tables.
Normally we'd create a database migration in order to do that, but for the purposes of this tutorial, let's just delete the database and start again.
- rm tmp.db
+ rm -f tmp.db db.sqlite3
rm -r snippets/migrations
python manage.py makemigrations snippets
python manage.py migrate
@@ -59,7 +59,7 @@ Now that we've got some users to work with, we'd better add representations of t
from django.contrib.auth.models import User
class UserSerializer(serializers.ModelSerializer):
- snippets = serializers.PrimaryKeyRelatedField(many=True)
+ snippets = serializers.PrimaryKeyRelatedField(many=True, queryset=Snippet.objects.all())
class Meta:
model = User
diff --git a/docs/tutorial/5-relationships-and-hyperlinked-apis.md b/docs/tutorial/5-relationships-and-hyperlinked-apis.md
index 50552616..58422929 100644
--- a/docs/tutorial/5-relationships-and-hyperlinked-apis.md
+++ b/docs/tutorial/5-relationships-and-hyperlinked-apis.md
@@ -44,7 +44,7 @@ Instead of using a concrete generic view, we'll use the base class for represent
As usual we need to add the new views that we've created in to our URLconf.
We'll add a url pattern for our new API root in `snippets/urls.py`:
- url(r'^$', 'api_root'),
+ url(r'^$', views.api_root),
And then add a url pattern for the snippet highlights:
--
cgit v1.2.3
From a257b04928f07656ac4541e0a3fae0afad2848bb Mon Sep 17 00:00:00 2001
From: Xavier Ordoquy
Date: Sun, 7 Dec 2014 12:12:40 +0100
Subject: Fix missing validated_data in `raise_errors_on_nested_writes` (#2221)
---
rest_framework/serializers.py | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index d8e544d4..39523077 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -561,7 +561,7 @@ class ListSerializer(BaseSerializer):
# ModelSerializer & HyperlinkedModelSerializer
# --------------------------------------------
-def raise_errors_on_nested_writes(method_name, serializer):
+def raise_errors_on_nested_writes(method_name, serializer, validated_data):
"""
Give explicit errors when users attempt to pass writable nested data.
@@ -586,7 +586,7 @@ def raise_errors_on_nested_writes(method_name, serializer):
# ...
# profile = ProfileSerializer()
assert not any(
- isinstance(field, BaseSerializer) and (key in validated_attrs)
+ isinstance(field, BaseSerializer) and (key in validated_data)
for key, field in serializer.fields.items()
), (
'The `.{method_name}()` method does not support writable nested'
@@ -605,7 +605,7 @@ def raise_errors_on_nested_writes(method_name, serializer):
# ...
# address = serializer.CharField('profile.address')
assert not any(
- '.' in field.source and (key in validated_attrs)
+ '.' in field.source and (key in validated_data)
for key, field in serializer.fields.items()
), (
'The `.{method_name}()` method does not support writable dotted-source '
@@ -682,7 +682,7 @@ class ModelSerializer(Serializer):
If you want to support writable nested relationships you'll need
to write an explicit `.create()` method.
"""
- raise_errors_on_nested_writes('create', self)
+ raise_errors_on_nested_writes('create', self, validated_data)
ModelClass = self.Meta.model
@@ -722,7 +722,7 @@ class ModelSerializer(Serializer):
return instance
def update(self, instance, validated_data):
- raise_errors_on_nested_writes('update', self)
+ raise_errors_on_nested_writes('update', self, validated_data)
for attr, value in validated_data.items():
setattr(instance, attr, value)
--
cgit v1.2.3
From 9b468fba60def77144949628211aac95c6316c70 Mon Sep 17 00:00:00 2001
From: Mark Henwood
Date: Sun, 7 Dec 2014 11:57:07 +0000
Subject: Amend ViewSet docs to warn of potential problem
I went through this exact problem and so thought the docs might
benefit from a small hint at the appropriate point.
---
docs/api-guide/viewsets.md | 3 +++
1 file changed, 3 insertions(+)
diff --git a/docs/api-guide/viewsets.md b/docs/api-guide/viewsets.md
index 28186c64..3e37cef8 100644
--- a/docs/api-guide/viewsets.md
+++ b/docs/api-guide/viewsets.md
@@ -201,6 +201,8 @@ Note that you can use any of the standard attributes or method overrides provide
def get_queryset(self):
return self.request.user.accounts.all()
+Note however that upon removal of the `queryset` property from your `ViewSet`, any associated [router][routers] will be unable to derive the base_name of your Model automatically, and so you you will have to specify the `base_name` kwarg as part of your [router registration][routers].
+
Also note that although this class provides the complete set of create/list/retrieve/update/destroy actions by default, you can restrict the available operations by using the standard permission classes.
## ReadOnlyModelViewSet
@@ -243,3 +245,4 @@ To create a base viewset class that provides `create`, `list` and `retrieve` ope
By creating your own base `ViewSet` classes, you can provide common behavior that can be reused in multiple viewsets across your API.
[cite]: http://guides.rubyonrails.org/routing.html
+[routers]: routers.md
--
cgit v1.2.3
From ff02a0af9c8d2dadebfadcc65e17adae3b0bd111 Mon Sep 17 00:00:00 2001
From: Loic Bistuer
Date: Wed, 3 Dec 2014 17:29:06 +0700
Subject: Fixed display issues with tooltips.
---
rest_framework/static/rest_framework/js/default.js | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/rest_framework/static/rest_framework/js/default.js b/rest_framework/static/rest_framework/js/default.js
index bcb1964d..c8812132 100644
--- a/rest_framework/static/rest_framework/js/default.js
+++ b/rest_framework/static/rest_framework/js/default.js
@@ -24,7 +24,8 @@ prettyPrint();
// Bootstrap tooltips.
$('.js-tooltip').tooltip({
- delay: 1000
+ delay: 1000,
+ container: 'body'
});
// Deal with rounded tab styling after tab clicks.
--
cgit v1.2.3
From b90f4228b274abc3010b159d6abe7b2381067253 Mon Sep 17 00:00:00 2001
From: Loic Bistuer
Date: Wed, 3 Dec 2014 17:30:07 +0700
Subject: Ported a couple of templates to Bootstrap 3.x.
---
.../templates/rest_framework/login_base.html | 23 +++++++++++-----------
.../templates/rest_framework/raw_data_form.html | 8 ++++----
2 files changed, 15 insertions(+), 16 deletions(-)
diff --git a/rest_framework/templates/rest_framework/login_base.html b/rest_framework/templates/rest_framework/login_base.html
index e050cbdc..8e6240a6 100644
--- a/rest_framework/templates/rest_framework/login_base.html
+++ b/rest_framework/templates/rest_framework/login_base.html
@@ -21,11 +21,11 @@
{% csrf_token %}