diff --git a/README.md b/README.md index 5414d4d..2b5c5cd 100755 --- a/README.md +++ b/README.md @@ -192,8 +192,8 @@ MARKDOWNX_MEDIA_PATH = 'markdownx/' # Path, where images will be stored in MEDIA # Image MARKDOWNX_UPLOAD_MAX_SIZE = 52428800 # 50MB - maximum file size -MARKDOWNX_UPLOAD_CONTENT_TYPES = ['image/jpeg', 'image/png'] # Acceptable file content types -MARKDOWNX_IMAGE_MAX_SIZE = {'size': (500, 500), 'quality': 90,} # Different options describing final image processing: size, compression etc. See below for more info. +MARKDOWNX_UPLOAD_CONTENT_TYPES = ['image/jpeg', 'image/png', 'image/svg+xml'] # Acceptable file content types +MARKDOWNX_IMAGE_MAX_SIZE = {'size': (500, 500), 'quality': 90,} # Different options describing final image processing: size, compression etc. See below for more info. Dimensions are not applied to SVG files. # Editor MARKDOWNX_EDITOR_RESIZABLE = True # Update editor's height to inner content height while typing diff --git a/markdownx/forms.py b/markdownx/forms.py index 778b1a0..8a03cac 100755 --- a/markdownx/forms.py +++ b/markdownx/forms.py @@ -1,5 +1,6 @@ -import os -import uuid +from os import path +from os import SEEK_END +from uuid import uuid4 from django import forms from django.utils.six import BytesIO @@ -16,39 +17,102 @@ from .settings import ( MARKDOWNX_UPLOAD_MAX_SIZE, ) +# ------------------------------------------------------- +# Constants +# ------------------------------------------------------- + +SVG_TYPE = 'image/svg+xml' +SVG_EXTENSION = 'svg' + +# ------------------------------------------------------- + class ImageForm(forms.Form): - image = forms.ImageField() + image = forms.FileField() def save(self, commit=True): - img = scale_and_crop(self.files['image'], **MARKDOWNX_IMAGE_MAX_SIZE) - thumb_io = BytesIO() - img.save(thumb_io, self.files['image'].content_type.split('/')[-1].upper()) + """ + Saves the uploaded image in the designated location. - file_name = str(self.files['image']) - thumb_io.seek(0, os.SEEK_END) - img = InMemoryUploadedFile(thumb_io, None, file_name, self.files['image'].content_type, thumb_io.tell(), None) + If image type is not SVG, a byteIO of image content_type is created and + subsequently save; otherwise, the SVG is saved in its existing `charset` + as an `image/svg+xml`. + + The dimension of image files (excluding SVG) are set using `Pillow`. + + :param commit: If `True`, the file is saved to the disk; otherwise, it is held in the memory. + :type commit: bool + :return: An instance of saved image if `commit` is `True`, else `full_path, uploaded_image`. + """ + image = self.files.get('image') + content_type = image.content_type + thumb_io = None + file_name = image.name + + if image.content_type is SVG_TYPE: + thumb_io = BytesIO() + preped_image = scale_and_crop(image, **MARKDOWNX_IMAGE_MAX_SIZE) + image_extension = image.content_type.split('/')[-1].upper() + preped_image.save(thumb_io, image_extension) + file_name = str(image) + thumb_io.seek(0, SEEK_END) + + uploaded_image = InMemoryUploadedFile( + file=thumb_io if thumb_io else image, + field_name=None, + name=file_name, + content_type=content_type, + size=thumb_io.tell() if thumb_io else getattr(image, '_size'), + charset=None + ) unique_file_name = self.get_unique_file_name(file_name) - full_path = os.path.join(MARKDOWNX_MEDIA_PATH, unique_file_name) - default_storage.save(full_path, img) + full_path = path.join(MARKDOWNX_MEDIA_PATH, unique_file_name) - return default_storage.url(full_path) + if commit: + default_storage.save(full_path, uploaded_image) + return default_storage.url(full_path) - def get_unique_file_name(instance, filename): + # If `commit=False`, return the path and in-memory image. + return full_path, uploaded_image + + @staticmethod + def get_unique_file_name(filename): + """ + Generates a universally unique ID using Python `UUID` and + attaches the extension of file name to it. + :param filename: Name of the uploaded file, including the extension. + :type filename: str + :return: Universally unique ID, ending with the extension extracted from `filename`. + :rtype: str + """ ext = filename.split('.')[-1] - filename = "%s.%s" % (uuid.uuid4(), ext) + filename = "%s.%s" % (uuid4(), ext) return filename def clean(self): + """ + Checks the upload against allowed extensions and maximum size. + :return: Upload + """ upload = self.cleaned_data.get('image') - if upload: - content_type = upload.content_type - if content_type in MARKDOWNX_UPLOAD_CONTENT_TYPES: - if upload._size > MARKDOWNX_UPLOAD_MAX_SIZE: - raise forms.ValidationError(_('Please keep filesize under %(max)s. Current filesize %(current)s') % {'max':filters.filesizeformat(MARKDOWNX_UPLOAD_MAX_SIZE), 'current':filters.filesizeformat(upload._size)}) - else: - raise forms.ValidationError(_('File type is not supported')) + if not upload: + raise forms.ValidationError(_('No files have been uploaded.')) - return upload + content_type = upload.content_type + file_size = getattr(upload, '_size') + + if content_type not in MARKDOWNX_UPLOAD_CONTENT_TYPES: + raise forms.ValidationError(_('File type is not supported.')) + + elif file_size > MARKDOWNX_UPLOAD_MAX_SIZE: + raise forms.ValidationError( + _('Please keep file size under %(max)s. Current file size %(current)s') % + { + 'max': filters.filesizeformat(MARKDOWNX_UPLOAD_MAX_SIZE), + 'current': filters.filesizeformat(upload._size) + } + ) + + return upload