Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django: Validate file type of uploaded file

I have an app that lets people upload files, represented as UploadedFiles. However, I want to make sure that users only upload xml files. I know I can do this using magic, but I don't know where to put this check - I can't put it in the clean function since the file is not yet uploaded when clean runs, as far as I can tell.

Here's the UploadedFile model:

class UploadedFile(models.Model):
    """This represents a file that has been uploaded to the server."""
    STATE_UPLOADED = 0
    STATE_ANNOTATED = 1
    STATE_PROCESSING = 2
    STATE_PROCESSED = 4
    STATES = (
        (STATE_UPLOADED, "Uploaded"),
        (STATE_ANNOTATED, "Annotated"),
        (STATE_PROCESSING, "Processing"),
        (STATE_PROCESSED, "Processed"),
    )

    status = models.SmallIntegerField(choices=STATES,
        default=0, blank=True, null=True) 
    file = models.FileField(upload_to=settings.XML_ROOT)
    project = models.ForeignKey(Project)

    def __unicode__(self):
        return self.file.name

    def name(self):
        return os.path.basename(self.file.name)

    def save(self, *args, **kwargs):
        if not self.status:
            self.status = self.STATE_UPLOADED
        super(UploadedFile, self).save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        os.remove(self.file.path)
        self.file.delete(False)
        super(UploadedFile, self).delete(*args, **kwargs)

    def get_absolute_url(self):
        return u'/upload/projects/%d' % self.id

    def clean(self):
        if not "XML" in magic.from_file(self.file.url):
            raise ValidationError(u'Not an xml file.')

class UploadedFileForm(forms.ModelForm):
    class Meta:                
        model = UploadedFile
        exclude = ('project',)
like image 928
Plasma Avatar asked Nov 28 '13 18:11

Plasma


People also ask

How to Validate file upload in Django?

From django 1.11, you can also use FileExtensionValidator. Note this must be used on a FileField and won't work on a CharField (for example), since the validator validates on value.name. Validating just the file name extension is not sufficient.


3 Answers

Validating files is a common challenge, so I would like to use a validator:

import magic  from django.utils.deconstruct import deconstructible from django.template.defaultfilters import filesizeformat   @deconstructible class FileValidator(object):     error_messages = {      'max_size': ("Ensure this file size is not greater than %(max_size)s."                   " Your file size is %(size)s."),      'min_size': ("Ensure this file size is not less than %(min_size)s. "                   "Your file size is %(size)s."),      'content_type': "Files of type %(content_type)s are not supported.",     }      def __init__(self, max_size=None, min_size=None, content_types=()):         self.max_size = max_size         self.min_size = min_size         self.content_types = content_types      def __call__(self, data):         if self.max_size is not None and data.size > self.max_size:             params = {                 'max_size': filesizeformat(self.max_size),                  'size': filesizeformat(data.size),             }             raise ValidationError(self.error_messages['max_size'],                                    'max_size', params)          if self.min_size is not None and data.size < self.min_size:             params = {                 'min_size': filesizeformat(self.min_size),                 'size': filesizeformat(data.size)             }             raise ValidationError(self.error_messages['min_size'],                                     'min_size', params)          if self.content_types:             content_type = magic.from_buffer(data.read(), mime=True)             data.seek(0)              if content_type not in self.content_types:                 params = { 'content_type': content_type }                 raise ValidationError(self.error_messages['content_type'],                                    'content_type', params)      def __eq__(self, other):         return (             isinstance(other, FileValidator) and             self.max_size == other.max_size and             self.min_size == other.min_size and             self.content_types == other.content_types         ) 

Then you can use FileValidator in your models.FileField or forms.FileField as follows:

validate_file = FileValidator(max_size=1024 * 100,                               content_types=('application/xml',)) file = models.FileField(upload_to=settings.XML_ROOT,                          validators=[validate_file]) 
like image 52
Sultan Avatar answered Sep 25 '22 06:09

Sultan


From django 1.11, you can also use FileExtensionValidator.

from django.core.validators import FileExtensionValidator class UploadedFile(models.Model):     file = models.FileField(upload_to=settings.XML_ROOT,          validators=[FileExtensionValidator(allowed_extensions=['xml'])]) 

Note this must be used on a FileField and won't work on a CharField (for example), since the validator validates on value.name.

ref: https://docs.djangoproject.com/en/dev/ref/validators/#fileextensionvalidator

like image 38
rbennell Avatar answered Sep 22 '22 06:09

rbennell


For posterity: the solution is to use the read method and pass that to magic.from_buffer.

class UploadedFileForm(ModelForm):
    def clean_file(self):
        file = self.cleaned_data.get("file", False)
        filetype = magic.from_buffer(file.read())
        if not "XML" in filetype:
            raise ValidationError("File is not XML.")
        return file

    class Meta:
        model = models.UploadedFile
        exclude = ('project',)
like image 43
Plasma Avatar answered Sep 26 '22 06:09

Plasma