From 6e748a056e3b38c7d5e793553bc909aeac620534 Mon Sep 17 00:00:00 2001 From: Aybars Badur Date: Fri, 10 May 2019 17:40:47 +0200 Subject: [PATCH 1/8] replaces return type of Base64FieldMixin to SimpleUploadedFile so one can get the content_type header from the base64field in models easily --- drf_extra_fields/fields.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/drf_extra_fields/fields.py b/drf_extra_fields/fields.py index 5da42fa..4cd1ff8 100644 --- a/drf_extra_fields/fields.py +++ b/drf_extra_fields/fields.py @@ -5,7 +5,7 @@ import uuid from django.core.exceptions import ValidationError -from django.core.files.base import ContentFile +from django.core.files.uploadedfile import SimpleUploadedFile from django.contrib.postgres import fields as postgres_fields from django.utils.translation import gettext_lazy as _ from psycopg2.extras import DateRange, DateTimeTZRange, NumericRange @@ -24,7 +24,6 @@ from rest_framework.utils import html from drf_extra_fields import compat - DEFAULT_CONTENT_TYPE = "application/octet-stream" @@ -53,9 +52,12 @@ def to_internal_value(self, base64_data): return None if isinstance(base64_data, str): - # Strip base64 header. + file_mime_type = None + + # Strip base64 header, get mime_type from base64 header. if ';base64,' in base64_data: header, base64_data = base64_data.split(';base64,') + file_mime_type = header.replace('data:', '') # Try to decode the file. Return validation error if it fails. try: @@ -69,7 +71,9 @@ def to_internal_value(self, base64_data): if file_extension not in self.ALLOWED_TYPES: raise ValidationError(self.INVALID_TYPE_MESSAGE) complete_file_name = file_name + "." + file_extension - data = ContentFile(decoded_file, name=complete_file_name) + data = SimpleUploadedFile(name=complete_file_name, content=decoded_file, + content_type=file_mime_type + ) return super(Base64FieldMixin, self).to_internal_value(data) raise ValidationError(_('Invalid type. This is not an base64 string: {}'.format( type(base64_data)))) @@ -156,6 +160,7 @@ class Base64FileField(Base64FieldMixin, FileField): A django-rest-framework field for handling file-uploads through raw post data. It uses base64 for en-/decoding the contents of the file. """ + @property def ALLOWED_TYPES(self): raise NotImplementedError('List allowed file extensions') @@ -168,7 +173,6 @@ def get_file_extension(self, filename, decoded_file): class RangeField(DictField): - range_type = None default_error_messages = dict(DictField.default_error_messages) From c36b60fe2fee5dab80f5ce1ce65f391a3424c60b Mon Sep 17 00:00:00 2001 From: Aybars Badur Date: Fri, 10 May 2019 17:42:55 +0200 Subject: [PATCH 2/8] uses requirements.txt in setup.py - so one can install the package with requirements with python setup.py develop/install etc. --- setup.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 669fad6..90ef71d 100644 --- a/setup.py +++ b/setup.py @@ -4,6 +4,9 @@ with open(os.path.join(os.path.dirname(__file__), 'README.md')) as readme: README = readme.read() +with open(os.path.join(os.path.dirname(__file__), 'requirements.txt')) as requirements_txt: + requirements = requirements_txt.read().strip().splitlines() + # allow setup.py to be run from any path os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir))) @@ -13,7 +16,6 @@ packages=['drf_extra_fields', 'drf_extra_fields.runtests'], include_package_data=True, - install_requires=['Django >= 2.2', 'djangorestframework >= 3.9.1'], extras_require={ "Base64ImageField": ["Pillow >= 6.2.1"], }, @@ -26,6 +28,7 @@ author_email='pypi@hipolabs.com', url='https://github.com/Hipo/drf-extra-fields', python_requires=">=3.5", + install_requires=requirements, classifiers=[ 'Environment :: Web Environment', 'Framework :: Django', From 8744d3f23dcd97a2ac638cd8568dc5c5bf0d6255 Mon Sep 17 00:00:00 2001 From: Aybars Badur Date: Fri, 10 May 2019 18:02:57 +0200 Subject: [PATCH 3/8] include requirements.txt in package so setup.py install can use it --- MANIFEST.in | 1 + 1 file changed, 1 insertion(+) diff --git a/MANIFEST.in b/MANIFEST.in index 04f196a..bb910eb 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,2 +1,3 @@ include README.md include LICENSE +include requirements.txt From ce8416343c30fe7dad5576525b80877b40e64b4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Faruk=20Abac=C4=B1?= Date: Thu, 3 Dec 2020 22:12:46 +0300 Subject: [PATCH 4/8] Add trust_provided_content_type parameter to Base64FieldMixin --- drf_extra_fields/fields.py | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/drf_extra_fields/fields.py b/drf_extra_fields/fields.py index 4cd1ff8..82d0c66 100644 --- a/drf_extra_fields/fields.py +++ b/drf_extra_fields/fields.py @@ -28,6 +28,10 @@ class Base64FieldMixin(object): + trust_provided_content_type = False + + EMPTY_VALUES = (None, "", [], (), {}) + @property def ALLOWED_TYPES(self): raise NotImplementedError @@ -40,10 +44,8 @@ def INVALID_FILE_MESSAGE(self): def INVALID_TYPE_MESSAGE(self): raise NotImplementedError - EMPTY_VALUES = (None, '', [], (), {}) - def __init__(self, *args, **kwargs): - self.represent_in_base64 = kwargs.pop('represent_in_base64', False) + self.represent_in_base64 = kwargs.pop("represent_in_base64", False) super(Base64FieldMixin, self).__init__(*args, **kwargs) def to_internal_value(self, base64_data): @@ -55,28 +57,36 @@ def to_internal_value(self, base64_data): file_mime_type = None # Strip base64 header, get mime_type from base64 header. - if ';base64,' in base64_data: - header, base64_data = base64_data.split(';base64,') - file_mime_type = header.replace('data:', '') + if ";base64," in base64_data: + header, base64_data = base64_data.split(";base64,") + if self.trust_provided_content_type: + file_mime_type = header.replace("data:", "") # Try to decode the file. Return validation error if it fails. try: decoded_file = base64.b64decode(base64_data) except (TypeError, binascii.Error, ValueError): raise ValidationError(self.INVALID_FILE_MESSAGE) + # Generate file name: file_name = self.get_file_name(decoded_file) + # Get the file name extension: file_extension = self.get_file_extension(file_name, decoded_file) + if file_extension not in self.ALLOWED_TYPES: raise ValidationError(self.INVALID_TYPE_MESSAGE) + complete_file_name = file_name + "." + file_extension - data = SimpleUploadedFile(name=complete_file_name, content=decoded_file, - content_type=file_mime_type - ) + data = SimpleUploadedFile( + name=complete_file_name, + content=decoded_file, + content_type=file_mime_type + ) + return super(Base64FieldMixin, self).to_internal_value(data) - raise ValidationError(_('Invalid type. This is not an base64 string: {}'.format( - type(base64_data)))) + + raise ValidationError(_("Invalid type. This is not an base64 string: {}".format(type(base64_data)))) def get_file_extension(self, filename, decoded_file): raise NotImplementedError @@ -91,10 +101,10 @@ def to_representation(self, file): # empty base64 str rather than let the exception propagate unhandled # up into serializers. if not file: - return '' + return "" try: - with open(file.path, 'rb') as f: + with open(file.path, "rb") as f: return base64.b64encode(f.read()).decode() except Exception: raise IOError("Error encoding file") From f73622f962e5cc4a6b8479dd55ac09aca57b06c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Faruk=20Abac=C4=B1?= Date: Fri, 4 Dec 2020 12:55:06 +0300 Subject: [PATCH 5/8] Move trust_provided_content_type to Base64FieldMixin's __init__ --- drf_extra_fields/fields.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drf_extra_fields/fields.py b/drf_extra_fields/fields.py index 82d0c66..9c05dec 100644 --- a/drf_extra_fields/fields.py +++ b/drf_extra_fields/fields.py @@ -28,8 +28,6 @@ class Base64FieldMixin(object): - trust_provided_content_type = False - EMPTY_VALUES = (None, "", [], (), {}) @property @@ -45,6 +43,7 @@ def INVALID_TYPE_MESSAGE(self): raise NotImplementedError def __init__(self, *args, **kwargs): + self.trust_provided_content_type = kwargs.pop("trust_provided_content_type", False) self.represent_in_base64 = kwargs.pop("represent_in_base64", False) super(Base64FieldMixin, self).__init__(*args, **kwargs) From e00452db3eaed90c4a8e9ebca2fc8322c4dc4c7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Faruk=20Abac=C4=B1?= Date: Fri, 4 Dec 2020 13:31:27 +0300 Subject: [PATCH 6/8] Bump version --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 90ef71d..ff40280 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ setup( name='drf-extra-fields', - version='3.0.4', + version='3.0.5', packages=['drf_extra_fields', 'drf_extra_fields.runtests'], include_package_data=True, From a1cb26adb8add9ba44a10f1bb7df4ad2f43303b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Faruk=20Abac=C4=B1?= Date: Wed, 13 Jan 2021 09:52:26 +0300 Subject: [PATCH 7/8] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index e32c2e4..3af17e7 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,8 @@ DRF-EXTRA-FIELDS Extra Fields for Django Rest Framework +**Possible breaking change in v3.1.0**: In this version we have changed file class used in `Base64FileField` from `ContentFile` to `SimpleUploadedFile` (you may see the change [here](https://github.com/Hipo/drf-extra-fields/pull/149/files#diff-5f77bcb61083cd9c026f6dfb3b77bf8fa824c45e620cdb7826ad713bde7b65f8L72-R85)). + [![Build Status](https://travis-ci.org/Hipo/drf-extra-fields.svg?branch=master)](https://travis-ci.org/Hipo/drf-extra-fields) [![codecov](https://codecov.io/gh/Hipo/drf-extra-fields/branch/master/graph/badge.svg)](https://codecov.io/gh/Hipo/drf-extra-fields) [![PyPI Version](https://img.shields.io/pypi/v/drf-extra-fields.svg)](https://pypi.org/project/drf-extra-fields) From ce1a9707ae30e3cda7cf2ef90cc4d64d69d08c29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Faruk=20Abac=C4=B1?= Date: Wed, 13 Jan 2021 09:53:06 +0300 Subject: [PATCH 8/8] Bump version to 3.1.0 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index ff40280..7056b7e 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ setup( name='drf-extra-fields', - version='3.0.5', + version='3.1.0', packages=['drf_extra_fields', 'drf_extra_fields.runtests'], include_package_data=True,