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
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
175 additions
and
166 deletions
+175
-166
docs/topics/release-notes.md
+1
-0
rest_framework/fields.py
+31
-28
rest_framework/generics.py
+39
-4
rest_framework/serializers.py
+10
-6
rest_framework/tests/fields.py
+22
-32
rest_framework/tests/generics.py
+39
-35
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`:
*
Serializer fields now support `label` and `help_text`.
*
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
:
Blank choice now added in browsable API on
null
able relationships.
*
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
import
inspect
import
re
import
warnings
from
django.core
import
validators
from
django.core.exceptions
import
ValidationError
from
django.conf
import
settings
...
...
@@ -21,7 +20,6 @@ from django.forms import widgets
from
django.utils.encoding
import
is_protected_type
from
django.utils.translation
import
ugettext_lazy
as
_
from
django.utils.datastructures
import
SortedDict
from
rest_framework
import
ISO_8601
from
rest_framework.compat
import
(
timezone
,
parse_date
,
parse_datetime
,
parse_time
)
...
...
@@ -46,6 +44,7 @@ def is_simple_callable(obj):
len_defaults
=
len
(
defaults
)
if
defaults
else
0
return
len_args
<=
len_defaults
def
get_component
(
obj
,
attr_name
):
"""
Given an object, and an attribute name,
...
...
@@ -72,18 +71,6 @@ def readable_date_formats(formats):
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
):
format
=
', '
.
join
(
formats
)
.
replace
(
ISO_8601
,
'hh:mm[:ss[.uuuuuu]]'
)
return
humanize_strptime
(
format
)
...
...
@@ -122,6 +109,7 @@ class Field(object):
partial
=
False
use_files
=
False
form_field_class
=
forms
.
CharField
type_label
=
'field'
def
__init__
(
self
,
source
=
None
,
label
=
None
,
help_text
=
None
):
self
.
parent
=
None
...
...
@@ -207,18 +195,17 @@ class Field(object):
return
{
'type'
:
self
.
type_name
}
return
{}
@property
def
humanized
(
self
):
humanized
=
{
'type'
:
self
.
type_name
,
'required'
:
getattr
(
self
,
'required'
,
False
),
}
optional_attrs
=
[
'read_only'
,
'help_text'
,
'label'
,
def
metadata
(
self
):
metadata
=
SortedDict
()
metadata
[
'type'
]
=
self
.
type_label
metadata
[
'required'
]
=
getattr
(
self
,
'required'
,
False
)
optional_attrs
=
[
'read_only'
,
'label'
,
'help_text'
,
'min_length'
,
'max_length'
]
for
attr
in
optional_attrs
:
if
getattr
(
self
,
attr
,
None
)
is
not
None
:
humanized
[
attr
]
=
getattr
(
self
,
attr
)
return
humanized
value
=
getattr
(
self
,
attr
,
None
)
if
value
is
not
None
and
value
!=
''
:
metadata
[
attr
]
=
force_text
(
value
,
strings_only
=
True
)
return
metadata
class
WritableField
(
Field
):
...
...
@@ -375,6 +362,7 @@ class ModelField(WritableField):
class
BooleanField
(
WritableField
):
type_name
=
'BooleanField'
type_label
=
'boolean'
form_field_class
=
forms
.
BooleanField
widget
=
widgets
.
CheckboxInput
default_error_messages
=
{
...
...
@@ -397,6 +385,7 @@ class BooleanField(WritableField):
class
CharField
(
WritableField
):
type_name
=
'CharField'
type_label
=
'string'
form_field_class
=
forms
.
CharField
def
__init__
(
self
,
max_length
=
None
,
min_length
=
None
,
*
args
,
**
kwargs
):
...
...
@@ -415,6 +404,7 @@ class CharField(WritableField):
class
URLField
(
CharField
):
type_name
=
'URLField'
type_label
=
'url'
def
__init__
(
self
,
**
kwargs
):
kwargs
[
'validators'
]
=
[
validators
.
URLValidator
()]
...
...
@@ -423,14 +413,15 @@ class URLField(CharField):
class
SlugField
(
CharField
):
type_name
=
'SlugField'
type_label
=
'slug'
form_field_class
=
forms
.
SlugField
default_error_messages
=
{
'invalid'
:
_
(
"Enter a valid 'slug' consisting of letters, numbers,"
" underscores or hyphens."
),
}
default_validators
=
[
validators
.
validate_slug
]
def
__init__
(
self
,
*
args
,
**
kwargs
):
super
(
SlugField
,
self
)
.
__init__
(
*
args
,
**
kwargs
)
...
...
@@ -440,10 +431,11 @@ class SlugField(CharField):
#result.widget = copy.deepcopy(self.widget, memo)
result
.
validators
=
self
.
validators
[:]
return
result
class
ChoiceField
(
WritableField
):
type_name
=
'ChoiceField'
type_label
=
'multiple choice'
form_field_class
=
forms
.
ChoiceField
widget
=
widgets
.
Select
default_error_messages
=
{
...
...
@@ -494,6 +486,7 @@ class ChoiceField(WritableField):
class
EmailField
(
CharField
):
type_name
=
'EmailField'
type_label
=
'email'
form_field_class
=
forms
.
EmailField
default_error_messages
=
{
...
...
@@ -517,6 +510,7 @@ class EmailField(CharField):
class
RegexField
(
CharField
):
type_name
=
'RegexField'
type_label
=
'regex'
form_field_class
=
forms
.
RegexField
def
__init__
(
self
,
regex
,
max_length
=
None
,
min_length
=
None
,
*
args
,
**
kwargs
):
...
...
@@ -546,6 +540,7 @@ class RegexField(CharField):
class
DateField
(
WritableField
):
type_name
=
'DateField'
type_label
=
'date'
widget
=
widgets
.
DateInput
form_field_class
=
forms
.
DateField
...
...
@@ -609,6 +604,7 @@ class DateField(WritableField):
class
DateTimeField
(
WritableField
):
type_name
=
'DateTimeField'
type_label
=
'datetime'
widget
=
widgets
.
DateTimeInput
form_field_class
=
forms
.
DateTimeField
...
...
@@ -678,6 +674,7 @@ class DateTimeField(WritableField):
class
TimeField
(
WritableField
):
type_name
=
'TimeField'
type_label
=
'time'
widget
=
widgets
.
TimeInput
form_field_class
=
forms
.
TimeField
...
...
@@ -734,6 +731,7 @@ class TimeField(WritableField):
class
IntegerField
(
WritableField
):
type_name
=
'IntegerField'
type_label
=
'integer'
form_field_class
=
forms
.
IntegerField
default_error_messages
=
{
...
...
@@ -764,6 +762,7 @@ class IntegerField(WritableField):
class
FloatField
(
WritableField
):
type_name
=
'FloatField'
type_label
=
'float'
form_field_class
=
forms
.
FloatField
default_error_messages
=
{
...
...
@@ -783,6 +782,7 @@ class FloatField(WritableField):
class
DecimalField
(
WritableField
):
type_name
=
'DecimalField'
type_label
=
'decimal'
form_field_class
=
forms
.
DecimalField
default_error_messages
=
{
...
...
@@ -853,6 +853,7 @@ class DecimalField(WritableField):
class
FileField
(
WritableField
):
use_files
=
True
type_name
=
'FileField'
type_label
=
'file upload'
form_field_class
=
forms
.
FileField
widget
=
widgets
.
FileInput
...
...
@@ -896,6 +897,8 @@ class FileField(WritableField):
class
ImageField
(
FileField
):
use_files
=
True
type_name
=
'ImageField'
type_label
=
'image upload'
form_field_class
=
forms
.
ImageField
default_error_messages
=
{
...
...
rest_framework/generics.py
View file @
fcaee6e5
...
...
@@ -3,13 +3,13 @@ Generic views that provide commonly needed behaviour.
"""
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.http
import
Http404
from
django.shortcuts
import
get_object_or_404
from
django.utils.translation
import
ugettext
as
_
from
rest_framework
import
views
,
mixins
from
rest_framework.
exceptions
import
ConfigurationError
from
rest_framework
import
views
,
mixins
,
exceptions
from
rest_framework.
request
import
clone_request
from
rest_framework.settings
import
api_settings
import
warnings
...
...
@@ -274,7 +274,7 @@ class GenericAPIView(views.APIView):
)
filter_kwargs
=
{
self
.
slug_field
:
slug
}
else
:
raise
ConfigurationError
(
raise
exceptions
.
ConfigurationError
(
'Expected view
%
s to be called with a URL keyword argument '
'named "
%
s". Fix your URL conf, or set the `.lookup_field` '
'attribute on the view correctly.'
%
...
...
@@ -310,6 +310,41 @@ class GenericAPIView(views.APIView):
"""
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 ###
...
...
rest_framework/serializers.py
View file @
fcaee6e5
...
...
@@ -521,12 +521,16 @@ class BaseSerializer(WritableField):
return
self
.
object
@property
def
humanized
(
self
):
humanized_fields
=
SortedDict
(
[(
name
,
field
.
humanized
)
for
name
,
field
in
self
.
fields
.
iteritems
()])
return
humanized_fields
def
metadata
(
self
):
"""
Return a dictionary of metadata about the fields on the serializer.
Useful for things like responding to OPTIONS requests, or generating
API schemas for auto-documentation.
"""
return
SortedDict
(
[(
field_name
,
field
.
metadata
())
for
field_name
,
field
in
six
.
iteritems
(
self
.
fields
)]
)
class
Serializer
(
six
.
with_metaclass
(
SerializerMetaclass
,
BaseSerializer
)):
...
...
rest_framework/tests/fields.py
View file @
fcaee6e5
...
...
@@ -3,17 +3,13 @@ General serializer field tests.
"""
from
__future__
import
unicode_literals
from
collections
import
namedtupl
e
import
datetim
e
from
decimal
import
Decimal
from
uuid
import
uuid4
import
datetime
from
django
import
forms
from
django.core
import
validators
from
django.db
import
models
from
django.test
import
TestCase
from
django.utils.datastructures
import
SortedDict
from
rest_framework
import
serializers
from
rest_framework.fields
import
Field
,
CharField
from
rest_framework.serializers
import
Serializer
...
...
@@ -784,12 +780,12 @@ class SlugFieldTests(TestCase):
"""
class
SlugFieldSerializer
(
serializers
.
ModelSerializer
):
slug_field
=
serializers
.
SlugField
(
source
=
'slug_field'
,
max_length
=
20
,
required
=
True
)
class
Meta
:
model
=
self
.
SlugFieldModel
s
=
SlugFieldSerializer
(
data
=
{
'slug_field'
:
'a b'
})
self
.
assertEqual
(
s
.
is_valid
(),
False
)
self
.
assertEqual
(
s
.
errors
,
{
'slug_field'
:
[
"Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens."
]})
...
...
@@ -839,7 +835,7 @@ class URLFieldTests(TestCase):
'max_length'
),
20
)
class
HumanizedField
(
TestCase
):
class
FieldMetadata
(
TestCase
):
def
setUp
(
self
):
self
.
required_field
=
Field
()
self
.
required_field
.
label
=
uuid4
()
.
hex
...
...
@@ -849,41 +845,35 @@ class HumanizedField(TestCase):
self
.
optional_field
.
label
=
uuid4
()
.
hex
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
):
self
.
assertEqual
(
self
.
required_field
.
humanized
[
'required'
],
True
)
self
.
assertEqual
(
self
.
required_field
.
metadata
()
[
'required'
],
True
)
def
test_optional
(
self
):
self
.
assertEqual
(
self
.
optional_field
.
humanized
[
'required'
],
False
)
self
.
assertEqual
(
self
.
optional_field
.
metadata
()
[
'required'
],
False
)
def
test_label
(
self
):
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
)
field2
=
CharField
(
10
,
required
=
False
)
class
HumanizedSerializer
(
TestCase
):
class
MetadataSerializerTestCase
(
TestCase
):
def
setUp
(
self
):
self
.
serializer
=
Humanizable
Serializer
()
self
.
serializer
=
Metadata
Serializer
()
def
test_
humanized
(
self
):
humanized
=
self
.
serializer
.
humanized
def
test_
serializer_metadata
(
self
):
metadata
=
self
.
serializer
.
metadata
()
expected
=
{
'field1'
:
{
u'required'
:
True
,
u'max_length'
:
3
,
u'type'
:
u'CharField'
,
u'read_only'
:
False
},
'field2'
:
{
u'required'
:
False
,
u'max_length'
:
10
,
u'type'
:
u'CharField'
,
u'read_only'
:
False
}}
self
.
assertEqual
(
set
(
expected
.
keys
()),
set
(
humanized
.
keys
()))
for
k
,
v
in
humanized
.
iteritems
():
self
.
assertEqual
(
v
,
expected
[
k
])
'field1'
:
{
'required'
:
True
,
'max_length'
:
3
,
'type'
:
'string'
,
'read_only'
:
False
},
'field2'
:
{
'required'
:
False
,
'max_length'
:
10
,
'type'
:
'string'
,
'read_only'
:
False
}}
self
.
assertEqual
(
expected
,
metadata
)
rest_framework/tests/generics.py
View file @
fcaee6e5
...
...
@@ -122,21 +122,24 @@ class TestRootView(TestCase):
],
'name'
:
'Root'
,
'description'
:
'Example description for OPTIONS.'
,
'actions'
:
{}
}
expected
[
'actions'
][
'GET'
]
=
{}
expected
[
'actions'
][
'POST'
]
=
{
'text'
:
{
'max_length'
:
100
,
'read_only'
:
False
,
'required'
:
True
,
'type'
:
'String'
,
},
'id'
:
{
'read_only'
:
True
,
'required'
:
False
,
'type'
:
'Integer'
,
},
'actions'
:
{
'POST'
:
{
'text'
:
{
'max_length'
:
100
,
'read_only'
:
False
,
'required'
:
True
,
'type'
:
'string'
,
"label"
:
"Text comes here"
,
"help_text"
:
"Text description."
},
'id'
:
{
'read_only'
:
True
,
'required'
:
False
,
'type'
:
'integer'
,
'label'
:
'ID'
,
},
}
}
}
self
.
assertEqual
(
response
.
status_code
,
status
.
HTTP_200_OK
)
self
.
assertEqual
(
response
.
data
,
expected
)
...
...
@@ -239,9 +242,9 @@ class TestInstanceView(TestCase):
"""
OPTIONS requests to RetrieveUpdateDestroyAPIView should return metadata
"""
request
=
factory
.
options
(
'/'
)
with
self
.
assertNumQueries
(
0
):
response
=
self
.
view
(
request
)
.
render
()
request
=
factory
.
options
(
'/
1
'
)
with
self
.
assertNumQueries
(
1
):
response
=
self
.
view
(
request
,
pk
=
1
)
.
render
()
expected
=
{
'parses'
:
[
'application/json'
,
...
...
@@ -254,24 +257,25 @@ class TestInstanceView(TestCase):
],
'name'
:
'Instance'
,
'description'
:
'Example description for OPTIONS.'
,
'actions'
:
{
}
}
for
method
in
(
'GET'
,
'DELETE'
):
expected
[
'actions'
][
method
]
=
{}
for
method
in
(
'PATCH'
,
'PUT'
):
expected
[
'actions'
][
method
]
=
{
'text'
:
{
'max_length'
:
100
,
'read_only'
:
False
,
'required'
:
True
,
'
type'
:
'String'
,
}
,
'id'
:
{
'read_only'
:
True
,
'required'
:
False
,
'type'
:
'Integer'
,
}
,
'actions'
:
{
'PUT'
:
{
'text'
:
{
'max_length'
:
100
,
'read_only'
:
False
,
'required'
:
True
,
'type'
:
'string'
,
'label'
:
'Text comes here'
,
'help_text'
:
'Text description.'
}
,
'
id'
:
{
'read_only'
:
True
,
'required'
:
False
,
'type'
:
'integer'
,
'label'
:
'ID'
,
}
,
}
}
}
self
.
assertEqual
(
response
.
status_code
,
status
.
HTTP_200_OK
)
self
.
assertEqual
(
response
.
data
,
expected
)
...
...
rest_framework/tests/permissions.py
View file @
fcaee6e5
...
...
@@ -114,44 +114,41 @@ class ModelPermissionsIntegrationTests(TestCase):
response
=
root_view
(
request
,
pk
=
'1'
)
self
.
assertEqual
(
response
.
status_code
,
status
.
HTTP_200_OK
)
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'
,
HTTP_AUTHORIZATION
=
self
.
permitted_credentials
)
response
=
instance_view
(
request
,
pk
=
'1'
)
self
.
assertEqual
(
response
.
status_code
,
status
.
HTTP_200_OK
)
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
):
request
=
factory
.
options
(
'/'
,
content_type
=
'application/json'
,
HTTP_AUTHORIZATION
=
self
.
disallowed_credentials
)
response
=
root_view
(
request
,
pk
=
'1'
)
self
.
assertEqual
(
response
.
status_code
,
status
.
HTTP_200_OK
)
self
.
assertIn
(
'actions'
,
response
.
data
)
self
.
assertEquals
(
response
.
data
[
'actions'
]
.
keys
(),
[
'GET'
,])
self
.
assertNotIn
(
'actions'
,
response
.
data
)
request
=
factory
.
options
(
'/1'
,
content_type
=
'application/json'
,
HTTP_AUTHORIZATION
=
self
.
disallowed_credentials
)
response
=
instance_view
(
request
,
pk
=
'1'
)
self
.
assertEqual
(
response
.
status_code
,
status
.
HTTP_200_OK
)
self
.
assertIn
(
'actions'
,
response
.
data
)
self
.
assertEquals
(
response
.
data
[
'actions'
]
.
keys
(),
[
'GET'
,])
self
.
assertNotIn
(
'actions'
,
response
.
data
)
def
test_options_updateonly
(
self
):
request
=
factory
.
options
(
'/'
,
content_type
=
'application/json'
,
HTTP_AUTHORIZATION
=
self
.
updateonly_credentials
)
response
=
root_view
(
request
,
pk
=
'1'
)
self
.
assertEqual
(
response
.
status_code
,
status
.
HTTP_200_OK
)
self
.
assertIn
(
'actions'
,
response
.
data
)
self
.
assertEquals
(
response
.
data
[
'actions'
]
.
keys
(),
[
'GET'
,])
self
.
assertNotIn
(
'actions'
,
response
.
data
)
request
=
factory
.
options
(
'/1'
,
content_type
=
'application/json'
,
HTTP_AUTHORIZATION
=
self
.
updateonly_credentials
)
response
=
instance_view
(
request
,
pk
=
'1'
)
self
.
assertEqual
(
response
.
status_code
,
status
.
HTTP_200_OK
)
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
):
...
...
rest_framework/tests/renderers.py
View file @
fcaee6e5
...
...
@@ -6,6 +6,7 @@ from django.core.cache import cache
from
django.test
import
TestCase
from
django.test.client
import
RequestFactory
from
django.utils
import
unittest
from
django.utils.translation
import
ugettext_lazy
as
_
from
rest_framework
import
status
,
permissions
from
rest_framework.compat
import
yaml
,
etree
,
patterns
,
url
,
include
from
rest_framework.response
import
Response
...
...
@@ -238,6 +239,13 @@ class JSONRendererTests(TestCase):
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
):
"""
Test basic JSON rendering.
...
...
rest_framework/utils/encoders.py
View file @
fcaee6e5
...
...
@@ -3,7 +3,8 @@ Helper classes for parsers.
"""
from
__future__
import
unicode_literals
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
import
datetime
import
decimal
...
...
@@ -19,7 +20,9 @@ class JSONEncoder(json.JSONEncoder):
def
default
(
self
,
o
):
# For Date Time string spec, see ECMA 262
# 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
()
if
o
.
microsecond
:
r
=
r
[:
23
]
+
r
[
26
:]
...
...
rest_framework/views.py
View file @
fcaee6e5
...
...
@@ -5,12 +5,11 @@ from __future__ import unicode_literals
from
django.core.exceptions
import
PermissionDenied
from
django.http
import
Http404
,
HttpResponse
from
django.utils.datastructures
import
SortedDict
from
django.views.decorators.csrf
import
csrf_exempt
from
rest_framework
import
status
,
exceptions
from
rest_framework.compat
import
View
from
rest_framework.fields
import
humanize_form_fields
from
rest_framework.request
import
clone_request
,
Request
from
rest_framework.request
import
Request
from
rest_framework.response
import
Response
from
rest_framework.settings
import
api_settings
from
rest_framework.utils.formatting
import
get_view_name
,
get_view_description
...
...
@@ -54,53 +53,6 @@ class APIView(View):
'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
):
"""
If `request.method` does not correspond to a handler method,
...
...
@@ -383,3 +335,15 @@ class APIView(View):
a less useful default implementation.
"""
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