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
d934824b
Commit
d934824b
authored
Sep 05, 2014
by
Tom Christie
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Workin on
parent
c1036c17
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
159 additions
and
168 deletions
+159
-168
rest_framework/exceptions.py
+7
-2
rest_framework/fields.py
+22
-4
rest_framework/generics.py
+1
-29
rest_framework/mixins.py
+47
-36
rest_framework/serializers.py
+41
-23
tests/test_generics.py
+5
-36
tests/test_validation.py
+2
-3
tests/test_write_only_fields.py
+34
-35
No files found.
rest_framework/exceptions.py
View file @
d934824b
...
...
@@ -15,7 +15,7 @@ class APIException(Exception):
Subclasses should provide `.status_code` and `.default_detail` properties.
"""
status_code
=
status
.
HTTP_500_INTERNAL_SERVER_ERROR
default_detail
=
''
default_detail
=
'
A server error occured
'
def
__init__
(
self
,
detail
=
None
):
self
.
detail
=
detail
or
self
.
default_detail
...
...
@@ -29,6 +29,11 @@ class ParseError(APIException):
default_detail
=
'Malformed request.'
class
ValidationError
(
APIException
):
status_code
=
status
.
HTTP_400_BAD_REQUEST
default_detail
=
'Invalid data in request.'
class
AuthenticationFailed
(
APIException
):
status_code
=
status
.
HTTP_401_UNAUTHORIZED
default_detail
=
'Incorrect authentication credentials.'
...
...
@@ -54,7 +59,7 @@ class MethodNotAllowed(APIException):
class
NotAcceptable
(
APIException
):
status_code
=
status
.
HTTP_406_NOT_ACCEPTABLE
default_detail
=
"Could not satisfy the request
's
Accept header"
default_detail
=
"Could not satisfy the request Accept header"
def
__init__
(
self
,
detail
=
None
,
available_renderers
=
None
):
self
.
detail
=
detail
or
self
.
default_detail
...
...
rest_framework/fields.py
View file @
d934824b
from
rest_framework.exceptions
import
ValidationError
from
rest_framework.utils
import
html
import
inspect
...
...
@@ -59,10 +60,6 @@ def set_value(dictionary, keys, value):
dictionary
[
keys
[
-
1
]]
=
value
class
ValidationError
(
Exception
):
pass
class
SkipField
(
Exception
):
pass
...
...
@@ -204,6 +201,22 @@ class Field(object):
msg
=
self
.
_MISSING_ERROR_MESSAGE
.
format
(
class_name
=
class_name
,
key
=
key
)
raise
AssertionError
(
msg
)
def
__new__
(
cls
,
*
args
,
**
kwargs
):
instance
=
super
(
Field
,
cls
)
.
__new__
(
cls
)
instance
.
_args
=
args
instance
.
_kwargs
=
kwargs
return
instance
def
__repr__
(
self
):
arg_string
=
', '
.
join
([
repr
(
val
)
for
val
in
self
.
_args
])
kwarg_string
=
', '
.
join
([
'
%
s=
%
s'
%
(
key
,
repr
(
val
))
for
key
,
val
in
self
.
_kwargs
.
items
()
])
if
arg_string
and
kwarg_string
:
arg_string
+=
', '
class_name
=
self
.
__class__
.
__name__
return
"
%
s(
%
s
%
s)"
%
(
class_name
,
arg_string
,
kwarg_string
)
class
BooleanField
(
Field
):
MESSAGES
=
{
...
...
@@ -308,6 +321,11 @@ class IntegerField(Field):
'invalid_integer'
:
'A valid integer is required.'
}
def
__init__
(
self
,
**
kwargs
):
self
.
max_value
=
kwargs
.
pop
(
'max_value'
)
self
.
min_value
=
kwargs
.
pop
(
'min_value'
)
super
(
CharField
,
self
)
.
__init__
(
**
kwargs
)
def
to_native
(
self
,
data
):
try
:
data
=
int
(
str
(
data
))
...
...
rest_framework/generics.py
View file @
d934824b
...
...
@@ -27,7 +27,7 @@ def strict_positive_int(integer_string, cutoff=None):
def
get_object_or_404
(
queryset
,
*
filter_args
,
**
filter_kwargs
):
"""
Same as Django's standard shortcut, but make sure to raise 404
Same as Django's standard shortcut, but make sure to
also
raise 404
if the filter_kwargs don't match the required types.
"""
try
:
...
...
@@ -249,34 +249,6 @@ class GenericAPIView(views.APIView):
#
# The are not called by GenericAPIView directly,
# but are used by the mixin methods.
def
pre_save
(
self
,
obj
):
"""
Placeholder method for calling before saving an object.
May be used to set attributes on the object that are implicit
in either the request, or the url.
"""
pass
def
post_save
(
self
,
obj
,
created
=
False
):
"""
Placeholder method for calling after saving an object.
"""
pass
def
pre_delete
(
self
,
obj
):
"""
Placeholder method for calling before deleting an object.
"""
pass
def
post_delete
(
self
,
obj
):
"""
Placeholder method for calling after deleting an object.
"""
pass
def
metadata
(
self
,
request
):
"""
Return a dictionary of metadata about the view.
...
...
rest_framework/mixins.py
View file @
d934824b
...
...
@@ -19,14 +19,10 @@ class CreateModelMixin(object):
"""
def
create
(
self
,
request
,
*
args
,
**
kwargs
):
serializer
=
self
.
get_serializer
(
data
=
request
.
DATA
)
if
serializer
.
is_valid
():
self
.
object
=
serializer
.
save
()
headers
=
self
.
get_success_headers
(
serializer
.
data
)
return
Response
(
serializer
.
data
,
status
=
status
.
HTTP_201_CREATED
,
headers
=
headers
)
return
Response
(
serializer
.
errors
,
status
=
status
.
HTTP_400_BAD_REQUEST
)
serializer
.
is_valid
(
raise_exception
=
True
)
serializer
.
save
()
headers
=
self
.
get_success_headers
(
serializer
.
data
)
return
Response
(
serializer
.
data
,
status
=
status
.
HTTP_201_CREATED
,
headers
=
headers
)
def
get_success_headers
(
self
,
data
):
try
:
...
...
@@ -40,15 +36,12 @@ class ListModelMixin(object):
List a queryset.
"""
def
list
(
self
,
request
,
*
args
,
**
kwargs
):
self
.
object_list
=
self
.
filter_queryset
(
self
.
get_queryset
())
# Switch between paginated or standard style responses
page
=
self
.
paginate_queryset
(
self
.
object_list
)
instance
=
self
.
filter_queryset
(
self
.
get_queryset
())
page
=
self
.
paginate_queryset
(
instance
)
if
page
is
not
None
:
serializer
=
self
.
get_pagination_serializer
(
page
)
else
:
serializer
=
self
.
get_serializer
(
self
.
object_list
,
many
=
True
)
serializer
=
self
.
get_serializer
(
instance
,
many
=
True
)
return
Response
(
serializer
.
data
)
...
...
@@ -57,8 +50,8 @@ class RetrieveModelMixin(object):
Retrieve a model instance.
"""
def
retrieve
(
self
,
request
,
*
args
,
**
kwargs
):
self
.
object
=
self
.
get_object
()
serializer
=
self
.
get_serializer
(
self
.
object
)
instance
=
self
.
get_object
()
serializer
=
self
.
get_serializer
(
instance
)
return
Response
(
serializer
.
data
)
...
...
@@ -68,22 +61,52 @@ class UpdateModelMixin(object):
"""
def
update
(
self
,
request
,
*
args
,
**
kwargs
):
partial
=
kwargs
.
pop
(
'partial'
,
False
)
self
.
object
=
self
.
get_object_or_none
()
instance
=
self
.
get_object
()
serializer
=
self
.
get_serializer
(
instance
,
data
=
request
.
DATA
,
partial
=
partial
)
serializer
.
is_valid
(
raise_exception
=
True
)
serializer
.
save
()
return
Response
(
serializer
.
data
)
def
partial_update
(
self
,
request
,
*
args
,
**
kwargs
):
kwargs
[
'partial'
]
=
True
return
self
.
update
(
request
,
*
args
,
**
kwargs
)
serializer
=
self
.
get_serializer
(
self
.
object
,
data
=
request
.
DATA
,
partial
=
partial
)
class
DestroyModelMixin
(
object
):
"""
Destroy a model instance.
"""
def
destroy
(
self
,
request
,
*
args
,
**
kwargs
):
instance
=
self
.
get_object
()
instance
.
delete
()
return
Response
(
status
=
status
.
HTTP_204_NO_CONTENT
)
if
not
serializer
.
is_valid
():
return
Response
(
serializer
.
errors
,
status
=
status
.
HTTP_400_BAD_REQUEST
)
if
self
.
object
is
None
:
# The AllowPUTAsCreateMixin was previously the default behaviour
# for PUT requests. This has now been removed and must be *explictly*
# included if it is the behavior that you want.
# For more info see: ...
class
AllowPUTAsCreateMixin
(
object
):
"""
The following mixin class may be used in order to support PUT-as-create
behavior for incoming requests.
"""
def
update
(
self
,
request
,
*
args
,
**
kwargs
):
partial
=
kwargs
.
pop
(
'partial'
,
False
)
instance
=
self
.
get_object_or_none
()
serializer
=
self
.
get_serializer
(
instance
,
data
=
request
.
DATA
,
partial
=
partial
)
serializer
.
is_valid
(
raise_exception
=
True
)
if
instance
is
None
:
lookup_url_kwarg
=
self
.
lookup_url_kwarg
or
self
.
lookup_field
lookup_value
=
self
.
kwargs
[
lookup_url_kwarg
]
extras
=
{
self
.
lookup_field
:
lookup_value
}
se
lf
.
object
=
se
rializer
.
save
(
extras
=
extras
)
serializer
.
save
(
extras
=
extras
)
return
Response
(
serializer
.
data
,
status
=
status
.
HTTP_201_CREATED
)
se
lf
.
object
=
se
rializer
.
save
()
return
Response
(
serializer
.
data
,
status
=
status
.
HTTP_200_OK
)
serializer
.
save
()
return
Response
(
serializer
.
data
)
def
partial_update
(
self
,
request
,
*
args
,
**
kwargs
):
kwargs
[
'partial'
]
=
True
...
...
@@ -103,15 +126,3 @@ class UpdateModelMixin(object):
# PATCH requests where the object does not exist should still
# return a 404 response.
raise
class
DestroyModelMixin
(
object
):
"""
Destroy a model instance.
"""
def
destroy
(
self
,
request
,
*
args
,
**
kwargs
):
obj
=
self
.
get_object
()
self
.
pre_delete
(
obj
)
obj
.
delete
()
self
.
post_delete
(
obj
)
return
Response
(
status
=
status
.
HTTP_204_NO_CONTENT
)
rest_framework/serializers.py
View file @
d934824b
...
...
@@ -13,7 +13,8 @@ response content is handled by parsers and renderers.
from
django.db
import
models
from
django.utils
import
six
from
collections
import
namedtuple
,
OrderedDict
from
rest_framework.fields
import
empty
,
set_value
,
Field
,
SkipField
,
ValidationError
from
rest_framework.exceptions
import
ValidationError
from
rest_framework.fields
import
empty
,
set_value
,
Field
,
SkipField
from
rest_framework.settings
import
api_settings
from
rest_framework.utils
import
html
import
copy
...
...
@@ -34,43 +35,53 @@ FieldResult = namedtuple('FieldResult', ['field', 'value', 'error'])
class
BaseSerializer
(
Field
):
"""
The BaseSerializer class provides a minimal class which may be used
for writing custom serializer implementations.
"""
def
__init__
(
self
,
instance
=
None
,
data
=
None
,
**
kwargs
):
super
(
BaseSerializer
,
self
)
.
__init__
(
**
kwargs
)
self
.
instance
=
instance
self
.
_initial_data
=
data
def
to_native
(
self
,
data
):
raise
NotImplementedError
()
raise
NotImplementedError
(
'`to_native()` must be implemented.'
)
def
to_primative
(
self
,
instance
):
raise
NotImplementedError
()
raise
NotImplementedError
(
'`to_primative()` must be implemented.'
)
def
update
(
self
,
instance
):
raise
NotImplementedError
()
def
update
(
self
,
instance
,
attrs
):
raise
NotImplementedError
(
'`update()` must be implemented.'
)
def
create
(
self
):
raise
NotImplementedError
()
def
create
(
self
,
attrs
):
raise
NotImplementedError
(
'`create()` must be implemented.'
)
def
save
(
self
,
extras
=
None
):
if
extras
is
not
None
:
self
.
_
validated_data
.
update
(
extras
)
self
.
validated_data
.
update
(
extras
)
if
self
.
instance
is
not
None
:
self
.
update
(
self
.
instance
)
self
.
update
(
self
.
instance
,
self
.
_validated_data
)
else
:
self
.
instance
=
self
.
create
()
self
.
instance
=
self
.
create
(
self
.
_validated_data
)
return
self
.
instance
def
is_valid
(
self
):
try
:
self
.
_validated_data
=
self
.
to_native
(
self
.
_initial_data
)
except
ValidationError
as
exc
:
self
.
_validated_data
=
{}
self
.
_errors
=
exc
.
args
[
0
]
return
False
self
.
_errors
=
{}
return
True
def
is_valid
(
self
,
raise_exception
=
False
):
if
not
hasattr
(
self
,
'_validated_data'
):
try
:
self
.
_validated_data
=
self
.
to_native
(
self
.
_initial_data
)
except
ValidationError
as
exc
:
self
.
_validated_data
=
{}
self
.
_errors
=
exc
.
detail
else
:
self
.
_errors
=
{}
if
self
.
_errors
and
raise_exception
:
raise
ValidationError
(
self
.
_errors
)
return
not
bool
(
self
.
_errors
)
@property
def
data
(
self
):
...
...
@@ -184,14 +195,20 @@ class Serializer(BaseSerializer):
"""
Dict of native values <- Dict of primitive datatypes.
"""
if
not
isinstance
(
data
,
dict
):
raise
ValidationError
({
'non_field_errors'
:
[
'Invalid data'
]})
ret
=
{}
errors
=
{}
fields
=
[
field
for
field
in
self
.
fields
.
values
()
if
not
field
.
read_only
]
for
field
in
fields
:
validate_method
=
getattr
(
self
,
'validate_'
+
field
.
field_name
,
None
)
primitive_value
=
field
.
get_value
(
data
)
try
:
validated_value
=
field
.
validate
(
primitive_value
)
if
validate_method
is
not
None
:
validated_value
=
validate_method
(
validated_value
)
except
ValidationError
as
exc
:
errors
[
field
.
field_name
]
=
str
(
exc
)
except
SkipField
:
...
...
@@ -202,6 +219,7 @@ class Serializer(BaseSerializer):
if
errors
:
raise
ValidationError
(
errors
)
# TODO: 'Non field errors'
return
self
.
validate
(
ret
)
def
to_primative
(
self
,
instance
):
...
...
@@ -340,12 +358,12 @@ class ModelSerializer(Serializer):
self
.
opts
=
self
.
_options_class
(
self
.
Meta
)
super
(
ModelSerializer
,
self
)
.
__init__
(
*
args
,
**
kwargs
)
def
create
(
self
):
def
create
(
self
,
attrs
):
ModelClass
=
self
.
opts
.
model
return
ModelClass
.
objects
.
create
(
**
self
.
validated_data
)
return
ModelClass
.
objects
.
create
(
**
attrs
)
def
update
(
self
,
obj
):
for
attr
,
value
in
self
.
validated_data
.
items
():
def
update
(
self
,
obj
,
attrs
):
for
attr
,
value
in
attrs
.
items
():
setattr
(
obj
,
attr
,
value
)
obj
.
save
()
...
...
tests/test_generics.py
View file @
d934824b
...
...
@@ -360,18 +360,15 @@ class TestInstanceView(TestCase):
def
test_put_to_deleted_instance
(
self
):
"""
PUT requests to RetrieveUpdateDestroyAPIView should
create an object
if i
t does not currently exist.
PUT requests to RetrieveUpdateDestroyAPIView should
return 404 if
an objec
t does not currently exist.
"""
self
.
objects
.
get
(
id
=
1
)
.
delete
()
data
=
{
'text'
:
'foobar'
}
request
=
factory
.
put
(
'/1'
,
data
,
format
=
'json'
)
with
self
.
assertNumQueries
(
2
):
with
self
.
assertNumQueries
(
1
):
response
=
self
.
view
(
request
,
pk
=
1
)
.
render
()
self
.
assertEqual
(
response
.
status_code
,
status
.
HTTP_201_CREATED
)
self
.
assertEqual
(
response
.
data
,
{
'id'
:
1
,
'text'
:
'foobar'
})
updated
=
self
.
objects
.
get
(
id
=
1
)
self
.
assertEqual
(
updated
.
text
,
'foobar'
)
self
.
assertEqual
(
response
.
status_code
,
status
.
HTTP_404_NOT_FOUND
)
def
test_put_to_filtered_out_instance
(
self
):
"""
...
...
@@ -382,35 +379,7 @@ class TestInstanceView(TestCase):
filtered_out_pk
=
BasicModel
.
objects
.
filter
(
text
=
'filtered out'
)[
0
]
.
pk
request
=
factory
.
put
(
'/{0}'
.
format
(
filtered_out_pk
),
data
,
format
=
'json'
)
response
=
self
.
view
(
request
,
pk
=
filtered_out_pk
)
.
render
()
self
.
assertEqual
(
response
.
status_code
,
status
.
HTTP_400_BAD_REQUEST
)
def
test_put_as_create_on_id_based_url
(
self
):
"""
PUT requests to RetrieveUpdateDestroyAPIView should create an object
at the requested url if it doesn't exist.
"""
data
=
{
'text'
:
'foobar'
}
# pk fields can not be created on demand, only the database can set the pk for a new object
request
=
factory
.
put
(
'/5'
,
data
,
format
=
'json'
)
with
self
.
assertNumQueries
(
2
):
response
=
self
.
view
(
request
,
pk
=
5
)
.
render
()
self
.
assertEqual
(
response
.
status_code
,
status
.
HTTP_201_CREATED
)
new_obj
=
self
.
objects
.
get
(
pk
=
5
)
self
.
assertEqual
(
new_obj
.
text
,
'foobar'
)
def
test_put_as_create_on_slug_based_url
(
self
):
"""
PUT requests to RetrieveUpdateDestroyAPIView should create an object
at the requested url if possible, else return HTTP_403_FORBIDDEN error-response.
"""
data
=
{
'text'
:
'foobar'
}
request
=
factory
.
put
(
'/test_slug'
,
data
,
format
=
'json'
)
with
self
.
assertNumQueries
(
2
):
response
=
self
.
slug_based_view
(
request
,
slug
=
'test_slug'
)
.
render
()
self
.
assertEqual
(
response
.
status_code
,
status
.
HTTP_201_CREATED
)
self
.
assertEqual
(
response
.
data
,
{
'slug'
:
'test_slug'
,
'text'
:
'foobar'
})
new_obj
=
SlugBasedModel
.
objects
.
get
(
slug
=
'test_slug'
)
self
.
assertEqual
(
new_obj
.
text
,
'foobar'
)
self
.
assertEqual
(
response
.
status_code
,
status
.
HTTP_404_NOT_FOUND
)
def
test_patch_cannot_create_an_object
(
self
):
"""
...
...
tests/test_validation.py
View file @
d934824b
...
...
@@ -48,11 +48,10 @@ class ShouldValidateModel(models.Model):
class
ShouldValidateModelSerializer
(
serializers
.
ModelSerializer
):
renamed
=
serializers
.
CharField
(
source
=
'should_validate_field'
,
required
=
False
)
def
validate_renamed
(
self
,
attrs
,
source
):
value
=
attrs
[
source
]
def
validate_renamed
(
self
,
value
):
if
len
(
value
)
<
3
:
raise
serializers
.
ValidationError
(
'Minimum 3 characters.'
)
return
attrs
return
value
class
Meta
:
model
=
ShouldValidateModel
...
...
tests/test_write_only_fields.py
View file @
d934824b
from
django.db
import
models
from
django.test
import
TestCase
from
rest_framework
import
serializers
#
from django.db import models
#
from django.test import TestCase
#
from rest_framework import serializers
class
ExampleModel
(
models
.
Model
):
email
=
models
.
EmailField
(
max_length
=
100
)
password
=
models
.
CharField
(
max_length
=
100
)
#
class ExampleModel(models.Model):
#
email = models.EmailField(max_length=100)
#
password = models.CharField(max_length=100)
class
WriteOnlyFieldTests
(
TestCase
):
def
test_write_only_fields
(
self
):
class
ExampleSerializer
(
serializers
.
Serializer
):
email
=
serializers
.
EmailField
()
password
=
serializers
.
CharField
(
write_only
=
True
)
#
class WriteOnlyFieldTests(TestCase):
#
def test_write_only_fields(self):
#
class ExampleSerializer(serializers.Serializer):
#
email = serializers.EmailField()
#
password = serializers.CharField(write_only=True)
data
=
{
'email'
:
'foo@example.com'
,
'password'
:
'123'
}
serializer
=
ExampleSerializer
(
data
=
data
)
self
.
assertTrue
(
serializer
.
is_valid
())
self
.
assertEquals
(
serializer
.
object
,
data
)
self
.
assertEquals
(
serializer
.
data
,
{
'email'
:
'foo@example.com'
})
#
data = {
#
'email': 'foo@example.com',
#
'password': '123'
#
}
#
serializer = ExampleSerializer(data=data)
#
self.assertTrue(serializer.is_valid())
# self.assertEquals(serializer.validated_data
, data)
#
self.assertEquals(serializer.data, {'email': 'foo@example.com'})
def
test_write_only_fields_meta
(
self
):
class
ExampleSerializer
(
serializers
.
ModelSerializer
):
class
Meta
:
model
=
ExampleModel
fields
=
(
'email'
,
'password'
)
write_only_fields
=
(
'password'
,)
#
def test_write_only_fields_meta(self):
#
class ExampleSerializer(serializers.ModelSerializer):
#
class Meta:
#
model = ExampleModel
#
fields = ('email', 'password')
#
write_only_fields = ('password',)
data
=
{
'email'
:
'foo@example.com'
,
'password'
:
'123'
}
serializer
=
ExampleSerializer
(
data
=
data
)
self
.
assertTrue
(
serializer
.
is_valid
())
self
.
assertTrue
(
isinstance
(
serializer
.
object
,
ExampleModel
))
self
.
assertEquals
(
serializer
.
object
.
email
,
data
[
'email'
])
self
.
assertEquals
(
serializer
.
object
.
password
,
data
[
'password'
])
self
.
assertEquals
(
serializer
.
data
,
{
'email'
:
'foo@example.com'
})
# data = {
# 'email': 'foo@example.com',
# 'password': '123'
# }
# serializer = ExampleSerializer(data=data)
# self.assertTrue(serializer.is_valid())
# self.assertTrue(isinstance(serializer.object, ExampleModel))
# self.assertEquals(serializer.validated_data, data)
# self.assertEquals(serializer.data, {'email': 'foo@example.com'})
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