Skip to content

Commit

Permalink
Update package structure
Browse files Browse the repository at this point in the history
  • Loading branch information
mbourqui committed May 3, 2018
1 parent 3f57be2 commit 0de3417
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 121 deletions.
5 changes: 5 additions & 0 deletions constrainedfilefield/fields/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from .file import *
from .image import *
131 changes: 131 additions & 0 deletions constrainedfilefield/fields/file.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# -*- coding: utf-8 -*-
__all__ = ['ConstrainedFileField']

import os

from django import forms
from django.conf import settings
from django.db import models
from django.template.defaultfilters import filesizeformat
from django.utils.translation import ugettext_lazy as _


class ConstrainedFileField(models.FileField):
"""
A FielField with additional constraints. Namely, the file size and type can be restricted. If
using the types, the magic library is required. Setting neither a file size nor type behaves
like a regular FileField.
Parameters
----------
content_types : list of str
List containing allowed content_types. Example: ['application/pdf', 'image/jpeg']
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
5 MB - 5242880 B
10 MB - 10485760 B
20 MB - 20971520 B
33 MiB - 2**25 B
50 MB - 5242880 B
100 MB 104857600 B
250 MB - 214958080 B
500 MB - 429916160 B
1 GiB - 1024 MiB - 2**30 B
js_checker : bool
Add a javascript file size checker to the form field
mime_lookup_length : int
See Also
--------
Based on https://github.com/kaleidos/django-validated-file/blob/master/validatedfile/fields.py
With inspiration from http://stackoverflow.com/a/9016664
"""

description = _("A 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.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)

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

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

if self.max_upload_size and data.size > self.max_upload_size:
# 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),
'current_size': filesizeformat(data.size)})

if self.content_types:
import magic
file = data.file
uploaded_content_type = getattr(file, 'content_type', '')

# magic_file_path used only for Windows.
magic_file_path = getattr(settings, "MAGIC_FILE_PATH", None)
if magic_file_path and os.name == 'nt':
mg = magic.Magic(mime=True, magic_file=magic_file_path)
else:
mg = magic.Magic(mime=True)
content_type_magic = mg.from_buffer(file.read(self.mime_lookup_length))
file.seek(0)

# Prefer mime-type from magic over mime-type from http header
if uploaded_content_type != content_type_magic:
uploaded_content_type = content_type_magic

if uploaded_content_type not in self.content_types:
raise forms.ValidationError(
_('Unsupported file type: %(type)s. Allowed types are %(allowed)s.') %
{'type': content_type_magic,
'allowed': self.content_types})
return data

def formfield(self, **kwargs):
"""
Usual Form for a django.models.FileField with optional javascript file size
checker. Can thus be customized as any other Form for a django.models.FileField.
Parameters
----------
kwargs
will be passed to super().formfield()
Returns
-------
django.forms.FileField
"""
formfield = super(ConstrainedFileField, self).formfield(**kwargs)
if self.js_checker:
formfield.widget.attrs.update(
{'onchange': 'validateFileSize(this, 0, %d);' % (self.max_upload_size,)})
return formfield

def deconstruct(self):
name, path, args, kwargs = super(ConstrainedFileField, self).deconstruct()
if self.max_upload_size:
kwargs["max_upload_size"] = self.max_upload_size
if self.content_types:
kwargs["content_types"] = self.content_types
if self.mime_lookup_length:
kwargs["mime_lookup_length"] = self.mime_lookup_length
if self.js_checker:
kwargs["js_checker"] = self.js_checker
return name, path, args, kwargs

def __str__(self):
if hasattr(self, 'model'):
return super(ConstrainedFileField).__str__()
else:
return self.__class__.__name__
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# -*- coding: utf-8 -*-
__all__ = ['ConstrainedImageField']

import os

from django import forms
Expand All @@ -8,127 +10,6 @@
from django.utils.translation import ugettext_lazy as _


class ConstrainedFileField(models.FileField):
"""
A FielField with additional constraints. Namely, the file size and type can be restricted. If
using the types, the magic library is required. Setting neither a file size nor type behaves
like a regular FileField.
Parameters
----------
content_types : list of str
List containing allowed content_types. Example: ['application/pdf', 'image/jpeg']
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
5 MB - 5242880 B
10 MB - 10485760 B
20 MB - 20971520 B
33 MiB - 2**25 B
50 MB - 5242880 B
100 MB 104857600 B
250 MB - 214958080 B
500 MB - 429916160 B
1 GiB - 1024 MiB - 2**30 B
js_checker : bool
Add a javascript file size checker to the form field
mime_lookup_length : int
See Also
--------
Based on https://github.com/kaleidos/django-validated-file/blob/master/validatedfile/fields.py
With inspiration from http://stackoverflow.com/a/9016664
"""

description = _("A 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.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)

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

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

if self.max_upload_size and data.size > self.max_upload_size:
# 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),
'current_size': filesizeformat(data.size)})

if self.content_types:
import magic
file = data.file
uploaded_content_type = getattr(file, 'content_type', '')

# magic_file_path used only for Windows.
magic_file_path = getattr(settings, "MAGIC_FILE_PATH", None)
if magic_file_path and os.name == 'nt':
mg = magic.Magic(mime=True, magic_file=magic_file_path)
else:
mg = magic.Magic(mime=True)
content_type_magic = mg.from_buffer(file.read(self.mime_lookup_length))
file.seek(0)

# Prefer mime-type from magic over mime-type from http header
if uploaded_content_type != content_type_magic:
uploaded_content_type = content_type_magic

if uploaded_content_type not in self.content_types:
raise forms.ValidationError(
_('Unsupported file type: %(type)s. Allowed types are %(allowed)s.') %
{'type': content_type_magic,
'allowed': self.content_types})
return data

def formfield(self, **kwargs):
"""
Usual Form for a django.models.FileField with optional javascript file size
checker. Can thus be customized as any other Form for a django.models.FileField.
Parameters
----------
kwargs
will be passed to super().formfield()
Returns
-------
django.forms.FileField
"""
formfield = super(ConstrainedFileField, self).formfield(**kwargs)
if self.js_checker:
formfield.widget.attrs.update(
{'onchange': 'validateFileSize(this, 0, %d);' % (self.max_upload_size,)})
return formfield

def deconstruct(self):
name, path, args, kwargs = super(ConstrainedFileField, self).deconstruct()
if self.max_upload_size:
kwargs["max_upload_size"] = self.max_upload_size
if self.content_types:
kwargs["content_types"] = self.content_types
if self.mime_lookup_length:
kwargs["mime_lookup_length"] = self.mime_lookup_length
if self.js_checker:
kwargs["js_checker"] = self.js_checker
return name, path, args, kwargs

def __str__(self):
if hasattr(self, 'model'):
return super(ConstrainedFileField).__str__()
else:
return self.__class__.__name__


class ConstrainedImageField(models.ImageField):
"""
An ImageField with additional constraints. Namely, the file size and type can be restricted. If
Expand Down

0 comments on commit 0de3417

Please sign in to comment.