Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
D
django-rest-framework
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
django-rest-framework
Commits
fcaee6e5
Commit
fcaee6e5
authored
May 24, 2013
by
Tom Christie
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Clean up OPTIONS implementation
parent
760e8642
Show whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
150 additions
and
141 deletions
+150
-141
docs/topics/release-notes.md
+1
-0
rest_framework/fields.py
+27
-24
rest_framework/generics.py
+39
-4
rest_framework/serializers.py
+10
-6
rest_framework/tests/fields.py
+19
-29
rest_framework/tests/generics.py
+21
-17
rest_framework/tests/permissions.py
+6
-9
rest_framework/tests/renderers.py
+8
-0
rest_framework/utils/encoders.py
+5
-2
rest_framework/views.py
+14
-50
No files found.
docs/topics/release-notes.md
View file @
fcaee6e5
...
@@ -44,6 +44,7 @@ You can determine your currently installed version using `pip freeze`:
...
@@ -44,6 +44,7 @@ You can determine your currently installed version using `pip freeze`:
*
Serializer fields now support `label` and `help_text`.
*
Serializer fields now support `label` and `help_text`.
*
Added `UnicodeJSONRenderer`.
*
Added `UnicodeJSONRenderer`.
*
`
OPTIONS` requests now return metadata about fields for `POST` and `PUT` requests.
*
Bugfix
:
`
charset` now properly included in `Content-Type` of responses.
*
Bugfix
:
`
charset` now properly included in `Content-Type` of responses.
*
Bugfix
:
Blank choice now added in browsable API on
null
able relationships.
*
Bugfix
:
Blank choice now added in browsable API on
null
able relationships.
*
Bugfix
:
Many to many relationships with `through` tables are now read-only.
*
Bugfix
:
Many to many relationships with `through` tables are now read-only.
...
...
rest_framework/fields.py
View file @
fcaee6e5
...
@@ -11,7 +11,6 @@ from decimal import Decimal, DecimalException
...
@@ -11,7 +11,6 @@ from decimal import Decimal, DecimalException
import
inspect
import
inspect
import
re
import
re
import
warnings
import
warnings
from
django.core
import
validators
from
django.core
import
validators
from
django.core.exceptions
import
ValidationError
from
django.core.exceptions
import
ValidationError
from
django.conf
import
settings
from
django.conf
import
settings
...
@@ -21,7 +20,6 @@ from django.forms import widgets
...
@@ -21,7 +20,6 @@ from django.forms import widgets
from
django.utils.encoding
import
is_protected_type
from
django.utils.encoding
import
is_protected_type
from
django.utils.translation
import
ugettext_lazy
as
_
from
django.utils.translation
import
ugettext_lazy
as
_
from
django.utils.datastructures
import
SortedDict
from
django.utils.datastructures
import
SortedDict
from
rest_framework
import
ISO_8601
from
rest_framework
import
ISO_8601
from
rest_framework.compat
import
(
timezone
,
parse_date
,
parse_datetime
,
from
rest_framework.compat
import
(
timezone
,
parse_date
,
parse_datetime
,
parse_time
)
parse_time
)
...
@@ -46,6 +44,7 @@ def is_simple_callable(obj):
...
@@ -46,6 +44,7 @@ def is_simple_callable(obj):
len_defaults
=
len
(
defaults
)
if
defaults
else
0
len_defaults
=
len
(
defaults
)
if
defaults
else
0
return
len_args
<=
len_defaults
return
len_args
<=
len_defaults
def
get_component
(
obj
,
attr_name
):
def
get_component
(
obj
,
attr_name
):
"""
"""
Given an object, and an attribute name,
Given an object, and an attribute name,
...
@@ -72,18 +71,6 @@ def readable_date_formats(formats):
...
@@ -72,18 +71,6 @@ def readable_date_formats(formats):
return
humanize_strptime
(
format
)
return
humanize_strptime
(
format
)
def
humanize_form_fields
(
form
):
"""Return a humanized description of all the fields in a form.
:param form: A Django form.
:return: A dictionary of {field_label: humanized description}
"""
fields
=
SortedDict
([(
name
,
humanize_field
(
field
))
for
name
,
field
in
form
.
fields
.
iteritems
()])
return
fields
def
readable_time_formats
(
formats
):
def
readable_time_formats
(
formats
):
format
=
', '
.
join
(
formats
)
.
replace
(
ISO_8601
,
'hh:mm[:ss[.uuuuuu]]'
)
format
=
', '
.
join
(
formats
)
.
replace
(
ISO_8601
,
'hh:mm[:ss[.uuuuuu]]'
)
return
humanize_strptime
(
format
)
return
humanize_strptime
(
format
)
...
@@ -122,6 +109,7 @@ class Field(object):
...
@@ -122,6 +109,7 @@ class Field(object):
partial
=
False
partial
=
False
use_files
=
False
use_files
=
False
form_field_class
=
forms
.
CharField
form_field_class
=
forms
.
CharField
type_label
=
'field'
def
__init__
(
self
,
source
=
None
,
label
=
None
,
help_text
=
None
):
def
__init__
(
self
,
source
=
None
,
label
=
None
,
help_text
=
None
):
self
.
parent
=
None
self
.
parent
=
None
...
@@ -207,18 +195,17 @@ class Field(object):
...
@@ -207,18 +195,17 @@ class Field(object):
return
{
'type'
:
self
.
type_name
}
return
{
'type'
:
self
.
type_name
}
return
{}
return
{}
@property
def
metadata
(
self
):
def
humanized
(
self
):
metadata
=
SortedDict
()
humanized
=
{
metadata
[
'type'
]
=
self
.
type_label
'type'
:
self
.
type_name
,
metadata
[
'required'
]
=
getattr
(
self
,
'required'
,
False
)
'required'
:
getattr
(
self
,
'required'
,
False
),
optional_attrs
=
[
'read_only'
,
'label'
,
'help_text'
,
}
optional_attrs
=
[
'read_only'
,
'help_text'
,
'label'
,
'min_length'
,
'max_length'
]
'min_length'
,
'max_length'
]
for
attr
in
optional_attrs
:
for
attr
in
optional_attrs
:
if
getattr
(
self
,
attr
,
None
)
is
not
None
:
value
=
getattr
(
self
,
attr
,
None
)
humanized
[
attr
]
=
getattr
(
self
,
attr
)
if
value
is
not
None
and
value
!=
''
:
return
humanized
metadata
[
attr
]
=
force_text
(
value
,
strings_only
=
True
)
return
metadata
class
WritableField
(
Field
):
class
WritableField
(
Field
):
...
@@ -375,6 +362,7 @@ class ModelField(WritableField):
...
@@ -375,6 +362,7 @@ class ModelField(WritableField):
class
BooleanField
(
WritableField
):
class
BooleanField
(
WritableField
):
type_name
=
'BooleanField'
type_name
=
'BooleanField'
type_label
=
'boolean'
form_field_class
=
forms
.
BooleanField
form_field_class
=
forms
.
BooleanField
widget
=
widgets
.
CheckboxInput
widget
=
widgets
.
CheckboxInput
default_error_messages
=
{
default_error_messages
=
{
...
@@ -397,6 +385,7 @@ class BooleanField(WritableField):
...
@@ -397,6 +385,7 @@ class BooleanField(WritableField):
class
CharField
(
WritableField
):
class
CharField
(
WritableField
):
type_name
=
'CharField'
type_name
=
'CharField'
type_label
=
'string'
form_field_class
=
forms
.
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
):
...
@@ -415,6 +404,7 @@ class CharField(WritableField):
...
@@ -415,6 +404,7 @@ class CharField(WritableField):
class
URLField
(
CharField
):
class
URLField
(
CharField
):
type_name
=
'URLField'
type_name
=
'URLField'
type_label
=
'url'
def
__init__
(
self
,
**
kwargs
):
def
__init__
(
self
,
**
kwargs
):
kwargs
[
'validators'
]
=
[
validators
.
URLValidator
()]
kwargs
[
'validators'
]
=
[
validators
.
URLValidator
()]
...
@@ -423,6 +413,7 @@ class URLField(CharField):
...
@@ -423,6 +413,7 @@ class URLField(CharField):
class
SlugField
(
CharField
):
class
SlugField
(
CharField
):
type_name
=
'SlugField'
type_name
=
'SlugField'
type_label
=
'slug'
form_field_class
=
forms
.
SlugField
form_field_class
=
forms
.
SlugField
default_error_messages
=
{
default_error_messages
=
{
...
@@ -444,6 +435,7 @@ class SlugField(CharField):
...
@@ -444,6 +435,7 @@ class SlugField(CharField):
class
ChoiceField
(
WritableField
):
class
ChoiceField
(
WritableField
):
type_name
=
'ChoiceField'
type_name
=
'ChoiceField'
type_label
=
'multiple choice'
form_field_class
=
forms
.
ChoiceField
form_field_class
=
forms
.
ChoiceField
widget
=
widgets
.
Select
widget
=
widgets
.
Select
default_error_messages
=
{
default_error_messages
=
{
...
@@ -494,6 +486,7 @@ class ChoiceField(WritableField):
...
@@ -494,6 +486,7 @@ class ChoiceField(WritableField):
class
EmailField
(
CharField
):
class
EmailField
(
CharField
):
type_name
=
'EmailField'
type_name
=
'EmailField'
type_label
=
'email'
form_field_class
=
forms
.
EmailField
form_field_class
=
forms
.
EmailField
default_error_messages
=
{
default_error_messages
=
{
...
@@ -517,6 +510,7 @@ class EmailField(CharField):
...
@@ -517,6 +510,7 @@ class EmailField(CharField):
class
RegexField
(
CharField
):
class
RegexField
(
CharField
):
type_name
=
'RegexField'
type_name
=
'RegexField'
type_label
=
'regex'
form_field_class
=
forms
.
RegexField
form_field_class
=
forms
.
RegexField
def
__init__
(
self
,
regex
,
max_length
=
None
,
min_length
=
None
,
*
args
,
**
kwargs
):
def
__init__
(
self
,
regex
,
max_length
=
None
,
min_length
=
None
,
*
args
,
**
kwargs
):
...
@@ -546,6 +540,7 @@ class RegexField(CharField):
...
@@ -546,6 +540,7 @@ class RegexField(CharField):
class
DateField
(
WritableField
):
class
DateField
(
WritableField
):
type_name
=
'DateField'
type_name
=
'DateField'
type_label
=
'date'
widget
=
widgets
.
DateInput
widget
=
widgets
.
DateInput
form_field_class
=
forms
.
DateField
form_field_class
=
forms
.
DateField
...
@@ -609,6 +604,7 @@ class DateField(WritableField):
...
@@ -609,6 +604,7 @@ class DateField(WritableField):
class
DateTimeField
(
WritableField
):
class
DateTimeField
(
WritableField
):
type_name
=
'DateTimeField'
type_name
=
'DateTimeField'
type_label
=
'datetime'
widget
=
widgets
.
DateTimeInput
widget
=
widgets
.
DateTimeInput
form_field_class
=
forms
.
DateTimeField
form_field_class
=
forms
.
DateTimeField
...
@@ -678,6 +674,7 @@ class DateTimeField(WritableField):
...
@@ -678,6 +674,7 @@ class DateTimeField(WritableField):
class
TimeField
(
WritableField
):
class
TimeField
(
WritableField
):
type_name
=
'TimeField'
type_name
=
'TimeField'
type_label
=
'time'
widget
=
widgets
.
TimeInput
widget
=
widgets
.
TimeInput
form_field_class
=
forms
.
TimeField
form_field_class
=
forms
.
TimeField
...
@@ -734,6 +731,7 @@ class TimeField(WritableField):
...
@@ -734,6 +731,7 @@ class TimeField(WritableField):
class
IntegerField
(
WritableField
):
class
IntegerField
(
WritableField
):
type_name
=
'IntegerField'
type_name
=
'IntegerField'
type_label
=
'integer'
form_field_class
=
forms
.
IntegerField
form_field_class
=
forms
.
IntegerField
default_error_messages
=
{
default_error_messages
=
{
...
@@ -764,6 +762,7 @@ class IntegerField(WritableField):
...
@@ -764,6 +762,7 @@ class IntegerField(WritableField):
class
FloatField
(
WritableField
):
class
FloatField
(
WritableField
):
type_name
=
'FloatField'
type_name
=
'FloatField'
type_label
=
'float'
form_field_class
=
forms
.
FloatField
form_field_class
=
forms
.
FloatField
default_error_messages
=
{
default_error_messages
=
{
...
@@ -783,6 +782,7 @@ class FloatField(WritableField):
...
@@ -783,6 +782,7 @@ class FloatField(WritableField):
class
DecimalField
(
WritableField
):
class
DecimalField
(
WritableField
):
type_name
=
'DecimalField'
type_name
=
'DecimalField'
type_label
=
'decimal'
form_field_class
=
forms
.
DecimalField
form_field_class
=
forms
.
DecimalField
default_error_messages
=
{
default_error_messages
=
{
...
@@ -853,6 +853,7 @@ class DecimalField(WritableField):
...
@@ -853,6 +853,7 @@ class DecimalField(WritableField):
class
FileField
(
WritableField
):
class
FileField
(
WritableField
):
use_files
=
True
use_files
=
True
type_name
=
'FileField'
type_name
=
'FileField'
type_label
=
'file upload'
form_field_class
=
forms
.
FileField
form_field_class
=
forms
.
FileField
widget
=
widgets
.
FileInput
widget
=
widgets
.
FileInput
...
@@ -896,6 +897,8 @@ class FileField(WritableField):
...
@@ -896,6 +897,8 @@ class FileField(WritableField):
class
ImageField
(
FileField
):
class
ImageField
(
FileField
):
use_files
=
True
use_files
=
True
type_name
=
'ImageField'
type_label
=
'image upload'
form_field_class
=
forms
.
ImageField
form_field_class
=
forms
.
ImageField
default_error_messages
=
{
default_error_messages
=
{
...
...
rest_framework/generics.py
View file @
fcaee6e5
...
@@ -3,13 +3,13 @@ Generic views that provide commonly needed behaviour.
...
@@ -3,13 +3,13 @@ Generic views that provide commonly needed behaviour.
"""
"""
from
__future__
import
unicode_literals
from
__future__
import
unicode_literals
from
django.core.exceptions
import
ImproperlyConfigured
from
django.core.exceptions
import
ImproperlyConfigured
,
PermissionDenied
from
django.core.paginator
import
Paginator
,
InvalidPage
from
django.core.paginator
import
Paginator
,
InvalidPage
from
django.http
import
Http404
from
django.http
import
Http404
from
django.shortcuts
import
get_object_or_404
from
django.shortcuts
import
get_object_or_404
from
django.utils.translation
import
ugettext
as
_
from
django.utils.translation
import
ugettext
as
_
from
rest_framework
import
views
,
mixins
from
rest_framework
import
views
,
mixins
,
exceptions
from
rest_framework.
exceptions
import
ConfigurationError
from
rest_framework.
request
import
clone_request
from
rest_framework.settings
import
api_settings
from
rest_framework.settings
import
api_settings
import
warnings
import
warnings
...
@@ -274,7 +274,7 @@ class GenericAPIView(views.APIView):
...
@@ -274,7 +274,7 @@ class GenericAPIView(views.APIView):
)
)
filter_kwargs
=
{
self
.
slug_field
:
slug
}
filter_kwargs
=
{
self
.
slug_field
:
slug
}
else
:
else
:
raise
ConfigurationError
(
raise
exceptions
.
ConfigurationError
(
'Expected view
%
s to be called with a URL keyword argument '
'Expected view
%
s to be called with a URL keyword argument '
'named "
%
s". Fix your URL conf, or set the `.lookup_field` '
'named "
%
s". Fix your URL conf, or set the `.lookup_field` '
'attribute on the view correctly.'
%
'attribute on the view correctly.'
%
...
@@ -310,6 +310,41 @@ class GenericAPIView(views.APIView):
...
@@ -310,6 +310,41 @@ class GenericAPIView(views.APIView):
"""
"""
pass
pass
def
metadata
(
self
,
request
):
"""
Return a dictionary of metadata about the view.
Used to return responses for OPTIONS requests.
We override the default behavior, and add some extra information
about the required request body for POST and PUT operations.
"""
ret
=
super
(
GenericAPIView
,
self
)
.
metadata
(
request
)
actions
=
{}
for
method
in
(
'PUT'
,
'POST'
):
if
method
not
in
self
.
allowed_methods
:
continue
cloned_request
=
clone_request
(
request
,
method
)
try
:
# Test global permissions
self
.
check_permissions
(
cloned_request
)
# Test object permissions
if
method
==
'PUT'
:
self
.
get_object
()
except
(
exceptions
.
APIException
,
PermissionDenied
,
Http404
):
pass
else
:
# If user has appropriate permissions for the view, include
# appropriate metadata about the fields that should be supplied.
serializer
=
self
.
get_serializer
()
actions
[
method
]
=
serializer
.
metadata
()
if
actions
:
ret
[
'actions'
]
=
actions
return
ret
##########################################################
##########################################################
### Concrete view classes that provide method handlers ###
### Concrete view classes that provide method handlers ###
...
...
rest_framework/serializers.py
View file @
fcaee6e5
...
@@ -521,12 +521,16 @@ class BaseSerializer(WritableField):
...
@@ -521,12 +521,16 @@ class BaseSerializer(WritableField):
return
self
.
object
return
self
.
object
@property
def
metadata
(
self
):
def
humanized
(
self
):
"""
humanized_fields
=
SortedDict
(
Return a dictionary of metadata about the fields on the serializer.
[(
name
,
field
.
humanized
)
Useful for things like responding to OPTIONS requests, or generating
for
name
,
field
in
self
.
fields
.
iteritems
()])
API schemas for auto-documentation.
return
humanized_fields
"""
return
SortedDict
(
[(
field_name
,
field
.
metadata
())
for
field_name
,
field
in
six
.
iteritems
(
self
.
fields
)]
)
class
Serializer
(
six
.
with_metaclass
(
SerializerMetaclass
,
BaseSerializer
)):
class
Serializer
(
six
.
with_metaclass
(
SerializerMetaclass
,
BaseSerializer
)):
...
...
rest_framework/tests/fields.py
View file @
fcaee6e5
...
@@ -3,17 +3,13 @@ General serializer field tests.
...
@@ -3,17 +3,13 @@ General serializer field tests.
"""
"""
from
__future__
import
unicode_literals
from
__future__
import
unicode_literals
from
collections
import
namedtupl
e
import
datetim
e
from
decimal
import
Decimal
from
decimal
import
Decimal
from
uuid
import
uuid4
from
uuid
import
uuid4
import
datetime
from
django
import
forms
from
django.core
import
validators
from
django.core
import
validators
from
django.db
import
models
from
django.db
import
models
from
django.test
import
TestCase
from
django.test
import
TestCase
from
django.utils.datastructures
import
SortedDict
from
django.utils.datastructures
import
SortedDict
from
rest_framework
import
serializers
from
rest_framework
import
serializers
from
rest_framework.fields
import
Field
,
CharField
from
rest_framework.fields
import
Field
,
CharField
from
rest_framework.serializers
import
Serializer
from
rest_framework.serializers
import
Serializer
...
@@ -839,7 +835,7 @@ class URLFieldTests(TestCase):
...
@@ -839,7 +835,7 @@ class URLFieldTests(TestCase):
'max_length'
),
20
)
'max_length'
),
20
)
class
HumanizedField
(
TestCase
):
class
FieldMetadata
(
TestCase
):
def
setUp
(
self
):
def
setUp
(
self
):
self
.
required_field
=
Field
()
self
.
required_field
=
Field
()
self
.
required_field
.
label
=
uuid4
()
.
hex
self
.
required_field
.
label
=
uuid4
()
.
hex
...
@@ -849,41 +845,35 @@ class HumanizedField(TestCase):
...
@@ -849,41 +845,35 @@ class HumanizedField(TestCase):
self
.
optional_field
.
label
=
uuid4
()
.
hex
self
.
optional_field
.
label
=
uuid4
()
.
hex
self
.
optional_field
.
required
=
False
self
.
optional_field
.
required
=
False
def
test_type
(
self
):
for
field
in
(
self
.
required_field
,
self
.
optional_field
):
self
.
assertEqual
(
field
.
humanized
[
'type'
],
field
.
type_name
)
def
test_required
(
self
):
def
test_required
(
self
):
self
.
assertEqual
(
self
.
required_field
.
humanized
[
'required'
],
True
)
self
.
assertEqual
(
self
.
required_field
.
metadata
()
[
'required'
],
True
)
def
test_optional
(
self
):
def
test_optional
(
self
):
self
.
assertEqual
(
self
.
optional_field
.
humanized
[
'required'
],
False
)
self
.
assertEqual
(
self
.
optional_field
.
metadata
()
[
'required'
],
False
)
def
test_label
(
self
):
def
test_label
(
self
):
for
field
in
(
self
.
required_field
,
self
.
optional_field
):
for
field
in
(
self
.
required_field
,
self
.
optional_field
):
self
.
assertEqual
(
field
.
humanized
[
'label'
],
field
.
label
)
self
.
assertEqual
(
field
.
metadata
()
[
'label'
],
field
.
label
)
class
Humanizable
Serializer
(
Serializer
):
class
Metadata
Serializer
(
Serializer
):
field1
=
CharField
(
3
,
required
=
True
)
field1
=
CharField
(
3
,
required
=
True
)
field2
=
CharField
(
10
,
required
=
False
)
field2
=
CharField
(
10
,
required
=
False
)
class
HumanizedSerializer
(
TestCase
):
class
MetadataSerializerTestCase
(
TestCase
):
def
setUp
(
self
):
def
setUp
(
self
):
self
.
serializer
=
Humanizable
Serializer
()
self
.
serializer
=
Metadata
Serializer
()
def
test_
humanized
(
self
):
def
test_
serializer_metadata
(
self
):
humanized
=
self
.
serializer
.
humanized
metadata
=
self
.
serializer
.
metadata
()
expected
=
{
expected
=
{
'field1'
:
{
u'required'
:
True
,
'field1'
:
{
'required'
:
True
,
u'max_length'
:
3
,
'max_length'
:
3
,
u'type'
:
u'CharField'
,
'type'
:
'string'
,
u'read_only'
:
False
},
'read_only'
:
False
},
'field2'
:
{
u'required'
:
False
,
'field2'
:
{
'required'
:
False
,
u'max_length'
:
10
,
'max_length'
:
10
,
u'type'
:
u'CharField'
,
'type'
:
'string'
,
u'read_only'
:
False
}}
'read_only'
:
False
}}
self
.
assertEqual
(
set
(
expected
.
keys
()),
set
(
humanized
.
keys
()))
self
.
assertEqual
(
expected
,
metadata
)
for
k
,
v
in
humanized
.
iteritems
():
self
.
assertEqual
(
v
,
expected
[
k
])
rest_framework/tests/generics.py
View file @
fcaee6e5
...
@@ -122,22 +122,25 @@ class TestRootView(TestCase):
...
@@ -122,22 +122,25 @@ class TestRootView(TestCase):
],
],
'name'
:
'Root'
,
'name'
:
'Root'
,
'description'
:
'Example description for OPTIONS.'
,
'description'
:
'Example description for OPTIONS.'
,
'actions'
:
{}
'actions'
:
{
}
'POST'
:
{
expected
[
'actions'
][
'GET'
]
=
{}
expected
[
'actions'
][
'POST'
]
=
{
'text'
:
{
'text'
:
{
'max_length'
:
100
,
'max_length'
:
100
,
'read_only'
:
False
,
'read_only'
:
False
,
'required'
:
True
,
'required'
:
True
,
'type'
:
'String'
,
'type'
:
'string'
,
"label"
:
"Text comes here"
,
"help_text"
:
"Text description."
},
},
'id'
:
{
'id'
:
{
'read_only'
:
True
,
'read_only'
:
True
,
'required'
:
False
,
'required'
:
False
,
'type'
:
'Integer'
,
'type'
:
'integer'
,
'label'
:
'ID'
,
},
},
}
}
}
}
self
.
assertEqual
(
response
.
status_code
,
status
.
HTTP_200_OK
)
self
.
assertEqual
(
response
.
status_code
,
status
.
HTTP_200_OK
)
self
.
assertEqual
(
response
.
data
,
expected
)
self
.
assertEqual
(
response
.
data
,
expected
)
...
@@ -239,9 +242,9 @@ class TestInstanceView(TestCase):
...
@@ -239,9 +242,9 @@ class TestInstanceView(TestCase):
"""
"""
OPTIONS requests to RetrieveUpdateDestroyAPIView should return metadata
OPTIONS requests to RetrieveUpdateDestroyAPIView should return metadata
"""
"""
request
=
factory
.
options
(
'/'
)
request
=
factory
.
options
(
'/
1
'
)
with
self
.
assertNumQueries
(
0
):
with
self
.
assertNumQueries
(
1
):
response
=
self
.
view
(
request
)
.
render
()
response
=
self
.
view
(
request
,
pk
=
1
)
.
render
()
expected
=
{
expected
=
{
'parses'
:
[
'parses'
:
[
'application/json'
,
'application/json'
,
...
@@ -254,24 +257,25 @@ class TestInstanceView(TestCase):
...
@@ -254,24 +257,25 @@ class TestInstanceView(TestCase):
],
],
'name'
:
'Instance'
,
'name'
:
'Instance'
,
'description'
:
'Example description for OPTIONS.'
,
'description'
:
'Example description for OPTIONS.'
,
'actions'
:
{}
'actions'
:
{
}
'PUT'
:
{
for
method
in
(
'GET'
,
'DELETE'
):
expected
[
'actions'
][
method
]
=
{}
for
method
in
(
'PATCH'
,
'PUT'
):
expected
[
'actions'
][
method
]
=
{
'text'
:
{
'text'
:
{
'max_length'
:
100
,
'max_length'
:
100
,
'read_only'
:
False
,
'read_only'
:
False
,
'required'
:
True
,
'required'
:
True
,
'type'
:
'String'
,
'type'
:
'string'
,
'label'
:
'Text comes here'
,
'help_text'
:
'Text description.'
},
},
'id'
:
{
'id'
:
{
'read_only'
:
True
,
'read_only'
:
True
,
'required'
:
False
,
'required'
:
False
,
'type'
:
'Integer'
,
'type'
:
'integer'
,
'label'
:
'ID'
,
},
},
}
}
}
}
self
.
assertEqual
(
response
.
status_code
,
status
.
HTTP_200_OK
)
self
.
assertEqual
(
response
.
status_code
,
status
.
HTTP_200_OK
)
self
.
assertEqual
(
response
.
data
,
expected
)
self
.
assertEqual
(
response
.
data
,
expected
)
...
...
rest_framework/tests/permissions.py
View file @
fcaee6e5
...
@@ -114,44 +114,41 @@ class ModelPermissionsIntegrationTests(TestCase):
...
@@ -114,44 +114,41 @@ class ModelPermissionsIntegrationTests(TestCase):
response
=
root_view
(
request
,
pk
=
'1'
)
response
=
root_view
(
request
,
pk
=
'1'
)
self
.
assertEqual
(
response
.
status_code
,
status
.
HTTP_200_OK
)
self
.
assertEqual
(
response
.
status_code
,
status
.
HTTP_200_OK
)
self
.
assertIn
(
'actions'
,
response
.
data
)
self
.
assertIn
(
'actions'
,
response
.
data
)
self
.
assertEqual
s
(
response
.
data
[
'actions'
]
.
keys
(),
[
'POST'
,
'GET'
,
])
self
.
assertEqual
(
list
(
response
.
data
[
'actions'
]
.
keys
()),
[
'POST'
])
request
=
factory
.
options
(
'/1'
,
content_type
=
'application/json'
,
request
=
factory
.
options
(
'/1'
,
content_type
=
'application/json'
,
HTTP_AUTHORIZATION
=
self
.
permitted_credentials
)
HTTP_AUTHORIZATION
=
self
.
permitted_credentials
)
response
=
instance_view
(
request
,
pk
=
'1'
)
response
=
instance_view
(
request
,
pk
=
'1'
)
self
.
assertEqual
(
response
.
status_code
,
status
.
HTTP_200_OK
)
self
.
assertEqual
(
response
.
status_code
,
status
.
HTTP_200_OK
)
self
.
assertIn
(
'actions'
,
response
.
data
)
self
.
assertIn
(
'actions'
,
response
.
data
)
self
.
assertEqual
s
(
response
.
data
[
'actions'
]
.
keys
(),
[
'PUT'
,
'PATCH'
,
'DELETE'
,
'GET'
,
])
self
.
assertEqual
(
list
(
response
.
data
[
'actions'
]
.
keys
()),
[
'PUT'
])
def
test_options_disallowed
(
self
):
def
test_options_disallowed
(
self
):
request
=
factory
.
options
(
'/'
,
content_type
=
'application/json'
,
request
=
factory
.
options
(
'/'
,
content_type
=
'application/json'
,
HTTP_AUTHORIZATION
=
self
.
disallowed_credentials
)
HTTP_AUTHORIZATION
=
self
.
disallowed_credentials
)
response
=
root_view
(
request
,
pk
=
'1'
)
response
=
root_view
(
request
,
pk
=
'1'
)
self
.
assertEqual
(
response
.
status_code
,
status
.
HTTP_200_OK
)
self
.
assertEqual
(
response
.
status_code
,
status
.
HTTP_200_OK
)
self
.
assertIn
(
'actions'
,
response
.
data
)
self
.
assertNotIn
(
'actions'
,
response
.
data
)
self
.
assertEquals
(
response
.
data
[
'actions'
]
.
keys
(),
[
'GET'
,])
request
=
factory
.
options
(
'/1'
,
content_type
=
'application/json'
,
request
=
factory
.
options
(
'/1'
,
content_type
=
'application/json'
,
HTTP_AUTHORIZATION
=
self
.
disallowed_credentials
)
HTTP_AUTHORIZATION
=
self
.
disallowed_credentials
)
response
=
instance_view
(
request
,
pk
=
'1'
)
response
=
instance_view
(
request
,
pk
=
'1'
)
self
.
assertEqual
(
response
.
status_code
,
status
.
HTTP_200_OK
)
self
.
assertEqual
(
response
.
status_code
,
status
.
HTTP_200_OK
)
self
.
assertIn
(
'actions'
,
response
.
data
)
self
.
assertNotIn
(
'actions'
,
response
.
data
)
self
.
assertEquals
(
response
.
data
[
'actions'
]
.
keys
(),
[
'GET'
,])
def
test_options_updateonly
(
self
):
def
test_options_updateonly
(
self
):
request
=
factory
.
options
(
'/'
,
content_type
=
'application/json'
,
request
=
factory
.
options
(
'/'
,
content_type
=
'application/json'
,
HTTP_AUTHORIZATION
=
self
.
updateonly_credentials
)
HTTP_AUTHORIZATION
=
self
.
updateonly_credentials
)
response
=
root_view
(
request
,
pk
=
'1'
)
response
=
root_view
(
request
,
pk
=
'1'
)
self
.
assertEqual
(
response
.
status_code
,
status
.
HTTP_200_OK
)
self
.
assertEqual
(
response
.
status_code
,
status
.
HTTP_200_OK
)
self
.
assertIn
(
'actions'
,
response
.
data
)
self
.
assertNotIn
(
'actions'
,
response
.
data
)
self
.
assertEquals
(
response
.
data
[
'actions'
]
.
keys
(),
[
'GET'
,])
request
=
factory
.
options
(
'/1'
,
content_type
=
'application/json'
,
request
=
factory
.
options
(
'/1'
,
content_type
=
'application/json'
,
HTTP_AUTHORIZATION
=
self
.
updateonly_credentials
)
HTTP_AUTHORIZATION
=
self
.
updateonly_credentials
)
response
=
instance_view
(
request
,
pk
=
'1'
)
response
=
instance_view
(
request
,
pk
=
'1'
)
self
.
assertEqual
(
response
.
status_code
,
status
.
HTTP_200_OK
)
self
.
assertEqual
(
response
.
status_code
,
status
.
HTTP_200_OK
)
self
.
assertIn
(
'actions'
,
response
.
data
)
self
.
assertIn
(
'actions'
,
response
.
data
)
self
.
assertEqual
s
(
response
.
data
[
'actions'
]
.
keys
(),
[
'PUT'
,
'PATCH'
,
'GET'
,
])
self
.
assertEqual
(
list
(
response
.
data
[
'actions'
]
.
keys
()),
[
'PUT'
])
class
OwnerModel
(
models
.
Model
):
class
OwnerModel
(
models
.
Model
):
...
...
rest_framework/tests/renderers.py
View file @
fcaee6e5
...
@@ -6,6 +6,7 @@ from django.core.cache import cache
...
@@ -6,6 +6,7 @@ from django.core.cache import cache
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
unittest
from
django.utils
import
unittest
from
django.utils.translation
import
ugettext_lazy
as
_
from
rest_framework
import
status
,
permissions
from
rest_framework
import
status
,
permissions
from
rest_framework.compat
import
yaml
,
etree
,
patterns
,
url
,
include
from
rest_framework.compat
import
yaml
,
etree
,
patterns
,
url
,
include
from
rest_framework.response
import
Response
from
rest_framework.response
import
Response
...
@@ -238,6 +239,13 @@ class JSONRendererTests(TestCase):
...
@@ -238,6 +239,13 @@ class JSONRendererTests(TestCase):
Tests specific to the JSON Renderer
Tests specific to the JSON Renderer
"""
"""
def
test_render_lazy_strings
(
self
):
"""
JSONRenderer should deal with lazy translated strings.
"""
ret
=
JSONRenderer
()
.
render
(
_
(
'test'
))
self
.
assertEqual
(
ret
,
b
'"test"'
)
def
test_without_content_type_args
(
self
):
def
test_without_content_type_args
(
self
):
"""
"""
Test basic JSON rendering.
Test basic JSON rendering.
...
...
rest_framework/utils/encoders.py
View file @
fcaee6e5
...
@@ -3,7 +3,8 @@ Helper classes for parsers.
...
@@ -3,7 +3,8 @@ Helper classes for parsers.
"""
"""
from
__future__
import
unicode_literals
from
__future__
import
unicode_literals
from
django.utils.datastructures
import
SortedDict
from
django.utils.datastructures
import
SortedDict
from
rest_framework.compat
import
timezone
from
django.utils.functional
import
Promise
from
rest_framework.compat
import
timezone
,
force_text
from
rest_framework.serializers
import
DictWithMetadata
,
SortedDictWithMetadata
from
rest_framework.serializers
import
DictWithMetadata
,
SortedDictWithMetadata
import
datetime
import
datetime
import
decimal
import
decimal
...
@@ -19,7 +20,9 @@ class JSONEncoder(json.JSONEncoder):
...
@@ -19,7 +20,9 @@ class JSONEncoder(json.JSONEncoder):
def
default
(
self
,
o
):
def
default
(
self
,
o
):
# For Date Time string spec, see ECMA 262
# For Date Time string spec, see ECMA 262
# http://ecma-international.org/ecma-262/5.1/#sec-15.9.1.15
# http://ecma-international.org/ecma-262/5.1/#sec-15.9.1.15
if
isinstance
(
o
,
datetime
.
datetime
):
if
isinstance
(
o
,
Promise
):
return
force_text
(
o
)
elif
isinstance
(
o
,
datetime
.
datetime
):
r
=
o
.
isoformat
()
r
=
o
.
isoformat
()
if
o
.
microsecond
:
if
o
.
microsecond
:
r
=
r
[:
23
]
+
r
[
26
:]
r
=
r
[:
23
]
+
r
[
26
:]
...
...
rest_framework/views.py
View file @
fcaee6e5
...
@@ -5,12 +5,11 @@ from __future__ import unicode_literals
...
@@ -5,12 +5,11 @@ from __future__ import unicode_literals
from
django.core.exceptions
import
PermissionDenied
from
django.core.exceptions
import
PermissionDenied
from
django.http
import
Http404
,
HttpResponse
from
django.http
import
Http404
,
HttpResponse
from
django.utils.datastructures
import
SortedDict
from
django.views.decorators.csrf
import
csrf_exempt
from
django.views.decorators.csrf
import
csrf_exempt
from
rest_framework
import
status
,
exceptions
from
rest_framework
import
status
,
exceptions
from
rest_framework.compat
import
View
from
rest_framework.compat
import
View
from
rest_framework.fields
import
humanize_form_fields
from
rest_framework.request
import
Request
from
rest_framework.request
import
clone_request
,
Request
from
rest_framework.response
import
Response
from
rest_framework.response
import
Response
from
rest_framework.settings
import
api_settings
from
rest_framework.settings
import
api_settings
from
rest_framework.utils.formatting
import
get_view_name
,
get_view_description
from
rest_framework.utils.formatting
import
get_view_name
,
get_view_description
...
@@ -54,53 +53,6 @@ class APIView(View):
...
@@ -54,53 +53,6 @@ class APIView(View):
'Vary'
:
'Accept'
'Vary'
:
'Accept'
}
}
def
metadata
(
self
,
request
):
content
=
{
'name'
:
get_view_name
(
self
.
__class__
),
'description'
:
get_view_description
(
self
.
__class__
),
'renders'
:
[
renderer
.
media_type
for
renderer
in
self
.
renderer_classes
],
'parses'
:
[
parser
.
media_type
for
parser
in
self
.
parser_classes
],
}
content
[
'actions'
]
=
self
.
action_metadata
(
request
)
return
content
def
action_metadata
(
self
,
request
):
"""Return a dictionary with the fields required fo reach allowed method. If no method is allowed,
return an empty dictionary.
:param request: Request for which to return the metadata of the allowed methods.
:return: A dictionary of the form {method: {field: {field attribute: value}}}
"""
actions
=
{}
for
method
in
self
.
allowed_methods
:
# skip HEAD and OPTIONS
if
method
in
(
'HEAD'
,
'OPTIONS'
):
continue
cloned_request
=
clone_request
(
request
,
method
)
try
:
self
.
check_permissions
(
cloned_request
)
# TODO: discuss whether and how to expose parameters like e.g. filter or paginate
if
method
in
(
'GET'
,
'DELETE'
):
actions
[
method
]
=
{}
continue
if
not
hasattr
(
self
,
'get_serializer'
):
continue
serializer
=
self
.
get_serializer
()
if
serializer
is
not
None
:
actions
[
method
]
=
serializer
.
humanized
except
exceptions
.
PermissionDenied
:
# don't add this method
pass
except
exceptions
.
NotAuthenticated
:
# don't add this method
pass
return
actions
if
len
(
actions
)
>
0
else
None
def
http_method_not_allowed
(
self
,
request
,
*
args
,
**
kwargs
):
def
http_method_not_allowed
(
self
,
request
,
*
args
,
**
kwargs
):
"""
"""
If `request.method` does not correspond to a handler method,
If `request.method` does not correspond to a handler method,
...
@@ -383,3 +335,15 @@ class APIView(View):
...
@@ -383,3 +335,15 @@ class APIView(View):
a less useful default implementation.
a less useful default implementation.
"""
"""
return
Response
(
self
.
metadata
(
request
),
status
=
status
.
HTTP_200_OK
)
return
Response
(
self
.
metadata
(
request
),
status
=
status
.
HTTP_200_OK
)
def
metadata
(
self
,
request
):
"""
Return a dictionary of metadata about the view.
Used to return responses for OPTIONS requests.
"""
ret
=
SortedDict
()
ret
[
'name'
]
=
get_view_name
(
self
.
__class__
)
ret
[
'description'
]
=
get_view_description
(
self
.
__class__
)
ret
[
'renders'
]
=
[
renderer
.
media_type
for
renderer
in
self
.
renderer_classes
]
ret
[
'parses'
]
=
[
parser
.
media_type
for
parser
in
self
.
parser_classes
]
return
ret
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment