- {% block branding %}Django REST framework {{ version }}{% endblock %}
+ {% block branding %}Django REST framework {{ version }}{% endblock %}
{% block userlinks %}
--
cgit v1.2.3
From 52db4eadc204c3e5ec234729c24275c8ed803634 Mon Sep 17 00:00:00 2001
From: Dustin Farris
Date: Wed, 8 Jan 2014 16:14:27 -0500
Subject: Testing nested serializers with models that have str foreign key
references.
---
rest_framework/runtests/settings.py | 3 +++
rest_framework/tests/accounts/__init__.py | 0
rest_framework/tests/accounts/models.py | 8 ++++++++
rest_framework/tests/accounts/serializers.py | 11 +++++++++++
rest_framework/tests/records/__init__.py | 0
rest_framework/tests/records/models.py | 6 ++++++
rest_framework/tests/test_serializer_nested.py | 5 +++++
rest_framework/tests/users/__init__.py | 0
rest_framework/tests/users/models.py | 6 ++++++
rest_framework/tests/users/serializers.py | 8 ++++++++
10 files changed, 47 insertions(+)
create mode 100644 rest_framework/tests/accounts/__init__.py
create mode 100644 rest_framework/tests/accounts/models.py
create mode 100644 rest_framework/tests/accounts/serializers.py
create mode 100644 rest_framework/tests/records/__init__.py
create mode 100644 rest_framework/tests/records/models.py
create mode 100644 rest_framework/tests/users/__init__.py
create mode 100644 rest_framework/tests/users/models.py
create mode 100644 rest_framework/tests/users/serializers.py
(limited to 'rest_framework')
diff --git a/rest_framework/runtests/settings.py b/rest_framework/runtests/settings.py
index be721658..3fc0eb2f 100644
--- a/rest_framework/runtests/settings.py
+++ b/rest_framework/runtests/settings.py
@@ -100,6 +100,9 @@ INSTALLED_APPS = (
'rest_framework',
'rest_framework.authtoken',
'rest_framework.tests',
+ 'rest_framework.tests.accounts',
+ 'rest_framework.tests.records',
+ 'rest_framework.tests.users',
)
# OAuth is optional and won't work if there is no oauth_provider & oauth2
diff --git a/rest_framework/tests/accounts/__init__.py b/rest_framework/tests/accounts/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/rest_framework/tests/accounts/models.py b/rest_framework/tests/accounts/models.py
new file mode 100644
index 00000000..525e601b
--- /dev/null
+++ b/rest_framework/tests/accounts/models.py
@@ -0,0 +1,8 @@
+from django.db import models
+
+from rest_framework.tests.users.models import User
+
+
+class Account(models.Model):
+ owner = models.ForeignKey(User, related_name='accounts_owned')
+ admins = models.ManyToManyField(User, blank=True, null=True, related_name='accounts_administered')
diff --git a/rest_framework/tests/accounts/serializers.py b/rest_framework/tests/accounts/serializers.py
new file mode 100644
index 00000000..a27b9ca6
--- /dev/null
+++ b/rest_framework/tests/accounts/serializers.py
@@ -0,0 +1,11 @@
+from rest_framework import serializers
+
+from rest_framework.tests.accounts.models import Account
+from rest_framework.tests.users.serializers import UserSerializer
+
+
+class AccountSerializer(serializers.ModelSerializer):
+ admins = UserSerializer(many=True)
+
+ class Meta:
+ model = Account
diff --git a/rest_framework/tests/records/__init__.py b/rest_framework/tests/records/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/rest_framework/tests/records/models.py b/rest_framework/tests/records/models.py
new file mode 100644
index 00000000..76954807
--- /dev/null
+++ b/rest_framework/tests/records/models.py
@@ -0,0 +1,6 @@
+from django.db import models
+
+
+class Record(models.Model):
+ account = models.ForeignKey('accounts.Account', blank=True, null=True)
+ owner = models.ForeignKey('users.User', blank=True, null=True)
diff --git a/rest_framework/tests/test_serializer_nested.py b/rest_framework/tests/test_serializer_nested.py
index 7114a060..686a1f5f 100644
--- a/rest_framework/tests/test_serializer_nested.py
+++ b/rest_framework/tests/test_serializer_nested.py
@@ -346,3 +346,8 @@ class NestedModelSerializerUpdateTests(TestCase):
result.save()
self.assertEqual(result.id, john.id)
+
+class ImportingModelSerializerWithStrForeignKeys(TestCase):
+ def test_import_model_serializer(self):
+ from rest_framework.tests.accounts.serializers import AccountSerializer
+ self.assertIsInstance(AccountSerializer(), serializers.ModelSerializer)
diff --git a/rest_framework/tests/users/__init__.py b/rest_framework/tests/users/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/rest_framework/tests/users/models.py b/rest_framework/tests/users/models.py
new file mode 100644
index 00000000..128bac90
--- /dev/null
+++ b/rest_framework/tests/users/models.py
@@ -0,0 +1,6 @@
+from django.db import models
+
+
+class User(models.Model):
+ account = models.ForeignKey('accounts.Account', blank=True, null=True, related_name='users')
+ active_record = models.ForeignKey('records.Record', blank=True, null=True)
diff --git a/rest_framework/tests/users/serializers.py b/rest_framework/tests/users/serializers.py
new file mode 100644
index 00000000..da496554
--- /dev/null
+++ b/rest_framework/tests/users/serializers.py
@@ -0,0 +1,8 @@
+from rest_framework import serializers
+
+from rest_framework.tests.users.models import User
+
+
+class UserSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = User
--
cgit v1.2.3
From 82a48ca55e9ff10db3feafc2bf0489057a9b4a19 Mon Sep 17 00:00:00 2001
From: Fredrik Möllerstrand
Date: Thu, 9 Jan 2014 15:39:37 +0000
Subject: fix docstring typos.
---
rest_framework/request.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
(limited to 'rest_framework')
diff --git a/rest_framework/request.py b/rest_framework/request.py
index b883d0d4..adf63785 100644
--- a/rest_framework/request.py
+++ b/rest_framework/request.py
@@ -223,7 +223,7 @@ class Request(object):
def user(self, value):
"""
Sets the user on the current request. This is necessary to maintain
- compatilbility with django.contrib.auth where the user proprety is
+ compatibility with django.contrib.auth where the user property is
set in the login and logout functions.
"""
self._user = value
--
cgit v1.2.3
From bf5b77ce6d171723f2d187aadd29c8ee4cdc3870 Mon Sep 17 00:00:00 2001
From: Dustin Farris
Date: Thu, 9 Jan 2014 11:42:41 -0500
Subject: Move serializer import to top-level causes test error
---
rest_framework/tests/test_serializer_nested.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
(limited to 'rest_framework')
diff --git a/rest_framework/tests/test_serializer_nested.py b/rest_framework/tests/test_serializer_nested.py
index 686a1f5f..d0a773fe 100644
--- a/rest_framework/tests/test_serializer_nested.py
+++ b/rest_framework/tests/test_serializer_nested.py
@@ -6,6 +6,7 @@ Doesn't cover model serializers.
from __future__ import unicode_literals
from django.test import TestCase
from rest_framework import serializers
+from rest_framework.tests.accounts.serializers import AccountSerializer
from . import models
@@ -349,5 +350,4 @@ class NestedModelSerializerUpdateTests(TestCase):
class ImportingModelSerializerWithStrForeignKeys(TestCase):
def test_import_model_serializer(self):
- from rest_framework.tests.accounts.serializers import AccountSerializer
self.assertIsInstance(AccountSerializer(), serializers.ModelSerializer)
--
cgit v1.2.3
From a7072eb730813dbd93cbe33da220c067beddc6d1 Mon Sep 17 00:00:00 2001
From: Xavier Ordoquy
Date: Thu, 9 Jan 2014 23:19:25 +0100
Subject: Test case for #1129
---
rest_framework/tests/test_relations.py | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
(limited to 'rest_framework')
diff --git a/rest_framework/tests/test_relations.py b/rest_framework/tests/test_relations.py
index d19219c9..f52e0e1e 100644
--- a/rest_framework/tests/test_relations.py
+++ b/rest_framework/tests/test_relations.py
@@ -98,3 +98,23 @@ class RelatedFieldSourceTests(TestCase):
obj = ClassWithQuerysetMethod()
value = field.field_to_native(obj, 'field_name')
self.assertEqual(value, ['BlogPost object'])
+
+ # Regression for #1129
+ def test_exception_for_incorect_fk(self):
+ """
+ Check that the exception message are correct if the source field
+ doesn't exist.
+ """
+ from rest_framework.tests.models import ManyToManySource
+ class Meta:
+ model = ManyToManySource
+ attrs = {
+ 'name': serializers.SlugRelatedField(
+ slug_field='name', source='banzai'),
+ 'Meta': Meta,
+ }
+
+ TestSerializer = type(str('TestSerializer'),
+ (serializers.ModelSerializer,), attrs)
+ with self.assertRaises(AttributeError):
+ TestSerializer(data={'name': 'foo'})
--
cgit v1.2.3
From f217f6870dec51b70ae5afcce8cb4bff54b511d9 Mon Sep 17 00:00:00 2001
From: Xavier Ordoquy
Date: Thu, 9 Jan 2014 23:32:50 +0100
Subject: Removed useless try/except
---
rest_framework/relations.py | 15 +++++----------
1 file changed, 5 insertions(+), 10 deletions(-)
(limited to 'rest_framework')
diff --git a/rest_framework/relations.py b/rest_framework/relations.py
index 35c00bf1..02185c2f 100644
--- a/rest_framework/relations.py
+++ b/rest_framework/relations.py
@@ -65,16 +65,11 @@ class RelatedField(WritableField):
def initialize(self, parent, field_name):
super(RelatedField, self).initialize(parent, field_name)
if self.queryset is None and not self.read_only:
- try:
- manager = getattr(self.parent.opts.model, self.source or field_name)
- if hasattr(manager, 'related'): # Forward
- self.queryset = manager.related.model._default_manager.all()
- else: # Reverse
- self.queryset = manager.field.rel.to._default_manager.all()
- except Exception:
- msg = ('Serializer related fields must include a `queryset`' +
- ' argument or set `read_only=True')
- raise Exception(msg)
+ manager = getattr(self.parent.opts.model, self.source or field_name)
+ if hasattr(manager, 'related'): # Forward
+ self.queryset = manager.related.model._default_manager.all()
+ else: # Reverse
+ self.queryset = manager.field.rel.to._default_manager.all()
### We need this stuff to make form choices work...
--
cgit v1.2.3
From 2332382b5109939238801e7d4c018455d159fe91 Mon Sep 17 00:00:00 2001
From: Dustin Farris
Date: Sun, 12 Jan 2014 20:28:19 -0500
Subject: Add a sanity check to avoid running into unresolved related models.
---
rest_framework/models.py | 23 ++++++++++++++++++++++-
rest_framework/serializers.py | 3 ++-
rest_framework/tests/test_models.py | 28 ++++++++++++++++++++++++++++
3 files changed, 52 insertions(+), 2 deletions(-)
create mode 100644 rest_framework/tests/test_models.py
(limited to 'rest_framework')
diff --git a/rest_framework/models.py b/rest_framework/models.py
index 5b53a526..249cdd82 100644
--- a/rest_framework/models.py
+++ b/rest_framework/models.py
@@ -1 +1,22 @@
-# Just to keep things like ./manage.py test happy
+import inspect
+
+from django.db import models
+
+
+def resolve_model(obj):
+ """
+ Resolve supplied `obj` to a Django model class.
+
+ `obj` must be a Django model class, or a string representation
+ of one.
+
+ String representations should have the format:
+ 'appname.ModelName'
+ """
+ if type(obj) == str and len(obj.split('.')) == 2:
+ app_name, model_name = obj.split('.')
+ return models.get_model(app_name, model_name)
+ elif inspect.isclass(obj) and issubclass(obj, models.Model):
+ return obj
+ else:
+ raise ValueError("{0} is not a valid Django model".format(obj))
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index b22ca578..6b31c304 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -20,6 +20,7 @@ from django.db import models
from django.forms import widgets
from django.utils.datastructures import SortedDict
from rest_framework.compat import get_concrete_model, six
+from rest_framework.models import resolve_model
# Note: We do the following so that users of the framework can use this style:
#
@@ -656,7 +657,7 @@ class ModelSerializer(Serializer):
if model_field.rel:
to_many = isinstance(model_field,
models.fields.related.ManyToManyField)
- related_model = model_field.rel.to
+ related_model = resolve_model(model_field.rel.to)
if to_many and not model_field.rel.through._meta.auto_created:
has_through_model = True
diff --git a/rest_framework/tests/test_models.py b/rest_framework/tests/test_models.py
new file mode 100644
index 00000000..5e92d48a
--- /dev/null
+++ b/rest_framework/tests/test_models.py
@@ -0,0 +1,28 @@
+from django.db import models
+from django.test import TestCase
+
+from rest_framework.models import resolve_model
+from rest_framework.tests.models import BasicModel
+
+
+class ResolveModelTests(TestCase):
+ """
+ `resolve_model` should return a Django model class given the
+ provided argument is a Django model class itself, or a properly
+ formatted string representation of one.
+ """
+ def test_resolve_django_model(self):
+ resolved_model = resolve_model(BasicModel)
+ self.assertEqual(resolved_model, BasicModel)
+
+ def test_resolve_string_representation(self):
+ resolved_model = resolve_model('tests.BasicModel')
+ self.assertEqual(resolved_model, BasicModel)
+
+ def test_resolve_non_django_model(self):
+ with self.assertRaises(ValueError):
+ resolve_model(TestCase)
+
+ def test_resolve_with_improper_string_representation(self):
+ with self.assertRaises(ValueError):
+ resolve_model('BasicModel')
--
cgit v1.2.3
From 65858428fd7498c1783af0ec13de51521fe29c63 Mon Sep 17 00:00:00 2001
From: S.Prymak
Date: Mon, 13 Jan 2014 16:35:20 +0200
Subject: Make HyperlinkedModelSerializer `url` field name customizable
---
rest_framework/serializers.py | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
(limited to 'rest_framework')
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index b22ca578..0b5c6a05 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -990,6 +990,7 @@ class HyperlinkedModelSerializerOptions(ModelSerializerOptions):
super(HyperlinkedModelSerializerOptions, self).__init__(meta)
self.view_name = getattr(meta, 'view_name', None)
self.lookup_field = getattr(meta, 'lookup_field', None)
+ self.url_field_name = getattr(meta, 'url_field_name', 'url')
class HyperlinkedModelSerializer(ModelSerializer):
@@ -1008,13 +1009,13 @@ class HyperlinkedModelSerializer(ModelSerializer):
if self.opts.view_name is None:
self.opts.view_name = self._get_default_view_name(self.opts.model)
- if 'url' not in fields:
+ if self.opts.url_field_name not in fields:
url_field = self._hyperlink_identify_field_class(
view_name=self.opts.view_name,
lookup_field=self.opts.lookup_field
)
ret = self._dict_class()
- ret['url'] = url_field
+ ret[self.opts.url_field_name] = url_field
ret.update(fields)
fields = ret
@@ -1050,7 +1051,7 @@ class HyperlinkedModelSerializer(ModelSerializer):
We need to override the default, to use the url as the identity.
"""
try:
- return data.get('url', None)
+ return data.get(self.opts.url_field_name, None)
except AttributeError:
return None
--
cgit v1.2.3
From b1b58762a3d84ac4cdc6553e8ed06983fd3502ca Mon Sep 17 00:00:00 2001
From: Dustin Farris
Date: Mon, 13 Jan 2014 11:47:44 -0500
Subject: Move models.resolve_model to serializers._resolve_model
---
rest_framework/models.py | 23 +----------------------
rest_framework/serializers.py | 29 +++++++++++++++++++++++++++--
rest_framework/tests/test_models.py | 28 ----------------------------
rest_framework/tests/test_serializers.py | 28 ++++++++++++++++++++++++++++
4 files changed, 56 insertions(+), 52 deletions(-)
delete mode 100644 rest_framework/tests/test_models.py
create mode 100644 rest_framework/tests/test_serializers.py
(limited to 'rest_framework')
diff --git a/rest_framework/models.py b/rest_framework/models.py
index 249cdd82..5b53a526 100644
--- a/rest_framework/models.py
+++ b/rest_framework/models.py
@@ -1,22 +1 @@
-import inspect
-
-from django.db import models
-
-
-def resolve_model(obj):
- """
- Resolve supplied `obj` to a Django model class.
-
- `obj` must be a Django model class, or a string representation
- of one.
-
- String representations should have the format:
- 'appname.ModelName'
- """
- if type(obj) == str and len(obj.split('.')) == 2:
- app_name, model_name = obj.split('.')
- return models.get_model(app_name, model_name)
- elif inspect.isclass(obj) and issubclass(obj, models.Model):
- return obj
- else:
- raise ValueError("{0} is not a valid Django model".format(obj))
+# Just to keep things like ./manage.py test happy
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index 6b31c304..0ea2cadb 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -13,14 +13,15 @@ response content is handled by parsers and renderers.
from __future__ import unicode_literals
import copy
import datetime
+import inspect
import types
from decimal import Decimal
+from django.core.exceptions import ImproperlyConfigured
from django.core.paginator import Page
from django.db import models
from django.forms import widgets
from django.utils.datastructures import SortedDict
from rest_framework.compat import get_concrete_model, six
-from rest_framework.models import resolve_model
# Note: We do the following so that users of the framework can use this style:
#
@@ -33,6 +34,27 @@ from rest_framework.relations import *
from rest_framework.fields import *
+def _resolve_model(obj):
+ """
+ Resolve supplied `obj` to a Django model class.
+
+ `obj` must be a Django model class itself, or a string
+ representation of one. Useful in situtations like GH #1225 where
+ Django may not have resolved a string-based reference to a model in
+ another model's foreign key definition.
+
+ String representations should have the format:
+ 'appname.ModelName'
+ """
+ if type(obj) == str and len(obj.split('.')) == 2:
+ app_name, model_name = obj.split('.')
+ return models.get_model(app_name, model_name)
+ elif inspect.isclass(obj) and issubclass(obj, models.Model):
+ return obj
+ else:
+ raise ValueError("{0} is not a Django model".format(obj))
+
+
def pretty_name(name):
"""Converts 'first_name' to 'First name'"""
if not name:
@@ -657,7 +679,10 @@ class ModelSerializer(Serializer):
if model_field.rel:
to_many = isinstance(model_field,
models.fields.related.ManyToManyField)
- related_model = resolve_model(model_field.rel.to)
+ try:
+ related_model = _resolve_model(model_field.rel.to)
+ except ValueError as error_message:
+ raise ImproperlyConfigured(error_message)
if to_many and not model_field.rel.through._meta.auto_created:
has_through_model = True
diff --git a/rest_framework/tests/test_models.py b/rest_framework/tests/test_models.py
deleted file mode 100644
index 5e92d48a..00000000
--- a/rest_framework/tests/test_models.py
+++ /dev/null
@@ -1,28 +0,0 @@
-from django.db import models
-from django.test import TestCase
-
-from rest_framework.models import resolve_model
-from rest_framework.tests.models import BasicModel
-
-
-class ResolveModelTests(TestCase):
- """
- `resolve_model` should return a Django model class given the
- provided argument is a Django model class itself, or a properly
- formatted string representation of one.
- """
- def test_resolve_django_model(self):
- resolved_model = resolve_model(BasicModel)
- self.assertEqual(resolved_model, BasicModel)
-
- def test_resolve_string_representation(self):
- resolved_model = resolve_model('tests.BasicModel')
- self.assertEqual(resolved_model, BasicModel)
-
- def test_resolve_non_django_model(self):
- with self.assertRaises(ValueError):
- resolve_model(TestCase)
-
- def test_resolve_with_improper_string_representation(self):
- with self.assertRaises(ValueError):
- resolve_model('BasicModel')
diff --git a/rest_framework/tests/test_serializers.py b/rest_framework/tests/test_serializers.py
new file mode 100644
index 00000000..082a400c
--- /dev/null
+++ b/rest_framework/tests/test_serializers.py
@@ -0,0 +1,28 @@
+from django.db import models
+from django.test import TestCase
+
+from rest_framework.serializers import _resolve_model
+from rest_framework.tests.models import BasicModel
+
+
+class ResolveModelTests(TestCase):
+ """
+ `_resolve_model` should return a Django model class given the
+ provided argument is a Django model class itself, or a properly
+ formatted string representation of one.
+ """
+ def test_resolve_django_model(self):
+ resolved_model = _resolve_model(BasicModel)
+ self.assertEqual(resolved_model, BasicModel)
+
+ def test_resolve_string_representation(self):
+ resolved_model = _resolve_model('tests.BasicModel')
+ self.assertEqual(resolved_model, BasicModel)
+
+ def test_resolve_non_django_model(self):
+ with self.assertRaises(ValueError):
+ _resolve_model(TestCase)
+
+ def test_resolve_improper_string_representation(self):
+ with self.assertRaises(ValueError):
+ _resolve_model('BasicModel')
--
cgit v1.2.3
From c4d77667cf80588a2195fdc025bda53a5b977105 Mon Sep 17 00:00:00 2001
From: Dustin Farris
Date: Mon, 13 Jan 2014 12:03:13 -0500
Subject: Move ImportingModelSerializerTests and add comments.
---
rest_framework/tests/test_serializer_import.py | 19 +++++++++++++++++++
rest_framework/tests/test_serializer_nested.py | 6 ------
2 files changed, 19 insertions(+), 6 deletions(-)
create mode 100644 rest_framework/tests/test_serializer_import.py
(limited to 'rest_framework')
diff --git a/rest_framework/tests/test_serializer_import.py b/rest_framework/tests/test_serializer_import.py
new file mode 100644
index 00000000..9f30a7ff
--- /dev/null
+++ b/rest_framework/tests/test_serializer_import.py
@@ -0,0 +1,19 @@
+from django.test import TestCase
+
+from rest_framework import serializers
+from rest_framework.tests.accounts.serializers import AccountSerializer
+
+
+class ImportingModelSerializerTests(TestCase):
+ """
+ In some situations like, GH #1225, it is possible, especially in
+ testing, to import a serializer who's related models have not yet
+ been resolved by Django. `AccountSerializer` is an example of such
+ a serializer (imported at the top of this file).
+ """
+ def test_import_model_serializer(self):
+ """
+ The serializer at the top of this file should have been
+ imported successfully, and we should be able to instantiate it.
+ """
+ self.assertIsInstance(AccountSerializer(), serializers.ModelSerializer)
diff --git a/rest_framework/tests/test_serializer_nested.py b/rest_framework/tests/test_serializer_nested.py
index d0a773fe..6d69ffbd 100644
--- a/rest_framework/tests/test_serializer_nested.py
+++ b/rest_framework/tests/test_serializer_nested.py
@@ -6,7 +6,6 @@ Doesn't cover model serializers.
from __future__ import unicode_literals
from django.test import TestCase
from rest_framework import serializers
-from rest_framework.tests.accounts.serializers import AccountSerializer
from . import models
@@ -346,8 +345,3 @@ class NestedModelSerializerUpdateTests(TestCase):
result = deserialize.object
result.save()
self.assertEqual(result.id, john.id)
-
-
-class ImportingModelSerializerWithStrForeignKeys(TestCase):
- def test_import_model_serializer(self):
- self.assertIsInstance(AccountSerializer(), serializers.ModelSerializer)
--
cgit v1.2.3
From d937ce331cc6cd2df04a989b49cca030f65319da Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Mon, 13 Jan 2014 17:37:08 +0000
Subject: Minor cleanup
---
rest_framework/exceptions.py | 26 +++++++++++++-------------
1 file changed, 13 insertions(+), 13 deletions(-)
(limited to 'rest_framework')
diff --git a/rest_framework/exceptions.py b/rest_framework/exceptions.py
index 2bd21de3..4276625a 100644
--- a/rest_framework/exceptions.py
+++ b/rest_framework/exceptions.py
@@ -6,6 +6,7 @@ In addition Django's built in 403 and 404 exceptions are handled.
"""
from __future__ import unicode_literals
from rest_framework import status
+import math
class APIException(Exception):
@@ -14,10 +15,9 @@ class APIException(Exception):
Subclasses should provide `.status_code` and `.detail` properties.
"""
status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
- default_detail = ""
+ default_detail = ''
- def __init__(self, detail=None, status_code=None):
- self.status_code = status_code or self.status_code
+ def __init__(self, detail=None):
self.detail = detail or self.default_detail
@@ -46,7 +46,7 @@ class MethodNotAllowed(APIException):
default_detail = "Method '%s' not allowed."
def __init__(self, method, detail=None):
- super(MethodNotAllowed, self).__init__((detail or self.default_detail) % method)
+ self.detail = (detail or self.default_detail) % method
class NotAcceptable(APIException):
@@ -54,7 +54,7 @@ class NotAcceptable(APIException):
default_detail = "Could not satisfy the request's Accept header"
def __init__(self, detail=None, available_renderers=None):
- super(NotAcceptable, self).__init__(detail)
+ self.detail = detail or self.default_detail
self.available_renderers = available_renderers
@@ -63,7 +63,7 @@ class UnsupportedMediaType(APIException):
default_detail = "Unsupported media type '%s' in request."
def __init__(self, media_type, detail=None):
- super(UnsupportedMediaType, self).__init__((detail or self.default_detail) % media_type)
+ self.detail = (detail or self.default_detail) % media_type
class Throttled(APIException):
@@ -72,10 +72,10 @@ class Throttled(APIException):
extra_detail = "Expected available in %d second%s."
def __init__(self, wait=None, detail=None):
- super(Throttled, self).__init__(detail)
-
- import math
- self.wait = wait and math.ceil(wait) or None
- if wait is not None:
- format = self.detail + self.extra_detail
- self.detail = format % (self.wait, self.wait != 1 and 's' or '')
\ No newline at end of file
+ if wait is None:
+ self.detail = detail or self.default_detail
+ self.wait = None
+ else:
+ format = (detail or self.default_detail) + self.extra_detail
+ self.detail = format % (wait, wait != 1 and 's' or '')
+ self.wait = math.ceil(wait)
--
cgit v1.2.3
From bc6c5df109a35bf76be662a47d9c88a2a3b82351 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Mon, 13 Jan 2014 17:39:22 +0000
Subject: Minor tweaks
---
rest_framework/serializers.py | 6 +-----
1 file changed, 1 insertion(+), 5 deletions(-)
(limited to 'rest_framework')
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index 0ea2cadb..b4087e54 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -16,7 +16,6 @@ import datetime
import inspect
import types
from decimal import Decimal
-from django.core.exceptions import ImproperlyConfigured
from django.core.paginator import Page
from django.db import models
from django.forms import widgets
@@ -679,10 +678,7 @@ class ModelSerializer(Serializer):
if model_field.rel:
to_many = isinstance(model_field,
models.fields.related.ManyToManyField)
- try:
- related_model = _resolve_model(model_field.rel.to)
- except ValueError as error_message:
- raise ImproperlyConfigured(error_message)
+ related_model = _resolve_model(model_field.rel.to)
if to_many and not model_field.rel.through._meta.auto_created:
has_through_model = True
--
cgit v1.2.3
From e9b0d70f5d8eb94d72da677846d5dab5b587e945 Mon Sep 17 00:00:00 2001
From: S.Prymak
Date: Tue, 14 Jan 2014 10:38:49 +0200
Subject: Override HyperlinkedModelSerializerOptions url field name with
URL_FIELD_NAME setting
---
rest_framework/serializers.py | 4 +++-
rest_framework/settings.py | 1 +
2 files changed, 4 insertions(+), 1 deletion(-)
(limited to 'rest_framework')
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index 0b5c6a05..73c407c7 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -20,6 +20,8 @@ from django.db import models
from django.forms import widgets
from django.utils.datastructures import SortedDict
from rest_framework.compat import get_concrete_model, six
+from rest_framework.settings import api_settings
+
# Note: We do the following so that users of the framework can use this style:
#
@@ -990,7 +992,7 @@ class HyperlinkedModelSerializerOptions(ModelSerializerOptions):
super(HyperlinkedModelSerializerOptions, self).__init__(meta)
self.view_name = getattr(meta, 'view_name', None)
self.lookup_field = getattr(meta, 'lookup_field', None)
- self.url_field_name = getattr(meta, 'url_field_name', 'url')
+ self.url_field_name = getattr(meta, 'url_field_name', api_settings.URL_FIELD_NAME)
class HyperlinkedModelSerializer(ModelSerializer):
diff --git a/rest_framework/settings.py b/rest_framework/settings.py
index 8abaf140..ce171d6d 100644
--- a/rest_framework/settings.py
+++ b/rest_framework/settings.py
@@ -95,6 +95,7 @@ DEFAULTS = {
'URL_FORMAT_OVERRIDE': 'format',
'FORMAT_SUFFIX_KWARG': 'format',
+ 'URL_FIELD_NAME': 'url',
# Input and output formats
'DATE_INPUT_FORMATS': (
--
cgit v1.2.3
From 85d74fc86a934309359a437dd487193013055977 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Tue, 14 Jan 2014 11:25:44 +0000
Subject: Added write_only and write_only_fields. Refs #1306
---
rest_framework/fields.py | 16 +++++++++-
rest_framework/serializers.py | 39 ++++++++++++++++++------
rest_framework/tests/test_write_only_fields.py | 42 ++++++++++++++++++++++++++
3 files changed, 86 insertions(+), 11 deletions(-)
create mode 100644 rest_framework/tests/test_write_only_fields.py
(limited to 'rest_framework')
diff --git a/rest_framework/fields.py b/rest_framework/fields.py
index f1de447c..258c0f6a 100644
--- a/rest_framework/fields.py
+++ b/rest_framework/fields.py
@@ -114,6 +114,10 @@ def strip_multiple_choice_msg(help_text):
return help_text.replace(multiple_choice_msg, '')
+class IgnoreFieldException(Exception):
+ pass
+
+
class Field(object):
read_only = True
creation_counter = 0
@@ -246,6 +250,7 @@ class WritableField(Field):
"""
Base for read/write fields.
"""
+ write_only = False
default_validators = []
default_error_messages = {
'required': _('This field is required.'),
@@ -255,7 +260,7 @@ class WritableField(Field):
default = None
def __init__(self, source=None, label=None, help_text=None,
- read_only=False, required=None,
+ read_only=False, write_only=False, required=None,
validators=[], error_messages=None, widget=None,
default=None, blank=None):
@@ -269,6 +274,10 @@ class WritableField(Field):
super(WritableField, self).__init__(source=source, label=label, help_text=help_text)
self.read_only = read_only
+ self.write_only = write_only
+
+ assert not (read_only and write_only), "Cannot set read_only=True and write_only=True"
+
if required is None:
self.required = not(read_only)
else:
@@ -318,6 +327,11 @@ class WritableField(Field):
if errors:
raise ValidationError(errors)
+ def field_to_native(self, obj, field_name):
+ if self.write_only:
+ raise IgnoreFieldException()
+ return super(WritableField, self).field_to_native(obj, field_name)
+
def field_from_native(self, data, files, field_name, into):
"""
Given a dictionary and a field name, updates the dictionary `into`,
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index b4087e54..9f047b03 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -344,7 +344,10 @@ class BaseSerializer(WritableField):
continue
field.initialize(parent=self, field_name=field_name)
key = self.get_field_key(field_name)
- value = field.field_to_native(obj, field_name)
+ try:
+ value = field.field_to_native(obj, field_name)
+ except IgnoreFieldException:
+ continue
method = getattr(self, 'transform_%s' % field_name, None)
if callable(method):
value = method(obj, value)
@@ -383,6 +386,9 @@ class BaseSerializer(WritableField):
Override default so that the serializer can be used as a nested field
across relationships.
"""
+ if self.write_only:
+ raise IgnoreFieldException()
+
if self.source == '*':
return self.to_native(obj)
@@ -615,6 +621,7 @@ class ModelSerializerOptions(SerializerOptions):
super(ModelSerializerOptions, self).__init__(meta)
self.model = getattr(meta, 'model', None)
self.read_only_fields = getattr(meta, 'read_only_fields', ())
+ self.write_only_fields = getattr(meta, 'write_only_fields', ())
class ModelSerializer(Serializer):
@@ -754,17 +761,29 @@ class ModelSerializer(Serializer):
# Add the `read_only` flag to any fields that have bee specified
# in the `read_only_fields` option
for field_name in self.opts.read_only_fields:
- assert field_name not in self.base_fields.keys(), \
- "field '%s' on serializer '%s' specified in " \
- "`read_only_fields`, but also added " \
- "as an explicit field. Remove it from `read_only_fields`." % \
- (field_name, self.__class__.__name__)
- assert field_name in ret, \
- "Non-existant field '%s' specified in `read_only_fields` " \
- "on serializer '%s'." % \
- (field_name, self.__class__.__name__)
+ assert field_name not in self.base_fields.keys(), (
+ "field '%s' on serializer '%s' specified in "
+ "`read_only_fields`, but also added "
+ "as an explicit field. Remove it from `read_only_fields`." %
+ (field_name, self.__class__.__name__))
+ assert field_name in ret, (
+ "Non-existant field '%s' specified in `read_only_fields` "
+ "on serializer '%s'." %
+ (field_name, self.__class__.__name__))
ret[field_name].read_only = True
+ for field_name in self.opts.write_only_fields:
+ assert field_name not in self.base_fields.keys(), (
+ "field '%s' on serializer '%s' specified in "
+ "`write_only_fields`, but also added "
+ "as an explicit field. Remove it from `write_only_fields`." %
+ (field_name, self.__class__.__name__))
+ assert field_name in ret, (
+ "Non-existant field '%s' specified in `write_only_fields` "
+ "on serializer '%s'." %
+ (field_name, self.__class__.__name__))
+ ret[field_name].write_only = True
+
return ret
def get_pk_field(self, model_field):
diff --git a/rest_framework/tests/test_write_only_fields.py b/rest_framework/tests/test_write_only_fields.py
new file mode 100644
index 00000000..aabb18d6
--- /dev/null
+++ b/rest_framework/tests/test_write_only_fields.py
@@ -0,0 +1,42 @@
+from django.db import models
+from django.test import TestCase
+from rest_framework import serializers
+
+
+class ExampleModel(models.Model):
+ email = models.EmailField(max_length=100)
+ password = models.CharField(max_length=100)
+
+
+class WriteOnlyFieldTests(TestCase):
+ def test_write_only_fields(self):
+ class ExampleSerializer(serializers.Serializer):
+ email = serializers.EmailField()
+ password = serializers.CharField(write_only=True)
+
+ data = {
+ 'email': 'foo@example.com',
+ 'password': '123'
+ }
+ serializer = ExampleSerializer(data=data)
+ self.assertTrue(serializer.is_valid())
+ self.assertEquals(serializer.object, data)
+ self.assertEquals(serializer.data, {'email': 'foo@example.com'})
+
+ def test_write_only_fields_meta(self):
+ class ExampleSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = ExampleModel
+ fields = ('email', 'password')
+ write_only_fields = ('password',)
+
+ data = {
+ 'email': 'foo@example.com',
+ 'password': '123'
+ }
+ serializer = ExampleSerializer(data=data)
+ self.assertTrue(serializer.is_valid())
+ self.assertTrue(isinstance(serializer.object, ExampleModel))
+ self.assertEquals(serializer.object.email, data['email'])
+ self.assertEquals(serializer.object.password, data['password'])
+ self.assertEquals(serializer.data, {'email': 'foo@example.com'})
--
cgit v1.2.3
From 97f8ede4d14017aa37ca377064215cd8a73d7e7a Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Tue, 14 Jan 2014 11:26:10 +0000
Subject: Version 2.3.11
---
rest_framework/__init__.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
(limited to 'rest_framework')
diff --git a/rest_framework/__init__.py b/rest_framework/__init__.py
index f5483b9d..883f24a2 100644
--- a/rest_framework/__init__.py
+++ b/rest_framework/__init__.py
@@ -8,7 +8,7 @@ ______ _____ _____ _____ __ _
"""
__title__ = 'Django REST framework'
-__version__ = '2.3.10'
+__version__ = '2.3.11'
__author__ = 'Tom Christie'
__license__ = 'BSD 2-Clause'
__copyright__ = 'Copyright 2011-2013 Tom Christie'
--
cgit v1.2.3
From ed8143b2b05cd16952262c939b15053f7b93893a Mon Sep 17 00:00:00 2001
From: S.Prymak
Date: Tue, 14 Jan 2014 13:58:17 +0200
Subject: Test for customizable HyperlinkedModelSerializer url field name
---
.../tests/test_hyperlinkedserializers.py | 46 ++++++++++++++++++++++
1 file changed, 46 insertions(+)
(limited to 'rest_framework')
diff --git a/rest_framework/tests/test_hyperlinkedserializers.py b/rest_framework/tests/test_hyperlinkedserializers.py
index 61e613d7..83d46043 100644
--- a/rest_framework/tests/test_hyperlinkedserializers.py
+++ b/rest_framework/tests/test_hyperlinkedserializers.py
@@ -3,6 +3,7 @@ import json
from django.test import TestCase
from rest_framework import generics, status, serializers
from rest_framework.compat import patterns, url
+from rest_framework.settings import api_settings
from rest_framework.test import APIRequestFactory
from rest_framework.tests.models import (
Anchor, BasicModel, ManyToManyModel, BlogPost, BlogPostComment,
@@ -331,3 +332,48 @@ class TestOverriddenURLField(TestCase):
serializer.data,
{'title': 'New blog post', 'url': 'foo bar'}
)
+
+
+class TestURLFieldNameBySettings(TestCase):
+ urls = 'rest_framework.tests.test_hyperlinkedserializers'
+
+ def setUp(self):
+ self.saved_url_field_name = api_settings.URL_FIELD_NAME
+ api_settings.URL_FIELD_NAME = 'global_url_field'
+
+ class Serializer(serializers.HyperlinkedModelSerializer):
+
+ class Meta:
+ model = BlogPost
+ fields = ('title', api_settings.URL_FIELD_NAME)
+
+ self.Serializer = Serializer
+ self.obj = BlogPost.objects.create(title="New blog post")
+
+ def tearDown(self):
+ api_settings.URL_FIELD_NAME = self.saved_url_field_name
+
+ def test_overridden_url_field_name(self):
+ request = factory.get('/posts/')
+ serializer = self.Serializer(self.obj, context={'request': request})
+ self.assertIn(api_settings.URL_FIELD_NAME, serializer.data)
+
+
+class TestURLFieldNameByOptions(TestCase):
+ urls = 'rest_framework.tests.test_hyperlinkedserializers'
+
+ def setUp(self):
+ class Serializer(serializers.HyperlinkedModelSerializer):
+
+ class Meta:
+ model = BlogPost
+ fields = ('title', 'serializer_url_field')
+ url_field_name = 'serializer_url_field'
+
+ self.Serializer = Serializer
+ self.obj = BlogPost.objects.create(title="New blog post")
+
+ def test_overridden_url_field_name(self):
+ request = factory.get('/posts/')
+ serializer = self.Serializer(self.obj, context={'request': request})
+ self.assertIn(self.Serializer.Meta.url_field_name, serializer.data)
--
cgit v1.2.3
From dde3b1dfcb5490712ee462fcf360395da0b5d4fd Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Tue, 14 Jan 2014 20:35:09 +0000
Subject: Use api_settings.URL_FIELD_NAME
---
rest_framework/mixins.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
(limited to 'rest_framework')
diff --git a/rest_framework/mixins.py b/rest_framework/mixins.py
index 43950c4b..5fbcf700 100644
--- a/rest_framework/mixins.py
+++ b/rest_framework/mixins.py
@@ -11,6 +11,7 @@ from django.http import Http404
from rest_framework import status
from rest_framework.response import Response
from rest_framework.request import clone_request
+from rest_framework.settings import api_settings
import warnings
@@ -60,7 +61,7 @@ class CreateModelMixin(object):
def get_success_headers(self, data):
try:
- return {'Location': data['url']}
+ return {'Location': data[api_settings.URL_FIELD_NAME]}
except (TypeError, KeyError):
return {}
--
cgit v1.2.3
From e9fda70b4ac86badbd5297f857126121472b7ec6 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Wed, 15 Jan 2014 08:53:23 +0000
Subject: Nicer write_only fields implementation. Closes #1355
---
rest_framework/fields.py | 6 +-----
rest_framework/serializers.py | 10 ++++------
2 files changed, 5 insertions(+), 11 deletions(-)
(limited to 'rest_framework')
diff --git a/rest_framework/fields.py b/rest_framework/fields.py
index 258c0f6a..2f475d6e 100644
--- a/rest_framework/fields.py
+++ b/rest_framework/fields.py
@@ -114,10 +114,6 @@ def strip_multiple_choice_msg(help_text):
return help_text.replace(multiple_choice_msg, '')
-class IgnoreFieldException(Exception):
- pass
-
-
class Field(object):
read_only = True
creation_counter = 0
@@ -329,7 +325,7 @@ class WritableField(Field):
def field_to_native(self, obj, field_name):
if self.write_only:
- raise IgnoreFieldException()
+ return None
return super(WritableField, self).field_to_native(obj, field_name)
def field_from_native(self, data, files, field_name, into):
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index 414a769f..536b040b 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -346,14 +346,12 @@ class BaseSerializer(WritableField):
continue
field.initialize(parent=self, field_name=field_name)
key = self.get_field_key(field_name)
- try:
- value = field.field_to_native(obj, field_name)
- except IgnoreFieldException:
- continue
+ value = field.field_to_native(obj, field_name)
method = getattr(self, 'transform_%s' % field_name, None)
if callable(method):
value = method(obj, value)
- ret[key] = value
+ if not getattr(field, 'write_only', False):
+ ret[key] = value
ret.fields[key] = self.augment_field(field, field_name, key, value)
return ret
@@ -389,7 +387,7 @@ class BaseSerializer(WritableField):
across relationships.
"""
if self.write_only:
- raise IgnoreFieldException()
+ return None
if self.source == '*':
return self.to_native(obj)
--
cgit v1.2.3
From 71c03b9db97edbde228777981de0ac7b664302de Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Wed, 15 Jan 2014 14:27:41 +0000
Subject: Security update to OrderingFilter
---
rest_framework/__init__.py | 2 +-
rest_framework/filters.py | 29 ++++++++--
rest_framework/tests/test_filters.py | 104 ++++++++++++++++++++++++++++++++++-
3 files changed, 128 insertions(+), 7 deletions(-)
(limited to 'rest_framework')
diff --git a/rest_framework/__init__.py b/rest_framework/__init__.py
index 883f24a2..6759680b 100644
--- a/rest_framework/__init__.py
+++ b/rest_framework/__init__.py
@@ -8,7 +8,7 @@ ______ _____ _____ _____ __ _
"""
__title__ = 'Django REST framework'
-__version__ = '2.3.11'
+__version__ = '2.3.12'
__author__ = 'Tom Christie'
__license__ = 'BSD 2-Clause'
__copyright__ = 'Copyright 2011-2013 Tom Christie'
diff --git a/rest_framework/filters.py b/rest_framework/filters.py
index 5c6a187c..de91caed 100644
--- a/rest_framework/filters.py
+++ b/rest_framework/filters.py
@@ -3,6 +3,7 @@ Provides generic filtering backends that can be used to filter the results
returned by list views.
"""
from __future__ import unicode_literals
+from django.core.exceptions import ImproperlyConfigured
from django.db import models
from rest_framework.compat import django_filters, six, guardian, get_model_name
from functools import reduce
@@ -107,6 +108,7 @@ class SearchFilter(BaseFilterBackend):
class OrderingFilter(BaseFilterBackend):
ordering_param = 'ordering' # The URL query parameter used for the ordering.
+ ordering_fields = None
def get_ordering(self, request):
"""
@@ -122,17 +124,34 @@ class OrderingFilter(BaseFilterBackend):
return (ordering,)
return ordering
- def remove_invalid_fields(self, queryset, ordering):
- field_names = [field.name for field in queryset.model._meta.fields]
- field_names += queryset.query.aggregates.keys()
- return [term for term in ordering if term.lstrip('-') in field_names]
+ def remove_invalid_fields(self, queryset, ordering, view):
+ valid_fields = getattr(view, 'ordering_fields', self.ordering_fields)
+
+ if valid_fields is None:
+ # Default to allowing filtering on serializer fields
+ serializer_class = getattr(view, 'serializer_class')
+ if serializer_class is None:
+ msg = ("Cannot use %s on a view which does not have either a "
+ "'serializer_class' or 'ordering_fields' attribute.")
+ raise ImproperlyConfigured(msg % self.__class__.__name__)
+ valid_fields = [
+ field.source or field_name
+ for field_name, field in serializer_class().fields.items()
+ if not getattr(field, 'write_only', False)
+ ]
+ elif valid_fields == '__all__':
+ # View explictly allows filtering on any model field
+ valid_fields = [field.name for field in queryset.model._meta.fields]
+ valid_fields += queryset.query.aggregates.keys()
+
+ return [term for term in ordering if term.lstrip('-') in valid_fields]
def filter_queryset(self, request, queryset, view):
ordering = self.get_ordering(request)
if ordering:
# Skip any incorrect parameters
- ordering = self.remove_invalid_fields(queryset, ordering)
+ ordering = self.remove_invalid_fields(queryset, ordering, view)
if not ordering:
# Use 'ordering' attribute by default
diff --git a/rest_framework/tests/test_filters.py b/rest_framework/tests/test_filters.py
index 614f45cc..18188186 100644
--- a/rest_framework/tests/test_filters.py
+++ b/rest_framework/tests/test_filters.py
@@ -368,7 +368,6 @@ class OrderingFilterRelatedModel(models.Model):
related_name="relateds")
-
class OrderingFilterTests(TestCase):
def setUp(self):
# Sequence of title/text is:
@@ -394,6 +393,7 @@ class OrderingFilterTests(TestCase):
model = OrdringFilterModel
filter_backends = (filters.OrderingFilter,)
ordering = ('title',)
+ ordering_fields = ('text',)
view = OrderingListView.as_view()
request = factory.get('?ordering=text')
@@ -412,6 +412,7 @@ class OrderingFilterTests(TestCase):
model = OrdringFilterModel
filter_backends = (filters.OrderingFilter,)
ordering = ('title',)
+ ordering_fields = ('text',)
view = OrderingListView.as_view()
request = factory.get('?ordering=-text')
@@ -430,6 +431,7 @@ class OrderingFilterTests(TestCase):
model = OrdringFilterModel
filter_backends = (filters.OrderingFilter,)
ordering = ('title',)
+ ordering_fields = ('text',)
view = OrderingListView.as_view()
request = factory.get('?ordering=foobar')
@@ -448,6 +450,7 @@ class OrderingFilterTests(TestCase):
model = OrdringFilterModel
filter_backends = (filters.OrderingFilter,)
ordering = ('title',)
+ oredering_fields = ('text',)
view = OrderingListView.as_view()
request = factory.get('')
@@ -466,6 +469,7 @@ class OrderingFilterTests(TestCase):
model = OrdringFilterModel
filter_backends = (filters.OrderingFilter,)
ordering = 'title'
+ ordering_fields = ('text',)
view = OrderingListView.as_view()
request = factory.get('')
@@ -494,6 +498,7 @@ class OrderingFilterTests(TestCase):
model = OrdringFilterModel
filter_backends = (filters.OrderingFilter,)
ordering = 'title'
+ ordering_fields = '__all__'
queryset = OrdringFilterModel.objects.all().annotate(
models.Count("relateds"))
@@ -510,4 +515,101 @@ class OrderingFilterTests(TestCase):
)
+class SensitiveOrderingFilterModel(models.Model):
+ username = models.CharField(max_length=20)
+ password = models.CharField(max_length=100)
+
+
+# Three different styles of serializer.
+# All should allow ordering by username, but not by password.
+class SensitiveDataSerializer1(serializers.ModelSerializer):
+ username = serializers.CharField()
+
+ class Meta:
+ model = SensitiveOrderingFilterModel
+ fields = ('id', 'username')
+
+
+class SensitiveDataSerializer2(serializers.ModelSerializer):
+ username = serializers.CharField()
+ password = serializers.CharField(write_only=True)
+
+ class Meta:
+ model = SensitiveOrderingFilterModel
+ fields = ('id', 'username', 'password')
+
+
+class SensitiveDataSerializer3(serializers.ModelSerializer):
+ user = serializers.CharField(source='username')
+
+ class Meta:
+ model = SensitiveOrderingFilterModel
+ fields = ('id', 'user')
+
+
+class SensitiveOrderingFilterTests(TestCase):
+ def setUp(self):
+ for idx in range(3):
+ username = {0: 'userA', 1: 'userB', 2: 'userC'}[idx]
+ password = {0: 'passA', 1: 'passC', 2: 'passB'}[idx]
+ SensitiveOrderingFilterModel(username=username, password=password).save()
+
+ def test_order_by_serializer_fields(self):
+ for serializer_cls in [
+ SensitiveDataSerializer1,
+ SensitiveDataSerializer2,
+ SensitiveDataSerializer3
+ ]:
+ class OrderingListView(generics.ListAPIView):
+ queryset = SensitiveOrderingFilterModel.objects.all().order_by('username')
+ filter_backends = (filters.OrderingFilter,)
+ serializer_class = serializer_cls
+
+ view = OrderingListView.as_view()
+ request = factory.get('?ordering=-username')
+ response = view(request)
+
+ if serializer_cls == SensitiveDataSerializer3:
+ username_field = 'user'
+ else:
+ username_field = 'username'
+
+ # Note: Inverse username ordering correctly applied.
+ self.assertEqual(
+ response.data,
+ [
+ {'id': 3, username_field: 'userC'},
+ {'id': 2, username_field: 'userB'},
+ {'id': 1, username_field: 'userA'},
+ ]
+ )
+ def test_cannot_order_by_non_serializer_fields(self):
+ for serializer_cls in [
+ SensitiveDataSerializer1,
+ SensitiveDataSerializer2,
+ SensitiveDataSerializer3
+ ]:
+ class OrderingListView(generics.ListAPIView):
+ queryset = SensitiveOrderingFilterModel.objects.all().order_by('username')
+ filter_backends = (filters.OrderingFilter,)
+ serializer_class = serializer_cls
+
+ view = OrderingListView.as_view()
+ request = factory.get('?ordering=password')
+ response = view(request)
+
+ if serializer_cls == SensitiveDataSerializer3:
+ username_field = 'user'
+ else:
+ username_field = 'username'
+
+ # Note: The passwords are not in order. Default ordering is used.
+ self.assertEqual(
+ response.data,
+ [
+ {'id': 1, username_field: 'userA'}, # PassB
+ {'id': 2, username_field: 'userB'}, # PassC
+ {'id': 3, username_field: 'userC'}, # PassA
+ ]
+ )
\ No newline at end of file
--
cgit v1.2.3
From f034cb595ae0b9091a9eb68a2b583a320ef39c4e Mon Sep 17 00:00:00 2001
From: Mathieu Pillard
Date: Fri, 17 Jan 2014 13:05:10 +0100
Subject: Encode django QuerySets to lists and not dicts in JSONEncoder
---
rest_framework/tests/test_renderers.py | 19 +++++++++++++++++++
rest_framework/utils/encoders.py | 3 +++
2 files changed, 22 insertions(+)
(limited to 'rest_framework')
diff --git a/rest_framework/tests/test_renderers.py b/rest_framework/tests/test_renderers.py
index 2ae8ae18..fb33df2c 100644
--- a/rest_framework/tests/test_renderers.py
+++ b/rest_framework/tests/test_renderers.py
@@ -3,6 +3,7 @@ from __future__ import unicode_literals
from decimal import Decimal
from django.core.cache import cache
+from django.db import models
from django.test import TestCase
from django.utils import unittest
from django.utils.translation import ugettext_lazy as _
@@ -34,6 +35,10 @@ expected_results = [
]
+class DummyTestModel(models.Model):
+ name = models.CharField(max_length=42, default='')
+
+
class BasicRendererTests(TestCase):
def test_expected_results(self):
for value, renderer_cls, expected in expected_results:
@@ -276,6 +281,20 @@ class JSONRendererTests(TestCase):
ret = JSONRenderer().render(_('test'))
self.assertEqual(ret, b'"test"')
+ def test_render_queryset_values(self):
+ o = DummyTestModel.objects.create(name='dummy')
+ qs = DummyTestModel.objects.values('id', 'name')
+ ret = JSONRenderer().render(qs)
+ data = json.loads(ret.decode('utf-8'))
+ self.assertEquals(data, [{'id': o.id, 'name': o.name}])
+
+ def test_render_queryset_values_list(self):
+ o = DummyTestModel.objects.create(name='dummy')
+ qs = DummyTestModel.objects.values_list('id', 'name')
+ ret = JSONRenderer().render(qs)
+ data = json.loads(ret.decode('utf-8'))
+ self.assertEquals(data, [[o.id, o.name]])
+
def test_render_dict_abc_obj(self):
class Dict(MutableMapping):
def __init__(self):
diff --git a/rest_framework/utils/encoders.py b/rest_framework/utils/encoders.py
index 3ac920c6..e5fa4194 100644
--- a/rest_framework/utils/encoders.py
+++ b/rest_framework/utils/encoders.py
@@ -2,6 +2,7 @@
Helper classes for parsers.
"""
from __future__ import unicode_literals
+from django.db.models.query import QuerySet
from django.utils.datastructures import SortedDict
from django.utils.functional import Promise
from rest_framework.compat import timezone, force_text
@@ -42,6 +43,8 @@ class JSONEncoder(json.JSONEncoder):
return str(o.total_seconds())
elif isinstance(o, decimal.Decimal):
return str(o)
+ elif isinstance(o, QuerySet):
+ return list(o)
elif hasattr(o, 'tolist'):
return o.tolist()
elif hasattr(o, '__getitem__'):
--
cgit v1.2.3
From 58d77c11c36623d2fd647a96e6871cbb93e17907 Mon Sep 17 00:00:00 2001
From: Ross McFarland
Date: Fri, 17 Jan 2014 08:14:05 -0800
Subject: always obey X-HTTP-METHOD-OVERRIDE header
required to support old flash/flex clients that need to do everything as
GET requests (apparently to allow for any error handlings)
---
rest_framework/request.py | 7 +++----
rest_framework/tests/test_request.py | 3 +++
2 files changed, 6 insertions(+), 4 deletions(-)
(limited to 'rest_framework')
diff --git a/rest_framework/request.py b/rest_framework/request.py
index 977d4d96..a570dcae 100644
--- a/rest_framework/request.py
+++ b/rest_framework/request.py
@@ -279,10 +279,9 @@ class Request(object):
if not _hasattr(self, '_method'):
self._method = self._request.method
- if self._method == 'POST':
- # Allow X-HTTP-METHOD-OVERRIDE header
- self._method = self.META.get('HTTP_X_HTTP_METHOD_OVERRIDE',
- self._method)
+ # Allow X-HTTP-METHOD-OVERRIDE header
+ self._method = self.META.get('HTTP_X_HTTP_METHOD_OVERRIDE',
+ self._method)
def _load_stream(self):
"""
diff --git a/rest_framework/tests/test_request.py b/rest_framework/tests/test_request.py
index 969d8024..3929bcc2 100644
--- a/rest_framework/tests/test_request.py
+++ b/rest_framework/tests/test_request.py
@@ -66,6 +66,9 @@ class TestMethodOverloading(TestCase):
request = Request(factory.post('/', {'foo': 'bar'}, HTTP_X_HTTP_METHOD_OVERRIDE='DELETE'))
self.assertEqual(request.method, 'DELETE')
+ request = Request(factory.get('/', {'foo': 'bar'}, HTTP_X_HTTP_METHOD_OVERRIDE='DELETE'))
+ self.assertEqual(request.method, 'DELETE')
+
class TestContentParsing(TestCase):
def test_standard_behaviour_determines_no_content_GET(self):
--
cgit v1.2.3
From c436725dd65c78d2dc5f46fe2246516a5fcf231e Mon Sep 17 00:00:00 2001
From: Rodolfo Henrique Carvalho
Date: Sat, 25 Jan 2014 23:58:42 +0100
Subject: Fix typo in docstring for post_delete hook.
---
rest_framework/generics.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
(limited to 'rest_framework')
diff --git a/rest_framework/generics.py b/rest_framework/generics.py
index fd411ad3..7bac510f 100644
--- a/rest_framework/generics.py
+++ b/rest_framework/generics.py
@@ -352,7 +352,7 @@ class GenericAPIView(views.APIView):
def post_delete(self, obj):
"""
- Placeholder method for calling after saving an object.
+ Placeholder method for calling after deleting an object.
"""
pass
--
cgit v1.2.3
From 7713ddc0a83b41921992fc31909483903975f795 Mon Sep 17 00:00:00 2001
From: Xavier Ordoquy
Date: Tue, 28 Jan 2014 13:20:51 +0100
Subject: Don’t forget to setup django for 1.7+ as it’s not a regular django
command.
---
rest_framework/runtests/runtests.py | 4 ++++
1 file changed, 4 insertions(+)
(limited to 'rest_framework')
diff --git a/rest_framework/runtests/runtests.py b/rest_framework/runtests/runtests.py
index da36d23f..2daaae4e 100755
--- a/rest_framework/runtests/runtests.py
+++ b/rest_framework/runtests/runtests.py
@@ -26,6 +26,10 @@ def usage():
def main():
+ try:
+ django.setup()
+ except AttributeError:
+ pass
TestRunner = get_runner(settings)
test_runner = TestRunner()
--
cgit v1.2.3
From 74fec7eeb4e7e2e593ed5e2213020024264681ce Mon Sep 17 00:00:00 2001
From: Ian Foote
Date: Tue, 28 Jan 2014 14:30:46 +0000
Subject: Import force_bytes on django >= 1.5
---
rest_framework/compat.py | 2 +-
rest_framework/tests/test_compat.py | 13 +++++++++++++
2 files changed, 14 insertions(+), 1 deletion(-)
create mode 100644 rest_framework/tests/test_compat.py
(limited to 'rest_framework')
diff --git a/rest_framework/compat.py b/rest_framework/compat.py
index b69749fe..d283e2f5 100644
--- a/rest_framework/compat.py
+++ b/rest_framework/compat.py
@@ -457,7 +457,7 @@ 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_or_smart_bytes
+ from django.utils.encoding import force_bytes as force_bytes_or_smart_bytes
except ImportError:
# In 1.3 and 1.4 the test client just uses smart_str
from django.utils.encoding import smart_str as force_bytes_or_smart_bytes
diff --git a/rest_framework/tests/test_compat.py b/rest_framework/tests/test_compat.py
new file mode 100644
index 00000000..4916d19b
--- /dev/null
+++ b/rest_framework/tests/test_compat.py
@@ -0,0 +1,13 @@
+import django
+from django.test import TestCase
+
+
+class TestCompat(TestCase):
+ def test_force_bytes_or_smart_bytes(self):
+ from rest_framework.compat import force_bytes_or_smart_bytes
+ if django.VERSION >= (1, 5):
+ from django.utils.encoding import force_bytes
+ self.assertEqual(force_bytes_or_smart_bytes, force_bytes)
+ else:
+ from django.utils.encoding import smart_str
+ self.assertEqual(force_bytes_or_smart_bytes, smart_str)
--
cgit v1.2.3
From 78e4468f0367cc2a3a5cc6f3570a791ad67c90d9 Mon Sep 17 00:00:00 2001
From: Ian Foote
Date: Tue, 28 Jan 2014 15:54:50 +0000
Subject: Add file upload test for APIRequestFactory
Remove test_compat
---
rest_framework/tests/test_compat.py | 13 -------------
rest_framework/tests/test_testing.py | 9 +++++++++
2 files changed, 9 insertions(+), 13 deletions(-)
delete mode 100644 rest_framework/tests/test_compat.py
(limited to 'rest_framework')
diff --git a/rest_framework/tests/test_compat.py b/rest_framework/tests/test_compat.py
deleted file mode 100644
index 4916d19b..00000000
--- a/rest_framework/tests/test_compat.py
+++ /dev/null
@@ -1,13 +0,0 @@
-import django
-from django.test import TestCase
-
-
-class TestCompat(TestCase):
- def test_force_bytes_or_smart_bytes(self):
- from rest_framework.compat import force_bytes_or_smart_bytes
- if django.VERSION >= (1, 5):
- from django.utils.encoding import force_bytes
- self.assertEqual(force_bytes_or_smart_bytes, force_bytes)
- else:
- from django.utils.encoding import smart_str
- self.assertEqual(force_bytes_or_smart_bytes, smart_str)
diff --git a/rest_framework/tests/test_testing.py b/rest_framework/tests/test_testing.py
index 48b8956b..71bd8b55 100644
--- a/rest_framework/tests/test_testing.py
+++ b/rest_framework/tests/test_testing.py
@@ -1,6 +1,8 @@
# -- coding: utf-8 --
from __future__ import unicode_literals
+from io import BytesIO
+
from django.contrib.auth.models import User
from django.test import TestCase
from rest_framework.compat import patterns, url
@@ -143,3 +145,10 @@ class TestAPIRequestFactory(TestCase):
force_authenticate(request, user=user)
response = view(request)
self.assertEqual(response.data['user'], 'example')
+
+ def test_upload_file(self):
+ # This is a 1x1 black png
+ simple_png = BytesIO(b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x06\x00\x00\x00\x1f\x15\xc4\x89\x00\x00\x00\rIDATx\x9cc````\x00\x00\x00\x05\x00\x01\xa5\xf6E@\x00\x00\x00\x00IEND\xaeB`\x82')
+ simple_png.name = 'test.png'
+ factory = APIRequestFactory()
+ factory.post('/', data={'image': simple_png})
--
cgit v1.2.3
From 6aadd1639b11091fa1987b1608fd4f797f09bc2b Mon Sep 17 00:00:00 2001
From: Xavier Ordoquy
Date: Tue, 28 Jan 2014 18:53:24 +0100
Subject: Moved all the reused models to the models files to avoid conflicts
with the refactored apps.
---
rest_framework/tests/models.py | 27 +++++++++++++++++++++++++++
rest_framework/tests/test_filters.py | 7 +------
rest_framework/tests/test_pagination.py | 7 +------
rest_framework/tests/test_relations_nested.py | 4 +---
rest_framework/tests/test_serializer.py | 19 +------------------
5 files changed, 31 insertions(+), 33 deletions(-)
(limited to 'rest_framework')
diff --git a/rest_framework/tests/models.py b/rest_framework/tests/models.py
index 32a726c0..ae664fc8 100644
--- a/rest_framework/tests/models.py
+++ b/rest_framework/tests/models.py
@@ -168,3 +168,30 @@ class NullableOneToOneSource(RESTFrameworkModel):
class BasicModelSerializer(serializers.ModelSerializer):
class Meta:
model = BasicModel
+
+
+# Models to test the serializers
+class AMOAFModel(RESTFrameworkModel):
+ char_field = models.CharField(max_length=1024, blank=True)
+ comma_separated_integer_field = models.CommaSeparatedIntegerField(max_length=1024, blank=True)
+ decimal_field = models.DecimalField(max_digits=64, decimal_places=32, blank=True)
+ email_field = models.EmailField(max_length=1024, blank=True)
+ file_field = models.FileField(max_length=1024, blank=True)
+ image_field = models.ImageField(max_length=1024, blank=True)
+ slug_field = models.SlugField(max_length=1024, blank=True)
+ url_field = models.URLField(max_length=1024, blank=True)
+
+class DVOAFModel(RESTFrameworkModel):
+ positive_integer_field = models.PositiveIntegerField(blank=True)
+ positive_small_integer_field = models.PositiveSmallIntegerField(blank=True)
+ email_field = models.EmailField(blank=True)
+ file_field = models.FileField(blank=True)
+ image_field = models.ImageField(blank=True)
+ slug_field = models.SlugField(blank=True)
+ url_field = models.URLField(blank=True)
+
+# Models to test filters
+class FilterableItem(models.Model):
+ text = models.CharField(max_length=100)
+ decimal = models.DecimalField(max_digits=4, decimal_places=2)
+ date = models.DateField()
diff --git a/rest_framework/tests/test_filters.py b/rest_framework/tests/test_filters.py
index 18188186..1b84bca0 100644
--- a/rest_framework/tests/test_filters.py
+++ b/rest_framework/tests/test_filters.py
@@ -9,16 +9,11 @@ from rest_framework import generics, serializers, status, filters
from rest_framework.compat import django_filters, patterns, url
from rest_framework.test import APIRequestFactory
from rest_framework.tests.models import BasicModel
+from .models import FilterableItem
factory = APIRequestFactory()
-class FilterableItem(models.Model):
- text = models.CharField(max_length=100)
- decimal = models.DecimalField(max_digits=4, decimal_places=2)
- date = models.DateField()
-
-
if django_filters:
# Basic filter on a list view.
class FilterFieldsRootView(generics.ListCreateAPIView):
diff --git a/rest_framework/tests/test_pagination.py b/rest_framework/tests/test_pagination.py
index cadb515f..f67e6df2 100644
--- a/rest_framework/tests/test_pagination.py
+++ b/rest_framework/tests/test_pagination.py
@@ -9,16 +9,11 @@ from rest_framework import generics, status, pagination, filters, serializers
from rest_framework.compat import django_filters
from rest_framework.test import APIRequestFactory
from rest_framework.tests.models import BasicModel
+from .models import FilterableItem
factory = APIRequestFactory()
-class FilterableItem(models.Model):
- text = models.CharField(max_length=100)
- decimal = models.DecimalField(max_digits=4, decimal_places=2)
- date = models.DateField()
-
-
class RootView(generics.ListCreateAPIView):
"""
Example description for OPTIONS.
diff --git a/rest_framework/tests/test_relations_nested.py b/rest_framework/tests/test_relations_nested.py
index d393b0c3..4d9da489 100644
--- a/rest_framework/tests/test_relations_nested.py
+++ b/rest_framework/tests/test_relations_nested.py
@@ -3,9 +3,7 @@ from django.db import models
from django.test import TestCase
from rest_framework import serializers
-
-class OneToOneTarget(models.Model):
- name = models.CharField(max_length=100)
+from .models import OneToOneTarget
class OneToOneSource(models.Model):
diff --git a/rest_framework/tests/test_serializer.py b/rest_framework/tests/test_serializer.py
index 75d6e785..af9fa68e 100644
--- a/rest_framework/tests/test_serializer.py
+++ b/rest_framework/tests/test_serializer.py
@@ -8,7 +8,7 @@ from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers, fields, relations
from rest_framework.tests.models import (HasPositiveIntegerAsChoice, Album, ActionItem, Anchor, BasicModel,
BlankFieldModel, BlogPost, BlogPostComment, Book, CallableDefaultValueModel, DefaultValueModel,
- ManyToManyModel, Person, ReadOnlyManyToManyModel, Photo, RESTFrameworkModel)
+ ManyToManyModel, Person, ReadOnlyManyToManyModel, Photo, RESTFrameworkModel, AMOAFModel, DVOAFModel)
from rest_framework.tests.models import BasicModelSerializer
import datetime
import pickle
@@ -1481,15 +1481,6 @@ class ManyFieldHelpTextTest(TestCase):
class AttributeMappingOnAutogeneratedFieldsTests(TestCase):
def setUp(self):
- class AMOAFModel(RESTFrameworkModel):
- char_field = models.CharField(max_length=1024, blank=True)
- comma_separated_integer_field = models.CommaSeparatedIntegerField(max_length=1024, blank=True)
- decimal_field = models.DecimalField(max_digits=64, decimal_places=32, blank=True)
- email_field = models.EmailField(max_length=1024, blank=True)
- file_field = models.FileField(max_length=1024, blank=True)
- image_field = models.ImageField(max_length=1024, blank=True)
- slug_field = models.SlugField(max_length=1024, blank=True)
- url_field = models.URLField(max_length=1024, blank=True)
class AMOAFSerializer(serializers.ModelSerializer):
class Meta:
@@ -1562,14 +1553,6 @@ class AttributeMappingOnAutogeneratedFieldsTests(TestCase):
class DefaultValuesOnAutogeneratedFieldsTests(TestCase):
def setUp(self):
- class DVOAFModel(RESTFrameworkModel):
- positive_integer_field = models.PositiveIntegerField(blank=True)
- positive_small_integer_field = models.PositiveSmallIntegerField(blank=True)
- email_field = models.EmailField(blank=True)
- file_field = models.FileField(blank=True)
- image_field = models.ImageField(blank=True)
- slug_field = models.SlugField(blank=True)
- url_field = models.URLField(blank=True)
class DVOAFSerializer(serializers.ModelSerializer):
class Meta:
--
cgit v1.2.3
From da89cfc9373c03115b1bc2f62a7f974363c6c050 Mon Sep 17 00:00:00 2001
From: Xavier Ordoquy
Date: Tue, 28 Jan 2014 18:53:58 +0100
Subject: Don’t rely on Django’s get as it breaks for 1.7
---
rest_framework/test.py | 4 ++++
1 file changed, 4 insertions(+)
(limited to 'rest_framework')
diff --git a/rest_framework/test.py b/rest_framework/test.py
index 234d10a4..23a31ee4 100644
--- a/rest_framework/test.py
+++ b/rest_framework/test.py
@@ -71,6 +71,10 @@ class APIRequestFactory(DjangoRequestFactory):
return ret, content_type
+ def get(self, path, data=None, format=None, content_type=None, **extra):
+ data, content_type = self._encode_data(data, format, content_type)
+ return self.generic('GET', path, data, content_type, **extra)
+
def post(self, path, data=None, format=None, content_type=None, **extra):
data, content_type = self._encode_data(data, format, content_type)
return self.generic('POST', path, data, content_type, **extra)
--
cgit v1.2.3
From 23a20160c4db0466c15f1a4d1edfda5004ebb551 Mon Sep 17 00:00:00 2001
From: Xavier Ordoquy
Date: Tue, 28 Jan 2014 18:54:49 +0100
Subject: Mock updated as Django’s get_template now has two arguments.
---
rest_framework/tests/test_htmlrenderer.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
(limited to 'rest_framework')
diff --git a/rest_framework/tests/test_htmlrenderer.py b/rest_framework/tests/test_htmlrenderer.py
index 8957a43c..bb87272d 100644
--- a/rest_framework/tests/test_htmlrenderer.py
+++ b/rest_framework/tests/test_htmlrenderer.py
@@ -50,7 +50,7 @@ class TemplateHTMLRendererTests(TestCase):
"""
self.get_template = django.template.loader.get_template
- def get_template(template_name):
+ def get_template(template_name, dirs=None):
if template_name == 'example.html':
return Template("example: {{ object }}")
raise TemplateDoesNotExist(template_name)
--
cgit v1.2.3
From 656117814c8efb277c4284888bfb6741036c88e3 Mon Sep 17 00:00:00 2001
From: Xavier Ordoquy
Date: Tue, 28 Jan 2014 18:55:29 +0100
Subject: Django 1.7 has new 404 and 403 body messages.
---
rest_framework/tests/test_htmlrenderer.py | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
(limited to 'rest_framework')
diff --git a/rest_framework/tests/test_htmlrenderer.py b/rest_framework/tests/test_htmlrenderer.py
index bb87272d..514d9e2b 100644
--- a/rest_framework/tests/test_htmlrenderer.py
+++ b/rest_framework/tests/test_htmlrenderer.py
@@ -108,11 +108,13 @@ class TemplateHTMLRendererExceptionTests(TestCase):
def test_not_found_html_view_with_template(self):
response = self.client.get('/not_found')
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
- self.assertEqual(response.content, six.b("404: Not found"))
+ self.assertTrue(response.content in (
+ six.b("404: Not found"), six.b("404 Not Found")))
self.assertEqual(response['Content-Type'], 'text/html; charset=utf-8')
def test_permission_denied_html_view_with_template(self):
response = self.client.get('/permission_denied')
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
- self.assertEqual(response.content, six.b("403: Permission denied"))
+ self.assertTrue(response.content in (
+ six.b("403: Permission denied"), six.b("403 Forbidden")))
self.assertEqual(response['Content-Type'], 'text/html; charset=utf-8')
--
cgit v1.2.3
From e4c25e0ff20c9b3d18dd05b2afa7f2fdf637b279 Mon Sep 17 00:00:00 2001
From: Xavier Ordoquy
Date: Tue, 28 Jan 2014 18:57:36 +0100
Subject: wsgi_request is now added to the response so we have to remove it
before pickling the response.
---
rest_framework/tests/test_renderers.py | 4 ++++
1 file changed, 4 insertions(+)
(limited to 'rest_framework')
diff --git a/rest_framework/tests/test_renderers.py b/rest_framework/tests/test_renderers.py
index fb33df2c..cddd00e7 100644
--- a/rest_framework/tests/test_renderers.py
+++ b/rest_framework/tests/test_renderers.py
@@ -601,6 +601,10 @@ class CacheRenderTest(TestCase):
method = getattr(self.client, http_method)
resp = method(url)
del resp.client, resp.request
+ try:
+ del resp.wsgi_request
+ except AttributeError:
+ pass
return resp
def test_obj_pickling(self):
--
cgit v1.2.3
From c6d89afdf7473c6b9f2af41a4bbc9bdaa83d39d1 Mon Sep 17 00:00:00 2001
From: Xavier Ordoquy
Date: Tue, 28 Jan 2014 23:43:09 +0100
Subject: Define the upload_to for file fields.
---
rest_framework/tests/models.py | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
(limited to 'rest_framework')
diff --git a/rest_framework/tests/models.py b/rest_framework/tests/models.py
index ae664fc8..19461e2a 100644
--- a/rest_framework/tests/models.py
+++ b/rest_framework/tests/models.py
@@ -176,8 +176,8 @@ class AMOAFModel(RESTFrameworkModel):
comma_separated_integer_field = models.CommaSeparatedIntegerField(max_length=1024, blank=True)
decimal_field = models.DecimalField(max_digits=64, decimal_places=32, blank=True)
email_field = models.EmailField(max_length=1024, blank=True)
- file_field = models.FileField(max_length=1024, blank=True)
- image_field = models.ImageField(max_length=1024, blank=True)
+ file_field = models.FileField(upload_to='test', max_length=1024, blank=True)
+ image_field = models.ImageField(upload_to='test', max_length=1024, blank=True)
slug_field = models.SlugField(max_length=1024, blank=True)
url_field = models.URLField(max_length=1024, blank=True)
@@ -185,8 +185,8 @@ class DVOAFModel(RESTFrameworkModel):
positive_integer_field = models.PositiveIntegerField(blank=True)
positive_small_integer_field = models.PositiveSmallIntegerField(blank=True)
email_field = models.EmailField(blank=True)
- file_field = models.FileField(blank=True)
- image_field = models.ImageField(blank=True)
+ file_field = models.FileField(upload_to='test', blank=True)
+ image_field = models.ImageField(upload_to='test', blank=True)
slug_field = models.SlugField(blank=True)
url_field = models.URLField(blank=True)
--
cgit v1.2.3
From 1319da59ce8e62d2b2d9fa938de8ac5b5ccfaf20 Mon Sep 17 00:00:00 2001
From: Xavier Ordoquy
Date: Thu, 30 Jan 2014 14:26:21 +0100
Subject: Make factory.get work with Django 1.7
---
rest_framework/test.py | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
(limited to 'rest_framework')
diff --git a/rest_framework/test.py b/rest_framework/test.py
index 23a31ee4..75cb4d0b 100644
--- a/rest_framework/test.py
+++ b/rest_framework/test.py
@@ -8,6 +8,7 @@ from django.conf import settings
from django.test.client import Client as DjangoClient
from django.test.client import ClientHandler
from django.test import testcases
+from django.utils.http import urlencode
from rest_framework.settings import api_settings
from rest_framework.compat import RequestFactory as DjangoRequestFactory
from rest_framework.compat import force_bytes_or_smart_bytes, six
@@ -71,9 +72,12 @@ class APIRequestFactory(DjangoRequestFactory):
return ret, content_type
- def get(self, path, data=None, format=None, content_type=None, **extra):
- data, content_type = self._encode_data(data, format, content_type)
- return self.generic('GET', path, data, content_type, **extra)
+ def get(self, path, data=None, **extra):
+ r = {
+ 'QUERY_STRING': urlencode(data or {}, doseq=True),
+ }
+ r.update(extra)
+ return self.generic('GET', path, **r)
def post(self, path, data=None, format=None, content_type=None, **extra):
data, content_type = self._encode_data(data, format, content_type)
--
cgit v1.2.3
From c2ee52239d995c676628777edda596706f5905d0 Mon Sep 17 00:00:00 2001
From: Xavier Ordoquy
Date: Thu, 30 Jan 2014 14:27:09 +0100
Subject: Refactoring due to the previous commit.
---
rest_framework/tests/test_filters.py | 51 ++++++++++++++++++++-------------
rest_framework/tests/test_pagination.py | 29 ++++++++++++-------
2 files changed, 50 insertions(+), 30 deletions(-)
(limited to 'rest_framework')
diff --git a/rest_framework/tests/test_filters.py b/rest_framework/tests/test_filters.py
index 1b84bca0..dd5d8e42 100644
--- a/rest_framework/tests/test_filters.py
+++ b/rest_framework/tests/test_filters.py
@@ -123,7 +123,7 @@ class IntegrationTestFiltering(CommonFilteringTestCase):
# Tests that the decimal filter works.
search_decimal = Decimal('2.25')
- request = factory.get('/?decimal=%s' % search_decimal)
+ request = factory.get('/', {'decimal': '%s' % search_decimal})
response = view(request).render()
self.assertEqual(response.status_code, status.HTTP_200_OK)
expected_data = [f for f in self.data if f['decimal'] == search_decimal]
@@ -131,7 +131,7 @@ class IntegrationTestFiltering(CommonFilteringTestCase):
# Tests that the date filter works.
search_date = datetime.date(2012, 9, 22)
- request = factory.get('/?date=%s' % search_date) # search_date str: '2012-09-22'
+ request = factory.get('/', {'date': '%s' % search_date}) # search_date str: '2012-09-22'
response = view(request).render()
self.assertEqual(response.status_code, status.HTTP_200_OK)
expected_data = [f for f in self.data if f['date'] == search_date]
@@ -146,7 +146,7 @@ class IntegrationTestFiltering(CommonFilteringTestCase):
# Tests that the decimal filter works.
search_decimal = Decimal('2.25')
- request = factory.get('/?decimal=%s' % search_decimal)
+ request = factory.get('/', {'decimal': '%s' % search_decimal})
response = view(request).render()
self.assertEqual(response.status_code, status.HTTP_200_OK)
expected_data = [f for f in self.data if f['decimal'] == search_decimal]
@@ -179,7 +179,7 @@ class IntegrationTestFiltering(CommonFilteringTestCase):
# Tests that the decimal filter set with 'lt' in the filter class works.
search_decimal = Decimal('4.25')
- request = factory.get('/?decimal=%s' % search_decimal)
+ request = factory.get('/', {'decimal': '%s' % search_decimal})
response = view(request).render()
self.assertEqual(response.status_code, status.HTTP_200_OK)
expected_data = [f for f in self.data if f['decimal'] < search_decimal]
@@ -187,7 +187,7 @@ class IntegrationTestFiltering(CommonFilteringTestCase):
# Tests that the date filter set with 'gt' in the filter class works.
search_date = datetime.date(2012, 10, 2)
- request = factory.get('/?date=%s' % search_date) # search_date str: '2012-10-02'
+ request = factory.get('/', {'date': '%s' % search_date}) # search_date str: '2012-10-02'
response = view(request).render()
self.assertEqual(response.status_code, status.HTTP_200_OK)
expected_data = [f for f in self.data if f['date'] > search_date]
@@ -195,7 +195,7 @@ class IntegrationTestFiltering(CommonFilteringTestCase):
# Tests that the text filter set with 'icontains' in the filter class works.
search_text = 'ff'
- request = factory.get('/?text=%s' % search_text)
+ request = factory.get('/', {'text': '%s' % search_text})
response = view(request).render()
self.assertEqual(response.status_code, status.HTTP_200_OK)
expected_data = [f for f in self.data if search_text in f['text'].lower()]
@@ -204,7 +204,10 @@ class IntegrationTestFiltering(CommonFilteringTestCase):
# Tests that multiple filters works.
search_decimal = Decimal('5.25')
search_date = datetime.date(2012, 10, 2)
- request = factory.get('/?decimal=%s&date=%s' % (search_decimal, search_date))
+ request = factory.get('/', {
+ 'decimal': '%s' % (search_decimal,),
+ 'date': '%s' % (search_date,)
+ })
response = view(request).render()
self.assertEqual(response.status_code, status.HTTP_200_OK)
expected_data = [f for f in self.data if f['date'] > search_date and
@@ -229,7 +232,7 @@ class IntegrationTestFiltering(CommonFilteringTestCase):
view = FilterFieldsRootView.as_view()
search_integer = 10
- request = factory.get('/?integer=%s' % search_integer)
+ request = factory.get('/', {'integer': '%s' % search_integer})
response = view(request).render()
self.assertEqual(response.status_code, status.HTTP_200_OK)
@@ -260,14 +263,18 @@ class IntegrationTestDetailFiltering(CommonFilteringTestCase):
# Tests that the decimal filter set that should fail.
search_decimal = Decimal('4.25')
high_item = self.objects.filter(decimal__gt=search_decimal)[0]
- response = self.client.get('{url}?decimal={param}'.format(url=self._get_url(high_item), param=search_decimal))
+ response = self.client.get(
+ '{url}'.format(url=self._get_url(high_item)),
+ {'decimal': '{param}'.format(param=search_decimal)})
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
# Tests that the decimal filter set that should succeed.
search_decimal = Decimal('4.25')
low_item = self.objects.filter(decimal__lt=search_decimal)[0]
low_item_data = self._serialize_object(low_item)
- response = self.client.get('{url}?decimal={param}'.format(url=self._get_url(low_item), param=search_decimal))
+ response = self.client.get(
+ '{url}'.format(url=self._get_url(low_item)),
+ {'decimal': '{param}'.format(param=search_decimal)})
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, low_item_data)
@@ -276,7 +283,11 @@ class IntegrationTestDetailFiltering(CommonFilteringTestCase):
search_date = datetime.date(2012, 10, 2)
valid_item = self.objects.filter(decimal__lt=search_decimal, date__gt=search_date)[0]
valid_item_data = self._serialize_object(valid_item)
- response = self.client.get('{url}?decimal={decimal}&date={date}'.format(url=self._get_url(valid_item), decimal=search_decimal, date=search_date))
+ response = self.client.get(
+ '{url}'.format(url=self._get_url(valid_item)), {
+ 'decimal': '{decimal}'.format(decimal=search_decimal),
+ 'date': '{date}'.format(date=search_date)
+ })
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, valid_item_data)
@@ -310,7 +321,7 @@ class SearchFilterTests(TestCase):
search_fields = ('title', 'text')
view = SearchListView.as_view()
- request = factory.get('?search=b')
+ request = factory.get('/', {'search': 'b'})
response = view(request)
self.assertEqual(
response.data,
@@ -327,7 +338,7 @@ class SearchFilterTests(TestCase):
search_fields = ('=title', 'text')
view = SearchListView.as_view()
- request = factory.get('?search=zzz')
+ request = factory.get('/', {'search': 'zzz'})
response = view(request)
self.assertEqual(
response.data,
@@ -343,7 +354,7 @@ class SearchFilterTests(TestCase):
search_fields = ('title', '^text')
view = SearchListView.as_view()
- request = factory.get('?search=b')
+ request = factory.get('/', {'search': 'b'})
response = view(request)
self.assertEqual(
response.data,
@@ -391,7 +402,7 @@ class OrderingFilterTests(TestCase):
ordering_fields = ('text',)
view = OrderingListView.as_view()
- request = factory.get('?ordering=text')
+ request = factory.get('/', {'ordering': 'text'})
response = view(request)
self.assertEqual(
response.data,
@@ -410,7 +421,7 @@ class OrderingFilterTests(TestCase):
ordering_fields = ('text',)
view = OrderingListView.as_view()
- request = factory.get('?ordering=-text')
+ request = factory.get('/', {'ordering': '-text'})
response = view(request)
self.assertEqual(
response.data,
@@ -429,7 +440,7 @@ class OrderingFilterTests(TestCase):
ordering_fields = ('text',)
view = OrderingListView.as_view()
- request = factory.get('?ordering=foobar')
+ request = factory.get('/', {'ordering': 'foobar'})
response = view(request)
self.assertEqual(
response.data,
@@ -498,7 +509,7 @@ class OrderingFilterTests(TestCase):
models.Count("relateds"))
view = OrderingListView.as_view()
- request = factory.get('?ordering=relateds__count')
+ request = factory.get('/', {'ordering': 'relateds__count'})
response = view(request)
self.assertEqual(
response.data,
@@ -561,7 +572,7 @@ class SensitiveOrderingFilterTests(TestCase):
serializer_class = serializer_cls
view = OrderingListView.as_view()
- request = factory.get('?ordering=-username')
+ request = factory.get('/', {'ordering': '-username'})
response = view(request)
if serializer_cls == SensitiveDataSerializer3:
@@ -591,7 +602,7 @@ class SensitiveOrderingFilterTests(TestCase):
serializer_class = serializer_cls
view = OrderingListView.as_view()
- request = factory.get('?ordering=password')
+ request = factory.get('/', {'ordering': 'password'})
response = view(request)
if serializer_cls == SensitiveDataSerializer3:
diff --git a/rest_framework/tests/test_pagination.py b/rest_framework/tests/test_pagination.py
index f67e6df2..24c1ba39 100644
--- a/rest_framework/tests/test_pagination.py
+++ b/rest_framework/tests/test_pagination.py
@@ -13,6 +13,15 @@ from .models import FilterableItem
factory = APIRequestFactory()
+# Helper function to split arguments out of an url
+def split_arguments_from_url(url):
+ if '?' not in url:
+ return url
+
+ path, args = url.split('?')
+ args = dict(r.split('=') for r in args.split('&'))
+ return path, args
+
class RootView(generics.ListCreateAPIView):
"""
@@ -79,7 +88,7 @@ class IntegrationTestPagination(TestCase):
self.assertNotEqual(response.data['next'], None)
self.assertEqual(response.data['previous'], None)
- request = factory.get(response.data['next'])
+ request = factory.get(*split_arguments_from_url(response.data['next']))
with self.assertNumQueries(2):
response = self.view(request).render()
self.assertEqual(response.status_code, status.HTTP_200_OK)
@@ -88,7 +97,7 @@ class IntegrationTestPagination(TestCase):
self.assertNotEqual(response.data['next'], None)
self.assertNotEqual(response.data['previous'], None)
- request = factory.get(response.data['next'])
+ request = factory.get(*split_arguments_from_url(response.data['next']))
with self.assertNumQueries(2):
response = self.view(request).render()
self.assertEqual(response.status_code, status.HTTP_200_OK)
@@ -141,7 +150,7 @@ class IntegrationTestPaginationAndFiltering(TestCase):
EXPECTED_NUM_QUERIES = 2
- request = factory.get('/?decimal=15.20')
+ request = factory.get('/', {'decimal': '15.20'})
with self.assertNumQueries(EXPECTED_NUM_QUERIES):
response = view(request).render()
self.assertEqual(response.status_code, status.HTTP_200_OK)
@@ -150,7 +159,7 @@ class IntegrationTestPaginationAndFiltering(TestCase):
self.assertNotEqual(response.data['next'], None)
self.assertEqual(response.data['previous'], None)
- request = factory.get(response.data['next'])
+ request = factory.get(*split_arguments_from_url(response.data['next']))
with self.assertNumQueries(EXPECTED_NUM_QUERIES):
response = view(request).render()
self.assertEqual(response.status_code, status.HTTP_200_OK)
@@ -159,7 +168,7 @@ class IntegrationTestPaginationAndFiltering(TestCase):
self.assertEqual(response.data['next'], None)
self.assertNotEqual(response.data['previous'], None)
- request = factory.get(response.data['previous'])
+ request = factory.get(*split_arguments_from_url(response.data['previous']))
with self.assertNumQueries(EXPECTED_NUM_QUERIES):
response = view(request).render()
self.assertEqual(response.status_code, status.HTTP_200_OK)
@@ -186,7 +195,7 @@ class IntegrationTestPaginationAndFiltering(TestCase):
view = BasicFilterFieldsRootView.as_view()
- request = factory.get('/?decimal=15.20')
+ request = factory.get('/', {'decimal': '15.20'})
with self.assertNumQueries(2):
response = view(request).render()
self.assertEqual(response.status_code, status.HTTP_200_OK)
@@ -195,7 +204,7 @@ class IntegrationTestPaginationAndFiltering(TestCase):
self.assertNotEqual(response.data['next'], None)
self.assertEqual(response.data['previous'], None)
- request = factory.get(response.data['next'])
+ request = factory.get(*split_arguments_from_url(response.data['next']))
with self.assertNumQueries(2):
response = view(request).render()
self.assertEqual(response.status_code, status.HTTP_200_OK)
@@ -204,7 +213,7 @@ class IntegrationTestPaginationAndFiltering(TestCase):
self.assertEqual(response.data['next'], None)
self.assertNotEqual(response.data['previous'], None)
- request = factory.get(response.data['previous'])
+ request = factory.get(*split_arguments_from_url(response.data['previous']))
with self.assertNumQueries(2):
response = view(request).render()
self.assertEqual(response.status_code, status.HTTP_200_OK)
@@ -312,7 +321,7 @@ class TestCustomPaginateByParam(TestCase):
"""
If paginate_by_param is set, the new kwarg should limit per view requests.
"""
- request = factory.get('/?page_size=5')
+ request = factory.get('/', {'page_size': 5})
response = self.view(request).render()
self.assertEqual(response.data['count'], 13)
self.assertEqual(response.data['results'], self.data[:5])
@@ -340,7 +349,7 @@ class TestMaxPaginateByParam(TestCase):
"""
If max_paginate_by is set, it should limit page size for the view.
"""
- request = factory.get('/?page_size=10')
+ request = factory.get('/', data={'page_size': 10})
response = self.view(request).render()
self.assertEqual(response.data['count'], 13)
self.assertEqual(response.data['results'], self.data[:5])
--
cgit v1.2.3
From 73e5b7e4b21baf87129509610fffe474a9d4ecaa Mon Sep 17 00:00:00 2001
From: Xavier Ordoquy
Date: Thu, 30 Jan 2014 14:27:46 +0100
Subject: Fixed the object representation in order to pass the tests.
---
rest_framework/tests/test_genericrelations.py | 28 ++++++++++++++++++++++++---
1 file changed, 25 insertions(+), 3 deletions(-)
(limited to 'rest_framework')
diff --git a/rest_framework/tests/test_genericrelations.py b/rest_framework/tests/test_genericrelations.py
index 2d341344..95810de7 100644
--- a/rest_framework/tests/test_genericrelations.py
+++ b/rest_framework/tests/test_genericrelations.py
@@ -5,7 +5,27 @@ from django.db import models
from django.test import TestCase
from rest_framework import serializers
+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
+
+@python_2_unicode_compatible
class Tag(models.Model):
"""
Tags have a descriptive slug, and are attached to an arbitrary object.
@@ -15,10 +35,11 @@ class Tag(models.Model):
object_id = models.PositiveIntegerField()
tagged_item = GenericForeignKey('content_type', 'object_id')
- def __unicode__(self):
+ def __str__(self):
return self.tag
+@python_2_unicode_compatible
class Bookmark(models.Model):
"""
A URL bookmark that may have multiple tags attached.
@@ -26,10 +47,11 @@ class Bookmark(models.Model):
url = models.URLField()
tags = GenericRelation(Tag)
- def __unicode__(self):
+ def __str__(self):
return 'Bookmark: %s' % self.url
+@python_2_unicode_compatible
class Note(models.Model):
"""
A textual note that may have multiple tags attached.
@@ -37,7 +59,7 @@ class Note(models.Model):
text = models.TextField()
tags = GenericRelation(Tag)
- def __unicode__(self):
+ def __str__(self):
return 'Note: %s' % self.text
--
cgit v1.2.3
From 9f0ead95976c379957faf7d3f02eb52bf80a2e17 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Thu, 30 Jan 2014 17:32:05 +0000
Subject: Remove TODO note, since it hasn't been TODONE.
---
rest_framework/views.py | 1 -
1 file changed, 1 deletion(-)
(limited to 'rest_framework')
diff --git a/rest_framework/views.py b/rest_framework/views.py
index e863af6d..78010fa0 100644
--- a/rest_framework/views.py
+++ b/rest_framework/views.py
@@ -112,7 +112,6 @@ class APIView(View):
@property
def default_response_headers(self):
- # TODO: deprecate?
# TODO: Only vary by accept if multiple renderers
return {
'Allow': ', '.join(self.allowed_methods),
--
cgit v1.2.3
From 18f26ff5cc193726956d97f1a7d5ced5e6c0f4ee Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Thu, 30 Jan 2014 17:47:55 +0000
Subject: Only add 'Vary: Accept' header when there is more than one possible
renderer.
---
rest_framework/views.py | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
(limited to 'rest_framework')
diff --git a/rest_framework/views.py b/rest_framework/views.py
index 78010fa0..02a6e25a 100644
--- a/rest_framework/views.py
+++ b/rest_framework/views.py
@@ -112,11 +112,13 @@ class APIView(View):
@property
def default_response_headers(self):
- # TODO: Only vary by accept if multiple renderers
- return {
+ headers = {
'Allow': ', '.join(self.allowed_methods),
- 'Vary': 'Accept'
}
+ if len(self.renderer_classes) > 1:
+ headers['Vary'] = 'Accept'
+ return headers
+
def http_method_not_allowed(self, request, *args, **kwargs):
"""
--
cgit v1.2.3
From 0043f30cab86f50b61ce265635d503c8212848c4 Mon Sep 17 00:00:00 2001
From: Ian Foote
Date: Fri, 31 Jan 2014 09:12:45 +0000
Subject: Use bytes BOUNDARY on django < 1.5
Django's encode_multipart was updated in django 1.5 to work internally
with unicode and convert to bytes.
In django >= 1.5 we therefore need to pass the BOUNDARY as unicode. In
django < 1.5 we still need to pass it as bytes.
---
rest_framework/renderers.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
(limited to 'rest_framework')
diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py
index 2fdd3337..e8afc26d 100644
--- a/rest_framework/renderers.py
+++ b/rest_framework/renderers.py
@@ -10,6 +10,7 @@ from __future__ import unicode_literals
import copy
import json
+import django
from django import forms
from django.core.exceptions import ImproperlyConfigured
from django.http.multipartparser import parse_header
@@ -597,7 +598,7 @@ class MultiPartRenderer(BaseRenderer):
media_type = 'multipart/form-data; boundary=BoUnDaRyStRiNg'
format = 'multipart'
charset = 'utf-8'
- BOUNDARY = 'BoUnDaRyStRiNg'
+ BOUNDARY = 'BoUnDaRyStRiNg' if django.VERSION >= (1, 5) else b'BoUnDaRyStRiNg'
def render(self, data, accepted_media_type=None, renderer_context=None):
return encode_multipart(self.BOUNDARY, data)
--
cgit v1.2.3
From 5a662dd60e79fd5efb84a5a8e7f7b00dd64e0bb0 Mon Sep 17 00:00:00 2001
From: Xavier Ordoquy
Date: Fri, 31 Jan 2014 11:20:53 +0100
Subject: Moving models back to test_serializer.
---
rest_framework/tests/models.py | 20 --------------------
rest_framework/tests/test_serializer.py | 22 +++++++++++++++++++++-
2 files changed, 21 insertions(+), 21 deletions(-)
(limited to 'rest_framework')
diff --git a/rest_framework/tests/models.py b/rest_framework/tests/models.py
index 19461e2a..bf988312 100644
--- a/rest_framework/tests/models.py
+++ b/rest_framework/tests/models.py
@@ -170,26 +170,6 @@ class BasicModelSerializer(serializers.ModelSerializer):
model = BasicModel
-# Models to test the serializers
-class AMOAFModel(RESTFrameworkModel):
- char_field = models.CharField(max_length=1024, blank=True)
- comma_separated_integer_field = models.CommaSeparatedIntegerField(max_length=1024, blank=True)
- decimal_field = models.DecimalField(max_digits=64, decimal_places=32, blank=True)
- email_field = models.EmailField(max_length=1024, blank=True)
- file_field = models.FileField(upload_to='test', max_length=1024, blank=True)
- image_field = models.ImageField(upload_to='test', max_length=1024, blank=True)
- slug_field = models.SlugField(max_length=1024, blank=True)
- url_field = models.URLField(max_length=1024, blank=True)
-
-class DVOAFModel(RESTFrameworkModel):
- positive_integer_field = models.PositiveIntegerField(blank=True)
- positive_small_integer_field = models.PositiveSmallIntegerField(blank=True)
- email_field = models.EmailField(blank=True)
- file_field = models.FileField(upload_to='test', blank=True)
- image_field = models.ImageField(upload_to='test', blank=True)
- slug_field = models.SlugField(blank=True)
- url_field = models.URLField(blank=True)
-
# Models to test filters
class FilterableItem(models.Model):
text = models.CharField(max_length=100)
diff --git a/rest_framework/tests/test_serializer.py b/rest_framework/tests/test_serializer.py
index af9fa68e..dbbb9a8f 100644
--- a/rest_framework/tests/test_serializer.py
+++ b/rest_framework/tests/test_serializer.py
@@ -8,12 +8,32 @@ from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers, fields, relations
from rest_framework.tests.models import (HasPositiveIntegerAsChoice, Album, ActionItem, Anchor, BasicModel,
BlankFieldModel, BlogPost, BlogPostComment, Book, CallableDefaultValueModel, DefaultValueModel,
- ManyToManyModel, Person, ReadOnlyManyToManyModel, Photo, RESTFrameworkModel, AMOAFModel, DVOAFModel)
+ ManyToManyModel, Person, ReadOnlyManyToManyModel, Photo, RESTFrameworkModel)
from rest_framework.tests.models import BasicModelSerializer
import datetime
import pickle
+class AMOAFModel(RESTFrameworkModel):
+ char_field = models.CharField(max_length=1024, blank=True)
+ comma_separated_integer_field = models.CommaSeparatedIntegerField(max_length=1024, blank=True)
+ decimal_field = models.DecimalField(max_digits=64, decimal_places=32, blank=True)
+ email_field = models.EmailField(max_length=1024, blank=True)
+ file_field = models.FileField(upload_to='test', max_length=1024, blank=True)
+ image_field = models.ImageField(upload_to='test', max_length=1024, blank=True)
+ slug_field = models.SlugField(max_length=1024, blank=True)
+ url_field = models.URLField(max_length=1024, blank=True)
+
+class DVOAFModel(RESTFrameworkModel):
+ positive_integer_field = models.PositiveIntegerField(blank=True)
+ positive_small_integer_field = models.PositiveSmallIntegerField(blank=True)
+ email_field = models.EmailField(blank=True)
+ file_field = models.FileField(upload_to='test', blank=True)
+ image_field = models.ImageField(upload_to='test', blank=True)
+ slug_field = models.SlugField(blank=True)
+ url_field = models.URLField(blank=True)
+
+
class SubComment(object):
def __init__(self, sub_comment):
self.sub_comment = sub_comment
--
cgit v1.2.3
From b182b9e246fb0c74801bea46b0d82e7384451165 Mon Sep 17 00:00:00 2001
From: juroe
Date: Tue, 4 Feb 2014 11:56:41 +0100
Subject: Fixes typo (Implicit instead of Implict).
---
rest_framework/serializers.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
(limited to 'rest_framework')
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index 536b040b..38b5089a 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -501,7 +501,7 @@ class BaseSerializer(WritableField):
else:
many = hasattr(data, '__iter__') and not isinstance(data, (Page, dict, six.text_type))
if many:
- warnings.warn('Implict list/queryset serialization is deprecated. '
+ warnings.warn('Implicit list/queryset serialization is deprecated. '
'Use the `many=True` flag when instantiating the serializer.',
DeprecationWarning, stacklevel=3)
@@ -563,7 +563,7 @@ class BaseSerializer(WritableField):
else:
many = hasattr(obj, '__iter__') and not isinstance(obj, (Page, dict))
if many:
- warnings.warn('Implict list/queryset serialization is deprecated. '
+ warnings.warn('Implicit list/queryset serialization is deprecated. '
'Use the `many=True` flag when instantiating the serializer.',
DeprecationWarning, stacklevel=2)
--
cgit v1.2.3
From f8cda8adbd7db4cd60b1dbdcd4bb5debc64ba572 Mon Sep 17 00:00:00 2001
From: Matthew King
Date: Sat, 1 Feb 2014 16:56:23 -0500
Subject: Generate random token directly
---
rest_framework/authtoken/models.py | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
(limited to 'rest_framework')
diff --git a/rest_framework/authtoken/models.py b/rest_framework/authtoken/models.py
index 024f62bf..8eac2cc4 100644
--- a/rest_framework/authtoken/models.py
+++ b/rest_framework/authtoken/models.py
@@ -1,5 +1,5 @@
-import uuid
-import hmac
+import binascii
+import os
from hashlib import sha1
from django.conf import settings
from django.db import models
@@ -34,8 +34,7 @@ class Token(models.Model):
return super(Token, self).save(*args, **kwargs)
def generate_key(self):
- unique = uuid.uuid4()
- return hmac.new(unique.bytes, digestmod=sha1).hexdigest()
+ return binascii.hexlify(os.urandom(20))
def __unicode__(self):
return self.key
--
cgit v1.2.3
From 41eb313e1c18051614809e2040e6ac8584936962 Mon Sep 17 00:00:00 2001
From: Artem Mezhenin
Date: Sun, 9 Feb 2014 01:01:05 +0400
Subject: update regex for matching URLs, fixes issue #1386
---
rest_framework/templatetags/rest_framework.py | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
(limited to 'rest_framework')
diff --git a/rest_framework/templatetags/rest_framework.py b/rest_framework/templatetags/rest_framework.py
index 83c046f9..7a70fd46 100644
--- a/rest_framework/templatetags/rest_framework.py
+++ b/rest_framework/templatetags/rest_framework.py
@@ -6,7 +6,7 @@ from django.utils.encoding import iri_to_uri
from django.utils.html import escape
from django.utils.safestring import SafeData, mark_safe
from rest_framework.compat import urlparse, force_text, six, smart_urlquote
-import re, string
+import re
register = template.Library()
@@ -185,7 +185,7 @@ WRAPPING_PUNCTUATION = [('(', ')'), ('<', '>'), ('[', ']'), ('<', '>'),
('"', '"'), ("'", "'")]
word_split_re = re.compile(r'(\s+)')
simple_url_re = re.compile(r'^https?://\[?\w', re.IGNORECASE)
-simple_url_2_re = re.compile(r'^www\.|^(?!http)\w[^@]+\.(com|edu|gov|int|mil|net|org)$', re.IGNORECASE)
+simple_url_2_re = re.compile(r'^www\.|^(?!http)\w[^@\[\]]+\.(com|edu|gov|int|mil|net|org)$', re.IGNORECASE)
simple_email_re = re.compile(r'^\S+@\S+\.\S+$')
@@ -211,7 +211,6 @@ def urlize_quoted_links(text, trim_url_limit=None, nofollow=True, autoescape=Tru
safe_input = isinstance(text, SafeData)
words = word_split_re.split(force_text(text))
for i, word in enumerate(words):
- match = None
if '.' in word or '@' in word or ':' in word:
# Deal with punctuation.
lead, middle, trail = '', word, ''
--
cgit v1.2.3
From 35f4908e48cc18e94be239f8065c95e87b2fb007 Mon Sep 17 00:00:00 2001
From: Artem Mezhenin
Date: Sun, 9 Feb 2014 02:46:25 +0400
Subject: issue #1386
* regex for matching URLs was rewritten
* added unittests
---
rest_framework/templatetags/rest_framework.py | 2 +-
rest_framework/tests/test_templatetags.py | 38 ++++++++++++++++++++++++++-
2 files changed, 38 insertions(+), 2 deletions(-)
(limited to 'rest_framework')
diff --git a/rest_framework/templatetags/rest_framework.py b/rest_framework/templatetags/rest_framework.py
index 7a70fd46..8a0e11ba 100644
--- a/rest_framework/templatetags/rest_framework.py
+++ b/rest_framework/templatetags/rest_framework.py
@@ -185,7 +185,7 @@ WRAPPING_PUNCTUATION = [('(', ')'), ('<', '>'), ('[', ']'), ('<', '>'),
('"', '"'), ("'", "'")]
word_split_re = re.compile(r'(\s+)')
simple_url_re = re.compile(r'^https?://\[?\w', re.IGNORECASE)
-simple_url_2_re = re.compile(r'^www\.|^(?!http)\w[^@\[\]]+\.(com|edu|gov|int|mil|net|org)$', re.IGNORECASE)
+simple_url_2_re = re.compile(r'^\w[^@\[\]\:\/,]+\.(com|edu|gov|int|mil|net|org)(:\d{2,5})?(/(\w[^@\[\]\:\,]+)?)?$', re.IGNORECASE)
simple_email_re = re.compile(r'^\S+@\S+\.\S+$')
diff --git a/rest_framework/tests/test_templatetags.py b/rest_framework/tests/test_templatetags.py
index 609a9e08..0c2259b9 100644
--- a/rest_framework/tests/test_templatetags.py
+++ b/rest_framework/tests/test_templatetags.py
@@ -2,7 +2,7 @@
from __future__ import unicode_literals
from django.test import TestCase
from rest_framework.test import APIRequestFactory
-from rest_framework.templatetags.rest_framework import add_query_param
+from rest_framework.templatetags.rest_framework import add_query_param, urlize_quoted_links
factory = APIRequestFactory()
@@ -17,3 +17,39 @@ class TemplateTagTests(TestCase):
json_url = add_query_param(request, "format", "json")
self.assertIn("q=%E6%9F%A5%E8%AF%A2", json_url)
self.assertIn("format=json", json_url)
+
+
+class Issue1386Tests(TestCase):
+ """
+ Covers #1386
+ """
+
+ def test_issue_1386(self):
+ """
+ Test function urlize_quoted_links with different args
+ """
+ correct_urls = [
+ "asdf.com/zxvc",
+ "asdf.net",
+ "www.as_df.org",
+ "as.d8f.ghj8.gov",
+ "www.a-op.s.d.edu/asdf/dfff_908/",
+ "cd8fr.com:80/hello",
+ "cdfr.com:808/hello",
+ "cdfr.com:8080/hello",
+ "cdfr.com:44808/hello/asdf/",
+ ]
+ for i in correct_urls:
+ res = urlize_quoted_links(i)
+ self.assertGreater(len(res), len(i))
+ self.assertIn(i, res)
+
+ incorrect_urls = [
+ "mailto://asdf@fdf.com",
+ "asdf://asdf.com",
+ "asdf.netnet",
+ "asdf:[/p]zxcv.com" # example from issue #1386
+ ]
+ for i in incorrect_urls:
+ res = urlize_quoted_links(i)
+ self.assertEqual(i, res)
\ No newline at end of file
--
cgit v1.2.3
From 97b7c25987c3bfa096a084dc671fc24816b08f87 Mon Sep 17 00:00:00 2001
From: Hassan Shamim
Date: Mon, 10 Feb 2014 12:54:56 -0600
Subject: Replace 'detail' with 'default_detail' in Exceptions guide and
APIException class docstring.
---
rest_framework/exceptions.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
(limited to 'rest_framework')
diff --git a/rest_framework/exceptions.py b/rest_framework/exceptions.py
index 4276625a..0ac5866e 100644
--- a/rest_framework/exceptions.py
+++ b/rest_framework/exceptions.py
@@ -12,7 +12,7 @@ import math
class APIException(Exception):
"""
Base class for REST framework exceptions.
- Subclasses should provide `.status_code` and `.detail` properties.
+ Subclasses should provide `.status_code` and `.default_detail` properties.
"""
status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
default_detail = ''
--
cgit v1.2.3
From 95670933d7954a99e02f0e19f285d8740ab5e449 Mon Sep 17 00:00:00 2001
From: Carlton Gibson
Date: Tue, 11 Feb 2014 14:44:56 +0100
Subject: Test and quick fix for #1257
---
rest_framework/serializers.py | 1 +
rest_framework/tests/test_serializer.py | 29 ++++++++++++++++++++++-------
2 files changed, 23 insertions(+), 7 deletions(-)
(limited to 'rest_framework')
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index 38b5089a..10256d47 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -893,6 +893,7 @@ class ModelSerializer(Serializer):
field_name = field.source or field_name
if field_name in exclusions \
and not field.read_only \
+ and field.required \
and not isinstance(field, Serializer):
exclusions.remove(field_name)
return exclusions
diff --git a/rest_framework/tests/test_serializer.py b/rest_framework/tests/test_serializer.py
index 75d6e785..6b1e333e 100644
--- a/rest_framework/tests/test_serializer.py
+++ b/rest_framework/tests/test_serializer.py
@@ -71,6 +71,15 @@ class ActionItemSerializer(serializers.ModelSerializer):
class Meta:
model = ActionItem
+class ActionItemSerializerOptionalFields(serializers.ModelSerializer):
+ """
+ Intended to test that fields with `required=False` are excluded from validation.
+ """
+ title = serializers.CharField(required=False)
+
+ class Meta:
+ model = ActionItem
+ fields = ('title',)
class ActionItemSerializerCustomRestore(serializers.ModelSerializer):
@@ -288,7 +297,13 @@ class BasicTests(TestCase):
serializer.save()
self.assertIsNotNone(serializer.data.get('id',None), 'Model is saved. `id` should be set.')
-
+ def test_fields_marked_as_not_required_are_excluded_from_validation(self):
+ """
+ Check that fields with `required=False` are included in list of exclusions.
+ """
+ serializer = ActionItemSerializerOptionalFields(self.actionitem)
+ exclusions = serializer.get_validation_exclusions()
+ self.assertTrue('title' in exclusions, '`title` field was marked `required=False` and should be excluded')
class DictStyleSerializer(serializers.Serializer):
@@ -1808,14 +1823,14 @@ class SerializerDefaultTrueBoolean(TestCase):
self.assertEqual(serializer.data['cat'], False)
self.assertEqual(serializer.data['dog'], False)
-
+
class BoolenFieldTypeTest(TestCase):
'''
Ensure the various Boolean based model fields are rendered as the proper
field type
-
+
'''
-
+
def setUp(self):
'''
Setup an ActionItemSerializer for BooleanTesting
@@ -1831,11 +1846,11 @@ class BoolenFieldTypeTest(TestCase):
'''
bfield = self.serializer.get_fields()['done']
self.assertEqual(type(bfield), fields.BooleanField)
-
+
def test_nullbooleanfield_type(self):
'''
- Test that BooleanField is infered from models.NullBooleanField
-
+ Test that BooleanField is infered from models.NullBooleanField
+
https://groups.google.com/forum/#!topic/django-rest-framework/D9mXEftpuQ8
'''
bfield = self.serializer.get_fields()['started']
--
cgit v1.2.3
From f1016441f5b9f6c95530d2ec306f90aa45762831 Mon Sep 17 00:00:00 2001
From: Carlton Gibson
Date: Tue, 11 Feb 2014 19:52:32 +0100
Subject: Test and fix for #1210. World's lowest hanging fruit.
---
rest_framework/fields.py | 3 ++-
rest_framework/tests/test_fields.py | 9 ++++++++-
2 files changed, 10 insertions(+), 2 deletions(-)
(limited to 'rest_framework')
diff --git a/rest_framework/fields.py b/rest_framework/fields.py
index 2f475d6e..05daaab7 100644
--- a/rest_framework/fields.py
+++ b/rest_framework/fields.py
@@ -477,7 +477,8 @@ class URLField(CharField):
type_label = 'url'
def __init__(self, **kwargs):
- kwargs['validators'] = [validators.URLValidator()]
+ if not 'validators' in kwargs:
+ kwargs['validators'] = [validators.URLValidator()]
super(URLField, self).__init__(**kwargs)
diff --git a/rest_framework/tests/test_fields.py b/rest_framework/tests/test_fields.py
index 5c96bce9..e127feef 100644
--- a/rest_framework/tests/test_fields.py
+++ b/rest_framework/tests/test_fields.py
@@ -860,7 +860,9 @@ class SlugFieldTests(TestCase):
class URLFieldTests(TestCase):
"""
- Tests for URLField attribute values
+ Tests for URLField attribute values.
+
+ (Includes test for #1210, checking that validators can be overridden.)
"""
class URLFieldModel(RESTFrameworkModel):
@@ -902,6 +904,11 @@ class URLFieldTests(TestCase):
self.assertEqual(getattr(serializer.fields['url_field'],
'max_length'), 20)
+ def test_validators_can_be_overridden(self):
+ url_field = serializers.URLField(validators=[])
+ validators = url_field.validators
+ self.assertEqual([], validators, 'Passing `validators` kwarg should have overridden default validators')
+
class FieldMetadata(TestCase):
def setUp(self):
--
cgit v1.2.3
From d00ea3bcac5d622c586b267d18aef4700657f269 Mon Sep 17 00:00:00 2001
From: Artem Mezhenin
Date: Thu, 13 Feb 2014 18:59:05 +0400
Subject: change regex back, issue #1386
---
rest_framework/templatetags/rest_framework.py | 8 ++++++--
rest_framework/tests/test_templatetags.py | 12 +++---------
2 files changed, 9 insertions(+), 11 deletions(-)
(limited to 'rest_framework')
diff --git a/rest_framework/templatetags/rest_framework.py b/rest_framework/templatetags/rest_framework.py
index 8a0e11ba..0fcc5346 100644
--- a/rest_framework/templatetags/rest_framework.py
+++ b/rest_framework/templatetags/rest_framework.py
@@ -185,7 +185,7 @@ WRAPPING_PUNCTUATION = [('(', ')'), ('<', '>'), ('[', ']'), ('<', '>'),
('"', '"'), ("'", "'")]
word_split_re = re.compile(r'(\s+)')
simple_url_re = re.compile(r'^https?://\[?\w', re.IGNORECASE)
-simple_url_2_re = re.compile(r'^\w[^@\[\]\:\/,]+\.(com|edu|gov|int|mil|net|org)(:\d{2,5})?(/(\w[^@\[\]\:\,]+)?)?$', re.IGNORECASE)
+simple_url_2_re = re.compile(r'^www\.|^(?!http)\w[^@]+\.(com|edu|gov|int|mil|net|org)$', re.IGNORECASE)
simple_email_re = re.compile(r'^\S+@\S+\.\S+$')
@@ -234,7 +234,11 @@ def urlize_quoted_links(text, trim_url_limit=None, nofollow=True, autoescape=Tru
if simple_url_re.match(middle):
url = smart_urlquote(middle)
elif simple_url_2_re.match(middle):
- url = smart_urlquote('http://%s' % middle)
+ # ValueError("Invalid IPv6 URL") can be raised here, see issue #1386
+ try:
+ url = smart_urlquote('http://%s' % middle)
+ except ValueError:
+ pass
elif not ':' in middle and simple_email_re.match(middle):
local, domain = middle.rsplit('@', 1)
try:
diff --git a/rest_framework/tests/test_templatetags.py b/rest_framework/tests/test_templatetags.py
index 0c2259b9..999c718a 100644
--- a/rest_framework/tests/test_templatetags.py
+++ b/rest_framework/tests/test_templatetags.py
@@ -29,26 +29,20 @@ class Issue1386Tests(TestCase):
Test function urlize_quoted_links with different args
"""
correct_urls = [
- "asdf.com/zxvc",
+ "asdf.com",
"asdf.net",
"www.as_df.org",
"as.d8f.ghj8.gov",
- "www.a-op.s.d.edu/asdf/dfff_908/",
- "cd8fr.com:80/hello",
- "cdfr.com:808/hello",
- "cdfr.com:8080/hello",
- "cdfr.com:44808/hello/asdf/",
]
for i in correct_urls:
res = urlize_quoted_links(i)
- self.assertGreater(len(res), len(i))
+ self.assertNotEqual(res, i)
self.assertIn(i, res)
incorrect_urls = [
"mailto://asdf@fdf.com",
- "asdf://asdf.com",
"asdf.netnet",
- "asdf:[/p]zxcv.com" # example from issue #1386
+ "asdf:[/p]zxcv.com", # example from issue #1386
]
for i in incorrect_urls:
res = urlize_quoted_links(i)
--
cgit v1.2.3
From 08ec23268dbb4a40000b6c4bf877f5563a4ba57b Mon Sep 17 00:00:00 2001
From: Artem Mezhenin
Date: Thu, 13 Feb 2014 19:39:53 +0400
Subject: (I hope) tests are fixed, issue #1386
---
rest_framework/tests/test_templatetags.py | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
(limited to 'rest_framework')
diff --git a/rest_framework/tests/test_templatetags.py b/rest_framework/tests/test_templatetags.py
index 999c718a..d4da0c23 100644
--- a/rest_framework/tests/test_templatetags.py
+++ b/rest_framework/tests/test_templatetags.py
@@ -42,8 +42,10 @@ class Issue1386Tests(TestCase):
incorrect_urls = [
"mailto://asdf@fdf.com",
"asdf.netnet",
- "asdf:[/p]zxcv.com", # example from issue #1386
]
for i in incorrect_urls:
res = urlize_quoted_links(i)
- self.assertEqual(i, res)
\ No newline at end of file
+ self.assertEqual(i, res)
+
+ # example from issue #1386, this shouldn't raise an exception
+ _ = urlize_quoted_links("asdf:[/p]zxcv.com")
--
cgit v1.2.3
From dbd993d108b51bebbf9fd8d567d1c782cf941404 Mon Sep 17 00:00:00 2001
From: Artem Mezhenin
Date: Thu, 13 Feb 2014 20:14:47 +0400
Subject: wrapper for smart_urlquote, issue #1386
---
rest_framework/templatetags/rest_framework.py | 19 +++++++++++++------
1 file changed, 13 insertions(+), 6 deletions(-)
(limited to 'rest_framework')
diff --git a/rest_framework/templatetags/rest_framework.py b/rest_framework/templatetags/rest_framework.py
index 0fcc5346..beb8c5b0 100644
--- a/rest_framework/templatetags/rest_framework.py
+++ b/rest_framework/templatetags/rest_framework.py
@@ -189,6 +189,17 @@ simple_url_2_re = re.compile(r'^www\.|^(?!http)\w[^@]+\.(com|edu|gov|int|mil|net
simple_email_re = re.compile(r'^\S+@\S+\.\S+$')
+def smart_urlquote_wrapper(matched_url):
+ """
+ Simple wrapper for smart_urlquote. ValueError("Invalid IPv6 URL") can
+ be raised here, see issue #1386
+ """
+ try:
+ return smart_urlquote(matched_url)
+ except ValueError:
+ return None
+
+
@register.filter
def urlize_quoted_links(text, trim_url_limit=None, nofollow=True, autoescape=True):
"""
@@ -232,13 +243,9 @@ def urlize_quoted_links(text, trim_url_limit=None, nofollow=True, autoescape=Tru
url = None
nofollow_attr = ' rel="nofollow"' if nofollow else ''
if simple_url_re.match(middle):
- url = smart_urlquote(middle)
+ url = smart_urlquote_wrapper(middle)
elif simple_url_2_re.match(middle):
- # ValueError("Invalid IPv6 URL") can be raised here, see issue #1386
- try:
- url = smart_urlquote('http://%s' % middle)
- except ValueError:
- pass
+ url = smart_urlquote_wrapper('http://%s' % middle)
elif not ':' in middle and simple_email_re.match(middle):
local, domain = middle.rsplit('@', 1)
try:
--
cgit v1.2.3
From 5ae94547bc08ade94c3f1df2223c0b8261cae59f Mon Sep 17 00:00:00 2001
From: Xavier Ordoquy
Date: Tue, 18 Feb 2014 11:42:17 +0100
Subject: Moved the python_2_unicode_compatible into compat module.
---
rest_framework/compat.py | 20 ++++++++++++++++++++
rest_framework/tests/test_genericrelations.py | 20 +-------------------
2 files changed, 21 insertions(+), 19 deletions(-)
(limited to 'rest_framework')
diff --git a/rest_framework/compat.py b/rest_framework/compat.py
index b69749fe..36f5653a 100644
--- a/rest_framework/compat.py
+++ b/rest_framework/compat.py
@@ -584,3 +584,23 @@ if six.PY3:
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/tests/test_genericrelations.py b/rest_framework/tests/test_genericrelations.py
index 95810de7..fa09c9e6 100644
--- a/rest_framework/tests/test_genericrelations.py
+++ b/rest_framework/tests/test_genericrelations.py
@@ -4,25 +4,7 @@ from django.contrib.contenttypes.generic import GenericRelation, GenericForeignK
from django.db import models
from django.test import TestCase
from rest_framework import serializers
-
-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
+from rest_framework.compat import python_2_unicode_compatible
@python_2_unicode_compatible
--
cgit v1.2.3
From f22aeeb0a3e12f377b4523865b0c519f1d01f1b5 Mon Sep 17 00:00:00 2001
From: Ian Foote
Date: Tue, 18 Feb 2014 12:08:12 +0000
Subject: Refactor UpdateModelMixin.update
Reduce nesting, return early in error cases.
---
rest_framework/mixins.py | 37 +++++++++++++++++--------------------
1 file changed, 17 insertions(+), 20 deletions(-)
(limited to 'rest_framework')
diff --git a/rest_framework/mixins.py b/rest_framework/mixins.py
index 5fbcf700..53630343 100644
--- a/rest_framework/mixins.py
+++ b/rest_framework/mixins.py
@@ -116,30 +116,27 @@ class UpdateModelMixin(object):
partial = kwargs.pop('partial', False)
self.object = self.get_object_or_none()
- if self.object is None:
- created = True
- save_kwargs = {'force_insert': True}
- success_status_code = status.HTTP_201_CREATED
- else:
- created = False
- save_kwargs = {'force_update': True}
- success_status_code = status.HTTP_200_OK
-
serializer = self.get_serializer(self.object, data=request.DATA,
files=request.FILES, partial=partial)
- if serializer.is_valid():
- try:
- self.pre_save(serializer.object)
- except ValidationError as err:
- # full_clean on model instance may be called in pre_save, so we
- # have to handle eventual errors.
- return Response(err.message_dict, status=status.HTTP_400_BAD_REQUEST)
- self.object = serializer.save(**save_kwargs)
- self.post_save(self.object, created=created)
- return Response(serializer.data, status=success_status_code)
+ if not serializer.is_valid():
+ return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
- return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
+ try:
+ self.pre_save(serializer.object)
+ except ValidationError as err:
+ # full_clean on model instance may be called in pre_save, so we
+ # have to handle eventual errors.
+ return Response(err.message_dict, status=status.HTTP_400_BAD_REQUEST)
+
+ if self.object is None:
+ self.object = serializer.save(force_insert=True)
+ self.post_save(self.object, created=True)
+ return Response(serializer.data, status=status.HTTP_201_CREATED)
+ else:
+ self.object = serializer.save(force_update=True)
+ self.post_save(self.object, created=False)
+ return Response(serializer.data, status=status.HTTP_200_OK)
def partial_update(self, request, *args, **kwargs):
kwargs['partial'] = True
--
cgit v1.2.3
From dca8b983568e1ca7534ad7244e6eb57e1b87cc68 Mon Sep 17 00:00:00 2001
From: Ian Foote
Date: Tue, 18 Feb 2014 12:28:02 +0000
Subject: Remove unnecessary else
---
rest_framework/mixins.py | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
(limited to 'rest_framework')
diff --git a/rest_framework/mixins.py b/rest_framework/mixins.py
index 53630343..7722d5be 100644
--- a/rest_framework/mixins.py
+++ b/rest_framework/mixins.py
@@ -133,10 +133,10 @@ class UpdateModelMixin(object):
self.object = serializer.save(force_insert=True)
self.post_save(self.object, created=True)
return Response(serializer.data, status=status.HTTP_201_CREATED)
- else:
- self.object = serializer.save(force_update=True)
- self.post_save(self.object, created=False)
- return Response(serializer.data, status=status.HTTP_200_OK)
+
+ self.object = serializer.save(force_update=True)
+ self.post_save(self.object, created=False)
+ return Response(serializer.data, status=status.HTTP_200_OK)
def partial_update(self, request, *args, **kwargs):
kwargs['partial'] = True
--
cgit v1.2.3
From d328f1827dcb53a92b11f3f146bad4db3ca83d89 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Tue, 18 Feb 2014 12:30:55 +0000
Subject: Tweak comment wrapping.
---
rest_framework/mixins.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
(limited to 'rest_framework')
diff --git a/rest_framework/mixins.py b/rest_framework/mixins.py
index 7722d5be..e1a24dc7 100644
--- a/rest_framework/mixins.py
+++ b/rest_framework/mixins.py
@@ -125,8 +125,8 @@ class UpdateModelMixin(object):
try:
self.pre_save(serializer.object)
except ValidationError as err:
- # full_clean on model instance may be called in pre_save, so we
- # have to handle eventual errors.
+ # full_clean on model instance may be called in pre_save,
+ # so we have to handle eventual errors.
return Response(err.message_dict, status=status.HTTP_400_BAD_REQUEST)
if self.object is None:
--
cgit v1.2.3
From 505f1173d0c5a20ea804cad3b503c4bf55cd04d8 Mon Sep 17 00:00:00 2001
From: Dustin Farris
Date: Wed, 26 Feb 2014 12:08:26 -0500
Subject: Demonstrate problem post/put'ing serialized data that contains 'None'
---
rest_framework/tests/serializers.py | 8 ++++++++
rest_framework/tests/test_nullable_fields.py | 30 ++++++++++++++++++++++++++++
rest_framework/tests/views.py | 8 ++++++++
3 files changed, 46 insertions(+)
create mode 100644 rest_framework/tests/serializers.py
create mode 100644 rest_framework/tests/test_nullable_fields.py
create mode 100644 rest_framework/tests/views.py
(limited to 'rest_framework')
diff --git a/rest_framework/tests/serializers.py b/rest_framework/tests/serializers.py
new file mode 100644
index 00000000..cc943c7d
--- /dev/null
+++ b/rest_framework/tests/serializers.py
@@ -0,0 +1,8 @@
+from rest_framework import serializers
+
+from rest_framework.tests.models import NullableForeignKeySource
+
+
+class NullableFKSourceSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = NullableForeignKeySource
diff --git a/rest_framework/tests/test_nullable_fields.py b/rest_framework/tests/test_nullable_fields.py
new file mode 100644
index 00000000..556543d9
--- /dev/null
+++ b/rest_framework/tests/test_nullable_fields.py
@@ -0,0 +1,30 @@
+from django.core.urlresolvers import reverse
+
+from rest_framework.compat import patterns, url
+from rest_framework.test import APITestCase
+from rest_framework.tests.models import NullableForeignKeySource
+from rest_framework.tests.serializers import NullableFKSourceSerializer
+from rest_framework.tests.views import NullableFKSourceDetail
+
+
+urlpatterns = patterns(
+ '',
+ url(r'^objects/(?P\d+)/$', NullableFKSourceDetail.as_view(), name='object-detail'),
+)
+
+
+class NullableForeignKeyTests(APITestCase):
+ """
+ DRF should be able to handle nullable fields when a TestClient
+ POST/PUT request is made with its own serialized object.
+ """
+ urls = 'rest_framework.tests.test_nullable_fields'
+
+ def test_updating_object_with_null_field_value(self):
+ obj = NullableForeignKeySource(name='example', target=None)
+ obj.save()
+ serialized_data = NullableFKSourceSerializer(obj).data
+
+ response = self.client.put(reverse('object-detail', args=[obj.pk]), serialized_data)
+
+ self.assertEqual(response.data, serialized_data)
diff --git a/rest_framework/tests/views.py b/rest_framework/tests/views.py
new file mode 100644
index 00000000..3917b74a
--- /dev/null
+++ b/rest_framework/tests/views.py
@@ -0,0 +1,8 @@
+from rest_framework import generics
+from rest_framework.tests.models import NullableForeignKeySource
+from rest_framework.tests.serializers import NullableFKSourceSerializer
+
+
+class NullableFKSourceDetail(generics.RetrieveUpdateDestroyAPIView):
+ model = NullableForeignKeySource
+ model_serializer_class = NullableFKSourceSerializer
--
cgit v1.2.3
From 6cd0394e20c16828d14257a7360e9abef2c3e674 Mon Sep 17 00:00:00 2001
From: Keats
Date: Fri, 21 Feb 2014 17:12:41 +0000
Subject: Display the media type of the API response on the browsable API
---
rest_framework/renderers.py | 11 ++++++++++-
rest_framework/templates/rest_framework/base.html | 2 +-
rest_framework/tests/test_renderers.py | 12 ++++++++++++
3 files changed, 23 insertions(+), 2 deletions(-)
(limited to 'rest_framework')
diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py
index e8afc26d..7cf1c051 100644
--- a/rest_framework/renderers.py
+++ b/rest_framework/renderers.py
@@ -427,7 +427,7 @@ class BrowsableAPIRenderer(BaseRenderer):
files = request.FILES
except ParseError:
data = None
- files = None
+ files = None
else:
data = None
files = None
@@ -544,6 +544,14 @@ class BrowsableAPIRenderer(BaseRenderer):
raw_data_patch_form = self.get_raw_data_form(view, 'PATCH', request)
raw_data_put_or_patch_form = raw_data_put_form or raw_data_patch_form
+ response_headers = dict(response.items())
+ renderer_content_type = ''
+ if renderer:
+ renderer_content_type = '%s' % renderer.media_type
+ if renderer.charset:
+ renderer_content_type += ' ;%s' % renderer.charset
+ response_headers['Content-Type'] = renderer_content_type
+
context = {
'content': self.get_content(renderer, data, accepted_media_type, renderer_context),
'view': view,
@@ -555,6 +563,7 @@ class BrowsableAPIRenderer(BaseRenderer):
'breadcrumblist': self.get_breadcrumbs(request),
'allowed_methods': view.allowed_methods,
'available_formats': [renderer.format for renderer in view.renderer_classes],
+ 'response_headers': response_headers,
'put_form': self.get_rendered_html_form(view, 'PUT', request),
'post_form': self.get_rendered_html_form(view, 'POST', request),
diff --git a/rest_framework/templates/rest_framework/base.html b/rest_framework/templates/rest_framework/base.html
index d19d5a2b..7067ee2f 100644
--- a/rest_framework/templates/rest_framework/base.html
+++ b/rest_framework/templates/rest_framework/base.html
@@ -118,7 +118,7 @@
HTTP {{ response.status_code }} {{ response.status_text }}{% autoescape off %}
-{% for key, val in response.items %}{{ key }}: {{ val|break_long_headers|urlize_quoted_links }}
+{% for key, val in response_headers.items %}{{ key }}: {{ val|break_long_headers|urlize_quoted_links }}
{% endfor %}
{{ content|urlize_quoted_links }}
{% endautoescape %}
diff --git a/rest_framework/tests/test_renderers.py b/rest_framework/tests/test_renderers.py
index fb33df2c..0f3432c9 100644
--- a/rest_framework/tests/test_renderers.py
+++ b/rest_framework/tests/test_renderers.py
@@ -256,6 +256,18 @@ class RendererEndToEndTests(TestCase):
self.assertEqual(resp.get('Content-Type', None), None)
self.assertEqual(resp.status_code, status.HTTP_204_NO_CONTENT)
+ def test_contains_headers_of_api_response(self):
+ """
+ Issue #1437
+
+ Test we display the headers of the API response and not those from the
+ HTML response
+ """
+ resp = self.client.get('/html1')
+ self.assertContains(resp, '>GET, HEAD, OPTIONS<')
+ self.assertContains(resp, '>application/json<')
+ self.assertNotContains(resp, '>text/html; charset=utf-8<')
+
_flat_repr = '{"foo": ["bar", "baz"]}'
_indented_repr = '{\n "foo": [\n "bar",\n "baz"\n ]\n}'
--
cgit v1.2.3
From 1addd09e2b0e26507aada864123f610ead62d8da Mon Sep 17 00:00:00 2001
From: Anton Shutik
Date: Thu, 27 Feb 2014 18:34:36 +0300
Subject: RelatedField default value handling fixed
---
rest_framework/fields.py | 10 ++++++----
rest_framework/relations.py | 9 ++++++++-
2 files changed, 14 insertions(+), 5 deletions(-)
(limited to 'rest_framework')
diff --git a/rest_framework/fields.py b/rest_framework/fields.py
index 05daaab7..68b95682 100644
--- a/rest_framework/fields.py
+++ b/rest_framework/fields.py
@@ -301,6 +301,11 @@ class WritableField(Field):
result.validators = self.validators[:]
return result
+ def get_default_value(self):
+ if is_simple_callable(self.default):
+ return self.default()
+ return self.default
+
def validate(self, value):
if value in validators.EMPTY_VALUES and self.required:
raise ValidationError(self.error_messages['required'])
@@ -349,10 +354,7 @@ class WritableField(Field):
except KeyError:
if self.default is not None and not self.partial:
# Note: partial updates shouldn't set defaults
- if is_simple_callable(self.default):
- native = self.default()
- else:
- native = self.default
+ native = self.get_default_value()
else:
if self.required:
raise ValidationError(self.error_messages['required'])
diff --git a/rest_framework/relations.py b/rest_framework/relations.py
index 02185c2f..626454ac 100644
--- a/rest_framework/relations.py
+++ b/rest_framework/relations.py
@@ -118,6 +118,13 @@ class RelatedField(WritableField):
choices = property(_get_choices, _set_choices)
+ ### Default value handling
+
+ def get_default_value(self):
+ default = super(RelatedField, self).get_default_value()
+ return default or \
+ [] if self.many else None
+
### Regular serializer stuff...
def field_to_native(self, obj, field_name):
@@ -166,7 +173,7 @@ class RelatedField(WritableField):
except KeyError:
if self.partial:
return
- value = [] if self.many else None
+ value = self.get_default_value()
if value in (None, '') and self.required:
raise ValidationError(self.error_messages['required'])
--
cgit v1.2.3
From f126856f65aa86de2c4cc1b1e3bb6a52cebb34b8 Mon Sep 17 00:00:00 2001
From: Dustin Farris
Date: Thu, 27 Feb 2014 12:17:32 -0500
Subject: Allow 'None' to pass as a null value in RelatedFields
---
rest_framework/relations.py | 7 ++++---
rest_framework/tests/test_nullable_fields.py | 6 +++---
2 files changed, 7 insertions(+), 6 deletions(-)
(limited to 'rest_framework')
diff --git a/rest_framework/relations.py b/rest_framework/relations.py
index 02185c2f..163a8984 100644
--- a/rest_framework/relations.py
+++ b/rest_framework/relations.py
@@ -33,6 +33,7 @@ class RelatedField(WritableField):
many_widget = widgets.SelectMultiple
form_field_class = forms.ChoiceField
many_form_field_class = forms.MultipleChoiceField
+ null_values = (None, '', 'None')
cache_choices = False
empty_label = None
@@ -168,9 +169,9 @@ class RelatedField(WritableField):
return
value = [] if self.many else None
- if value in (None, '') and self.required:
- raise ValidationError(self.error_messages['required'])
- elif value in (None, ''):
+ if value in self.null_values:
+ if self.required:
+ raise ValidationError(self.error_messages['required'])
into[(self.source or field_name)] = None
elif self.many:
into[(self.source or field_name)] = [self.from_native(item) for item in value]
diff --git a/rest_framework/tests/test_nullable_fields.py b/rest_framework/tests/test_nullable_fields.py
index 556543d9..6ee55c00 100644
--- a/rest_framework/tests/test_nullable_fields.py
+++ b/rest_framework/tests/test_nullable_fields.py
@@ -15,12 +15,12 @@ urlpatterns = patterns(
class NullableForeignKeyTests(APITestCase):
"""
- DRF should be able to handle nullable fields when a TestClient
- POST/PUT request is made with its own serialized object.
+ DRF should be able to handle nullable foreign keys when a test
+ Client POST/PUT request is made with its own serialized object.
"""
urls = 'rest_framework.tests.test_nullable_fields'
- def test_updating_object_with_null_field_value(self):
+ def test_updating_object_with_null_fk(self):
obj = NullableForeignKeySource(name='example', target=None)
obj.save()
serialized_data = NullableFKSourceSerializer(obj).data
--
cgit v1.2.3
From 818b4bf8b354d43360e3fd9d0b10440636a25212 Mon Sep 17 00:00:00 2001
From: Val Neekman
Date: Thu, 27 Feb 2014 12:27:54 -0800
Subject: handle negative time value and prevent a divide by zero
---
rest_framework/throttling.py | 2 ++
1 file changed, 2 insertions(+)
(limited to 'rest_framework')
diff --git a/rest_framework/throttling.py b/rest_framework/throttling.py
index a946d837..efa9fb94 100644
--- a/rest_framework/throttling.py
+++ b/rest_framework/throttling.py
@@ -136,6 +136,8 @@ class SimpleRateThrottle(BaseThrottle):
remaining_duration = self.duration
available_requests = self.num_requests - len(self.history) + 1
+ if available_requests <= 0:
+ return None
return remaining_duration / float(available_requests)
--
cgit v1.2.3
From 3c62f0efc3cff7c1d7da9f13e0b0629d963069cb Mon Sep 17 00:00:00 2001
From: Anton Shutik
Date: Fri, 28 Feb 2014 13:59:21 +0300
Subject: RelatedField.get_default_value: return empty list if self.many==True
---
rest_framework/relations.py | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
(limited to 'rest_framework')
diff --git a/rest_framework/relations.py b/rest_framework/relations.py
index 626454ac..19dc9d6e 100644
--- a/rest_framework/relations.py
+++ b/rest_framework/relations.py
@@ -122,8 +122,9 @@ class RelatedField(WritableField):
def get_default_value(self):
default = super(RelatedField, self).get_default_value()
- return default or \
- [] if self.many else None
+ if self.many and default is None:
+ return []
+ return default
### Regular serializer stuff...
--
cgit v1.2.3
From 693d9d9c630338379a1fb5271eb67eaddb60d525 Mon Sep 17 00:00:00 2001
From: hongfeiZhang
Date: Sat, 1 Mar 2014 11:37:31 +0800
Subject: In the method permission_denied, did not use the request parameter.
---
rest_framework/views.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
(limited to 'rest_framework')
diff --git a/rest_framework/views.py b/rest_framework/views.py
index 02a6e25a..2cf9b220 100644
--- a/rest_framework/views.py
+++ b/rest_framework/views.py
@@ -131,7 +131,7 @@ class APIView(View):
"""
If request is not permitted, determine what kind of exception to raise.
"""
- if not self.request.successful_authenticator:
+ if not request.successful_authenticator:
raise exceptions.NotAuthenticated()
raise exceptions.PermissionDenied()
--
cgit v1.2.3
From dea2766abac5ef55fa226f413711cfd49af2a745 Mon Sep 17 00:00:00 2001
From: Anton Shutik
Date: Tue, 4 Mar 2014 13:11:54 +0300
Subject: Added tests for "get_default_value" function
---
rest_framework/tests/test_serializer.py | 52 +++++++++++++++++++++++++++++++++
1 file changed, 52 insertions(+)
(limited to 'rest_framework')
diff --git a/rest_framework/tests/test_serializer.py b/rest_framework/tests/test_serializer.py
index 6b1e333e..a2013749 100644
--- a/rest_framework/tests/test_serializer.py
+++ b/rest_framework/tests/test_serializer.py
@@ -880,6 +880,58 @@ class DefaultValueTests(TestCase):
self.assertEqual(instance.text, 'overridden')
+class WritableFieldDefaultValueTests(TestCase):
+
+ def setUp(self):
+ self.expected = {'default': 'value'}
+ self.create_field = fields.WritableField
+
+ def test_get_default_value_with_noncallable(self):
+ field = self.create_field(default=self.expected)
+ got = field.get_default_value()
+ self.assertEqual(got, self.expected)
+
+ def test_get_default_value_with_callable(self):
+ field = self.create_field(default=lambda : self.expected)
+ got = field.get_default_value()
+ self.assertEqual(got, self.expected)
+
+ def test_get_default_value_when_not_required(self):
+ field = self.create_field(default=self.expected, required=False)
+ got = field.get_default_value()
+ self.assertEqual(got, self.expected)
+
+ def test_get_default_value_returns_None(self):
+ field = self.create_field()
+ got = field.get_default_value()
+ self.assertIsNone(got)
+
+ def test_get_default_value_returns_non_True_values(self):
+ values = [None, '', False, 0, [], (), {}] # values that assumed as 'False' in the 'if' clause
+ for expected in values:
+ field = self.create_field(default=expected)
+ got = field.get_default_value()
+ self.assertEqual(got, expected)
+
+
+class RelatedFieldDefaultValueTests(WritableFieldDefaultValueTests):
+
+ def setUp(self):
+ self.expected = {'foo': 'bar'}
+ self.create_field = relations.RelatedField
+
+ def test_get_default_value_returns_empty_list(self):
+ field = self.create_field(many=True)
+ got = field.get_default_value()
+ self.assertListEqual(got, [])
+
+ def test_get_default_value_returns_expected(self):
+ expected = [1, 2, 3]
+ field = self.create_field(many=True, default=expected)
+ got = field.get_default_value()
+ self.assertListEqual(got, expected)
+
+
class CallableDefaultValueTests(TestCase):
def setUp(self):
class CallableDefaultValueSerializer(serializers.ModelSerializer):
--
cgit v1.2.3
From 07cb436d610b0e9b6e60b84412c8d6552c0c55a0 Mon Sep 17 00:00:00 2001
From: David Larlet
Date: Tue, 4 Mar 2014 16:32:34 +0100
Subject: Typo in keywords arguments name
Because it matters ;)---
rest_framework/views.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
(limited to 'rest_framework')
diff --git a/rest_framework/views.py b/rest_framework/views.py
index 2cf9b220..a2668f2c 100644
--- a/rest_framework/views.py
+++ b/rest_framework/views.py
@@ -295,7 +295,7 @@ class APIView(View):
# Dispatch methods
- def initialize_request(self, request, *args, **kargs):
+ def initialize_request(self, request, *args, **kwargs):
"""
Returns the initial request object.
"""
--
cgit v1.2.3
From 84e7bf6796f0ab84f6797d0e9670b9bba4773e08 Mon Sep 17 00:00:00 2001
From: David Larlet
Date: Tue, 4 Mar 2014 17:21:07 +0100
Subject: Update documentation of render methods (obj to data)
---
rest_framework/renderers.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
(limited to 'rest_framework')
diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py
index 7cf1c051..7a7da561 100644
--- a/rest_framework/renderers.py
+++ b/rest_framework/renderers.py
@@ -146,7 +146,7 @@ class XMLRenderer(BaseRenderer):
def render(self, data, accepted_media_type=None, renderer_context=None):
"""
- Renders *obj* into serialized XML.
+ Renders `data` into serialized XML.
"""
if data is None:
return ''
@@ -196,7 +196,7 @@ class YAMLRenderer(BaseRenderer):
def render(self, data, accepted_media_type=None, renderer_context=None):
"""
- Renders *obj* into serialized YAML.
+ Renders `data` into serialized YAML.
"""
assert yaml, 'YAMLRenderer requires pyyaml to be installed'
--
cgit v1.2.3
From 94fe03779b8e193a4cfd67be28ab9276e36f4179 Mon Sep 17 00:00:00 2001
From: Rodolfo Carvalho
Date: Wed, 5 Mar 2014 17:01:54 +0100
Subject: Fix typo
---
rest_framework/serializers.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
(limited to 'rest_framework')
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index 10256d47..c95b0593 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -758,7 +758,7 @@ class ModelSerializer(Serializer):
ret[accessor_name] = field
- # Add the `read_only` flag to any fields that have bee specified
+ # Add the `read_only` flag to any fields that have been specified
# in the `read_only_fields` option
for field_name in self.opts.read_only_fields:
assert field_name not in self.base_fields.keys(), (
--
cgit v1.2.3
From e0682e9298092721c0d3eb358ce4be8039e7ccf6 Mon Sep 17 00:00:00 2001
From: Eric Buehl
Date: Wed, 5 Mar 2014 17:15:52 +0000
Subject: don't implicitly import provider.oauth2
---
rest_framework/authentication.py | 4 ++--
rest_framework/compat.py | 13 ++-----------
rest_framework/permissions.py | 7 +++----
rest_framework/tests/test_authentication.py | 12 ++++++------
4 files changed, 13 insertions(+), 23 deletions(-)
(limited to 'rest_framework')
diff --git a/rest_framework/authentication.py b/rest_framework/authentication.py
index e491ce5f..b0e88d88 100644
--- a/rest_framework/authentication.py
+++ b/rest_framework/authentication.py
@@ -326,11 +326,11 @@ class OAuth2Authentication(BaseAuthentication):
"""
try:
- token = oauth2_provider.models.AccessToken.objects.select_related('user')
+ token = oauth2_provider.oauth2.models.AccessToken.objects.select_related('user')
# provider_now switches to timezone aware datetime when
# the oauth2_provider version supports to it.
token = token.get(token=access_token, expires__gt=provider_now())
- except oauth2_provider.models.AccessToken.DoesNotExist:
+ except oauth2_provider.oauth2.models.AccessToken.DoesNotExist:
raise exceptions.AuthenticationFailed('Invalid token')
user = token.user
diff --git a/rest_framework/compat.py b/rest_framework/compat.py
index 3089b7fb..f60a180d 100644
--- a/rest_framework/compat.py
+++ b/rest_framework/compat.py
@@ -550,13 +550,8 @@ except (ImportError, ImproperlyConfigured):
# OAuth 2 support is optional
try:
- import provider.oauth2 as oauth2_provider
- from provider.oauth2 import models as oauth2_provider_models
- from provider.oauth2 import forms as oauth2_provider_forms
- from provider import scope as oauth2_provider_scope
- from provider import constants as oauth2_constants
- from provider import __version__ as provider_version
- if provider_version in ('0.2.3', '0.2.4'):
+ import provider as oauth2_provider
+ 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
@@ -566,10 +561,6 @@ try:
from django.utils.timezone import now as provider_now
except ImportError:
oauth2_provider = None
- oauth2_provider_models = None
- oauth2_provider_forms = None
- oauth2_provider_scope = None
- oauth2_constants = None
provider_now = None
# Handle lazy strings
diff --git a/rest_framework/permissions.py b/rest_framework/permissions.py
index f24a5123..6460056a 100644
--- a/rest_framework/permissions.py
+++ b/rest_framework/permissions.py
@@ -8,8 +8,7 @@ import warnings
SAFE_METHODS = ['GET', 'HEAD', 'OPTIONS']
from django.http import Http404
-from rest_framework.compat import (get_model_name, oauth2_provider_scope,
- oauth2_constants)
+from rest_framework.compat import (get_model_name, oauth2_provider)
class BasePermission(object):
@@ -219,8 +218,8 @@ class TokenHasReadWriteScope(BasePermission):
if hasattr(token, 'resource'): # OAuth 1
return read_only or not request.auth.resource.is_readonly
elif hasattr(token, 'scope'): # OAuth 2
- required = oauth2_constants.READ if read_only else oauth2_constants.WRITE
- return oauth2_provider_scope.check(required, request.auth.scope)
+ required = oauth2_provider.constants.READ if read_only else oauth2_provider.constants.WRITE
+ return oauth2_provider.scope.check(required, request.auth.scope)
assert False, ('TokenHasReadWriteScope requires either the'
'`OAuthAuthentication` or `OAuth2Authentication` authentication '
diff --git a/rest_framework/tests/test_authentication.py b/rest_framework/tests/test_authentication.py
index f072b81b..90383eef 100644
--- a/rest_framework/tests/test_authentication.py
+++ b/rest_framework/tests/test_authentication.py
@@ -19,7 +19,7 @@ from rest_framework.authentication import (
)
from rest_framework.authtoken.models import Token
from rest_framework.compat import patterns, url, include
-from rest_framework.compat import oauth2_provider, oauth2_provider_models, oauth2_provider_scope
+from rest_framework.compat import oauth2_provider
from rest_framework.compat import oauth, oauth_provider
from rest_framework.test import APIRequestFactory, APIClient
from rest_framework.views import APIView
@@ -488,7 +488,7 @@ class OAuth2Tests(TestCase):
self.ACCESS_TOKEN = "access_token"
self.REFRESH_TOKEN = "refresh_token"
- self.oauth2_client = oauth2_provider_models.Client.objects.create(
+ self.oauth2_client = oauth2_provider.oauth2.models.Client.objects.create(
client_id=self.CLIENT_ID,
client_secret=self.CLIENT_SECRET,
redirect_uri='',
@@ -497,12 +497,12 @@ class OAuth2Tests(TestCase):
user=None,
)
- self.access_token = oauth2_provider_models.AccessToken.objects.create(
+ self.access_token = oauth2_provider.oauth2.models.AccessToken.objects.create(
token=self.ACCESS_TOKEN,
client=self.oauth2_client,
user=self.user,
)
- self.refresh_token = oauth2_provider_models.RefreshToken.objects.create(
+ self.refresh_token = oauth2_provider.oauth2.models.RefreshToken.objects.create(
user=self.user,
access_token=self.access_token,
client=self.oauth2_client
@@ -581,7 +581,7 @@ class OAuth2Tests(TestCase):
def test_post_form_with_invalid_scope_failing_auth(self):
"""Ensure POSTing with a readonly scope instead of a write scope fails"""
read_only_access_token = self.access_token
- read_only_access_token.scope = oauth2_provider_scope.SCOPE_NAME_DICT['read']
+ read_only_access_token.scope = oauth2_provider.scope.SCOPE_NAME_DICT['read']
read_only_access_token.save()
auth = self._create_authorization_header(token=read_only_access_token.token)
response = self.csrf_client.get('/oauth2-with-scope-test/', HTTP_AUTHORIZATION=auth)
@@ -593,7 +593,7 @@ class OAuth2Tests(TestCase):
def test_post_form_with_valid_scope_passing_auth(self):
"""Ensure POSTing with a write scope succeed"""
read_write_access_token = self.access_token
- read_write_access_token.scope = oauth2_provider_scope.SCOPE_NAME_DICT['write']
+ read_write_access_token.scope = oauth2_provider.scope.SCOPE_NAME_DICT['write']
read_write_access_token.save()
auth = self._create_authorization_header(token=read_write_access_token.token)
response = self.csrf_client.post('/oauth2-with-scope-test/', HTTP_AUTHORIZATION=auth)
--
cgit v1.2.3
From c1148241eee3df1139f9855ee3220c82f60726d5 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Thu, 6 Mar 2014 09:01:05 +0000
Subject: Version 2.3.13
---
rest_framework/__init__.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
(limited to 'rest_framework')
diff --git a/rest_framework/__init__.py b/rest_framework/__init__.py
index 6759680b..d6689a87 100644
--- a/rest_framework/__init__.py
+++ b/rest_framework/__init__.py
@@ -8,7 +8,7 @@ ______ _____ _____ _____ __ _
"""
__title__ = 'Django REST framework'
-__version__ = '2.3.12'
+__version__ = '2.3.13'
__author__ = 'Tom Christie'
__license__ = 'BSD 2-Clause'
__copyright__ = 'Copyright 2011-2013 Tom Christie'
--
cgit v1.2.3
From ef94861c2d31592c3760a0c0758beb084f452c03 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Thu, 6 Mar 2014 09:25:18 +0000
Subject: It's 2014 now, dontchaknow
---
rest_framework/__init__.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
(limited to 'rest_framework')
diff --git a/rest_framework/__init__.py b/rest_framework/__init__.py
index d6689a87..2d76b55d 100644
--- a/rest_framework/__init__.py
+++ b/rest_framework/__init__.py
@@ -11,7 +11,7 @@ __title__ = 'Django REST framework'
__version__ = '2.3.13'
__author__ = 'Tom Christie'
__license__ = 'BSD 2-Clause'
-__copyright__ = 'Copyright 2011-2013 Tom Christie'
+__copyright__ = 'Copyright 2011-2014 Tom Christie'
# Version synonym
VERSION = __version__
--
cgit v1.2.3
From 9e291879d1705dea18131fc66be31e422afa1e62 Mon Sep 17 00:00:00 2001
From: Xavier Ordoquy
Date: Thu, 6 Mar 2014 15:24:07 +0100
Subject: Added an optional unique field to Album and checked that duplicates
are detected.
---
rest_framework/tests/models.py | 2 +-
rest_framework/tests/test_serializer.py | 7 +++++--
2 files changed, 6 insertions(+), 3 deletions(-)
(limited to 'rest_framework')
diff --git a/rest_framework/tests/models.py b/rest_framework/tests/models.py
index bf988312..6c8f2342 100644
--- a/rest_framework/tests/models.py
+++ b/rest_framework/tests/models.py
@@ -103,7 +103,7 @@ class BlogPostComment(RESTFrameworkModel):
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()
diff --git a/rest_framework/tests/test_serializer.py b/rest_framework/tests/test_serializer.py
index 198c269f..17ef191a 100644
--- a/rest_framework/tests/test_serializer.py
+++ b/rest_framework/tests/test_serializer.py
@@ -611,12 +611,15 @@ class ModelValidationTests(TestCase):
"""
Just check if serializers.ModelSerializer handles unique checks via .full_clean()
"""
- serializer = AlbumsSerializer(data={'title': 'a'})
+ serializer = AlbumsSerializer(data={'title': 'a', 'ref': '1'})
serializer.is_valid()
serializer.save()
second_serializer = AlbumsSerializer(data={'title': 'a'})
self.assertFalse(second_serializer.is_valid())
- self.assertEqual(second_serializer.errors, {'title': ['Album with this Title already exists.']})
+ self.assertEqual(second_serializer.errors, {'title': ['Album with this Title already exists.'],})
+ third_serializer = AlbumsSerializer(data={'title': 'b', 'ref': '1'})
+ self.assertFalse(third_serializer.is_valid())
+ self.assertEqual(third_serializer.errors, {'ref': ['Album with this Ref already exists.'],})
def test_foreign_key_is_null_with_partial(self):
"""
--
cgit v1.2.3
From de899824b8352912d2a0d2fa030b8e5a053a3ae5 Mon Sep 17 00:00:00 2001
From: Xavier Ordoquy
Date: Thu, 6 Mar 2014 16:43:30 +0100
Subject: Forgot to add the ref field to the field list.
---
rest_framework/tests/test_serializer.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
(limited to 'rest_framework')
diff --git a/rest_framework/tests/test_serializer.py b/rest_framework/tests/test_serializer.py
index 17ef191a..56714d1e 100644
--- a/rest_framework/tests/test_serializer.py
+++ b/rest_framework/tests/test_serializer.py
@@ -161,7 +161,7 @@ class AlbumsSerializer(serializers.ModelSerializer):
class Meta:
model = Album
- fields = ['title'] # lists are also valid options
+ fields = ['title', 'ref'] # lists are also valid options
class PositiveIntegerAsChoiceSerializer(serializers.ModelSerializer):
--
cgit v1.2.3
From caf4d36cb3484313a36453a229bfc002a075f811 Mon Sep 17 00:00:00 2001
From: Xavier Ordoquy
Date: Thu, 6 Mar 2014 21:17:41 +0100
Subject: More complex test case.
---
rest_framework/tests/test_serializer.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
(limited to 'rest_framework')
diff --git a/rest_framework/tests/test_serializer.py b/rest_framework/tests/test_serializer.py
index 56714d1e..44163011 100644
--- a/rest_framework/tests/test_serializer.py
+++ b/rest_framework/tests/test_serializer.py
@@ -617,9 +617,9 @@ class ModelValidationTests(TestCase):
second_serializer = AlbumsSerializer(data={'title': 'a'})
self.assertFalse(second_serializer.is_valid())
self.assertEqual(second_serializer.errors, {'title': ['Album with this Title already exists.'],})
- third_serializer = AlbumsSerializer(data={'title': 'b', 'ref': '1'})
+ third_serializer = AlbumsSerializer(data=[{'title': 'b', 'ref': '1'}, {'title': 'c'}])
self.assertFalse(third_serializer.is_valid())
- self.assertEqual(third_serializer.errors, {'ref': ['Album with this Ref already exists.'],})
+ self.assertEqual(third_serializer.errors, [{'ref': ['Album with this Ref already exists.']}, {}])
def test_foreign_key_is_null_with_partial(self):
"""
--
cgit v1.2.3
From 51e6982397cc032d6b3fd66f452713d448eb9084 Mon Sep 17 00:00:00 2001
From: Xavier Ordoquy
Date: Thu, 6 Mar 2014 21:18:37 +0100
Subject: Fixed the validation for optional fields that have a value.
---
rest_framework/serializers.py | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
(limited to 'rest_framework')
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index c95b0593..5c726dfc 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -881,7 +881,7 @@ class ModelSerializer(Serializer):
except KeyError:
return ModelField(model_field=model_field, **kwargs)
- def get_validation_exclusions(self):
+ def get_validation_exclusions(self, instance=None):
"""
Return a list of field names to exclude from model validation.
"""
@@ -893,7 +893,7 @@ class ModelSerializer(Serializer):
field_name = field.source or field_name
if field_name in exclusions \
and not field.read_only \
- and field.required \
+ and (field.required or hasattr(instance, field_name)) \
and not isinstance(field, Serializer):
exclusions.remove(field_name)
return exclusions
@@ -908,7 +908,7 @@ class ModelSerializer(Serializer):
the full_clean validation checking.
"""
try:
- instance.full_clean(exclude=self.get_validation_exclusions())
+ instance.full_clean(exclude=self.get_validation_exclusions(instance))
except ValidationError as err:
self._errors = err.message_dict
return None
--
cgit v1.2.3
From 34887ed75625a58d00c986b3ea5526877f4724b2 Mon Sep 17 00:00:00 2001
From: Eric Buehl
Date: Thu, 6 Mar 2014 20:19:21 +0000
Subject: it's safe to import scope and constants
---
rest_framework/compat.py | 4 ++++
rest_framework/permissions.py | 7 ++++---
rest_framework/tests/test_authentication.py | 6 +++---
3 files changed, 11 insertions(+), 6 deletions(-)
(limited to 'rest_framework')
diff --git a/rest_framework/compat.py b/rest_framework/compat.py
index f60a180d..d155f554 100644
--- a/rest_framework/compat.py
+++ b/rest_framework/compat.py
@@ -551,6 +551,8 @@ except (ImportError, ImproperlyConfigured):
# OAuth 2 support is optional
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
@@ -561,6 +563,8 @@ try:
from django.utils.timezone import now as provider_now
except ImportError:
oauth2_provider = None
+ oauth2_provider_scope = None
+ oauth2_constants = None
provider_now = None
# Handle lazy strings
diff --git a/rest_framework/permissions.py b/rest_framework/permissions.py
index 6460056a..f24a5123 100644
--- a/rest_framework/permissions.py
+++ b/rest_framework/permissions.py
@@ -8,7 +8,8 @@ import warnings
SAFE_METHODS = ['GET', 'HEAD', 'OPTIONS']
from django.http import Http404
-from rest_framework.compat import (get_model_name, oauth2_provider)
+from rest_framework.compat import (get_model_name, oauth2_provider_scope,
+ oauth2_constants)
class BasePermission(object):
@@ -218,8 +219,8 @@ class TokenHasReadWriteScope(BasePermission):
if hasattr(token, 'resource'): # OAuth 1
return read_only or not request.auth.resource.is_readonly
elif hasattr(token, 'scope'): # OAuth 2
- required = oauth2_provider.constants.READ if read_only else oauth2_provider.constants.WRITE
- return oauth2_provider.scope.check(required, request.auth.scope)
+ required = oauth2_constants.READ if read_only else oauth2_constants.WRITE
+ return oauth2_provider_scope.check(required, request.auth.scope)
assert False, ('TokenHasReadWriteScope requires either the'
'`OAuthAuthentication` or `OAuth2Authentication` authentication '
diff --git a/rest_framework/tests/test_authentication.py b/rest_framework/tests/test_authentication.py
index 90383eef..8caeb081 100644
--- a/rest_framework/tests/test_authentication.py
+++ b/rest_framework/tests/test_authentication.py
@@ -19,7 +19,7 @@ from rest_framework.authentication import (
)
from rest_framework.authtoken.models import Token
from rest_framework.compat import patterns, url, include
-from rest_framework.compat import oauth2_provider
+from rest_framework.compat import oauth2_provider, oauth2_provider_scope
from rest_framework.compat import oauth, oauth_provider
from rest_framework.test import APIRequestFactory, APIClient
from rest_framework.views import APIView
@@ -581,7 +581,7 @@ class OAuth2Tests(TestCase):
def test_post_form_with_invalid_scope_failing_auth(self):
"""Ensure POSTing with a readonly scope instead of a write scope fails"""
read_only_access_token = self.access_token
- read_only_access_token.scope = oauth2_provider.scope.SCOPE_NAME_DICT['read']
+ read_only_access_token.scope = oauth2_provider_scope.SCOPE_NAME_DICT['read']
read_only_access_token.save()
auth = self._create_authorization_header(token=read_only_access_token.token)
response = self.csrf_client.get('/oauth2-with-scope-test/', HTTP_AUTHORIZATION=auth)
@@ -593,7 +593,7 @@ class OAuth2Tests(TestCase):
def test_post_form_with_valid_scope_passing_auth(self):
"""Ensure POSTing with a write scope succeed"""
read_write_access_token = self.access_token
- read_write_access_token.scope = oauth2_provider.scope.SCOPE_NAME_DICT['write']
+ read_write_access_token.scope = oauth2_provider_scope.SCOPE_NAME_DICT['write']
read_write_access_token.save()
auth = self._create_authorization_header(token=read_write_access_token.token)
response = self.csrf_client.post('/oauth2-with-scope-test/', HTTP_AUTHORIZATION=auth)
--
cgit v1.2.3
From 2353878951b0607a95d539c27c362d0353c53119 Mon Sep 17 00:00:00 2001
From: Peter Inglesby
Date: Thu, 6 Mar 2014 21:39:44 +0000
Subject: Add SEARCH_PARAM and ORDERING_PARAM to settings
Fixes #1434---
rest_framework/filters.py | 7 ++++--
rest_framework/settings.py | 4 ++++
rest_framework/tests/test_filters.py | 42 +++++++++++++++++++++++++++++++++++-
rest_framework/tests/utils.py | 24 +++++++++++++++++++++
4 files changed, 74 insertions(+), 3 deletions(-)
create mode 100644 rest_framework/tests/utils.py
(limited to 'rest_framework')
diff --git a/rest_framework/filters.py b/rest_framework/filters.py
index de91caed..96d15eb9 100644
--- a/rest_framework/filters.py
+++ b/rest_framework/filters.py
@@ -6,6 +6,7 @@ from __future__ import unicode_literals
from django.core.exceptions import ImproperlyConfigured
from django.db import models
from rest_framework.compat import django_filters, six, guardian, get_model_name
+from rest_framework.settings import api_settings
from functools import reduce
import operator
@@ -69,7 +70,8 @@ class DjangoFilterBackend(BaseFilterBackend):
class SearchFilter(BaseFilterBackend):
- search_param = 'search' # The URL query parameter used for the search.
+ # The URL query parameter used for the search.
+ search_param = api_settings.SEARCH_PARAM
def get_search_terms(self, request):
"""
@@ -107,7 +109,8 @@ class SearchFilter(BaseFilterBackend):
class OrderingFilter(BaseFilterBackend):
- ordering_param = 'ordering' # The URL query parameter used for the ordering.
+ # The URL query parameter used for the ordering.
+ ordering_param = api_settings.ORDERING_PARAM
ordering_fields = None
def get_ordering(self, request):
diff --git a/rest_framework/settings.py b/rest_framework/settings.py
index ce171d6d..38753c96 100644
--- a/rest_framework/settings.py
+++ b/rest_framework/settings.py
@@ -69,6 +69,10 @@ DEFAULTS = {
'PAGINATE_BY_PARAM': None,
'MAX_PAGINATE_BY': None,
+ # Filtering
+ 'SEARCH_PARAM': 'search',
+ 'ORDERING_PARAM': 'ordering',
+
# Authentication
'UNAUTHENTICATED_USER': 'django.contrib.auth.models.AnonymousUser',
'UNAUTHENTICATED_TOKEN': None,
diff --git a/rest_framework/tests/test_filters.py b/rest_framework/tests/test_filters.py
index dd5d8e42..23226bbc 100644
--- a/rest_framework/tests/test_filters.py
+++ b/rest_framework/tests/test_filters.py
@@ -7,9 +7,11 @@ from django.test import TestCase
from django.utils import unittest
from rest_framework import generics, serializers, status, filters
from rest_framework.compat import django_filters, patterns, url
+from rest_framework.settings import api_settings
from rest_framework.test import APIRequestFactory
from rest_framework.tests.models import BasicModel
from .models import FilterableItem
+from .utils import temporary_setting
factory = APIRequestFactory()
@@ -363,6 +365,24 @@ class SearchFilterTests(TestCase):
]
)
+ def test_search_with_nonstandard_search_param(self):
+ with temporary_setting('SEARCH_PARAM', 'query', module=filters):
+ class SearchListView(generics.ListAPIView):
+ model = SearchFilterModel
+ filter_backends = (filters.SearchFilter,)
+ search_fields = ('title', 'text')
+
+ view = SearchListView.as_view()
+ request = factory.get('/', {'query': 'b'})
+ response = view(request)
+ self.assertEqual(
+ response.data,
+ [
+ {'id': 1, 'title': 'z', 'text': 'abc'},
+ {'id': 2, 'title': 'zz', 'text': 'bcd'}
+ ]
+ )
+
class OrdringFilterModel(models.Model):
title = models.CharField(max_length=20)
@@ -520,6 +540,26 @@ class OrderingFilterTests(TestCase):
]
)
+ def test_ordering_with_nonstandard_ordering_param(self):
+ with temporary_setting('ORDERING_PARAM', 'order', filters):
+ class OrderingListView(generics.ListAPIView):
+ model = OrdringFilterModel
+ filter_backends = (filters.OrderingFilter,)
+ ordering = ('title',)
+ ordering_fields = ('text',)
+
+ view = OrderingListView.as_view()
+ request = factory.get('/', {'order': 'text'})
+ response = view(request)
+ self.assertEqual(
+ response.data,
+ [
+ {'id': 1, 'title': 'zyx', 'text': 'abc'},
+ {'id': 2, 'title': 'yxw', 'text': 'bcd'},
+ {'id': 3, 'title': 'xwv', 'text': 'cde'},
+ ]
+ )
+
class SensitiveOrderingFilterModel(models.Model):
username = models.CharField(max_length=20)
@@ -618,4 +658,4 @@ class SensitiveOrderingFilterTests(TestCase):
{'id': 2, username_field: 'userB'}, # PassC
{'id': 3, username_field: 'userC'}, # PassA
]
- )
\ No newline at end of file
+ )
diff --git a/rest_framework/tests/utils.py b/rest_framework/tests/utils.py
new file mode 100644
index 00000000..ee157824
--- /dev/null
+++ b/rest_framework/tests/utils.py
@@ -0,0 +1,24 @@
+from contextlib import contextmanager
+from rest_framework.settings import api_settings
+
+
+@contextmanager
+def temporary_setting(setting, value, module=None):
+ """
+ Temporarily change value of setting for test.
+
+ Optionally reload given module, useful when module uses value of setting on
+ import.
+ """
+ original_value = getattr(api_settings, setting)
+ setattr(api_settings, setting, value)
+
+ if module is not None:
+ reload(module)
+
+ yield
+
+ setattr(api_settings, setting, original_value)
+
+ if module is not None:
+ reload(module)
--
cgit v1.2.3
From 29f5ce7aeb57abde3924527f63bb761e0c2342d3 Mon Sep 17 00:00:00 2001
From: Peter Inglesby
Date: Thu, 6 Mar 2014 23:51:02 +0000
Subject: Use six to reload module
---
rest_framework/tests/utils.py | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
(limited to 'rest_framework')
diff --git a/rest_framework/tests/utils.py b/rest_framework/tests/utils.py
index ee157824..a8f2eb0b 100644
--- a/rest_framework/tests/utils.py
+++ b/rest_framework/tests/utils.py
@@ -1,4 +1,5 @@
from contextlib import contextmanager
+from rest_framework.compat import six
from rest_framework.settings import api_settings
@@ -14,11 +15,11 @@ def temporary_setting(setting, value, module=None):
setattr(api_settings, setting, value)
if module is not None:
- reload(module)
+ six.moves.reload_module(module)
yield
setattr(api_settings, setting, original_value)
if module is not None:
- reload(module)
+ six.moves.reload_module(module)
--
cgit v1.2.3
From 3fa95132d8fced3e45f5175912672163cb71933b Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Fri, 7 Mar 2014 14:16:14 +0000
Subject: Don't barf if PIL is not installed.
---
rest_framework/tests/test_serializer.py | 48 +++++++++++++++++++--------------
1 file changed, 28 insertions(+), 20 deletions(-)
(limited to 'rest_framework')
diff --git a/rest_framework/tests/test_serializer.py b/rest_framework/tests/test_serializer.py
index 44163011..85a899c5 100644
--- a/rest_framework/tests/test_serializer.py
+++ b/rest_framework/tests/test_serializer.py
@@ -3,6 +3,7 @@ from __future__ import unicode_literals
from django.db import models
from django.db.models.fields import BLANK_CHOICE_DASH
from django.test import TestCase
+from django.utils import unittest
from django.utils.datastructures import MultiValueDict
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers, fields, relations
@@ -12,26 +13,31 @@ from rest_framework.tests.models import (HasPositiveIntegerAsChoice, Album, Acti
from rest_framework.tests.models import BasicModelSerializer
import datetime
import pickle
-
-
-class AMOAFModel(RESTFrameworkModel):
- char_field = models.CharField(max_length=1024, blank=True)
- comma_separated_integer_field = models.CommaSeparatedIntegerField(max_length=1024, blank=True)
- decimal_field = models.DecimalField(max_digits=64, decimal_places=32, blank=True)
- email_field = models.EmailField(max_length=1024, blank=True)
- file_field = models.FileField(upload_to='test', max_length=1024, blank=True)
- image_field = models.ImageField(upload_to='test', max_length=1024, blank=True)
- slug_field = models.SlugField(max_length=1024, blank=True)
- url_field = models.URLField(max_length=1024, blank=True)
-
-class DVOAFModel(RESTFrameworkModel):
- positive_integer_field = models.PositiveIntegerField(blank=True)
- positive_small_integer_field = models.PositiveSmallIntegerField(blank=True)
- email_field = models.EmailField(blank=True)
- file_field = models.FileField(upload_to='test', blank=True)
- image_field = models.ImageField(upload_to='test', blank=True)
- slug_field = models.SlugField(blank=True)
- url_field = models.URLField(blank=True)
+try:
+ import PIL
+except:
+ PIL = None
+
+
+if PIL is not None:
+ class AMOAFModel(RESTFrameworkModel):
+ char_field = models.CharField(max_length=1024, blank=True)
+ comma_separated_integer_field = models.CommaSeparatedIntegerField(max_length=1024, blank=True)
+ decimal_field = models.DecimalField(max_digits=64, decimal_places=32, blank=True)
+ email_field = models.EmailField(max_length=1024, blank=True)
+ file_field = models.FileField(upload_to='test', max_length=1024, blank=True)
+ image_field = models.ImageField(upload_to='test', max_length=1024, blank=True)
+ slug_field = models.SlugField(max_length=1024, blank=True)
+ url_field = models.URLField(max_length=1024, blank=True)
+
+ class DVOAFModel(RESTFrameworkModel):
+ positive_integer_field = models.PositiveIntegerField(blank=True)
+ positive_small_integer_field = models.PositiveSmallIntegerField(blank=True)
+ email_field = models.EmailField(blank=True)
+ file_field = models.FileField(upload_to='test', blank=True)
+ image_field = models.ImageField(upload_to='test', blank=True)
+ slug_field = models.SlugField(blank=True)
+ url_field = models.URLField(blank=True)
class SubComment(object):
@@ -1568,6 +1574,7 @@ class ManyFieldHelpTextTest(TestCase):
self.assertEqual('Some help text.', rel_field.help_text)
+@unittest.skipUnless(PIL is not None, 'PIL is not installed')
class AttributeMappingOnAutogeneratedFieldsTests(TestCase):
def setUp(self):
@@ -1640,6 +1647,7 @@ class AttributeMappingOnAutogeneratedFieldsTests(TestCase):
self.field_test('url_field')
+@unittest.skipUnless(PIL is not None, 'PIL is not installed')
class DefaultValuesOnAutogeneratedFieldsTests(TestCase):
def setUp(self):
--
cgit v1.2.3
From 0e677e9dd178ae7a0250829729a666b54f4eac61 Mon Sep 17 00:00:00 2001
From: Xavier Ordoquy
Date: Fri, 7 Mar 2014 16:11:51 +0100
Subject: Reintroduced url arguments in the urls for the tests.
---
rest_framework/test.py | 4 ++++
rest_framework/tests/test_testing.py | 10 ++++++++++
2 files changed, 14 insertions(+)
(limited to 'rest_framework')
diff --git a/rest_framework/test.py b/rest_framework/test.py
index 75cb4d0b..df5a5b3b 100644
--- a/rest_framework/test.py
+++ b/rest_framework/test.py
@@ -76,6 +76,10 @@ class APIRequestFactory(DjangoRequestFactory):
r = {
'QUERY_STRING': urlencode(data or {}, doseq=True),
}
+ # Fix to support old behavior where you have the arguments in the url
+ # See #1461
+ if not data and '?' in path:
+ r['QUERY_STRING'] = path.split('?')[1]
r.update(extra)
return self.generic('GET', path, **r)
diff --git a/rest_framework/tests/test_testing.py b/rest_framework/tests/test_testing.py
index 71bd8b55..a55d4b22 100644
--- a/rest_framework/tests/test_testing.py
+++ b/rest_framework/tests/test_testing.py
@@ -152,3 +152,13 @@ class TestAPIRequestFactory(TestCase):
simple_png.name = 'test.png'
factory = APIRequestFactory()
factory.post('/', data={'image': simple_png})
+
+ def test_request_factory_url_arguments(self):
+ """
+ This is a non regression test against #1461
+ """
+ factory = APIRequestFactory()
+ request = factory.get('/view/?demo=test')
+ self.assertEqual(dict(request.GET), {'demo': ['test']})
+ request = factory.get('/view/', {'demo': 'test'})
+ self.assertEqual(dict(request.GET), {'demo': ['test']})
--
cgit v1.2.3
From 1909472aa27907190467b81a10fc4ee496bb8889 Mon Sep 17 00:00:00 2001
From: Benjamin Dauvergne
Date: Thu, 13 Mar 2014 23:53:53 +0100
Subject: authentication: allow all transport modes of access token in
OAuth2Authentication
RFC6750 describe three transport modes for access tokens when accessing a
protected resource:
- Auhthorization header with the Bearer authentication type
- form-encoded body parameter
- URI query parameter
This patch add support for last two transport modes.
---
rest_framework/authentication.py | 12 ++++++++++--
rest_framework/tests/test_authentication.py | 26 ++++++++++++++++++++++++++
2 files changed, 36 insertions(+), 2 deletions(-)
(limited to 'rest_framework')
diff --git a/rest_framework/authentication.py b/rest_framework/authentication.py
index b0e88d88..da9ca510 100644
--- a/rest_framework/authentication.py
+++ b/rest_framework/authentication.py
@@ -6,6 +6,7 @@ import base64
from django.contrib.auth import authenticate
from django.core.exceptions import ImproperlyConfigured
+from django.conf import settings
from rest_framework import exceptions, HTTP_HEADER_ENCODING
from rest_framework.compat import CsrfViewMiddleware
from rest_framework.compat import oauth, oauth_provider, oauth_provider_store
@@ -291,6 +292,7 @@ class OAuth2Authentication(BaseAuthentication):
OAuth 2 authentication backend using `django-oauth2-provider`
"""
www_authenticate_realm = 'api'
+ allow_query_params_token = settings.DEBUG
def __init__(self, *args, **kwargs):
super(OAuth2Authentication, self).__init__(*args, **kwargs)
@@ -308,7 +310,13 @@ class OAuth2Authentication(BaseAuthentication):
auth = get_authorization_header(request).split()
- if not auth or auth[0].lower() != b'bearer':
+ if auth and auth[0].lower() == b'bearer':
+ access_token = auth[1]
+ elif 'access_token' in request.POST:
+ access_token = request.POST['access_token']
+ elif 'access_token' in request.GET and self.allow_query_params_token:
+ access_token = request.GET['access_token']
+ else:
return None
if len(auth) == 1:
@@ -318,7 +326,7 @@ class OAuth2Authentication(BaseAuthentication):
msg = 'Invalid bearer header. Token string should not contain spaces.'
raise exceptions.AuthenticationFailed(msg)
- return self.authenticate_credentials(request, auth[1])
+ return self.authenticate_credentials(request, access_token)
def authenticate_credentials(self, request, access_token):
"""
diff --git a/rest_framework/tests/test_authentication.py b/rest_framework/tests/test_authentication.py
index 8caeb081..c37d2a51 100644
--- a/rest_framework/tests/test_authentication.py
+++ b/rest_framework/tests/test_authentication.py
@@ -3,6 +3,7 @@ from django.contrib.auth.models import User
from django.http import HttpResponse
from django.test import TestCase
from django.utils import unittest
+from django.utils.http import urlencode
from rest_framework import HTTP_HEADER_ENCODING
from rest_framework import exceptions
from rest_framework import permissions
@@ -53,10 +54,14 @@ urlpatterns = patterns('',
permission_classes=[permissions.TokenHasReadWriteScope]))
)
+class OAuth2AuthenticationDebug(OAuth2Authentication):
+ allow_query_params_token = True
+
if oauth2_provider is not None:
urlpatterns += patterns('',
url(r'^oauth2/', include('provider.oauth2.urls', namespace='oauth2')),
url(r'^oauth2-test/$', MockView.as_view(authentication_classes=[OAuth2Authentication])),
+ url(r'^oauth2-test-debug/$', MockView.as_view(authentication_classes=[OAuth2AuthenticationDebug])),
url(r'^oauth2-with-scope-test/$', MockView.as_view(authentication_classes=[OAuth2Authentication],
permission_classes=[permissions.TokenHasReadWriteScope])),
)
@@ -545,6 +550,27 @@ class OAuth2Tests(TestCase):
response = self.csrf_client.get('/oauth2-test/', HTTP_AUTHORIZATION=auth)
self.assertEqual(response.status_code, 200)
+ @unittest.skipUnless(oauth2_provider, 'django-oauth2-provider not installed')
+ def test_post_form_passing_auth_url_transport(self):
+ """Ensure GETing form over OAuth with correct client credentials in form data succeed"""
+ response = self.csrf_client.post('/oauth2-test/',
+ data={'access_token': self.access_token.token})
+ self.assertEqual(response.status_code, 200)
+
+ @unittest.skipUnless(oauth2_provider, 'django-oauth2-provider not installed')
+ def test_get_form_passing_auth_url_transport(self):
+ """Ensure GETing form over OAuth with correct client credentials in query succeed when DEBUG is True"""
+ query = urlencode({'access_token': self.access_token.token})
+ response = self.csrf_client.get('/oauth2-test-debug/?%s' % query)
+ self.assertEqual(response.status_code, 200)
+
+ @unittest.skipUnless(oauth2_provider, 'django-oauth2-provider not installed')
+ def test_get_form_failing_auth_url_transport(self):
+ """Ensure GETing form over OAuth with correct client credentials in query fails when DEBUG is False"""
+ query = urlencode({'access_token': self.access_token.token})
+ response = self.csrf_client.get('/oauth2-test/?%s' % query)
+ self.assertIn(response.status_code, (status.HTTP_401_UNAUTHORIZED, status.HTTP_403_FORBIDDEN))
+
@unittest.skipUnless(oauth2_provider, 'django-oauth2-provider not installed')
def test_post_form_passing_auth(self):
"""Ensure POSTing form over OAuth with correct credentials passes and does not require CSRF"""
--
cgit v1.2.3
From 5c87db96c54aeb8ee62213b3ab2a054546d9756c Mon Sep 17 00:00:00 2001
From: elmkarami
Date: Wed, 19 Mar 2014 15:41:25 +0000
Subject: Update serializers.py
Prevent iterating over a string that is supposed to be an iterable <==> Prevent read_only_fields = ('some_string)---
rest_framework/serializers.py | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
(limited to 'rest_framework')
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index 10256d47..62ef5eed 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -757,6 +757,9 @@ class ModelSerializer(Serializer):
field.read_only = True
ret[accessor_name] = field
+
+ #Ensure that 'read_only_fields is an iterable
+ assert isinstance(self.opts.read_only_fields, (list, tuple)), '`read_only_fields` must be a list or tuple'
# Add the `read_only` flag to any fields that have bee specified
# in the `read_only_fields` option
@@ -771,7 +774,10 @@ class ModelSerializer(Serializer):
"on serializer '%s'." %
(field_name, self.__class__.__name__))
ret[field_name].read_only = True
-
+
+ # Ensure that 'write_only_fields' is an iterabe
+ assert isinstance(self.opts.write_only_fields, (list, tuple)), '`read_only_fields` must be a list or tuple'
+
for field_name in self.opts.write_only_fields:
assert field_name not in self.base_fields.keys(), (
"field '%s' on serializer '%s' specified in "
--
cgit v1.2.3
From 03f96988baa6b7fa3a94fd49a5a1631f92b19b4a Mon Sep 17 00:00:00 2001
From: elmkarami
Date: Wed, 19 Mar 2014 17:11:44 +0000
Subject: Update serializers.py
Prevent iterating over a string that is supposed to be an iterable <==> Prevent read_only_fields = ('some_string)---
rest_framework/serializers.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
(limited to 'rest_framework')
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index 62ef5eed..03db418d 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -758,7 +758,7 @@ class ModelSerializer(Serializer):
ret[accessor_name] = field
- #Ensure that 'read_only_fields is an iterable
+ # Ensure that 'read_only_fields' is an iterable
assert isinstance(self.opts.read_only_fields, (list, tuple)), '`read_only_fields` must be a list or tuple'
# Add the `read_only` flag to any fields that have bee specified
@@ -775,7 +775,7 @@ class ModelSerializer(Serializer):
(field_name, self.__class__.__name__))
ret[field_name].read_only = True
- # Ensure that 'write_only_fields' is an iterabe
+ # Ensure that 'write_only_fields' is an iterable
assert isinstance(self.opts.write_only_fields, (list, tuple)), '`read_only_fields` must be a list or tuple'
for field_name in self.opts.write_only_fields:
--
cgit v1.2.3
From 499d3cb8f0cb2f8327050e4fe775ee4bdf288285 Mon Sep 17 00:00:00 2001
From: elmkarami
Date: Wed, 19 Mar 2014 17:23:15 +0000
Subject: Update serializers.py
---
rest_framework/serializers.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
(limited to 'rest_framework')
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index 03db418d..4cb2d81c 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -776,7 +776,7 @@ class ModelSerializer(Serializer):
ret[field_name].read_only = True
# Ensure that 'write_only_fields' is an iterable
- assert isinstance(self.opts.write_only_fields, (list, tuple)), '`read_only_fields` must be a list or tuple'
+ assert isinstance(self.opts.write_only_fields, (list, tuple)), '`write_only_fields` must be a list or tuple'
for field_name in self.opts.write_only_fields:
assert field_name not in self.base_fields.keys(), (
--
cgit v1.2.3
From 19c03f4a60f339397b8ed03c9e6f20b3c604ffc3 Mon Sep 17 00:00:00 2001
From: Vladislav Vlastovskiy
Date: Thu, 20 Mar 2014 01:49:30 +0400
Subject: Added test writable star source Uses nested serializer with parent
object
---
rest_framework/tests/test_serializer.py | 26 ++++++++++++++++++++++++++
1 file changed, 26 insertions(+)
(limited to 'rest_framework')
diff --git a/rest_framework/tests/test_serializer.py b/rest_framework/tests/test_serializer.py
index 85a899c5..b78ceaa6 100644
--- a/rest_framework/tests/test_serializer.py
+++ b/rest_framework/tests/test_serializer.py
@@ -508,6 +508,32 @@ class ValidationTests(TestCase):
)
self.assertEqual(serializer.is_valid(), True)
+ def test_writable_star_source_on_nested_serializer_with_parent_object(self):
+ class Serializer(serializers.Serializer):
+ title = serializers.WritableField(source='title')
+
+ class AlbumSerializer(serializers.ModelSerializer):
+ nested = Serializer(source='*')
+
+ class Meta:
+ model = Album
+ fields = ('nested',)
+
+ class PhotoSerializer(serializers.ModelSerializer):
+ album = AlbumSerializer(source='album')
+
+ class Meta:
+ model = Photo
+ fields = ('album', )
+
+ photo = Photo(album=Album())
+
+ data = {'album': {'nested': {'title': 'test'}}}
+
+ serializer = PhotoSerializer(photo, data=data)
+ self.assertEqual(serializer.is_valid(), True)
+ self.assertEqual(serializer.data, data)
+
def test_writable_star_source_with_inner_source_fields(self):
"""
Tests that a serializer with source="*" correctly expands the
--
cgit v1.2.3
From c3aa10e589cb524dc3bb39a4fccee8238763d25a Mon Sep 17 00:00:00 2001
From: Vladislav Vlastovskiy
Date: Thu, 20 Mar 2014 01:50:40 +0400
Subject: Moved get component from object after test source is star
---
rest_framework/serializers.py | 31 ++++++++++++++++---------------
1 file changed, 16 insertions(+), 15 deletions(-)
(limited to 'rest_framework')
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index 5c726dfc..cc0e027f 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -438,25 +438,26 @@ class BaseSerializer(WritableField):
raise ValidationError(self.error_messages['required'])
return
- # Set the serializer object if it exists
- obj = get_component(self.parent.object, self.source or field_name) if self.parent.object else None
-
- # If we have a model manager or similar object then we need
- # to iterate through each instance.
- if (self.many and
- not hasattr(obj, '__iter__') and
- is_simple_callable(getattr(obj, 'all', None))):
- obj = obj.all()
-
- if self.source == '*':
- if value:
- reverted_data = self.restore_fields(value, {})
- if not self._errors:
- into.update(reverted_data)
else:
if value in (None, ''):
into[(self.source or field_name)] = None
else:
+ # Set the serializer object if it exists
+ obj = get_component(self.parent.object, self.source or field_name) if self.parent.object else None
+
+ # If we have a model manager or similar object then we need
+ # to iterate through each instance.
+ if (self.many and
+ not hasattr(obj, '__iter__') and
+ is_simple_callable(getattr(obj, 'all', None))):
+ obj = obj.all()
+
+ if self.source == '*':
+ if value:
+ reverted_data = self.restore_fields(value, {})
+ if not self._errors:
+ into.update(reverted_data)
+
kwargs = {
'instance': obj,
'data': value,
--
cgit v1.2.3
From e8167f96e6c1a112e76b647ac32164be931b09a8 Mon Sep 17 00:00:00 2001
From: Vladislav Vlastovskiy
Date: Thu, 20 Mar 2014 08:53:41 +0400
Subject: Fixed copy-paste
---
rest_framework/serializers.py | 11 +++++------
1 file changed, 5 insertions(+), 6 deletions(-)
(limited to 'rest_framework')
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index cc0e027f..01606e9c 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -438,6 +438,11 @@ class BaseSerializer(WritableField):
raise ValidationError(self.error_messages['required'])
return
+ if self.source == '*':
+ if value:
+ reverted_data = self.restore_fields(value, {})
+ if not self._errors:
+ into.update(reverted_data)
else:
if value in (None, ''):
into[(self.source or field_name)] = None
@@ -452,12 +457,6 @@ class BaseSerializer(WritableField):
is_simple_callable(getattr(obj, 'all', None))):
obj = obj.all()
- if self.source == '*':
- if value:
- reverted_data = self.restore_fields(value, {})
- if not self._errors:
- into.update(reverted_data)
-
kwargs = {
'instance': obj,
'data': value,
--
cgit v1.2.3
From f5fc6937ece8c2bc70088979cc19c2c0a660c7a0 Mon Sep 17 00:00:00 2001
From: Vladislav Vlastovskiy
Date: Thu, 20 Mar 2014 20:27:07 +0400
Subject: Change serializer name for removing confusion
---
rest_framework/tests/test_serializer.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
(limited to 'rest_framework')
diff --git a/rest_framework/tests/test_serializer.py b/rest_framework/tests/test_serializer.py
index b78ceaa6..3ee2b38a 100644
--- a/rest_framework/tests/test_serializer.py
+++ b/rest_framework/tests/test_serializer.py
@@ -509,11 +509,11 @@ class ValidationTests(TestCase):
self.assertEqual(serializer.is_valid(), True)
def test_writable_star_source_on_nested_serializer_with_parent_object(self):
- class Serializer(serializers.Serializer):
+ class TitleSerializer(serializers.Serializer):
title = serializers.WritableField(source='title')
class AlbumSerializer(serializers.ModelSerializer):
- nested = Serializer(source='*')
+ nested = TitleSerializer(source='*')
class Meta:
model = Album
--
cgit v1.2.3
From 3b71be725a727be802eb2e43d4d155b734320023 Mon Sep 17 00:00:00 2001
From: Daniel Kontsek
Date: Sat, 22 Mar 2014 10:22:08 +0100
Subject: Fixed encoding parameter in QueryDict
---
rest_framework/request.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
(limited to 'rest_framework')
diff --git a/rest_framework/request.py b/rest_framework/request.py
index ca70b49e..40467c03 100644
--- a/rest_framework/request.py
+++ b/rest_framework/request.py
@@ -346,7 +346,7 @@ class Request(object):
media_type = self.content_type
if stream is None or media_type is None:
- empty_data = QueryDict('', self._request._encoding)
+ empty_data = QueryDict('', encoding=self._request._encoding)
empty_files = MultiValueDict()
return (empty_data, empty_files)
@@ -362,7 +362,7 @@ class Request(object):
# re-raise. Ensures we don't simply repeat the error when
# attempting to render the browsable renderer response, or when
# logging the request or similar.
- self._data = QueryDict('', self._request._encoding)
+ self._data = QueryDict('', encoding=self._request._encoding)
self._files = MultiValueDict()
raise
--
cgit v1.2.3
From 3560796bbff33917df9f8e6885328467c7f809f9 Mon Sep 17 00:00:00 2001
From: Ravi Kotecha
Date: Mon, 31 Mar 2014 11:38:26 +0100
Subject: add regression tests for field Validators
pep8 and add issue no
fix formatting for python 2.6 and strings for python 3.2
---
rest_framework/tests/test_validation.py | 44 +++++++++++++++++++++++++++++++++
1 file changed, 44 insertions(+)
(limited to 'rest_framework')
diff --git a/rest_framework/tests/test_validation.py b/rest_framework/tests/test_validation.py
index 124c874d..31549df8 100644
--- a/rest_framework/tests/test_validation.py
+++ b/rest_framework/tests/test_validation.py
@@ -1,4 +1,5 @@
from __future__ import unicode_literals
+from django.core.validators import MaxValueValidator
from django.db import models
from django.test import TestCase
from rest_framework import generics, serializers, status
@@ -102,3 +103,46 @@ class TestAvoidValidation(TestCase):
self.assertFalse(serializer.is_valid())
self.assertDictEqual(serializer.errors,
{'non_field_errors': ['Invalid data']})
+
+
+# regression tests for issue: 1493
+
+class ValidationMaxValueValidatorModel(models.Model):
+ number_value = models.PositiveIntegerField(validators=[MaxValueValidator(100)])
+
+
+class ValidationMaxValueValidatorModelSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = ValidationMaxValueValidatorModel
+
+
+class UpdateMaxValueValidationModel(generics.RetrieveUpdateDestroyAPIView):
+ model = ValidationMaxValueValidatorModel
+ serializer_class = ValidationMaxValueValidatorModelSerializer
+
+
+class TestMaxValueValidatorValidation(TestCase):
+
+ def test_max_value_validation_serializer_success(self):
+ serializer = ValidationMaxValueValidatorModelSerializer(data={'number_value': 99})
+ self.assertTrue(serializer.is_valid())
+
+ def test_max_value_validation_serializer_fails(self):
+ serializer = ValidationMaxValueValidatorModelSerializer(data={'number_value': 101})
+ self.assertFalse(serializer.is_valid())
+ self.assertDictEqual({'number_value': ['Ensure this value is less than or equal to 100.']}, serializer.errors)
+
+ def test_max_value_validation_success(self):
+ obj = ValidationMaxValueValidatorModel.objects.create(number_value=100)
+ request = factory.patch('/{0}'.format(obj.pk), {'number_value': 98}, format='json')
+ view = UpdateMaxValueValidationModel().as_view()
+ response = view(request, pk=obj.pk).render()
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+
+ def test_max_value_validation_fail(self):
+ obj = ValidationMaxValueValidatorModel.objects.create(number_value=100)
+ request = factory.patch('/{0}'.format(obj.pk), {'number_value': 101}, format='json')
+ view = UpdateMaxValueValidationModel().as_view()
+ response = view(request, pk=obj.pk).render()
+ self.assertEqual(response.content, '{"number_value": ["Ensure this value is less than or equal to 100."]}')
+ self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
--
cgit v1.2.3
From 591cf8a48c6e5ce37d205c4b7e418fb7d2c31b0f Mon Sep 17 00:00:00 2001
From: Xavier Ordoquy
Date: Mon, 31 Mar 2014 13:17:31 +0200
Subject: Content is a binary string.
---
rest_framework/tests/test_validation.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
(limited to 'rest_framework')
diff --git a/rest_framework/tests/test_validation.py b/rest_framework/tests/test_validation.py
index 31549df8..e13e4078 100644
--- a/rest_framework/tests/test_validation.py
+++ b/rest_framework/tests/test_validation.py
@@ -144,5 +144,5 @@ class TestMaxValueValidatorValidation(TestCase):
request = factory.patch('/{0}'.format(obj.pk), {'number_value': 101}, format='json')
view = UpdateMaxValueValidationModel().as_view()
response = view(request, pk=obj.pk).render()
- self.assertEqual(response.content, '{"number_value": ["Ensure this value is less than or equal to 100."]}')
+ self.assertEqual(response.content, b'{"number_value": ["Ensure this value is less than or equal to 100."]}')
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
--
cgit v1.2.3
From 6322feb32dceb7be67b2117686f0a7570a615294 Mon Sep 17 00:00:00 2001
From: jacobg
Date: Fri, 4 Apr 2014 10:22:02 -0400
Subject: add a __str__ implementation to APIException
Add a __str__ implementation to rest_framework.exceptions.APIException. This helps for logging raised exceptions. Thanks.---
rest_framework/exceptions.py | 2 ++
1 file changed, 2 insertions(+)
(limited to 'rest_framework')
diff --git a/rest_framework/exceptions.py b/rest_framework/exceptions.py
index 0ac5866e..5f774a9f 100644
--- a/rest_framework/exceptions.py
+++ b/rest_framework/exceptions.py
@@ -20,6 +20,8 @@ class APIException(Exception):
def __init__(self, detail=None):
self.detail = detail or self.default_detail
+ def __str__(self):
+ return self.detail
class ParseError(APIException):
status_code = status.HTTP_400_BAD_REQUEST
--
cgit v1.2.3
From 2a1571b3bf36ff153af68401f7aefa0620f80807 Mon Sep 17 00:00:00 2001
From: Mauro de Carvalho
Date: Mon, 7 Apr 2014 18:27:59 -0300
Subject: Fixed comment.
---
rest_framework/fields.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
(limited to 'rest_framework')
diff --git a/rest_framework/fields.py b/rest_framework/fields.py
index 68b95682..946a5954 100644
--- a/rest_framework/fields.py
+++ b/rest_framework/fields.py
@@ -164,7 +164,7 @@ class Field(object):
Called to set up a field prior to field_to_native or field_from_native.
parent - The parent serializer.
- model_field - The model field this field corresponds to, if one exists.
+ field_name - The name of the field being initialized.
"""
self.parent = parent
self.root = parent.root or parent
--
cgit v1.2.3
From c1ac65edce1bcfff4c87df3bb9c4df14fe8e9d6c Mon Sep 17 00:00:00 2001
From: Carlton Gibson
Date: Wed, 9 Apr 2014 15:51:00 +0200
Subject: Adds test that blank option is added when required=False on
RelatedFields
---
rest_framework/relations.py | 2 ++
rest_framework/tests/test_relations.py | 22 ++++++++++++++++++++++
2 files changed, 24 insertions(+)
(limited to 'rest_framework')
diff --git a/rest_framework/relations.py b/rest_framework/relations.py
index 308545ce..3463954d 100644
--- a/rest_framework/relations.py
+++ b/rest_framework/relations.py
@@ -59,6 +59,8 @@ class RelatedField(WritableField):
super(RelatedField, self).__init__(*args, **kwargs)
if not self.required:
+ # Accessed in ModelChoiceIterator django/forms/models.py:1034
+ # If set adds empty choice.
self.empty_label = BLANK_CHOICE_DASH[0][1]
self.queryset = queryset
diff --git a/rest_framework/tests/test_relations.py b/rest_framework/tests/test_relations.py
index f52e0e1e..c421096a 100644
--- a/rest_framework/tests/test_relations.py
+++ b/rest_framework/tests/test_relations.py
@@ -118,3 +118,25 @@ class RelatedFieldSourceTests(TestCase):
(serializers.ModelSerializer,), attrs)
with self.assertRaises(AttributeError):
TestSerializer(data={'name': 'foo'})
+
+
+class RelatedFieldChoicesTests(TestCase):
+ """
+ Tests for #1408 "Web browseable API doesn't have blank option on drop down list box"
+ https://github.com/tomchristie/django-rest-framework/issues/1408
+ """
+ def test_blank_option_is_added_to_choice_if_required_equals_false(self):
+ """
+
+ """
+ post = BlogPost(title="Checking blank option is added")
+ post.save()
+
+ queryset = BlogPost.objects.all()
+ field = serializers.RelatedField(required=False, queryset=queryset)
+
+ choice_count = BlogPost.objects.count()
+ widget_count = len(field.widget.choices)
+
+ self.assertEqual(widget_count, choice_count + 1, 'BLANK_CHOICE_DASH option should have been added')
+
--
cgit v1.2.3
From a73498d7974b15a25902fbdd1024742b95a166d4 Mon Sep 17 00:00:00 2001
From: Carlton Gibson
Date: Wed, 9 Apr 2014 19:54:13 +0200
Subject: Skip new test for Django < 1.6
---
rest_framework/tests/test_relations.py | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
(limited to 'rest_framework')
diff --git a/rest_framework/tests/test_relations.py b/rest_framework/tests/test_relations.py
index c421096a..37ac826b 100644
--- a/rest_framework/tests/test_relations.py
+++ b/rest_framework/tests/test_relations.py
@@ -2,8 +2,10 @@
General tests for relational fields.
"""
from __future__ import unicode_literals
+from django import get_version
from django.db import models
from django.test import TestCase
+from django.utils import unittest
from rest_framework import serializers
from rest_framework.tests.models import BlogPost
@@ -119,7 +121,7 @@ class RelatedFieldSourceTests(TestCase):
with self.assertRaises(AttributeError):
TestSerializer(data={'name': 'foo'})
-
+@unittest.skipIf(get_version() < '1.6.0', 'Upstream behaviour changed in v1.6')
class RelatedFieldChoicesTests(TestCase):
"""
Tests for #1408 "Web browseable API doesn't have blank option on drop down list box"
--
cgit v1.2.3
From 613df5c6501f715c0775229f34fcba9f4291c05d Mon Sep 17 00:00:00 2001
From: Ian Leith
Date: Fri, 11 Apr 2014 05:49:49 +0100
Subject: Fix dict_keys equality test for python 3.
---
rest_framework/utils/mediatypes.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
(limited to 'rest_framework')
diff --git a/rest_framework/utils/mediatypes.py b/rest_framework/utils/mediatypes.py
index c09c2933..92f99efd 100644
--- a/rest_framework/utils/mediatypes.py
+++ b/rest_framework/utils/mediatypes.py
@@ -74,7 +74,7 @@ class _MediaType(object):
return 0
elif self.sub_type == '*':
return 1
- elif not self.params or self.params.keys() == ['q']:
+ elif not self.params or list(self.params.keys()) == ['q']:
return 2
return 3
--
cgit v1.2.3