mirror of
https://github.com/encode/django-rest-framework.git
synced 2024-11-26 11:33:59 +03:00
Merge remote-tracking branch 'reference/master' into p3k
This commit is contained in:
commit
5fad46d7e2
23
README.md
23
README.md
|
@ -58,6 +58,29 @@ To run the tests.
|
||||||
|
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2.1.6
|
||||||
|
|
||||||
|
**Date**: 23rd Nov 2012
|
||||||
|
|
||||||
|
* Bugfix: Unfix DjangoModelPermissions. (I am a doofus.)
|
||||||
|
|
||||||
|
## 2.1.5
|
||||||
|
|
||||||
|
**Date**: 23rd Nov 2012
|
||||||
|
|
||||||
|
* Bugfix: Fix DjangoModelPermissions.
|
||||||
|
|
||||||
|
## 2.1.4
|
||||||
|
|
||||||
|
**Date**: 22nd Nov 2012
|
||||||
|
|
||||||
|
* Support for partial updates with serializers.
|
||||||
|
* Added `RegexField`.
|
||||||
|
* Added `SerializerMethodField`.
|
||||||
|
* Serializer performance improvements.
|
||||||
|
* Added `obtain_token_view` to get tokens when using `TokenAuthentication`.
|
||||||
|
* Bugfix: Django 1.5 configurable user support for `TokenAuthentication`.
|
||||||
|
|
||||||
## 2.1.3
|
## 2.1.3
|
||||||
|
|
||||||
**Date**: 16th Nov 2012
|
**Date**: 16th Nov 2012
|
||||||
|
|
|
@ -116,7 +116,7 @@ When using `TokenAuthentication`, you may want to provide a mechanism for client
|
||||||
REST framework provides a built-in view to provide this behavior. To use it, add the `obtain_auth_token` view to your URLconf:
|
REST framework provides a built-in view to provide this behavior. To use it, add the `obtain_auth_token` view to your URLconf:
|
||||||
|
|
||||||
urlpatterns += patterns('',
|
urlpatterns += patterns('',
|
||||||
url(r'^api-token-auth/', 'rest_framework.authtoken.obtain_auth_token')
|
url(r'^api-token-auth/', 'rest_framework.authtoken.views.obtain_auth_token')
|
||||||
)
|
)
|
||||||
|
|
||||||
Note that the URL part of the pattern can be whatever you want to use.
|
Note that the URL part of the pattern can be whatever you want to use.
|
||||||
|
|
|
@ -77,6 +77,10 @@ When deserializing data, we can either create a new instance, or update an exist
|
||||||
serializer = CommentSerializer(data=data) # Create new instance
|
serializer = CommentSerializer(data=data) # Create new instance
|
||||||
serializer = CommentSerializer(comment, data=data) # Update `instance`
|
serializer = CommentSerializer(comment, data=data) # Update `instance`
|
||||||
|
|
||||||
|
By default, serializers must be passed values for all required fields or they will throw validation errors. You can use the `partial` argument in order to allow partial updates.
|
||||||
|
|
||||||
|
serializer = CommentSerializer(comment, data={'content': u'foo bar'}, partial=True) # Update `instance` with partial data
|
||||||
|
|
||||||
## Validation
|
## Validation
|
||||||
|
|
||||||
When deserializing data, you always need to call `is_valid()` before attempting to access the deserialized object. If any validation errors occur, the `.errors` and `.non_field_errors` properties will contain the resulting error messages.
|
When deserializing data, you always need to call `is_valid()` before attempting to access the deserialized object. If any validation errors occur, the `.errors` and `.non_field_errors` properties will contain the resulting error messages.
|
||||||
|
|
|
@ -19,6 +19,10 @@ Using the `APIView` class is pretty much the same as using a regular `View` clas
|
||||||
|
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
|
from rest_framework.views import APIView
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from rest_framework import authentication, permissions
|
||||||
|
|
||||||
class ListUsers(APIView):
|
class ListUsers(APIView):
|
||||||
"""
|
"""
|
||||||
View to list all users in the system.
|
View to list all users in the system.
|
||||||
|
|
|
@ -64,6 +64,11 @@ The following people have helped make REST framework great.
|
||||||
* Eugene Mechanism - [mechanism]
|
* Eugene Mechanism - [mechanism]
|
||||||
* Jonas Liljestrand - [jonlil]
|
* Jonas Liljestrand - [jonlil]
|
||||||
* Justin Davis - [irrelative]
|
* Justin Davis - [irrelative]
|
||||||
|
* Dustin Bachrach - [dbachrach]
|
||||||
|
* Mark Shirley - [maspwr]
|
||||||
|
* Olivier Aubert - [oaubert]
|
||||||
|
* Yuri Prezument - [yprez]
|
||||||
|
* Fabian Buechler - [fabianbuechler]
|
||||||
|
|
||||||
Many thanks to everyone who's contributed to the project.
|
Many thanks to everyone who's contributed to the project.
|
||||||
|
|
||||||
|
@ -163,3 +168,8 @@ To contact the author directly:
|
||||||
[mechanism]: https://github.com/mechanism
|
[mechanism]: https://github.com/mechanism
|
||||||
[jonlil]: https://github.com/jonlil
|
[jonlil]: https://github.com/jonlil
|
||||||
[irrelative]: https://github.com/irrelative
|
[irrelative]: https://github.com/irrelative
|
||||||
|
[dbachrach]: https://github.com/dbachrach
|
||||||
|
[maspwr]: https://github.com/maspwr
|
||||||
|
[oaubert]: https://github.com/oaubert
|
||||||
|
[yprez]: https://github.com/yprez
|
||||||
|
[fabianbuechler]: https://github.com/fabianbuechler
|
||||||
|
|
|
@ -4,8 +4,23 @@
|
||||||
>
|
>
|
||||||
> — Eric S. Raymond, [The Cathedral and the Bazaar][cite].
|
> — Eric S. Raymond, [The Cathedral and the Bazaar][cite].
|
||||||
|
|
||||||
## Master
|
## 2.1.6
|
||||||
|
|
||||||
|
**Date**: 23rd Nov 2012
|
||||||
|
|
||||||
|
* Bugfix: Unfix DjangoModelPermissions. (I am a doofus.)
|
||||||
|
|
||||||
|
## 2.1.5
|
||||||
|
|
||||||
|
**Date**: 23rd Nov 2012
|
||||||
|
|
||||||
|
* Bugfix: Fix DjangoModelPermissions.
|
||||||
|
|
||||||
|
## 2.1.4
|
||||||
|
|
||||||
|
**Date**: 22nd Nov 2012
|
||||||
|
|
||||||
|
* Support for partial updates with serializers.
|
||||||
* Added `RegexField`.
|
* Added `RegexField`.
|
||||||
* Added `SerializerMethodField`.
|
* Added `SerializerMethodField`.
|
||||||
* Serializer performance improvements.
|
* Serializer performance improvements.
|
||||||
|
|
|
@ -41,8 +41,8 @@ We don't need our `JSONResponse` class anymore, so go ahead and delete that. On
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.decorators import api_view
|
from rest_framework.decorators import api_view
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from snippet.models import Snippet
|
from snippets.models import Snippet
|
||||||
from snippet.serializers import SnippetSerializer
|
from snippets.serializers import SnippetSerializer
|
||||||
|
|
||||||
|
|
||||||
@api_view(['GET', 'POST'])
|
@api_view(['GET', 'POST'])
|
||||||
|
@ -113,7 +113,7 @@ Now update the `urls.py` file slightly, to append a set of `format_suffix_patter
|
||||||
from django.conf.urls import patterns, url
|
from django.conf.urls import patterns, url
|
||||||
from rest_framework.urlpatterns import format_suffix_patterns
|
from rest_framework.urlpatterns import format_suffix_patterns
|
||||||
|
|
||||||
urlpatterns = patterns('snippet.views',
|
urlpatterns = patterns('snippets.views',
|
||||||
url(r'^snippets/$', 'snippet_list'),
|
url(r'^snippets/$', 'snippet_list'),
|
||||||
url(r'^snippets/(?P<pk>[0-9]+)$', 'snippet_detail')
|
url(r'^snippets/(?P<pk>[0-9]+)$', 'snippet_detail')
|
||||||
)
|
)
|
||||||
|
|
|
@ -6,8 +6,8 @@ We can also write our API views using class based views, rather than function ba
|
||||||
|
|
||||||
We'll start by rewriting the root view as a class based view. All this involves is a little bit of refactoring.
|
We'll start by rewriting the root view as a class based view. All this involves is a little bit of refactoring.
|
||||||
|
|
||||||
from snippet.models import Snippet
|
from snippets.models import Snippet
|
||||||
from snippet.serializers import SnippetSerializer
|
from snippets.serializers import SnippetSerializer
|
||||||
from django.http import Http404
|
from django.http import Http404
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
@ -66,7 +66,7 @@ We'll also need to refactor our URLconf slightly now we're using class based vie
|
||||||
|
|
||||||
from django.conf.urls import patterns, url
|
from django.conf.urls import patterns, url
|
||||||
from rest_framework.urlpatterns import format_suffix_patterns
|
from rest_framework.urlpatterns import format_suffix_patterns
|
||||||
from snippetpost import views
|
from snippets import views
|
||||||
|
|
||||||
urlpatterns = patterns('',
|
urlpatterns = patterns('',
|
||||||
url(r'^snippets/$', views.SnippetList.as_view()),
|
url(r'^snippets/$', views.SnippetList.as_view()),
|
||||||
|
@ -85,8 +85,8 @@ The create/retrieve/update/delete operations that we've been using so far are go
|
||||||
|
|
||||||
Let's take a look at how we can compose our views by using the mixin classes.
|
Let's take a look at how we can compose our views by using the mixin classes.
|
||||||
|
|
||||||
from snippet.models import Snippet
|
from snippets.models import Snippet
|
||||||
from snippet.serializers import SnippetSerializer
|
from snippets.serializers import SnippetSerializer
|
||||||
from rest_framework import mixins
|
from rest_framework import mixins
|
||||||
from rest_framework import generics
|
from rest_framework import generics
|
||||||
|
|
||||||
|
@ -128,8 +128,8 @@ Pretty similar. This time we're using the `SingleObjectBaseView` class to provi
|
||||||
|
|
||||||
Using the mixin classes we've rewritten the views to use slightly less code than before, but we can go one step further. REST framework provides a set of already mixed-in generic views that we can use.
|
Using the mixin classes we've rewritten the views to use slightly less code than before, but we can go one step further. REST framework provides a set of already mixed-in generic views that we can use.
|
||||||
|
|
||||||
from snippet.models import Snippet
|
from snippets.models import Snippet
|
||||||
from snippet.serializers import SnippetSerializer
|
from snippets.serializers import SnippetSerializer
|
||||||
from rest_framework import generics
|
from rest_framework import generics
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
markdown>=2.1.0
|
markdown>=2.1.0
|
||||||
PyYAML>=3.10
|
PyYAML>=3.10
|
||||||
-e git+https://github.com/alex/django-filter.git@0e4b3d703b31574922ab86fc78a86164aad0c1d0#egg=django-filter
|
django-filter>=0.5.4
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
__version__ = '2.1.3'
|
__version__ = '2.1.6'
|
||||||
|
|
||||||
VERSION = __version__ # synonym
|
VERSION = __version__ # synonym
|
||||||
|
|
|
@ -18,7 +18,7 @@ class ObtainAuthToken(APIView):
|
||||||
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})
|
||||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
return Response(serializer.errors, status=status.HTTP_401_UNAUTHORIZED)
|
||||||
|
|
||||||
|
|
||||||
obtain_auth_token = ObtainAuthToken.as_view()
|
obtain_auth_token = ObtainAuthToken.as_view()
|
||||||
|
|
|
@ -12,6 +12,7 @@ from django.core import validators
|
||||||
from django.core.exceptions import ObjectDoesNotExist, ValidationError
|
from django.core.exceptions import ObjectDoesNotExist, ValidationError
|
||||||
from django.core.urlresolvers import resolve, get_script_prefix
|
from django.core.urlresolvers import resolve, get_script_prefix
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
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 is_protected_type
|
from django.utils.encoding import is_protected_type
|
||||||
|
@ -45,6 +46,7 @@ class Field(object):
|
||||||
empty = ''
|
empty = ''
|
||||||
type_name = None
|
type_name = None
|
||||||
_use_files = None
|
_use_files = None
|
||||||
|
form_field_class = forms.CharField
|
||||||
|
|
||||||
def __init__(self, source=None):
|
def __init__(self, source=None):
|
||||||
self.parent = None
|
self.parent = None
|
||||||
|
@ -64,6 +66,8 @@ class Field(object):
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
self.root = parent.root or parent
|
self.root = parent.root or parent
|
||||||
self.context = self.root.context
|
self.context = self.root.context
|
||||||
|
if self.root.partial:
|
||||||
|
self.required = False
|
||||||
|
|
||||||
def field_from_native(self, data, files, field_name, into):
|
def field_from_native(self, data, files, field_name, into):
|
||||||
"""
|
"""
|
||||||
|
@ -402,6 +406,7 @@ class PrimaryKeyRelatedField(RelatedField):
|
||||||
Represents a to-one relationship as a pk value.
|
Represents a to-one relationship as a pk value.
|
||||||
"""
|
"""
|
||||||
default_read_only = False
|
default_read_only = False
|
||||||
|
form_field_class = forms.ChoiceField
|
||||||
|
|
||||||
# TODO: Remove these field hacks...
|
# TODO: Remove these field hacks...
|
||||||
def prepare_value(self, obj):
|
def prepare_value(self, obj):
|
||||||
|
@ -448,6 +453,7 @@ class ManyPrimaryKeyRelatedField(ManyRelatedField):
|
||||||
Represents a to-many relationship as a pk value.
|
Represents a to-many relationship as a pk value.
|
||||||
"""
|
"""
|
||||||
default_read_only = False
|
default_read_only = False
|
||||||
|
form_field_class = forms.MultipleChoiceField
|
||||||
|
|
||||||
def prepare_value(self, obj):
|
def prepare_value(self, obj):
|
||||||
return self.to_native(obj.pk)
|
return self.to_native(obj.pk)
|
||||||
|
@ -491,6 +497,7 @@ class ManyPrimaryKeyRelatedField(ManyRelatedField):
|
||||||
|
|
||||||
class SlugRelatedField(RelatedField):
|
class SlugRelatedField(RelatedField):
|
||||||
default_read_only = False
|
default_read_only = False
|
||||||
|
form_field_class = forms.ChoiceField
|
||||||
|
|
||||||
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)
|
||||||
|
@ -512,7 +519,7 @@ class SlugRelatedField(RelatedField):
|
||||||
|
|
||||||
|
|
||||||
class ManySlugRelatedField(ManyRelatedMixin, SlugRelatedField):
|
class ManySlugRelatedField(ManyRelatedMixin, SlugRelatedField):
|
||||||
pass
|
form_field_class = forms.MultipleChoiceField
|
||||||
|
|
||||||
|
|
||||||
### Hyperlinked relationships
|
### Hyperlinked relationships
|
||||||
|
@ -525,6 +532,7 @@ class HyperlinkedRelatedField(RelatedField):
|
||||||
slug_field = 'slug'
|
slug_field = 'slug'
|
||||||
slug_url_kwarg = None # Defaults to same as `slug_field` unless overridden
|
slug_url_kwarg = None # Defaults to same as `slug_field` unless overridden
|
||||||
default_read_only = False
|
default_read_only = False
|
||||||
|
form_field_class = forms.ChoiceField
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
try:
|
try:
|
||||||
|
@ -624,7 +632,7 @@ class ManyHyperlinkedRelatedField(ManyRelatedMixin, HyperlinkedRelatedField):
|
||||||
"""
|
"""
|
||||||
Represents a to-many relationship, using hyperlinking.
|
Represents a to-many relationship, using hyperlinking.
|
||||||
"""
|
"""
|
||||||
pass
|
form_field_class = forms.MultipleChoiceField
|
||||||
|
|
||||||
|
|
||||||
class HyperlinkedIdentityField(Field):
|
class HyperlinkedIdentityField(Field):
|
||||||
|
@ -682,6 +690,7 @@ class HyperlinkedIdentityField(Field):
|
||||||
|
|
||||||
class BooleanField(WritableField):
|
class BooleanField(WritableField):
|
||||||
type_name = 'BooleanField'
|
type_name = 'BooleanField'
|
||||||
|
form_field_class = forms.BooleanField
|
||||||
widget = widgets.CheckboxInput
|
widget = widgets.CheckboxInput
|
||||||
default_error_messages = {
|
default_error_messages = {
|
||||||
'invalid': _("'%s' value must be either True or False."),
|
'invalid': _("'%s' value must be either True or False."),
|
||||||
|
@ -703,6 +712,7 @@ class BooleanField(WritableField):
|
||||||
|
|
||||||
class CharField(WritableField):
|
class CharField(WritableField):
|
||||||
type_name = 'CharField'
|
type_name = 'CharField'
|
||||||
|
form_field_class = forms.CharField
|
||||||
|
|
||||||
def __init__(self, max_length=None, min_length=None, *args, **kwargs):
|
def __init__(self, max_length=None, min_length=None, *args, **kwargs):
|
||||||
self.max_length, self.min_length = max_length, min_length
|
self.max_length, self.min_length = max_length, min_length
|
||||||
|
@ -747,6 +757,7 @@ class SlugField(CharField):
|
||||||
|
|
||||||
class ChoiceField(WritableField):
|
class ChoiceField(WritableField):
|
||||||
type_name = 'ChoiceField'
|
type_name = 'ChoiceField'
|
||||||
|
form_field_class = forms.ChoiceField
|
||||||
widget = widgets.Select
|
widget = widgets.Select
|
||||||
default_error_messages = {
|
default_error_messages = {
|
||||||
'invalid_choice': _('Select a valid choice. %(value)s is not one of the available choices.'),
|
'invalid_choice': _('Select a valid choice. %(value)s is not one of the available choices.'),
|
||||||
|
@ -793,6 +804,7 @@ class ChoiceField(WritableField):
|
||||||
|
|
||||||
class EmailField(CharField):
|
class EmailField(CharField):
|
||||||
type_name = 'EmailField'
|
type_name = 'EmailField'
|
||||||
|
form_field_class = forms.EmailField
|
||||||
|
|
||||||
default_error_messages = {
|
default_error_messages = {
|
||||||
'invalid': _('Enter a valid e-mail address.'),
|
'invalid': _('Enter a valid e-mail address.'),
|
||||||
|
@ -843,6 +855,8 @@ class RegexField(CharField):
|
||||||
|
|
||||||
class DateField(WritableField):
|
class DateField(WritableField):
|
||||||
type_name = 'DateField'
|
type_name = 'DateField'
|
||||||
|
widget = widgets.DateInput
|
||||||
|
form_field_class = forms.DateField
|
||||||
|
|
||||||
default_error_messages = {
|
default_error_messages = {
|
||||||
'invalid': _("'%s' value has an invalid date format. It must be "
|
'invalid': _("'%s' value has an invalid date format. It must be "
|
||||||
|
@ -880,6 +894,8 @@ class DateField(WritableField):
|
||||||
|
|
||||||
class DateTimeField(WritableField):
|
class DateTimeField(WritableField):
|
||||||
type_name = 'DateTimeField'
|
type_name = 'DateTimeField'
|
||||||
|
widget = widgets.DateTimeInput
|
||||||
|
form_field_class = forms.DateTimeField
|
||||||
|
|
||||||
default_error_messages = {
|
default_error_messages = {
|
||||||
'invalid': _("'%s' value has an invalid format. It must be in "
|
'invalid': _("'%s' value has an invalid format. It must be in "
|
||||||
|
@ -934,6 +950,7 @@ class DateTimeField(WritableField):
|
||||||
|
|
||||||
class IntegerField(WritableField):
|
class IntegerField(WritableField):
|
||||||
type_name = 'IntegerField'
|
type_name = 'IntegerField'
|
||||||
|
form_field_class = forms.IntegerField
|
||||||
|
|
||||||
default_error_messages = {
|
default_error_messages = {
|
||||||
'invalid': _('Enter a whole number.'),
|
'invalid': _('Enter a whole number.'),
|
||||||
|
@ -963,6 +980,7 @@ class IntegerField(WritableField):
|
||||||
|
|
||||||
class FloatField(WritableField):
|
class FloatField(WritableField):
|
||||||
type_name = 'FloatField'
|
type_name = 'FloatField'
|
||||||
|
form_field_class = forms.FloatField
|
||||||
|
|
||||||
default_error_messages = {
|
default_error_messages = {
|
||||||
'invalid': _("'%s' value must be a float."),
|
'invalid': _("'%s' value must be a float."),
|
||||||
|
@ -982,6 +1000,7 @@ class FloatField(WritableField):
|
||||||
class FileField(WritableField):
|
class FileField(WritableField):
|
||||||
_use_files = True
|
_use_files = True
|
||||||
type_name = 'FileField'
|
type_name = 'FileField'
|
||||||
|
form_field_class = forms.FileField
|
||||||
widget = widgets.FileInput
|
widget = widgets.FileInput
|
||||||
|
|
||||||
default_error_messages = {
|
default_error_messages = {
|
||||||
|
@ -1024,6 +1043,7 @@ class FileField(WritableField):
|
||||||
|
|
||||||
class ImageField(FileField):
|
class ImageField(FileField):
|
||||||
_use_files = True
|
_use_files = True
|
||||||
|
form_field_class = forms.ImageField
|
||||||
|
|
||||||
default_error_messages = {
|
default_error_messages = {
|
||||||
'invalid_image': _("Upload a valid image. The file you uploaded was either not an image or a corrupted image."),
|
'invalid_image': _("Upload a valid image. The file you uploaded was either not an image or a corrupted image."),
|
||||||
|
|
|
@ -2,6 +2,7 @@ from django.http import Http404
|
||||||
from rest_framework import exceptions
|
from rest_framework import exceptions
|
||||||
from rest_framework.settings import api_settings
|
from rest_framework.settings import api_settings
|
||||||
from rest_framework.utils.mediatypes import order_by_precedence, media_type_matches
|
from rest_framework.utils.mediatypes import order_by_precedence, media_type_matches
|
||||||
|
from rest_framework.utils.mediatypes import _MediaType
|
||||||
|
|
||||||
|
|
||||||
class BaseContentNegotiation(object):
|
class BaseContentNegotiation(object):
|
||||||
|
@ -48,7 +49,8 @@ class DefaultContentNegotiation(BaseContentNegotiation):
|
||||||
for media_type in media_type_set:
|
for media_type in media_type_set:
|
||||||
if media_type_matches(renderer.media_type, media_type):
|
if media_type_matches(renderer.media_type, media_type):
|
||||||
# Return the most specific media type as accepted.
|
# Return the most specific media type as accepted.
|
||||||
if len(renderer.media_type) > len(media_type):
|
if (_MediaType(renderer.media_type).precedence >
|
||||||
|
_MediaType(media_type).precedence):
|
||||||
# Eg client requests '*/*'
|
# Eg client requests '*/*'
|
||||||
# Accepted media type is 'application/json'
|
# Accepted media type is 'application/json'
|
||||||
return renderer, renderer.media_type
|
return renderer, renderer.media_type
|
||||||
|
|
|
@ -308,26 +308,6 @@ class BrowsableAPIRenderer(BaseRenderer):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def serializer_to_form_fields(self, serializer):
|
def serializer_to_form_fields(self, serializer):
|
||||||
field_mapping = {
|
|
||||||
serializers.FloatField: forms.FloatField,
|
|
||||||
serializers.IntegerField: forms.IntegerField,
|
|
||||||
serializers.DateTimeField: forms.DateTimeField,
|
|
||||||
serializers.DateField: forms.DateField,
|
|
||||||
serializers.EmailField: forms.EmailField,
|
|
||||||
serializers.RegexField: forms.RegexField,
|
|
||||||
serializers.CharField: forms.CharField,
|
|
||||||
serializers.ChoiceField: forms.ChoiceField,
|
|
||||||
serializers.BooleanField: forms.BooleanField,
|
|
||||||
serializers.PrimaryKeyRelatedField: forms.ChoiceField,
|
|
||||||
serializers.ManyPrimaryKeyRelatedField: forms.MultipleChoiceField,
|
|
||||||
serializers.SlugRelatedField: forms.ChoiceField,
|
|
||||||
serializers.ManySlugRelatedField: forms.MultipleChoiceField,
|
|
||||||
serializers.HyperlinkedRelatedField: forms.ChoiceField,
|
|
||||||
serializers.ManyHyperlinkedRelatedField: forms.MultipleChoiceField,
|
|
||||||
serializers.FileField: forms.FileField,
|
|
||||||
serializers.ImageField: forms.ImageField,
|
|
||||||
}
|
|
||||||
|
|
||||||
fields = {}
|
fields = {}
|
||||||
for k, v in serializer.get_fields().items():
|
for k, v in serializer.get_fields().items():
|
||||||
if getattr(v, 'read_only', True):
|
if getattr(v, 'read_only', True):
|
||||||
|
@ -351,13 +331,7 @@ class BrowsableAPIRenderer(BaseRenderer):
|
||||||
|
|
||||||
kwargs['label'] = k
|
kwargs['label'] = k
|
||||||
|
|
||||||
try:
|
fields[k] = v.form_field_class(**kwargs)
|
||||||
fields[k] = field_mapping[v.__class__](**kwargs)
|
|
||||||
except KeyError:
|
|
||||||
if getattr(v, 'choices', None) is not None:
|
|
||||||
fields[k] = forms.ChoiceField(**kwargs)
|
|
||||||
else:
|
|
||||||
fields[k] = forms.CharField(**kwargs)
|
|
||||||
return fields
|
return fields
|
||||||
|
|
||||||
def get_form(self, view, method, request):
|
def get_form(self, view, method, request):
|
||||||
|
|
|
@ -62,7 +62,7 @@ def _get_declared_fields(bases, attrs):
|
||||||
|
|
||||||
# If this class is subclassing another Serializer, add that Serializer's
|
# If this class is subclassing another Serializer, add that Serializer's
|
||||||
# fields. Note that we loop over the bases in *reverse*. This is necessary
|
# fields. Note that we loop over the bases in *reverse*. This is necessary
|
||||||
# in order to the correct order of fields.
|
# in order to maintain the correct order of fields.
|
||||||
for base in bases[::-1]:
|
for base in bases[::-1]:
|
||||||
if hasattr(base, 'base_fields'):
|
if hasattr(base, 'base_fields'):
|
||||||
fields = list(base.base_fields.items()) + fields
|
fields = list(base.base_fields.items()) + fields
|
||||||
|
@ -93,19 +93,19 @@ class BaseSerializer(Field):
|
||||||
_options_class = SerializerOptions
|
_options_class = SerializerOptions
|
||||||
_dict_class = SortedDictWithMetadata # Set to unsorted dict for backwards compatibility with unsorted implementations.
|
_dict_class = SortedDictWithMetadata # Set to unsorted dict for backwards compatibility with unsorted implementations.
|
||||||
|
|
||||||
def __init__(self, instance=None, data=None, files=None, context=None, **kwargs):
|
def __init__(self, instance=None, data=None, files=None, context=None, partial=False, **kwargs):
|
||||||
super(BaseSerializer, self).__init__(**kwargs)
|
super(BaseSerializer, self).__init__(**kwargs)
|
||||||
self.opts = self._options_class(self.Meta)
|
self.opts = self._options_class(self.Meta)
|
||||||
self.fields = copy.deepcopy(self.base_fields)
|
|
||||||
self.parent = None
|
self.parent = None
|
||||||
self.root = None
|
self.root = None
|
||||||
|
self.partial = partial
|
||||||
|
|
||||||
self.context = context or {}
|
self.context = context or {}
|
||||||
|
|
||||||
self.init_data = data
|
self.init_data = data
|
||||||
self.init_files = files
|
self.init_files = files
|
||||||
self.object = instance
|
self.object = instance
|
||||||
self.default_fields = self.get_default_fields()
|
self.fields = self.get_fields()
|
||||||
|
|
||||||
self._data = None
|
self._data = None
|
||||||
self._files = None
|
self._files = None
|
||||||
|
@ -130,13 +130,15 @@ class BaseSerializer(Field):
|
||||||
ret = SortedDict()
|
ret = SortedDict()
|
||||||
|
|
||||||
# Get the explicitly declared fields
|
# Get the explicitly declared fields
|
||||||
for key, field in self.fields.items():
|
base_fields = copy.deepcopy(self.base_fields)
|
||||||
|
for key, field in base_fields.items():
|
||||||
ret[key] = field
|
ret[key] = field
|
||||||
# Set up the field
|
# Set up the field
|
||||||
field.initialize(parent=self, field_name=key)
|
field.initialize(parent=self, field_name=key)
|
||||||
|
|
||||||
# Add in the default fields
|
# Add in the default fields
|
||||||
for key, val in self.default_fields.items():
|
default_fields = self.get_default_fields()
|
||||||
|
for key, val in default_fields.items():
|
||||||
if key not in ret:
|
if key not in ret:
|
||||||
ret[key] = val
|
ret[key] = val
|
||||||
|
|
||||||
|
@ -183,8 +185,7 @@ class BaseSerializer(Field):
|
||||||
ret = self._dict_class()
|
ret = self._dict_class()
|
||||||
ret.fields = {}
|
ret.fields = {}
|
||||||
|
|
||||||
fields = self.get_fields()
|
for field_name, field in self.fields.items():
|
||||||
for field_name, field in fields.items():
|
|
||||||
key = self.get_field_key(field_name)
|
key = self.get_field_key(field_name)
|
||||||
value = field.field_to_native(obj, field_name)
|
value = field.field_to_native(obj, field_name)
|
||||||
ret[key] = value
|
ret[key] = value
|
||||||
|
@ -196,9 +197,8 @@ class BaseSerializer(Field):
|
||||||
Core of deserialization, together with `restore_object`.
|
Core of deserialization, together with `restore_object`.
|
||||||
Converts a dictionary of data into a dictionary of deserialized fields.
|
Converts a dictionary of data into a dictionary of deserialized fields.
|
||||||
"""
|
"""
|
||||||
fields = self.get_fields()
|
|
||||||
reverted_data = {}
|
reverted_data = {}
|
||||||
for field_name, field in fields.items():
|
for field_name, field in self.fields.items():
|
||||||
try:
|
try:
|
||||||
field.field_from_native(data, files, field_name, reverted_data)
|
field.field_from_native(data, files, field_name, reverted_data)
|
||||||
except ValidationError as err:
|
except ValidationError as err:
|
||||||
|
@ -210,10 +210,7 @@ class BaseSerializer(Field):
|
||||||
"""
|
"""
|
||||||
Run `validate_<fieldname>()` and `validate()` methods on the serializer
|
Run `validate_<fieldname>()` and `validate()` methods on the serializer
|
||||||
"""
|
"""
|
||||||
# TODO: refactor this so we're not determining the fields again
|
for field_name, field in self.fields.items():
|
||||||
fields = self.get_fields()
|
|
||||||
|
|
||||||
for field_name, field in fields.items():
|
|
||||||
try:
|
try:
|
||||||
validate_method = getattr(self, 'validate_%s' % field_name, None)
|
validate_method = getattr(self, 'validate_%s' % field_name, None)
|
||||||
if validate_method:
|
if validate_method:
|
||||||
|
|
|
@ -167,14 +167,14 @@ class TokenAuthTests(TestCase):
|
||||||
client = Client(enforce_csrf_checks=True)
|
client = Client(enforce_csrf_checks=True)
|
||||||
response = client.post('/auth-token/login/',
|
response = client.post('/auth-token/login/',
|
||||||
json.dumps({'username': self.username, 'password': "badpass"}), 'application/json')
|
json.dumps({'username': self.username, 'password': "badpass"}), 'application/json')
|
||||||
self.assertEqual(response.status_code, 400)
|
self.assertEqual(response.status_code, 401)
|
||||||
|
|
||||||
def test_token_login_json_missing_fields(self):
|
def test_token_login_json_missing_fields(self):
|
||||||
"""Ensure token login view using JSON POST fails if missing fields."""
|
"""Ensure token login view using JSON POST fails if missing fields."""
|
||||||
client = Client(enforce_csrf_checks=True)
|
client = Client(enforce_csrf_checks=True)
|
||||||
response = client.post('/auth-token/login/',
|
response = client.post('/auth-token/login/',
|
||||||
json.dumps({'username': self.username}), 'application/json')
|
json.dumps({'username': self.username}), 'application/json')
|
||||||
self.assertEqual(response.status_code, 400)
|
self.assertEqual(response.status_code, 401)
|
||||||
|
|
||||||
def test_token_login_form(self):
|
def test_token_login_form(self):
|
||||||
"""Ensure token login view using form POST works."""
|
"""Ensure token login view using form POST works."""
|
||||||
|
|
|
@ -117,6 +117,18 @@ class BasicTests(TestCase):
|
||||||
self.assertTrue(serializer.object is expected)
|
self.assertTrue(serializer.object is expected)
|
||||||
self.assertEquals(serializer.data['sub_comment'], 'And Merry Christmas!')
|
self.assertEquals(serializer.data['sub_comment'], 'And Merry Christmas!')
|
||||||
|
|
||||||
|
def test_partial_update(self):
|
||||||
|
msg = 'Merry New Year!'
|
||||||
|
partial_data = {'content': msg}
|
||||||
|
serializer = CommentSerializer(self.comment, data=partial_data)
|
||||||
|
self.assertEquals(serializer.is_valid(), False)
|
||||||
|
serializer = CommentSerializer(self.comment, data=partial_data, partial=True)
|
||||||
|
expected = self.comment
|
||||||
|
self.assertEqual(serializer.is_valid(), True)
|
||||||
|
self.assertEquals(serializer.object, expected)
|
||||||
|
self.assertTrue(serializer.object is expected)
|
||||||
|
self.assertEquals(serializer.data['content'], msg)
|
||||||
|
|
||||||
def test_model_fields_as_expected(self):
|
def test_model_fields_as_expected(self):
|
||||||
"""
|
"""
|
||||||
Make sure that the fields returned are the same as defined
|
Make sure that the fields returned are the same as defined
|
||||||
|
|
Loading…
Reference in New Issue
Block a user