mirror of
https://github.com/encode/django-rest-framework.git
synced 2024-11-26 03:23:59 +03:00
Merge remote-tracking branch 'upstream/master' into null-one-to-one
This commit is contained in:
commit
81691ff900
|
@ -87,10 +87,9 @@ To run the tests.
|
||||||
|
|
||||||
* Added `PATCH` support.
|
* Added `PATCH` support.
|
||||||
* Added `RetrieveUpdateAPIView`.
|
* Added `RetrieveUpdateAPIView`.
|
||||||
* Relation changes are now persisted in `save` instead of in `.restore_object`.
|
* Relation changes are now persisted in `.save` instead of in `.restore_object`.
|
||||||
* Remove unused internal `save_m2m` flag on `ModelSerializer.save()`.
|
* Remove unused internal `save_m2m` flag on `ModelSerializer.save()`.
|
||||||
* Tweak behavior of hyperlinked fields with an explicit format suffix.
|
* Tweak behavior of hyperlinked fields with an explicit format suffix.
|
||||||
* Relation changes are now persisted in `.save()` instead of in `.restore_object()`.
|
|
||||||
* Bugfix: Fix issue with FileField raising exception instead of validation error when files=None.
|
* Bugfix: Fix issue with FileField raising exception instead of validation error when files=None.
|
||||||
* Bugfix: Partial updates should not set default values if field is not included.
|
* Bugfix: Partial updates should not set default values if field is not included.
|
||||||
|
|
||||||
|
|
|
@ -97,6 +97,8 @@ You can also set the pagination style on a per-view basis, using the `ListAPIVie
|
||||||
paginate_by = 10
|
paginate_by = 10
|
||||||
paginate_by_param = 'page_size'
|
paginate_by_param = 'page_size'
|
||||||
|
|
||||||
|
Note that using a `paginate_by` value of `None` will turn off pagination for the view.
|
||||||
|
|
||||||
For more complex requirements such as serialization that differs depending on the requested media type you can override the `.get_paginate_by()` and `.get_pagination_serializer_class()` methods.
|
For more complex requirements such as serialization that differs depending on the requested media type you can override the `.get_paginate_by()` and `.get_pagination_serializer_class()` methods.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
|
@ -159,4 +159,17 @@ For example:
|
||||||
files = {name: uploaded}
|
files = {name: uploaded}
|
||||||
return DataAndFiles(data, files)
|
return DataAndFiles(data, files)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Third party packages
|
||||||
|
|
||||||
|
The following third party packages are also available.
|
||||||
|
|
||||||
|
## MessagePack
|
||||||
|
|
||||||
|
[MessagePack][messagepack] is a fast, efficient binary serialization format. [Juan Riaza][juanriaza] maintains the `djangorestframework-msgpack` package which provides MessagePack renderer and parser support for REST framework. Documentation is [available here][djangorestframework-msgpack].
|
||||||
|
|
||||||
[cite]: https://groups.google.com/d/topic/django-developers/dxI4qVzrBY4/discussion
|
[cite]: https://groups.google.com/d/topic/django-developers/dxI4qVzrBY4/discussion
|
||||||
|
[messagepack]: https://github.com/juanriaza/django-rest-framework-msgpack
|
||||||
|
[juanriaza]: https://github.com/juanriaza
|
||||||
|
[djangorestframework-msgpack]: https://github.com/juanriaza/django-rest-framework-msgpack
|
|
@ -271,6 +271,15 @@ Exceptions raised and handled by an HTML renderer will attempt to render using o
|
||||||
|
|
||||||
Templates will render with a `RequestContext` which includes the `status_code` and `details` keys.
|
Templates will render with a `RequestContext` which includes the `status_code` and `details` keys.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Third party packages
|
||||||
|
|
||||||
|
The following third party packages are also available.
|
||||||
|
|
||||||
|
## MessagePack
|
||||||
|
|
||||||
|
[MessagePack][messagepack] is a fast, efficient binary serialization format. [Juan Riaza][juanriaza] maintains the `djangorestframework-msgpack` package which provides MessagePack renderer and parser support for REST framework. Documentation is [available here][djangorestframework-msgpack].
|
||||||
|
|
||||||
[cite]: https://docs.djangoproject.com/en/dev/ref/template-response/#the-rendering-process
|
[cite]: https://docs.djangoproject.com/en/dev/ref/template-response/#the-rendering-process
|
||||||
[conneg]: content-negotiation.md
|
[conneg]: content-negotiation.md
|
||||||
|
@ -280,4 +289,7 @@ Templates will render with a `RequestContext` which includes the `status_code` a
|
||||||
[quote]: http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven
|
[quote]: http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven
|
||||||
[application/vnd.github+json]: http://developer.github.com/v3/media/
|
[application/vnd.github+json]: http://developer.github.com/v3/media/
|
||||||
[application/vnd.collection+json]: http://www.amundsen.com/media-types/collection/
|
[application/vnd.collection+json]: http://www.amundsen.com/media-types/collection/
|
||||||
[django-error-views]: https://docs.djangoproject.com/en/dev/topics/http/views/#customizing-error-views
|
[django-error-views]: https://docs.djangoproject.com/en/dev/topics/http/views/#customizing-error-views
|
||||||
|
[messagepack]: https://github.com/juanriaza/django-rest-framework-msgpack
|
||||||
|
[juanriaza]: https://github.com/juanriaza
|
||||||
|
[djangorestframework-msgpack]: https://github.com/juanriaza/django-rest-framework-msgpack
|
|
@ -65,7 +65,7 @@ Default:
|
||||||
|
|
||||||
(
|
(
|
||||||
'rest_framework.authentication.SessionAuthentication',
|
'rest_framework.authentication.SessionAuthentication',
|
||||||
'rest_framework.authentication.UserBasicAuthentication'
|
'rest_framework.authentication.BasicAuthentication'
|
||||||
)
|
)
|
||||||
|
|
||||||
## DEFAULT_PERMISSION_CLASSES
|
## DEFAULT_PERMISSION_CLASSES
|
||||||
|
@ -106,7 +106,7 @@ The default page size to use for pagination. If set to `None`, pagination is di
|
||||||
|
|
||||||
Default: `None`
|
Default: `None`
|
||||||
|
|
||||||
## PAGINATE_BY_KWARG
|
## PAGINATE_BY_PARAM
|
||||||
|
|
||||||
The name of a query parameter, which can be used by the client to overide the default page size to use for pagination. If set to `None`, clients may not override the default page size.
|
The name of a query parameter, which can be used by the client to overide the default page size to use for pagination. If set to `None`, clients may not override the default page size.
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
The following people have helped make REST framework great.
|
The following people have helped make REST framework great.
|
||||||
|
|
||||||
* Tom Christie - [tomchristie]
|
* Tom Christie - [tomchristie]
|
||||||
* Marko Tibold - [markotibold]
|
* Marko Tibold - [markotibold]
|
||||||
* Paul Bagwell - [pbgwl]
|
* Paul Bagwell - [pbgwl]
|
||||||
* Sébastien Piquemal - [sebpiq]
|
* Sébastien Piquemal - [sebpiq]
|
||||||
|
@ -85,6 +85,9 @@ The following people have helped make REST framework great.
|
||||||
* Toran Billups - [toranb]
|
* Toran Billups - [toranb]
|
||||||
* Sébastien Béal - [sebastibe]
|
* Sébastien Béal - [sebastibe]
|
||||||
* Andrew Hankinson - [ahankinson]
|
* Andrew Hankinson - [ahankinson]
|
||||||
|
* Juan Riaza - [juanriaza]
|
||||||
|
* Michael Mior - [michaelmior]
|
||||||
|
* Marc Tamlyn - [mjtamlyn]
|
||||||
|
|
||||||
Many thanks to everyone who's contributed to the project.
|
Many thanks to everyone who's contributed to the project.
|
||||||
|
|
||||||
|
@ -96,7 +99,7 @@ Project hosting is with [GitHub].
|
||||||
|
|
||||||
Continuous integration testing is managed with [Travis CI][travis-ci].
|
Continuous integration testing is managed with [Travis CI][travis-ci].
|
||||||
|
|
||||||
The [live sandbox][sandbox] is hosted on [Heroku].
|
The [live sandbox][sandbox] is hosted on [Heroku].
|
||||||
|
|
||||||
Various inspiration taken from the [Piston], [Tastypie] and [Dagny] projects.
|
Various inspiration taken from the [Piston], [Tastypie] and [Dagny] projects.
|
||||||
|
|
||||||
|
@ -107,7 +110,7 @@ Development of REST framework 2.0 was sponsored by [DabApps].
|
||||||
For usage questions please see the [REST framework discussion group][group].
|
For usage questions please see the [REST framework discussion group][group].
|
||||||
|
|
||||||
You can also contact [@_tomchristie][twitter] directly on twitter.
|
You can also contact [@_tomchristie][twitter] directly on twitter.
|
||||||
|
|
||||||
[email]: mailto:tom@tomchristie.com
|
[email]: mailto:tom@tomchristie.com
|
||||||
[twitter]: http://twitter.com/_tomchristie
|
[twitter]: http://twitter.com/_tomchristie
|
||||||
[bootstrap]: http://twitter.github.com/bootstrap/
|
[bootstrap]: http://twitter.github.com/bootstrap/
|
||||||
|
@ -205,3 +208,6 @@ You can also contact [@_tomchristie][twitter] directly on twitter.
|
||||||
[toranb]: https://github.com/toranb
|
[toranb]: https://github.com/toranb
|
||||||
[sebastibe]: https://github.com/sebastibe
|
[sebastibe]: https://github.com/sebastibe
|
||||||
[ahankinson]: https://github.com/ahankinson
|
[ahankinson]: https://github.com/ahankinson
|
||||||
|
[juanriaza]: https://github.com/juanriaza
|
||||||
|
[michaelmior]: https://github.com/michaelmior
|
||||||
|
[mjtamlyn]: https://github.com/mjtamlyn
|
||||||
|
|
|
@ -16,13 +16,18 @@ Major version numbers (x.0.0) are reserved for project milestones. No major poi
|
||||||
|
|
||||||
## 2.1.x series
|
## 2.1.x series
|
||||||
|
|
||||||
|
### Master
|
||||||
|
|
||||||
|
* Deprecate django.utils.simplejson in favor of Python 2.6's built-in json module.
|
||||||
|
* Bugfix: Validation errors instead of exceptions when serializers receive incorrect types.
|
||||||
|
* Bugfix: Validation errors instead of exceptions when related fields receive incorrect types.
|
||||||
|
|
||||||
### 2.1.15
|
### 2.1.15
|
||||||
|
|
||||||
**Date**: 3rd Jan 2013
|
**Date**: 3rd Jan 2013
|
||||||
|
|
||||||
* Added `PATCH` support.
|
* Added `PATCH` support.
|
||||||
* Added `RetrieveUpdateAPIView`.
|
* Added `RetrieveUpdateAPIView`.
|
||||||
* Relation changes are now persisted in `save` instead of in `.restore_object`.
|
|
||||||
* Remove unused internal `save_m2m` flag on `ModelSerializer.save()`.
|
* Remove unused internal `save_m2m` flag on `ModelSerializer.save()`.
|
||||||
* Tweak behavior of hyperlinked fields with an explicit format suffix.
|
* Tweak behavior of hyperlinked fields with an explicit format suffix.
|
||||||
* Relation changes are now persisted in `.save()` instead of in `.restore_object()`.
|
* Relation changes are now persisted in `.save()` instead of in `.restore_object()`.
|
||||||
|
@ -37,9 +42,9 @@ Major version numbers (x.0.0) are reserved for project milestones. No major poi
|
||||||
* Bugfix: Model fields with `blank=True` are now `required=False` by default.
|
* Bugfix: Model fields with `blank=True` are now `required=False` by default.
|
||||||
* Bugfix: Nested serializers now support nullable relationships.
|
* Bugfix: Nested serializers now support nullable relationships.
|
||||||
|
|
||||||
**Note**: From 2.1.14 onwards, relational fields move out of the `fields.py` module and into the new `relations.py` module, in order to seperate them from regular data type fields, such as `CharField` and `IntegerField`.
|
**Note**: From 2.1.14 onwards, relational fields move out of the `fields.py` module and into the new `relations.py` module, in order to separate them from regular data type fields, such as `CharField` and `IntegerField`.
|
||||||
|
|
||||||
This change will not affect user code, so long as it's following the recommended import style of `from rest_framework import serializers` and refering to fields using the style `serializers.PrimaryKeyRelatedField`.
|
This change will not affect user code, so long as it's following the recommended import style of `from rest_framework import serializers` and referring to fields using the style `serializers.PrimaryKeyRelatedField`.
|
||||||
|
|
||||||
|
|
||||||
### 2.1.13
|
### 2.1.13
|
||||||
|
|
|
@ -12,10 +12,11 @@ class ObtainAuthToken(APIView):
|
||||||
permission_classes = ()
|
permission_classes = ()
|
||||||
parser_classes = (parsers.FormParser, parsers.MultiPartParser, parsers.JSONParser,)
|
parser_classes = (parsers.FormParser, parsers.MultiPartParser, parsers.JSONParser,)
|
||||||
renderer_classes = (renderers.JSONRenderer,)
|
renderer_classes = (renderers.JSONRenderer,)
|
||||||
|
serializer_class = AuthTokenSerializer
|
||||||
model = Token
|
model = Token
|
||||||
|
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
serializer = AuthTokenSerializer(data=request.DATA)
|
serializer = self.serializer_class(data=request.DATA)
|
||||||
if serializer.is_valid():
|
if serializer.is_valid():
|
||||||
token, created = Token.objects.get_or_create(user=serializer.object['user'])
|
token, created = Token.objects.get_or_create(user=serializer.object['user'])
|
||||||
return Response({'token': token.key})
|
return Response({'token': token.key})
|
||||||
|
|
|
@ -8,11 +8,11 @@ on the request, such as form content or json encoded data.
|
||||||
from django.http import QueryDict
|
from django.http import QueryDict
|
||||||
from django.http.multipartparser import MultiPartParser as DjangoMultiPartParser
|
from django.http.multipartparser import MultiPartParser as DjangoMultiPartParser
|
||||||
from django.http.multipartparser import MultiPartParserError
|
from django.http.multipartparser import MultiPartParserError
|
||||||
from django.utils import simplejson as json
|
|
||||||
from rest_framework.compat import yaml, ETParseError
|
from rest_framework.compat import yaml, ETParseError
|
||||||
from rest_framework.exceptions import ParseError
|
from rest_framework.exceptions import ParseError
|
||||||
from xml.etree import ElementTree as ET
|
from xml.etree import ElementTree as ET
|
||||||
from xml.parsers.expat import ExpatError
|
from xml.parsers.expat import ExpatError
|
||||||
|
import json
|
||||||
import datetime
|
import datetime
|
||||||
import decimal
|
import decimal
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ from django import forms
|
||||||
from django.forms import widgets
|
from django.forms import widgets
|
||||||
from django.forms.models import ModelChoiceIterator
|
from django.forms.models import ModelChoiceIterator
|
||||||
from django.utils.encoding import smart_unicode
|
from django.utils.encoding import smart_unicode
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from rest_framework.fields import Field, WritableField
|
from rest_framework.fields import Field, WritableField
|
||||||
from rest_framework.reverse import reverse
|
from rest_framework.reverse import reverse
|
||||||
from urlparse import urlparse
|
from urlparse import urlparse
|
||||||
|
@ -171,6 +172,11 @@ class PrimaryKeyRelatedField(RelatedField):
|
||||||
default_read_only = False
|
default_read_only = False
|
||||||
form_field_class = forms.ChoiceField
|
form_field_class = forms.ChoiceField
|
||||||
|
|
||||||
|
default_error_messages = {
|
||||||
|
'does_not_exist': _("Invalid pk '%s' - object does not exist."),
|
||||||
|
'invalid': _('Invalid value.'),
|
||||||
|
}
|
||||||
|
|
||||||
# TODO: Remove these field hacks...
|
# TODO: Remove these field hacks...
|
||||||
def prepare_value(self, obj):
|
def prepare_value(self, obj):
|
||||||
return self.to_native(obj.pk)
|
return self.to_native(obj.pk)
|
||||||
|
@ -196,7 +202,10 @@ class PrimaryKeyRelatedField(RelatedField):
|
||||||
try:
|
try:
|
||||||
return self.queryset.get(pk=data)
|
return self.queryset.get(pk=data)
|
||||||
except ObjectDoesNotExist:
|
except ObjectDoesNotExist:
|
||||||
msg = "Invalid pk '%s' - object does not exist." % smart_unicode(data)
|
msg = self.error_messages['does_not_exist'] % smart_unicode(data)
|
||||||
|
raise ValidationError(msg)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
msg = self.error_messages['invalid']
|
||||||
raise ValidationError(msg)
|
raise ValidationError(msg)
|
||||||
|
|
||||||
def field_to_native(self, obj, field_name):
|
def field_to_native(self, obj, field_name):
|
||||||
|
@ -221,6 +230,11 @@ class ManyPrimaryKeyRelatedField(ManyRelatedField):
|
||||||
default_read_only = False
|
default_read_only = False
|
||||||
form_field_class = forms.MultipleChoiceField
|
form_field_class = forms.MultipleChoiceField
|
||||||
|
|
||||||
|
default_error_messages = {
|
||||||
|
'does_not_exist': _("Invalid pk '%s' - object does not exist."),
|
||||||
|
'invalid': _('Invalid value.'),
|
||||||
|
}
|
||||||
|
|
||||||
def prepare_value(self, obj):
|
def prepare_value(self, obj):
|
||||||
return self.to_native(obj.pk)
|
return self.to_native(obj.pk)
|
||||||
|
|
||||||
|
@ -255,7 +269,10 @@ class ManyPrimaryKeyRelatedField(ManyRelatedField):
|
||||||
try:
|
try:
|
||||||
return self.queryset.get(pk=data)
|
return self.queryset.get(pk=data)
|
||||||
except ObjectDoesNotExist:
|
except ObjectDoesNotExist:
|
||||||
msg = "Invalid pk '%s' - object does not exist." % smart_unicode(data)
|
msg = self.error_messages['does_not_exist'] % smart_unicode(data)
|
||||||
|
raise ValidationError(msg)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
msg = self.error_messages['invalid']
|
||||||
raise ValidationError(msg)
|
raise ValidationError(msg)
|
||||||
|
|
||||||
### Slug relationships
|
### Slug relationships
|
||||||
|
@ -265,6 +282,11 @@ class SlugRelatedField(RelatedField):
|
||||||
default_read_only = False
|
default_read_only = False
|
||||||
form_field_class = forms.ChoiceField
|
form_field_class = forms.ChoiceField
|
||||||
|
|
||||||
|
default_error_messages = {
|
||||||
|
'does_not_exist': _("Object with %s=%s does not exist."),
|
||||||
|
'invalid': _('Invalid value.'),
|
||||||
|
}
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.slug_field = kwargs.pop('slug_field', None)
|
self.slug_field = kwargs.pop('slug_field', None)
|
||||||
assert self.slug_field, 'slug_field is required'
|
assert self.slug_field, 'slug_field is required'
|
||||||
|
@ -280,8 +302,11 @@ class SlugRelatedField(RelatedField):
|
||||||
try:
|
try:
|
||||||
return self.queryset.get(**{self.slug_field: data})
|
return self.queryset.get(**{self.slug_field: data})
|
||||||
except ObjectDoesNotExist:
|
except ObjectDoesNotExist:
|
||||||
raise ValidationError('Object with %s=%s does not exist.' %
|
raise ValidationError(self.error_messages['does_not_exist'] %
|
||||||
(self.slug_field, unicode(data)))
|
(self.slug_field, unicode(data)))
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
msg = self.error_messages['invalid']
|
||||||
|
raise ValidationError(msg)
|
||||||
|
|
||||||
|
|
||||||
class ManySlugRelatedField(ManyRelatedMixin, SlugRelatedField):
|
class ManySlugRelatedField(ManyRelatedMixin, SlugRelatedField):
|
||||||
|
@ -300,6 +325,14 @@ class HyperlinkedRelatedField(RelatedField):
|
||||||
default_read_only = False
|
default_read_only = False
|
||||||
form_field_class = forms.ChoiceField
|
form_field_class = forms.ChoiceField
|
||||||
|
|
||||||
|
default_error_messages = {
|
||||||
|
'no_match': _('Invalid hyperlink - No URL match'),
|
||||||
|
'incorrect_match': _('Invalid hyperlink - Incorrect URL match'),
|
||||||
|
'configuration_error': _('Invalid hyperlink due to configuration error'),
|
||||||
|
'does_not_exist': _("Invalid hyperlink - object does not exist."),
|
||||||
|
'invalid': _('Invalid value.'),
|
||||||
|
}
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
try:
|
try:
|
||||||
self.view_name = kwargs.pop('view_name')
|
self.view_name = kwargs.pop('view_name')
|
||||||
|
@ -336,21 +369,21 @@ class HyperlinkedRelatedField(RelatedField):
|
||||||
slug = getattr(obj, self.slug_field, None)
|
slug = getattr(obj, self.slug_field, None)
|
||||||
|
|
||||||
if not slug:
|
if not slug:
|
||||||
raise ValidationError('Could not resolve URL for field using view name "%s"' % view_name)
|
raise Exception('Could not resolve URL for field using view name "%s"' % view_name)
|
||||||
|
|
||||||
kwargs = {self.slug_url_kwarg: slug}
|
kwargs = {self.slug_url_kwarg: slug}
|
||||||
try:
|
try:
|
||||||
return reverse(self.view_name, kwargs=kwargs, request=request, format=format)
|
return reverse(view_name, kwargs=kwargs, request=request, format=format)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
kwargs = {self.pk_url_kwarg: obj.pk, self.slug_url_kwarg: slug}
|
kwargs = {self.pk_url_kwarg: obj.pk, self.slug_url_kwarg: slug}
|
||||||
try:
|
try:
|
||||||
return reverse(self.view_name, kwargs=kwargs, request=request, format=format)
|
return reverse(view_name, kwargs=kwargs, request=request, format=format)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
raise ValidationError('Could not resolve URL for field using view name "%s"' % view_name)
|
raise Exception('Could not resolve URL for field using view name "%s"' % view_name)
|
||||||
|
|
||||||
def from_native(self, value):
|
def from_native(self, value):
|
||||||
# Convert URL -> model instance pk
|
# Convert URL -> model instance pk
|
||||||
|
@ -358,7 +391,13 @@ class HyperlinkedRelatedField(RelatedField):
|
||||||
if self.queryset is None:
|
if self.queryset is None:
|
||||||
raise Exception('Writable related fields must include a `queryset` argument')
|
raise Exception('Writable related fields must include a `queryset` argument')
|
||||||
|
|
||||||
if value.startswith('http:') or value.startswith('https:'):
|
try:
|
||||||
|
http_prefix = value.startswith('http:') or value.startswith('https:')
|
||||||
|
except AttributeError:
|
||||||
|
msg = self.error_messages['invalid']
|
||||||
|
raise ValidationError(msg)
|
||||||
|
|
||||||
|
if http_prefix:
|
||||||
# If needed convert absolute URLs to relative path
|
# If needed convert absolute URLs to relative path
|
||||||
value = urlparse(value).path
|
value = urlparse(value).path
|
||||||
prefix = get_script_prefix()
|
prefix = get_script_prefix()
|
||||||
|
@ -368,10 +407,10 @@ class HyperlinkedRelatedField(RelatedField):
|
||||||
try:
|
try:
|
||||||
match = resolve(value)
|
match = resolve(value)
|
||||||
except:
|
except:
|
||||||
raise ValidationError('Invalid hyperlink - No URL match')
|
raise ValidationError(self.error_messages['no_match'])
|
||||||
|
|
||||||
if match.url_name != self.view_name:
|
if match.view_name != self.view_name:
|
||||||
raise ValidationError('Invalid hyperlink - Incorrect URL match')
|
raise ValidationError(self.error_messages['incorrect_match'])
|
||||||
|
|
||||||
pk = match.kwargs.get(self.pk_url_kwarg, None)
|
pk = match.kwargs.get(self.pk_url_kwarg, None)
|
||||||
slug = match.kwargs.get(self.slug_url_kwarg, None)
|
slug = match.kwargs.get(self.slug_url_kwarg, None)
|
||||||
|
@ -383,14 +422,18 @@ class HyperlinkedRelatedField(RelatedField):
|
||||||
elif slug is not None:
|
elif slug is not None:
|
||||||
slug_field = self.get_slug_field()
|
slug_field = self.get_slug_field()
|
||||||
queryset = self.queryset.filter(**{slug_field: slug})
|
queryset = self.queryset.filter(**{slug_field: slug})
|
||||||
# If none of those are defined, it's an error.
|
# If none of those are defined, it's probably a configuation error.
|
||||||
else:
|
else:
|
||||||
raise ValidationError('Invalid hyperlink')
|
raise ValidationError(self.error_messages['configuration_error'])
|
||||||
|
|
||||||
try:
|
try:
|
||||||
obj = queryset.get()
|
obj = queryset.get()
|
||||||
except ObjectDoesNotExist:
|
except ObjectDoesNotExist:
|
||||||
raise ValidationError('Invalid hyperlink - object does not exist.')
|
raise ValidationError(self.error_messages['does_not_exist'])
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
msg = self.error_messages['invalid']
|
||||||
|
raise ValidationError(msg)
|
||||||
|
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
|
|
||||||
|
@ -449,18 +492,18 @@ class HyperlinkedIdentityField(Field):
|
||||||
slug = getattr(obj, self.slug_field, None)
|
slug = getattr(obj, self.slug_field, None)
|
||||||
|
|
||||||
if not slug:
|
if not slug:
|
||||||
raise ValidationError('Could not resolve URL for field using view name "%s"' % view_name)
|
raise Exception('Could not resolve URL for field using view name "%s"' % view_name)
|
||||||
|
|
||||||
kwargs = {self.slug_url_kwarg: slug}
|
kwargs = {self.slug_url_kwarg: slug}
|
||||||
try:
|
try:
|
||||||
return reverse(self.view_name, kwargs=kwargs, request=request, format=format)
|
return reverse(view_name, kwargs=kwargs, request=request, format=format)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
kwargs = {self.pk_url_kwarg: obj.pk, self.slug_url_kwarg: slug}
|
kwargs = {self.pk_url_kwarg: obj.pk, self.slug_url_kwarg: slug}
|
||||||
try:
|
try:
|
||||||
return reverse(self.view_name, kwargs=kwargs, request=request, format=format)
|
return reverse(view_name, kwargs=kwargs, request=request, format=format)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
raise ValidationError('Could not resolve URL for field using view name "%s"' % view_name)
|
raise Exception('Could not resolve URL for field using view name "%s"' % view_name)
|
||||||
|
|
|
@ -8,10 +8,10 @@ REST framework also provides an HTML renderer the renders the browsable API.
|
||||||
"""
|
"""
|
||||||
import copy
|
import copy
|
||||||
import string
|
import string
|
||||||
|
import json
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.http.multipartparser import parse_header
|
from django.http.multipartparser import parse_header
|
||||||
from django.template import RequestContext, loader, Template
|
from django.template import RequestContext, loader, Template
|
||||||
from django.utils import simplejson as json
|
|
||||||
from rest_framework.compat import yaml
|
from rest_framework.compat import yaml
|
||||||
from rest_framework.exceptions import ConfigurationError
|
from rest_framework.exceptions import ConfigurationError
|
||||||
from rest_framework.settings import api_settings
|
from rest_framework.settings import api_settings
|
||||||
|
|
|
@ -208,6 +208,11 @@ class BaseSerializer(Field):
|
||||||
Converts a dictionary of data into a dictionary of deserialized fields.
|
Converts a dictionary of data into a dictionary of deserialized fields.
|
||||||
"""
|
"""
|
||||||
reverted_data = {}
|
reverted_data = {}
|
||||||
|
|
||||||
|
if data is not None and not isinstance(data, dict):
|
||||||
|
self._errors['non_field_errors'] = [u'Invalid data']
|
||||||
|
return None
|
||||||
|
|
||||||
for field_name, field in self.fields.items():
|
for field_name, field in self.fields.items():
|
||||||
field.initialize(parent=self, field_name=field_name)
|
field.initialize(parent=self, field_name=field_name)
|
||||||
try:
|
try:
|
||||||
|
@ -276,7 +281,7 @@ class BaseSerializer(Field):
|
||||||
"""
|
"""
|
||||||
if hasattr(data, '__iter__') and not isinstance(data, dict):
|
if hasattr(data, '__iter__') and not isinstance(data, dict):
|
||||||
# TODO: error data when deserializing lists
|
# TODO: error data when deserializing lists
|
||||||
return (self.from_native(item) for item in data)
|
return [self.from_native(item, None) for item in data]
|
||||||
|
|
||||||
self._errors = {}
|
self._errors = {}
|
||||||
if data is not None or files is not None:
|
if data is not None or files is not None:
|
||||||
|
@ -428,7 +433,7 @@ class ModelSerializer(Serializer):
|
||||||
# TODO: filter queryset using:
|
# TODO: filter queryset using:
|
||||||
# .using(db).complex_filter(self.rel.limit_choices_to)
|
# .using(db).complex_filter(self.rel.limit_choices_to)
|
||||||
kwargs = {
|
kwargs = {
|
||||||
'null': model_field.null,
|
'null': model_field.null or model_field.blank,
|
||||||
'queryset': model_field.rel.to._default_manager
|
'queryset': model_field.rel.to._default_manager
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -115,8 +115,8 @@ def import_from_string(val, setting_name):
|
||||||
module_path, class_name = '.'.join(parts[:-1]), parts[-1]
|
module_path, class_name = '.'.join(parts[:-1]), parts[-1]
|
||||||
module = importlib.import_module(module_path)
|
module = importlib.import_module(module_path)
|
||||||
return getattr(module, class_name)
|
return getattr(module, class_name)
|
||||||
except:
|
except ImportError as e:
|
||||||
msg = "Could not import '%s' for API setting '%s'" % (val, setting_name)
|
msg = "Could not import '%s' for API setting '%s'. %s: %s." % (val, setting_name, e.__class__.__name__, e)
|
||||||
raise ImportError(msg)
|
raise ImportError(msg)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ register = template.Library()
|
||||||
# conflicts with this rest_framework template tag module.
|
# conflicts with this rest_framework template tag module.
|
||||||
|
|
||||||
try: # Django 1.5+
|
try: # Django 1.5+
|
||||||
from django.contrib.staticfiles.templatetags import StaticFilesNode
|
from django.contrib.staticfiles.templatetags.staticfiles import StaticFilesNode
|
||||||
|
|
||||||
@register.tag('static')
|
@register.tag('static')
|
||||||
def do_static(parser, token):
|
def do_static(parser, token):
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from django.test import Client, TestCase
|
from django.test import Client, TestCase
|
||||||
from django.utils import simplejson as json
|
|
||||||
|
|
||||||
from rest_framework import permissions
|
from rest_framework import permissions
|
||||||
from rest_framework.authtoken.models import Token
|
from rest_framework.authtoken.models import Token
|
||||||
|
@ -9,6 +8,7 @@ from rest_framework.authentication import TokenAuthentication
|
||||||
from rest_framework.compat import patterns
|
from rest_framework.compat import patterns
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
|
|
||||||
|
import json
|
||||||
import base64
|
import base64
|
||||||
|
|
||||||
|
|
||||||
|
|
0
rest_framework/tests/extras/__init__.py
Normal file
0
rest_framework/tests/extras/__init__.py
Normal file
1
rest_framework/tests/extras/bad_import.py
Normal file
1
rest_framework/tests/extras/bad_import.py
Normal file
|
@ -0,0 +1 @@
|
||||||
|
raise ValueError
|
|
@ -1,6 +1,6 @@
|
||||||
|
import json
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.utils import simplejson as json
|
|
||||||
from rest_framework import generics, serializers, status
|
from rest_framework import generics, serializers, status
|
||||||
from rest_framework.tests.utils import RequestFactory
|
from rest_framework.tests.utils import RequestFactory
|
||||||
from rest_framework.tests.models import BasicModel, Comment, SlugBasedModel
|
from rest_framework.tests.models import BasicModel, Comment, SlugBasedModel
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
|
import json
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.test.client import RequestFactory
|
from django.test.client import RequestFactory
|
||||||
from django.utils import simplejson as json
|
|
||||||
from rest_framework import generics, status, serializers
|
from rest_framework import generics, status, serializers
|
||||||
from rest_framework.compat import patterns, url
|
from rest_framework.compat import patterns, url
|
||||||
from rest_framework.tests.models import Anchor, BasicModel, ManyToManyModel, BlogPost, BlogPostComment, Album, Photo, OptionalRelationModel
|
from rest_framework.tests.models import Anchor, BasicModel, ManyToManyModel, BlogPost, BlogPostComment, Album, Photo, OptionalRelationModel
|
||||||
|
|
|
@ -181,10 +181,10 @@ class UnitTestPagination(TestCase):
|
||||||
"""
|
"""
|
||||||
Ensure context gets passed through to the object serializer.
|
Ensure context gets passed through to the object serializer.
|
||||||
"""
|
"""
|
||||||
serializer = PassOnContextPaginationSerializer(self.first_page)
|
serializer = PassOnContextPaginationSerializer(self.first_page, context={'foo': 'bar'})
|
||||||
serializer.data
|
serializer.data
|
||||||
results = serializer.fields[serializer.results_field]
|
results = serializer.fields[serializer.results_field]
|
||||||
self.assertTrue(serializer.context is results.context)
|
self.assertEquals(serializer.context, results.context)
|
||||||
|
|
||||||
|
|
||||||
class TestUnpaginated(TestCase):
|
class TestUnpaginated(TestCase):
|
||||||
|
|
33
rest_framework/tests/relations.py
Normal file
33
rest_framework/tests/relations.py
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
"""
|
||||||
|
General tests for relational fields.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django.db import models
|
||||||
|
from django.test import TestCase
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
|
||||||
|
class NullModel(models.Model):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class FieldTests(TestCase):
|
||||||
|
def test_pk_related_field_with_empty_string(self):
|
||||||
|
"""
|
||||||
|
Regression test for #446
|
||||||
|
|
||||||
|
https://github.com/tomchristie/django-rest-framework/issues/446
|
||||||
|
"""
|
||||||
|
field = serializers.PrimaryKeyRelatedField(queryset=NullModel.objects.all())
|
||||||
|
self.assertRaises(serializers.ValidationError, field.from_native, '')
|
||||||
|
self.assertRaises(serializers.ValidationError, field.from_native, [])
|
||||||
|
|
||||||
|
def test_hyperlinked_related_field_with_empty_string(self):
|
||||||
|
field = serializers.HyperlinkedRelatedField(queryset=NullModel.objects.all(), view_name='')
|
||||||
|
self.assertRaises(serializers.ValidationError, field.from_native, '')
|
||||||
|
self.assertRaises(serializers.ValidationError, field.from_native, [])
|
||||||
|
|
||||||
|
def test_slug_related_field_with_empty_string(self):
|
||||||
|
field = serializers.SlugRelatedField(queryset=NullModel.objects.all(), slug_field='pk')
|
||||||
|
self.assertRaises(serializers.ValidationError, field.from_native, '')
|
||||||
|
self.assertRaises(serializers.ValidationError, field.from_native, [])
|
|
@ -1,12 +1,12 @@
|
||||||
"""
|
"""
|
||||||
Tests for content parsing, and form-overloaded content parsing.
|
Tests for content parsing, and form-overloaded content parsing.
|
||||||
"""
|
"""
|
||||||
|
import json
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.contrib.auth import authenticate, login, logout
|
from django.contrib.auth import authenticate, login, logout
|
||||||
from django.contrib.sessions.middleware import SessionMiddleware
|
from django.contrib.sessions.middleware import SessionMiddleware
|
||||||
from django.test import TestCase, Client
|
from django.test import TestCase, Client
|
||||||
from django.test.client import RequestFactory
|
from django.test.client import RequestFactory
|
||||||
from django.utils import simplejson as json
|
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.authentication import SessionAuthentication
|
from rest_framework.authentication import SessionAuthentication
|
||||||
from rest_framework.compat import patterns
|
from rest_framework.compat import patterns
|
||||||
|
|
|
@ -69,6 +69,7 @@ class AlbumsSerializer(serializers.ModelSerializer):
|
||||||
model = Album
|
model = Album
|
||||||
fields = ['title'] # lists are also valid options
|
fields = ['title'] # lists are also valid options
|
||||||
|
|
||||||
|
|
||||||
class PositiveIntegerAsChoiceSerializer(serializers.ModelSerializer):
|
class PositiveIntegerAsChoiceSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = HasPositiveIntegerAsChoice
|
model = HasPositiveIntegerAsChoice
|
||||||
|
@ -240,6 +241,25 @@ class ValidationTests(TestCase):
|
||||||
self.assertFalse(serializer.is_valid())
|
self.assertFalse(serializer.is_valid())
|
||||||
self.assertEquals(serializer.errors, {'content': [u'Test not in value']})
|
self.assertEquals(serializer.errors, {'content': [u'Test not in value']})
|
||||||
|
|
||||||
|
def test_bad_type_data_is_false(self):
|
||||||
|
"""
|
||||||
|
Data of the wrong type is not valid.
|
||||||
|
"""
|
||||||
|
data = ['i am', 'a', 'list']
|
||||||
|
serializer = CommentSerializer(self.comment, data=data)
|
||||||
|
self.assertEquals(serializer.is_valid(), False)
|
||||||
|
self.assertEquals(serializer.errors, {'non_field_errors': [u'Invalid data']})
|
||||||
|
|
||||||
|
data = 'and i am a string'
|
||||||
|
serializer = CommentSerializer(self.comment, data=data)
|
||||||
|
self.assertEquals(serializer.is_valid(), False)
|
||||||
|
self.assertEquals(serializer.errors, {'non_field_errors': [u'Invalid data']})
|
||||||
|
|
||||||
|
data = 42
|
||||||
|
serializer = CommentSerializer(self.comment, data=data)
|
||||||
|
self.assertEquals(serializer.is_valid(), False)
|
||||||
|
self.assertEquals(serializer.errors, {'non_field_errors': [u'Invalid data']})
|
||||||
|
|
||||||
def test_cross_field_validation(self):
|
def test_cross_field_validation(self):
|
||||||
|
|
||||||
class CommentSerializerWithCrossFieldValidator(CommentSerializer):
|
class CommentSerializerWithCrossFieldValidator(CommentSerializer):
|
||||||
|
|
21
rest_framework/tests/settings.py
Normal file
21
rest_framework/tests/settings.py
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
"""Tests for the settings module"""
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
from rest_framework.settings import APISettings, DEFAULTS, IMPORT_STRINGS
|
||||||
|
|
||||||
|
|
||||||
|
class TestSettings(TestCase):
|
||||||
|
"""Tests relating to the api settings"""
|
||||||
|
|
||||||
|
def test_non_import_errors(self):
|
||||||
|
"""Make sure other errors aren't suppressed."""
|
||||||
|
settings = APISettings({'DEFAULT_MODEL_SERIALIZER_CLASS': 'rest_framework.tests.extras.bad_import.ModelSerializer'}, DEFAULTS, IMPORT_STRINGS)
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
settings.DEFAULT_MODEL_SERIALIZER_CLASS
|
||||||
|
|
||||||
|
def test_import_error_message_maintained(self):
|
||||||
|
"""Make sure real import errors are captured and raised sensibly."""
|
||||||
|
settings = APISettings({'DEFAULT_MODEL_SERIALIZER_CLASS': 'rest_framework.tests.extras.not_here.ModelSerializer'}, DEFAULTS, IMPORT_STRINGS)
|
||||||
|
with self.assertRaises(ImportError) as cm:
|
||||||
|
settings.DEFAULT_MODEL_SERIALIZER_CLASS
|
||||||
|
self.assertTrue('ImportError' in str(cm.exception))
|
|
@ -4,7 +4,7 @@ Helper classes for parsers.
|
||||||
import datetime
|
import datetime
|
||||||
import decimal
|
import decimal
|
||||||
import types
|
import types
|
||||||
from django.utils import simplejson as json
|
import json
|
||||||
from django.utils.datastructures import SortedDict
|
from django.utils.datastructures import SortedDict
|
||||||
from rest_framework.compat import timezone
|
from rest_framework.compat import timezone
|
||||||
from rest_framework.serializers import DictWithMetadata, SortedDictWithMetadata
|
from rest_framework.serializers import DictWithMetadata, SortedDictWithMetadata
|
||||||
|
|
Loading…
Reference in New Issue
Block a user