views:

59

answers:

3

I've written an app that uses forms to collect information that is then sent in an email. Many of these forms have a filefield used to attach files to the email. I'd like to validate two things, the size of the file (to ensure the emails are accepted by our mail server. I'd also like to check the file extension, to discourage attaching file types not useable for our users.

(This is the python class I'm trying to extend)

class FileField(Field):
    widget = FileInput
    default_error_messages = {
        'invalid': _(u"No file was submitted. Check the encoding type on the form."),
        'missing': _(u"No file was submitted."),
        'empty': _(u"The submitted file is empty."),
        'max_length': _(u'Ensure this filename has at most %(max)d characters (it has %(length)d).'),
    }

    def __init__(self, *args, **kwargs):
        self.max_length = kwargs.pop('max_length', None)
        super(FileField, self).__init__(*args, **kwargs)

    def clean(self, data, initial=None):
        super(FileField, self).clean(initial or data)
        if not self.required and data in EMPTY_VALUES:
            return None
        elif not data and initial:
            return initial

        # UploadedFile objects should have name and size attributes.
        try:
            file_name = data.name
            file_size = data.size
        except AttributeError:
            raise ValidationError(self.error_messages['invalid'])

        if self.max_length is not None and len(file_name) > self.max_length:
            error_values =  {'max': self.max_length, 'length': len(file_name)}
            raise ValidationError(self.error_messages['max_length'] % error_values)
        if not file_name:
            raise ValidationError(self.error_messages['invalid'])
        if not file_size:
            raise ValidationError(self.error_messages['empty'])
    return data
+1  A: 

Just overload the "clean" method:

def clean(self, data, initial=None):
    try:
        if data.size > somesize:
            raise ValidationError('File is too big')

        (junk, ext) = os.path.splitext(data.name)
        if not ext in ('.jpg', '.gif', '.png'):
            raise ValidationError('Invalid file type')

    except AttributeError:
        raise ValidationError(self.error_messages['invalid'])

    return FileField.clean(self, data, initial)
synic
I ended up following your method. Thanks for your help!
duallain
+1  A: 

Hello duallain,

in my opinion subclassing the actual field class is way to much effort. It should be easier to simply extend your form class. You could add a method which "cleans" the file field.

For example:

class MyForm(forms.Form):
  attachment = forms.FileField(...)

  def clean_attachment(self):
    data = self.cleaned_data['attachment'] // UploadedFile object
    exts = ['jpg', 'png'] // allowed extensions

    // 1. check file size
    if data.size > x:
      raise forms.ValidationError("file to big")

    // 2. check file extension
    file_extension = data.name.split('.')[1] // simple method

    if file_extension not in exts:
      raise forms.ValidationError("Wrong file type")

    return data

This is only a basic example and there are some rough edges. But you can use this example and improve it until you have a version that works for you.

Recommended readings:

Django Doc - Cleaning a specific field

Django Doc - UploadedFile class

Django Doc - File class

Jens
Thank you for your answer. I ended up using your method for doing the extension validation. I choose to implement a subclass because this validation was to be done for about 20 forms. Your meta-method will be helpful to validate some other fields.
duallain
But be aware that this will only work for filenames that contain one dot. Filenames with multiple dots will break the validation. Instead use something like this: (shortname, extension) = os.path.splitext(filename).
Jens
A: 

This is what I ended up doing:

In my app's setting file:

exts = ['doc', 'docx', 'pdf', 'jpg', 'png', 'xls', 'xlsx', '.xlsm', '.xlsb']
max_email_attach_size = 10485760 #10MB written in bytes

In a new file I called formfunctions:

from django import forms
from django.forms.util import ErrorList, ValidationError
from app.settings import exts, max_email_attach_size


class SizedFileField(forms.FileField):

    def clean(self, data, initial=None):

        if not data in (None, ''):

            try:
                if data.size > max_email_attach_size:
                    raise ValidationError("The file is too big")

                file_extension = data.name.split('.')[1]
                if file_extension not in exts:
                    raise ValidationError("Invalid File Type")

            except AttributeError:
                raise ValidationError(self.error_messages['invalid'])

        return forms.FileField.clean(self, data, initial)

and in my forms file:

from formfunctions import SizedFileField

An example class from the forms file:

class ExampleClass(forms.Form):
    Email_Body = forms.CharField(widget=forms.Textarea, required=False)
    Todays_Date = forms.CharField()
    Attachment = SizedFileField(required=False)
duallain