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
ae53fdff
Commit
ae53fdff
authored
Oct 22, 2014
by
Tom Christie
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
First pass at unique_for_date, unique_for_month, unique_for_year
parent
c5d1be8e
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
191 additions
and
34 deletions
+191
-34
rest_framework/fields.py
+8
-2
rest_framework/serializers.py
+16
-10
rest_framework/validators.py
+99
-22
tests/test_validators.py
+68
-0
No files found.
rest_framework/fields.py
View file @
ae53fdff
...
@@ -268,11 +268,17 @@ class Field(object):
...
@@ -268,11 +268,17 @@ class Field(object):
"""
"""
errors
=
[]
errors
=
[]
for
validator
in
self
.
validators
:
for
validator
in
self
.
validators
:
if
getattr
(
validator
,
'requires_context'
,
False
):
if
hasattr
(
validator
,
'set_context'
):
validator
.
serializer_field
=
self
validator
.
set_context
(
self
)
try
:
try
:
validator
(
value
)
validator
(
value
)
except
ValidationError
as
exc
:
except
ValidationError
as
exc
:
# If the validation error contains a mapping of fields to
# errors then simply raise it immediately rather than
# attempting to accumulate a list of errors.
if
isinstance
(
exc
.
detail
,
dict
):
raise
errors
.
extend
(
exc
.
detail
)
errors
.
extend
(
exc
.
detail
)
except
DjangoValidationError
as
exc
:
except
DjangoValidationError
as
exc
:
errors
.
extend
(
exc
.
messages
)
errors
.
extend
(
exc
.
messages
)
...
...
rest_framework/serializers.py
View file @
ae53fdff
...
@@ -23,7 +23,9 @@ from rest_framework.utils.field_mapping import (
...
@@ -23,7 +23,9 @@ from rest_framework.utils.field_mapping import (
get_relation_kwargs
,
get_nested_relation_kwargs
,
get_relation_kwargs
,
get_nested_relation_kwargs
,
ClassLookupDict
ClassLookupDict
)
)
from
rest_framework.validators
import
UniqueTogetherValidator
from
rest_framework.validators
import
(
UniqueForDateValidator
,
UniqueTogetherValidator
)
import
copy
import
copy
import
inspect
import
inspect
import
warnings
import
warnings
...
@@ -578,15 +580,9 @@ class ModelSerializer(Serializer):
...
@@ -578,15 +580,9 @@ class ModelSerializer(Serializer):
validators
=
[]
validators
=
[]
model_class
=
self
.
Meta
.
model
model_class
=
self
.
Meta
.
model
for
unique_together
in
model_class
.
_meta
.
unique_together
:
# Note that we make sure to check `unique_together` both on the
if
field_names
.
issuperset
(
set
(
unique_together
)):
# base model class, but also on any parent classes.
validator
=
UniqueTogetherValidator
(
for
parent_class
in
[
model_class
]
+
list
(
model_class
.
_meta
.
parents
.
keys
()):
queryset
=
model_class
.
_default_manager
,
fields
=
unique_together
)
validators
.
append
(
validator
)
for
parent_class
in
model_class
.
_meta
.
parents
.
keys
():
for
unique_together
in
parent_class
.
_meta
.
unique_together
:
for
unique_together
in
parent_class
.
_meta
.
unique_together
:
if
field_names
.
issuperset
(
set
(
unique_together
)):
if
field_names
.
issuperset
(
set
(
unique_together
)):
validator
=
UniqueTogetherValidator
(
validator
=
UniqueTogetherValidator
(
...
@@ -595,6 +591,16 @@ class ModelSerializer(Serializer):
...
@@ -595,6 +591,16 @@ class ModelSerializer(Serializer):
)
)
validators
.
append
(
validator
)
validators
.
append
(
validator
)
info
=
model_meta
.
get_field_info
(
model_class
)
for
field_name
,
field
in
info
.
fields_and_pk
.
items
():
if
field
.
unique_for_date
and
field_name
in
field_names
:
validator
=
UniqueForDateValidator
(
queryset
=
model_class
.
_default_manager
,
field
=
field_name
,
date_field
=
field
.
unique_for_date
)
validators
.
append
(
validator
)
return
validators
return
validators
def
_get_base_fields
(
self
):
def
_get_base_fields
(
self
):
...
...
rest_framework/validators.py
View file @
ae53fdff
...
@@ -6,38 +6,36 @@ This gives us better separation of concerns, allows us to use single-step
...
@@ -6,38 +6,36 @@ This gives us better separation of concerns, allows us to use single-step
object creation, and makes it possible to switch between using the implicit
object creation, and makes it possible to switch between using the implicit
`ModelSerializer` class and an equivelent explicit `Serializer` class.
`ModelSerializer` class and an equivelent explicit `Serializer` class.
"""
"""
from
django.core.exceptions
import
ValidationError
from
django.utils.translation
import
ugettext_lazy
as
_
from
django.utils.translation
import
ugettext_lazy
as
_
from
rest_framework.exceptions
import
ValidationError
from
rest_framework.utils.representation
import
smart_repr
from
rest_framework.utils.representation
import
smart_repr
class
UniqueValidator
:
class
UniqueValidator
:
"""
"""
Validator that corresponds to `unique=True` on a model field.
Validator that corresponds to `unique=True` on a model field.
Should be applied to an individual field on the serializer.
"""
"""
# Validators with `requires_context` will have the field instance
# passed to them when the field is instantiated.
requires_context
=
True
message
=
_
(
'This field must be unique.'
)
message
=
_
(
'This field must be unique.'
)
def
__init__
(
self
,
queryset
):
def
__init__
(
self
,
queryset
):
self
.
queryset
=
queryset
self
.
queryset
=
queryset
self
.
serializer_field
=
None
self
.
serializer_field
=
None
def
__call__
(
self
,
value
):
def
set_context
(
self
,
serializer_field
):
field
=
self
.
serializer_field
# Determine the underlying model field name. This may not be the
# same as the serializer field name if `source=<>` is set.
# Determine the model field name that the serializer field corresponds to.
self
.
field_name
=
serializer_field
.
source_attrs
[
0
]
field_name
=
field
.
source_attrs
[
0
]
if
field
.
source_attrs
else
field
.
field_name
# Determine the existing instance, if this is an update operation.
# Determine the existing instance, if this is an update operation.
instance
=
getattr
(
field
.
parent
,
'instance'
,
None
)
self
.
instance
=
getattr
(
serializer_
field
.
parent
,
'instance'
,
None
)
def
__call__
(
self
,
value
):
# Ensure uniqueness.
# Ensure uniqueness.
filter_kwargs
=
{
field_name
:
value
}
filter_kwargs
=
{
self
.
field_name
:
value
}
queryset
=
self
.
queryset
.
filter
(
**
filter_kwargs
)
queryset
=
self
.
queryset
.
filter
(
**
filter_kwargs
)
if
instanc
e
:
if
self
.
instance
is
not
Non
e
:
queryset
=
queryset
.
exclude
(
pk
=
instance
.
pk
)
queryset
=
queryset
.
exclude
(
pk
=
self
.
instance
.
pk
)
if
queryset
.
exists
():
if
queryset
.
exists
():
raise
ValidationError
(
self
.
message
)
raise
ValidationError
(
self
.
message
)
...
@@ -51,8 +49,9 @@ class UniqueValidator:
...
@@ -51,8 +49,9 @@ class UniqueValidator:
class
UniqueTogetherValidator
:
class
UniqueTogetherValidator
:
"""
"""
Validator that corresponds to `unique_together = (...)` on a model class.
Validator that corresponds to `unique_together = (...)` on a model class.
Should be applied to the serializer class, not to an individual field.
"""
"""
requires_context
=
True
message
=
_
(
'The fields {field_names} must make a unique set.'
)
message
=
_
(
'The fields {field_names} must make a unique set.'
)
def
__init__
(
self
,
queryset
,
fields
):
def
__init__
(
self
,
queryset
,
fields
):
...
@@ -60,19 +59,18 @@ class UniqueTogetherValidator:
...
@@ -60,19 +59,18 @@ class UniqueTogetherValidator:
self
.
fields
=
fields
self
.
fields
=
fields
self
.
serializer_field
=
None
self
.
serializer_field
=
None
def
__call__
(
self
,
value
):
def
set_context
(
self
,
serializer
):
serializer
=
self
.
serializer_field
# Determine the existing instance, if this is an update operation.
# Determine the existing instance, if this is an update operation.
instance
=
getattr
(
serializer
,
'instance'
,
None
)
self
.
instance
=
getattr
(
serializer
,
'instance'
,
None
)
def
__call__
(
self
,
attrs
):
# Ensure uniqueness.
# Ensure uniqueness.
filter_kwargs
=
dict
([
filter_kwargs
=
dict
([
(
field_name
,
value
[
field_name
])
for
field_name
in
self
.
fields
(
field_name
,
attrs
[
field_name
])
for
field_name
in
self
.
fields
])
])
queryset
=
self
.
queryset
.
filter
(
**
filter_kwargs
)
queryset
=
self
.
queryset
.
filter
(
**
filter_kwargs
)
if
instanc
e
:
if
self
.
instance
is
not
Non
e
:
queryset
=
queryset
.
exclude
(
pk
=
instance
.
pk
)
queryset
=
queryset
.
exclude
(
pk
=
self
.
instance
.
pk
)
if
queryset
.
exists
():
if
queryset
.
exists
():
field_names
=
', '
.
join
(
self
.
fields
)
field_names
=
', '
.
join
(
self
.
fields
)
raise
ValidationError
(
self
.
message
.
format
(
field_names
=
field_names
))
raise
ValidationError
(
self
.
message
.
format
(
field_names
=
field_names
))
...
@@ -83,3 +81,82 @@ class UniqueTogetherValidator:
...
@@ -83,3 +81,82 @@ class UniqueTogetherValidator:
smart_repr
(
self
.
queryset
),
smart_repr
(
self
.
queryset
),
smart_repr
(
self
.
fields
)
smart_repr
(
self
.
fields
)
)
)
class
BaseUniqueForValidator
:
message
=
None
def
__init__
(
self
,
queryset
,
field
,
date_field
):
self
.
queryset
=
queryset
self
.
field
=
field
self
.
date_field
=
date_field
def
set_context
(
self
,
serializer
):
# Determine the underlying model field names. These may not be the
# same as the serializer field names if `source=<>` is set.
self
.
field_name
=
serializer
.
fields
[
self
.
field
]
.
source_attrs
[
0
]
self
.
date_field_name
=
serializer
.
fields
[
self
.
date_field
]
.
source_attrs
[
0
]
# Determine the existing instance, if this is an update operation.
self
.
instance
=
getattr
(
serializer
,
'instance'
,
None
)
def
get_filter_kwargs
(
self
,
attrs
):
raise
NotImplementedError
(
'`get_filter_kwargs` must be implemented.'
)
def
__call__
(
self
,
attrs
):
filter_kwargs
=
self
.
get_filter_kwargs
(
attrs
)
queryset
=
self
.
queryset
.
filter
(
**
filter_kwargs
)
if
self
.
instance
is
not
None
:
queryset
=
queryset
.
exclude
(
pk
=
self
.
instance
.
pk
)
if
queryset
.
exists
():
message
=
self
.
message
.
format
(
date_field
=
self
.
date_field
)
raise
ValidationError
({
self
.
field
:
message
})
def
__repr__
(
self
):
return
'<
%
s(queryset=
%
s, field=
%
s, date_field=
%
s)>'
%
(
self
.
__class__
.
__name__
,
smart_repr
(
self
.
queryset
),
smart_repr
(
self
.
field
),
smart_repr
(
self
.
date_field
)
)
class
UniqueForDateValidator
(
BaseUniqueForValidator
):
message
=
_
(
'This field must be unique for the "{date_field}" date.'
)
def
get_filter_kwargs
(
self
,
attrs
):
value
=
attrs
[
self
.
field
]
date
=
attrs
[
self
.
date_field
]
filter_kwargs
=
{}
filter_kwargs
[
self
.
field_name
]
=
value
filter_kwargs
[
'
%
s__day'
%
self
.
date_field_name
]
=
date
.
day
filter_kwargs
[
'
%
s__month'
%
self
.
date_field_name
]
=
date
.
month
filter_kwargs
[
'
%
s__year'
%
self
.
date_field_name
]
=
date
.
year
return
filter_kwargs
class
UniqueForMonthValidator
(
BaseUniqueForValidator
):
message
=
_
(
'This field must be unique for the "{date_field}" month.'
)
def
get_filter_kwargs
(
self
,
attrs
):
value
=
attrs
[
self
.
field
]
date
=
attrs
[
self
.
date_field
]
filter_kwargs
=
{}
filter_kwargs
[
self
.
field_name
]
=
value
filter_kwargs
[
'
%
s__month'
%
self
.
date_field_name
]
=
date
.
month
return
filter_kwargs
class
UniqueForYearValidator
(
BaseUniqueForValidator
):
message
=
_
(
'This field must be unique for the "{date_field}" year.'
)
def
get_filter_kwargs
(
self
,
attrs
):
value
=
attrs
[
self
.
field
]
date
=
attrs
[
self
.
date_field
]
filter_kwargs
=
{}
filter_kwargs
[
self
.
field_name
]
=
value
filter_kwargs
[
'
%
s__year'
%
self
.
date_field_name
]
=
date
.
year
return
filter_kwargs
tests/test_validators.py
View file @
ae53fdff
from
django.db
import
models
from
django.db
import
models
from
django.test
import
TestCase
from
django.test
import
TestCase
from
rest_framework
import
serializers
from
rest_framework
import
serializers
import
datetime
def
dedent
(
blocktext
):
def
dedent
(
blocktext
):
...
@@ -147,3 +148,70 @@ class TestUniquenessTogetherValidation(TestCase):
...
@@ -147,3 +148,70 @@ class TestUniquenessTogetherValidation(TestCase):
race_name = CharField(max_length=100)
race_name = CharField(max_length=100)
"""
)
"""
)
assert
repr
(
serializer
)
==
expected
assert
repr
(
serializer
)
==
expected
# Tests for `UniqueForDateValidator`
# ----------------------------------
class
UniqueForDateModel
(
models
.
Model
):
slug
=
models
.
CharField
(
max_length
=
100
,
unique_for_date
=
'published'
)
published
=
models
.
DateField
()
class
UniqueForDateSerializer
(
serializers
.
ModelSerializer
):
class
Meta
:
model
=
UniqueForDateModel
class
TestUniquenessForDateValidation
(
TestCase
):
def
setUp
(
self
):
self
.
instance
=
UniqueForDateModel
.
objects
.
create
(
slug
=
'existing'
,
published
=
'2000-01-01'
)
def
test_repr
(
self
):
serializer
=
UniqueForDateSerializer
()
expected
=
dedent
(
"""
UniqueForDateSerializer(validators=[<UniqueForDateValidator(queryset=UniqueForDateModel.objects.all(), field='slug', date_field='published')>]):
id = IntegerField(label='ID', read_only=True)
slug = CharField(max_length=100)
published = DateField()
"""
)
assert
repr
(
serializer
)
==
expected
def
test_is_not_unique_for_date
(
self
):
"""
Failing unique for date validation should result in field error.
"""
data
=
{
'slug'
:
'existing'
,
'published'
:
'2000-01-01'
}
serializer
=
UniqueForDateSerializer
(
data
=
data
)
assert
not
serializer
.
is_valid
()
assert
serializer
.
errors
==
{
'slug'
:
[
'This field must be unique for the "published" date.'
]
}
def
test_is_unique_for_date
(
self
):
"""
Passing unique for date validation.
"""
data
=
{
'slug'
:
'existing'
,
'published'
:
'2000-01-02'
}
serializer
=
UniqueForDateSerializer
(
data
=
data
)
assert
serializer
.
is_valid
()
assert
serializer
.
validated_data
==
{
'slug'
:
'existing'
,
'published'
:
datetime
.
date
(
2000
,
1
,
2
)
}
def
test_updated_instance_excluded_from_unique_for_date
(
self
):
"""
When performing an update, the existing instance does not count
as a match against unique_for_date.
"""
data
=
{
'slug'
:
'existing'
,
'published'
:
'2000-01-01'
}
serializer
=
UniqueForDateSerializer
(
instance
=
self
.
instance
,
data
=
data
)
assert
serializer
.
is_valid
()
assert
serializer
.
validated_data
==
{
'slug'
:
'existing'
,
'published'
:
datetime
.
date
(
2000
,
1
,
1
)
}
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