diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 6caae9242..489b9cda2 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -5,19 +5,25 @@ They are very similar to Django's form fields. """ from __future__ import unicode_literals +import base64 import copy import datetime +import imghdr import inspect import re +import uuid import warnings from decimal import Decimal, DecimalException from django import forms + +from django.core.files.base import ContentFile from django.core import validators from django.core.exceptions import ValidationError from django.conf import settings from django.db.models.fields import BLANK_CHOICE_DASH from django.http import QueryDict 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 @@ -1038,3 +1044,46 @@ class SerializerMethodField(Field): def field_to_native(self, obj, field_name): value = getattr(self.parent, self.method_name)(obj) return self.to_native(value) + + +DEFAULT_CONTENT_TYPE = "application/octet-stream" +ALLOWED_IMAGE_TYPES = ( + "jpeg", + "jpg", + "png", + "gif" + ) + + +class Base64ImageField(ImageField): + """ + A django-rest-framework field for handling image-uploads through raw post data. + It uses base64 for en-/decoding the contents of the file. + """ + def from_native(self, base64_data): + # Check if this is a base64 string + if isinstance(base64_data, basestring): + # Try to decode the file. Return validation error if it fails. + try: + decoded_file = base64.b64decode(base64_data) + except TypeError: + raise ValidationError(_("Please upload a valid image.")) + # Generate file name: + file_name = str(uuid.uuid4())[:12] # 12 characters are more than enough. + # Get the file name extension: + file_extension = self.get_file_extension(file_name, decoded_file) + if file_extension not in ALLOWED_IMAGE_TYPES: + raise ValidationError(_("The type of the image couldn't been determined.")) + complete_file_name = file_name + "." + file_extension + data = ContentFile(decoded_file, name=complete_file_name) + return super(Base64ImageField, self).from_native(data) + raise ValidationError(_('This is not an base64 string')) + + def to_native(self, value): + # Return url including domain name. + return "" + + def get_file_extension(self, filename, decoded_file): + extension = imghdr.what(filename, decoded_file) + extension = "jpg" if extension == "jpeg" else extension + return extension \ No newline at end of file diff --git a/rest_framework/tests/test_fields.py b/rest_framework/tests/test_fields.py index b04b947f2..f0690e965 100644 --- a/rest_framework/tests/test_fields.py +++ b/rest_framework/tests/test_fields.py @@ -1002,3 +1002,55 @@ class BooleanField(TestCase): bool_field = serializers.BooleanField(required=True) self.assertFalse(BooleanRequiredSerializer(data={}).is_valid()) + + +class UploadedBase64Image(object): + def __init__(self, file=None, created=None): + self.file = file + self.created = created or datetime.datetime.now() + + +class UploadedBase64ImageSerializer(serializers.Serializer): + file = serializers.Base64ImageField() + created = serializers.DateTimeField() + + def restore_object(self, attrs, instance=None): + if instance: + instance.file = attrs['file'] + instance.created = attrs['created'] + return instance + return UploadedBase64Image(**attrs) + + +class Base64ImageSerializerTests(TestCase): + + def test_create(self): + now = datetime.datetime.now() + file = 'R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==' + serializer = UploadedBase64ImageSerializer(data={'created': now, 'file': file}) + uploaded_image = UploadedBase64Image(file=file, created=now) + self.assertTrue(serializer.is_valid()) + self.assertEqual(serializer.object.created, uploaded_image.created) + self.assertFalse(serializer.object is uploaded_image) + + + def test_creation_failure(self): + """ + Passing file=None should result in an ValidationError + """ + errmsg = 'This field is required.' + now = datetime.datetime.now() + serializer = UploadedBase64ImageSerializer(data={'created': now}) + self.assertFalse(serializer.is_valid()) + self.assertEqual(serializer.errors, {'file': [errmsg]}) + + def test_validation_error_with_non_file(self): + """ + Passing non-base64 should raise a validation error. + """ + now = datetime.datetime.now() + errmsg = "Please upload a valid image." + + serializer = UploadedBase64ImageSerializer(data={'created': now, 'file': 'abc'}) + self.assertFalse(serializer.is_valid()) + self.assertEqual(serializer.errors, {'file': [errmsg]}) \ No newline at end of file