mirror of
https://github.com/encode/django-rest-framework.git
synced 2024-11-10 19:56:59 +03:00
FileField and ImageField
This commit is contained in:
parent
ac71d9aaae
commit
dee3f78cb6
|
@ -13,4 +13,3 @@ django-filter>=0.5.4
|
|||
django-oauth-plus>=2.2.1
|
||||
oauth2>=1.5.211
|
||||
django-oauth2-provider>=0.2.4
|
||||
Pillow==2.3.0
|
||||
|
|
|
@ -84,15 +84,6 @@ except ImportError:
|
|||
from collections import UserDict
|
||||
from collections import MutableMapping as DictMixin
|
||||
|
||||
# Try to import PIL in either of the two ways it can end up installed.
|
||||
try:
|
||||
from PIL import Image
|
||||
except ImportError:
|
||||
try:
|
||||
import Image
|
||||
except ImportError:
|
||||
Image = None
|
||||
|
||||
|
||||
def get_model_name(model_cls):
|
||||
try:
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
from django import forms
|
||||
from django.conf import settings
|
||||
from django.core import validators
|
||||
from django.core.exceptions import ValidationError
|
||||
|
@ -427,8 +428,6 @@ class CharField(Field):
|
|||
return str(data)
|
||||
|
||||
def to_representation(self, value):
|
||||
if value is None:
|
||||
return None
|
||||
return str(value)
|
||||
|
||||
|
||||
|
@ -446,8 +445,6 @@ class EmailField(CharField):
|
|||
return str(data).strip()
|
||||
|
||||
def to_representation(self, value):
|
||||
if value is None:
|
||||
return None
|
||||
return str(value).strip()
|
||||
|
||||
|
||||
|
@ -513,8 +510,6 @@ class IntegerField(Field):
|
|||
return data
|
||||
|
||||
def to_representation(self, value):
|
||||
if value is None:
|
||||
return None
|
||||
return int(value)
|
||||
|
||||
|
||||
|
@ -543,8 +538,6 @@ class FloatField(Field):
|
|||
self.fail('invalid')
|
||||
|
||||
def to_representation(self, value):
|
||||
if value is None:
|
||||
return None
|
||||
return float(value)
|
||||
|
||||
|
||||
|
@ -616,9 +609,6 @@ class DecimalField(Field):
|
|||
return value
|
||||
|
||||
def to_representation(self, value):
|
||||
if value in (None, ''):
|
||||
return None
|
||||
|
||||
if not isinstance(value, decimal.Decimal):
|
||||
value = decimal.Decimal(str(value).strip())
|
||||
|
||||
|
@ -689,7 +679,7 @@ class DateTimeField(Field):
|
|||
self.fail('invalid', format=humanized_format)
|
||||
|
||||
def to_representation(self, value):
|
||||
if value is None or self.format is None:
|
||||
if self.format is None:
|
||||
return value
|
||||
|
||||
if self.format.lower() == ISO_8601:
|
||||
|
@ -741,7 +731,7 @@ class DateField(Field):
|
|||
self.fail('invalid', format=humanized_format)
|
||||
|
||||
def to_representation(self, value):
|
||||
if value is None or self.format is None:
|
||||
if self.format is None:
|
||||
return value
|
||||
|
||||
# Applying a `DateField` to a datetime value is almost always
|
||||
|
@ -795,7 +785,7 @@ class TimeField(Field):
|
|||
self.fail('invalid', format=humanized_format)
|
||||
|
||||
def to_representation(self, value):
|
||||
if value is None or self.format is None:
|
||||
if self.format is None:
|
||||
return value
|
||||
|
||||
# Applying a `TimeField` to a datetime value is almost always
|
||||
|
@ -875,14 +865,68 @@ class MultipleChoiceField(ChoiceField):
|
|||
# File types...
|
||||
|
||||
class FileField(Field):
|
||||
pass # TODO
|
||||
default_error_messages = {
|
||||
'required': _("No file was submitted."),
|
||||
'invalid': _("The submitted data was not a file. Check the encoding type on the form."),
|
||||
'no_name': _("No filename could be determined."),
|
||||
'empty': _("The submitted file is empty."),
|
||||
'max_length': _('Ensure this filename has at most {max_length} characters (it has {length}).'),
|
||||
}
|
||||
use_url = api_settings.UPLOADED_FILES_USE_URL
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.max_length = kwargs.pop('max_length', None)
|
||||
self.allow_empty_file = kwargs.pop('allow_empty_file', False)
|
||||
self.use_url = kwargs.pop('use_url', self.use_url)
|
||||
super(FileField, self).__init__(*args, **kwargs)
|
||||
|
||||
def to_internal_value(self, data):
|
||||
try:
|
||||
# `UploadedFile` objects should have name and size attributes.
|
||||
file_name = data.name
|
||||
file_size = data.size
|
||||
except AttributeError:
|
||||
self.fail('invalid')
|
||||
|
||||
if not file_name:
|
||||
self.fail('no_name')
|
||||
if not self.allow_empty_file and not file_size:
|
||||
self.fail('empty')
|
||||
if self.max_length and len(file_name) > self.max_length:
|
||||
self.fail('max_length', max_length=self.max_length, length=len(file_name))
|
||||
|
||||
return data
|
||||
|
||||
def to_representation(self, value):
|
||||
if self.use_url:
|
||||
return settings.MEDIA_URL + value.url
|
||||
return value.name
|
||||
|
||||
|
||||
class ImageField(Field):
|
||||
pass # TODO
|
||||
class ImageField(FileField):
|
||||
default_error_messages = {
|
||||
'invalid_image': _(
|
||||
'Upload a valid image. The file you uploaded was either not an '
|
||||
'image or a corrupted image.'
|
||||
),
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._DjangoImageField = kwargs.pop('_DjangoImageField', forms.ImageField)
|
||||
super(ImageField, self).__init__(*args, **kwargs)
|
||||
|
||||
def to_internal_value(self, data):
|
||||
# Image validation is a bit grungy, so we'll just outright
|
||||
# defer to Django's implementation so we don't need to
|
||||
# consider it, or treat PIL as a test dependancy.
|
||||
file_object = super(ImageField, self).to_internal_value(data)
|
||||
django_field = self._DjangoImageField()
|
||||
django_field.error_messages = self.error_messages
|
||||
django_field.to_python(file_object)
|
||||
return file_object
|
||||
|
||||
|
||||
# Advanced field types...
|
||||
# Composite field types...
|
||||
|
||||
class ListField(Field):
|
||||
child = None
|
||||
|
@ -922,6 +966,8 @@ class ListField(Field):
|
|||
return [self.child.to_representation(item) for item in data]
|
||||
|
||||
|
||||
# Miscellaneous field types...
|
||||
|
||||
class ReadOnlyField(Field):
|
||||
"""
|
||||
A read-only field that simply returns the field value.
|
||||
|
|
|
@ -110,7 +110,8 @@ DEFAULTS = {
|
|||
# Encoding
|
||||
'UNICODE_JSON': True,
|
||||
'COMPACT_JSON': True,
|
||||
'COERCE_DECIMAL_TO_STRING': True
|
||||
'COERCE_DECIMAL_TO_STRING': True,
|
||||
'UPLOADED_FILES_USE_URL': True
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from decimal import Decimal
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.utils import timezone
|
||||
from rest_framework import fields, serializers
|
||||
import datetime
|
||||
|
@ -516,7 +517,7 @@ class TestDecimalField(FieldValues):
|
|||
Decimal('1.0'): '1.0',
|
||||
Decimal('0.0'): '0.0',
|
||||
Decimal('1.09'): '1.1',
|
||||
Decimal('0.04'): '0.0',
|
||||
Decimal('0.04'): '0.0'
|
||||
}
|
||||
field = fields.DecimalField(max_digits=3, decimal_places=1)
|
||||
|
||||
|
@ -576,7 +577,7 @@ class TestDateField(FieldValues):
|
|||
datetime.datetime(2001, 1, 1, 12, 00): ['Expected a date but got a datetime.'],
|
||||
}
|
||||
outputs = {
|
||||
datetime.date(2001, 1, 1): '2001-01-01',
|
||||
datetime.date(2001, 1, 1): '2001-01-01'
|
||||
}
|
||||
field = fields.DateField()
|
||||
|
||||
|
@ -639,7 +640,7 @@ class TestDateTimeField(FieldValues):
|
|||
}
|
||||
outputs = {
|
||||
datetime.datetime(2001, 1, 1, 13, 00): '2001-01-01T13:00:00',
|
||||
datetime.datetime(2001, 1, 1, 13, 00, tzinfo=timezone.UTC()): '2001-01-01T13:00:00Z',
|
||||
datetime.datetime(2001, 1, 1, 13, 00, tzinfo=timezone.UTC()): '2001-01-01T13:00:00Z'
|
||||
}
|
||||
field = fields.DateTimeField(default_timezone=timezone.UTC())
|
||||
|
||||
|
@ -847,6 +848,92 @@ class TestMultipleChoiceField(FieldValues):
|
|||
)
|
||||
|
||||
|
||||
# File fields...
|
||||
|
||||
class MockFile:
|
||||
def __init__(self, name='', size=0, url=''):
|
||||
self.name = name
|
||||
self.size = size
|
||||
self.url = url
|
||||
|
||||
def __eq__(self, other):
|
||||
return (
|
||||
isinstance(other, MockFile) and
|
||||
self.name == other.name and
|
||||
self.size == other.size and
|
||||
self.url == other.url
|
||||
)
|
||||
|
||||
|
||||
class TestFileField(FieldValues):
|
||||
"""
|
||||
Values for `FileField`.
|
||||
"""
|
||||
valid_inputs = [
|
||||
(MockFile(name='example', size=10), MockFile(name='example', size=10))
|
||||
]
|
||||
invalid_inputs = [
|
||||
('invalid', ['The submitted data was not a file. Check the encoding type on the form.']),
|
||||
(MockFile(name='example.txt', size=0), ['The submitted file is empty.']),
|
||||
(MockFile(name='', size=10), ['No filename could be determined.']),
|
||||
(MockFile(name='x' * 100, size=10), ['Ensure this filename has at most 10 characters (it has 100).'])
|
||||
]
|
||||
outputs = [
|
||||
(MockFile(name='example.txt', url='/example.txt'), '/example.txt')
|
||||
]
|
||||
field = fields.FileField(max_length=10)
|
||||
|
||||
|
||||
class TestFieldFieldWithName(FieldValues):
|
||||
"""
|
||||
Values for `FileField` with a filename output instead of URLs.
|
||||
"""
|
||||
valid_inputs = {}
|
||||
invalid_inputs = {}
|
||||
outputs = [
|
||||
(MockFile(name='example.txt', url='/example.txt'), 'example.txt')
|
||||
]
|
||||
field = fields.FileField(use_url=False)
|
||||
|
||||
|
||||
# Stub out mock Django `forms.ImageField` class so we don't *actually*
|
||||
# call into it's regular validation, or require PIL for testing.
|
||||
class FailImageValidation(object):
|
||||
def to_python(self, value):
|
||||
raise ValidationError(self.error_messages['invalid_image'])
|
||||
|
||||
|
||||
class PassImageValidation(object):
|
||||
def to_python(self, value):
|
||||
return value
|
||||
|
||||
|
||||
class TestInvalidImageField(FieldValues):
|
||||
"""
|
||||
Values for an invalid `ImageField`.
|
||||
"""
|
||||
valid_inputs = {}
|
||||
invalid_inputs = [
|
||||
(MockFile(name='example.txt', size=10), ['Upload a valid image. The file you uploaded was either not an image or a corrupted image.'])
|
||||
]
|
||||
outputs = {}
|
||||
field = fields.ImageField(_DjangoImageField=FailImageValidation)
|
||||
|
||||
|
||||
class TestValidImageField(FieldValues):
|
||||
"""
|
||||
Values for an valid `ImageField`.
|
||||
"""
|
||||
valid_inputs = [
|
||||
(MockFile(name='example.txt', size=10), MockFile(name='example.txt', size=10))
|
||||
]
|
||||
invalid_inputs = {}
|
||||
outputs = {}
|
||||
field = fields.ImageField(_DjangoImageField=PassImageValidation)
|
||||
|
||||
|
||||
# Composite fields...
|
||||
|
||||
class TestListField(FieldValues):
|
||||
"""
|
||||
Values for `ListField`.
|
||||
|
|
16
tox.ini
16
tox.ini
|
@ -21,7 +21,6 @@ basepython = python3.4
|
|||
deps = Django==1.7
|
||||
django-filter==0.7
|
||||
defusedxml==0.3
|
||||
Pillow==2.3.0
|
||||
pytest-django==2.6.1
|
||||
|
||||
[testenv:py3.3-django1.7]
|
||||
|
@ -29,7 +28,6 @@ basepython = python3.3
|
|||
deps = Django==1.7
|
||||
django-filter==0.7
|
||||
defusedxml==0.3
|
||||
Pillow==2.3.0
|
||||
pytest-django==2.6.1
|
||||
|
||||
[testenv:py3.2-django1.7]
|
||||
|
@ -37,7 +35,6 @@ basepython = python3.2
|
|||
deps = Django==1.7
|
||||
django-filter==0.7
|
||||
defusedxml==0.3
|
||||
Pillow==2.3.0
|
||||
pytest-django==2.6.1
|
||||
|
||||
[testenv:py2.7-django1.7]
|
||||
|
@ -49,7 +46,6 @@ deps = Django==1.7
|
|||
# oauth2==1.5.211
|
||||
# django-oauth2-provider==0.2.4
|
||||
django-guardian==1.2.3
|
||||
Pillow==2.3.0
|
||||
pytest-django==2.6.1
|
||||
|
||||
[testenv:py3.4-django1.6]
|
||||
|
@ -57,7 +53,6 @@ basepython = python3.4
|
|||
deps = Django==1.6.3
|
||||
django-filter==0.7
|
||||
defusedxml==0.3
|
||||
Pillow==2.3.0
|
||||
pytest-django==2.6.1
|
||||
|
||||
[testenv:py3.3-django1.6]
|
||||
|
@ -65,7 +60,6 @@ basepython = python3.3
|
|||
deps = Django==1.6.3
|
||||
django-filter==0.7
|
||||
defusedxml==0.3
|
||||
Pillow==2.3.0
|
||||
pytest-django==2.6.1
|
||||
|
||||
[testenv:py3.2-django1.6]
|
||||
|
@ -73,7 +67,6 @@ basepython = python3.2
|
|||
deps = Django==1.6.3
|
||||
django-filter==0.7
|
||||
defusedxml==0.3
|
||||
Pillow==2.3.0
|
||||
pytest-django==2.6.1
|
||||
|
||||
[testenv:py2.7-django1.6]
|
||||
|
@ -85,7 +78,6 @@ deps = Django==1.6.3
|
|||
oauth2==1.5.211
|
||||
django-oauth2-provider==0.2.4
|
||||
django-guardian==1.2.3
|
||||
Pillow==2.3.0
|
||||
pytest-django==2.6.1
|
||||
|
||||
[testenv:py2.6-django1.6]
|
||||
|
@ -97,7 +89,6 @@ deps = Django==1.6.3
|
|||
oauth2==1.5.211
|
||||
django-oauth2-provider==0.2.4
|
||||
django-guardian==1.2.3
|
||||
Pillow==2.3.0
|
||||
pytest-django==2.6.1
|
||||
|
||||
[testenv:py3.4-django1.5]
|
||||
|
@ -105,7 +96,6 @@ basepython = python3.4
|
|||
deps = django==1.5.6
|
||||
django-filter==0.7
|
||||
defusedxml==0.3
|
||||
Pillow==2.3.0
|
||||
pytest-django==2.6.1
|
||||
|
||||
[testenv:py3.3-django1.5]
|
||||
|
@ -113,7 +103,6 @@ basepython = python3.3
|
|||
deps = django==1.5.6
|
||||
django-filter==0.7
|
||||
defusedxml==0.3
|
||||
Pillow==2.3.0
|
||||
pytest-django==2.6.1
|
||||
|
||||
[testenv:py3.2-django1.5]
|
||||
|
@ -121,7 +110,6 @@ basepython = python3.2
|
|||
deps = django==1.5.6
|
||||
django-filter==0.7
|
||||
defusedxml==0.3
|
||||
Pillow==2.3.0
|
||||
pytest-django==2.6.1
|
||||
|
||||
[testenv:py2.7-django1.5]
|
||||
|
@ -133,7 +121,6 @@ deps = django==1.5.6
|
|||
oauth2==1.5.211
|
||||
django-oauth2-provider==0.2.3
|
||||
django-guardian==1.2.3
|
||||
Pillow==2.3.0
|
||||
pytest-django==2.6.1
|
||||
|
||||
[testenv:py2.6-django1.5]
|
||||
|
@ -145,7 +132,6 @@ deps = django==1.5.6
|
|||
oauth2==1.5.211
|
||||
django-oauth2-provider==0.2.3
|
||||
django-guardian==1.2.3
|
||||
Pillow==2.3.0
|
||||
pytest-django==2.6.1
|
||||
|
||||
[testenv:py2.7-django1.4]
|
||||
|
@ -157,7 +143,6 @@ deps = django==1.4.11
|
|||
oauth2==1.5.211
|
||||
django-oauth2-provider==0.2.3
|
||||
django-guardian==1.2.3
|
||||
Pillow==2.3.0
|
||||
pytest-django==2.6.1
|
||||
|
||||
[testenv:py2.6-django1.4]
|
||||
|
@ -169,5 +154,4 @@ deps = django==1.4.11
|
|||
oauth2==1.5.211
|
||||
django-oauth2-provider==0.2.3
|
||||
django-guardian==1.2.3
|
||||
Pillow==2.3.0
|
||||
pytest-django==2.6.1
|
||||
|
|
Loading…
Reference in New Issue
Block a user