Skip to content

Commit

Permalink
Support min image file size
Browse files Browse the repository at this point in the history
  • Loading branch information
mbourqui committed Apr 4, 2018
1 parent cf0573d commit 4341c24
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 19 deletions.
28 changes: 16 additions & 12 deletions constrainedfilefield/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ def formfield(self, **kwargs):
formfield = super(ConstrainedFileField, self).formfield(**kwargs)
if self.js_checker:
formfield.widget.attrs.update(
{'onchange': 'validateFileSize(this, %d);' % (self.max_upload_size,)})
{'onchange': 'validateFileSize(this, 0, %d);' % (self.max_upload_size,)})
return formfield

def deconstruct(self):
Expand Down Expand Up @@ -139,7 +139,7 @@ class ConstrainedImageField(models.ImageField):
----------
content_types : list of str
List containing allowed content_types. Example: ['application/pdf', 'image/jpeg']
max_upload_size : int
[min|max]_upload_size : int
Maximum file size allowed for upload, in bytes
1 MB - 1048576 B - 1024**2 B - 2**20 B
2.5 MB - 2621440 B
Expand All @@ -163,28 +163,31 @@ class ConstrainedImageField(models.ImageField):
"""

description = _("A file field with constraints on size and/or type")
description = _("An image file field with constraints on size and/or type")

def __init__(self, *args, **kwargs):
self.max_upload_size = kwargs.pop("max_upload_size", 0)
assert isinstance(self.max_upload_size, int) and self.max_upload_size >= 0
self.upload_size = {}
for boundary in ['min', 'max']:
self.upload_size[boundary] = kwargs.pop(boundary + "_upload_size", 0)
assert isinstance(self.upload_size[boundary], int) and self.upload_size[boundary] >= 0
self.content_types = kwargs.pop("content_types", [])
self.mime_lookup_length = kwargs.pop("mime_lookup_length", 4096)
assert isinstance(self.mime_lookup_length, int) and self.mime_lookup_length >= 0
self.js_checker = kwargs.pop("js_checker", False)
# TODO: min_upload_size
# TODO: [min|max]_[height|width]

super(ConstrainedImageField, self).__init__(*args, **kwargs)

def clean(self, *args, **kwargs):
data = super(ConstrainedImageField, self).clean(*args, **kwargs)

if self.max_upload_size and data.size > self.max_upload_size:
small = self.upload_size['min'] and data.size < self.upload_size['min']
large = self.upload_size['max'] and data.size > self.upload_size['max']
if small or large:
# Ensure no one bypasses the js checker
raise forms.ValidationError(
_('File size exceeds limit: %(current_size)s. Limit is %(max_size)s.') %
{'max_size': filesizeformat(self.max_upload_size),
_('File size ' + 'below' if small else 'exceeds' + ' limit: %(current_size)s. Limit is %(max_size)s.') %
{'size': filesizeformat(self.upload_size['min' if small else 'max']),
'current_size': filesizeformat(data.size)})

if self.content_types:
Expand Down Expand Up @@ -230,13 +233,14 @@ def formfield(self, **kwargs):
formfield = super(ConstrainedImageField, self).formfield(**kwargs)
if self.js_checker:
formfield.widget.attrs.update(
{'onchange': 'validateFileSize(this, %d);' % (self.max_upload_size,)})
{'onchange': 'validateFileSize(this, %d, %d);' % (self.upload_size['min'], self.upload_size['max'],)})
return formfield

def deconstruct(self):
name, path, args, kwargs = super(ConstrainedImageField, self).deconstruct()
if self.max_upload_size:
kwargs["max_upload_size"] = self.max_upload_size
for boundary in ['min', 'max']:
if self.upload_size[boundary]:
kwargs[boundary + "_upload_size"] = self.upload_size[boundary]
if self.content_types:
kwargs["content_types"] = self.content_types
if self.mime_lookup_length:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
function validateFileSize(input, maxSize, message) {
message = message || "File size ({size}) exceeds limit ({limit})"
if (input.files[0].size > maxSize) {
alert(message.replace('{size}', input.files[0].size).replace('{limit}', maxSize));
function validateFileSize(input, min, max, message) {
var size = input.files[0].size;
var small = size < min;
var large = size > max;
if (small || large) {
message = message || "File size ({size}) is too "
message += small ? "low" : "large";
message += ". Please upload a file of at "
message += small ? "least" : "most";
message += " {limit}."
alert(message.replace('{size}', input.files[0].size).replace('{limit}', small ? min : max));
input.value = '';
}
}
21 changes: 20 additions & 1 deletion constrainedfilefield/tests/models.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from django.db import models

from constrainedfilefield.fields import ConstrainedFileField
from constrainedfilefield.fields import ConstrainedFileField, ConstrainedImageField


class TestModel(models.Model):
Expand All @@ -10,6 +10,13 @@ class TestModel(models.Model):
upload_to='testfile',
content_types=['image/png'],
max_upload_size=10240)
the_image = ConstrainedImageField(
null=True,
blank=True,
upload_to='testfile',
content_types=['image/png'],
min_upload_size=1024,
max_upload_size=10240)


class TestModelJs(models.Model):
Expand All @@ -20,13 +27,25 @@ class TestModelJs(models.Model):
content_types=['image/png'],
max_upload_size=10240,
js_checker=True)
the_image = ConstrainedImageField(
null=True,
blank=True,
upload_to='testfile',
content_types=['image/png'],
min_upload_size=1024,
max_upload_size=10240,
js_checker=True)


class TestModelNoValidate(models.Model):
the_file = ConstrainedFileField(
null=True,
blank=True,
upload_to='testfile')
the_image = ConstrainedImageField(
null=True,
blank=True,
upload_to='testfile')


class TestContainer(models.Model):
Expand Down
5 changes: 4 additions & 1 deletion constrainedfilefield/tests/tests/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,15 @@ def test_create_empty_instance(self):

def test_create_instance_with_file(self):
instance = TestModel.objects.create(
the_file=File(self._get_sample_file('image2k.png'), 'the_file.png')
the_file=File(self._get_sample_file('image2k.png'), 'the_file.png'),
the_image=File(self._get_sample_file('image2k.png'), 'the_image.png')
)

self._check_file_url(instance.the_file, 'the_file.png')
self._check_file_url(instance.the_image, 'the_image.png')

instance.the_file.delete()
instance.the_image.delete()
instance.delete()

def test_form_ok(self):
Expand Down
7 changes: 6 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,13 @@
# allow setup.py to be run from any path
os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir)))

IMAGE_REQUIRE = [
'Pillow >= 4.0.0'
]

TESTS_REQUIRE = [
'python-magic >= 0.4.2',
]
] + IMAGE_REQUIRE

setup(
name='django-constrainedfilefield',
Expand All @@ -49,6 +53,7 @@
extras_require={
'filetype': TESTS_REQUIRE,
'coverage': TESTS_REQUIRE,
'image': IMAGE_REQUIRE,
},
keywords='django filefield validation file',
classifiers=[
Expand Down

0 comments on commit 4341c24

Please sign in to comment.