web-dev-qa-db-fra.com

Accepter uniquement un certain type de fichier dans FileField, côté serveur

Comment puis-je restreindre FileField à accepter uniquement un certain type de fichier (vidéo, audio, pdf, etc.) de manière élégante, côté serveur?

55
maroxe

Un moyen très simple consiste à utiliser un validateur personnalisé.

Dans le validators.py De votre application:

def validate_file_extension(value):
    import os
    from Django.core.exceptions import ValidationError
    ext = os.path.splitext(value.name)[1]  # [0] returns path+filename
    valid_extensions = ['.pdf', '.doc', '.docx', '.jpg', '.png', '.xlsx', '.xls']
    if not ext.lower() in valid_extensions:
        raise ValidationError(u'Unsupported file extension.')

Puis dans votre models.py:

from .validators import validate_file_extension

... et utilisez le validateur pour votre champ de formulaire:

class Document(models.Model):
    file = models.FileField(upload_to="documents/%Y/%m/%d", validators=[validate_file_extension])

Voir aussi: Comment limiter les types de fichiers lors du téléchargement de fichiers pour ModelForms avec FileFields? .

71
SaeX

Django dans la version 1.11 a un FileExtensionValidator nouvellement ajouté pour les champs du modèle, les documents sont ici: https://docs.djangoproject.com/en/dev/ref/validators/#fileextensionvalidator .

Un exemple de validation d'une extension de fichier:

from Django.core.validators import FileExtensionValidator
from Django.db import models

class MyModel(models.Model):
    pdf_file = models.FileField(upload_to='foo/',
                                validators=[FileExtensionValidator(allowed_extensions=['pdf'])])

Notez que cette méthode n'est pas non sûre . Citation de Django docs:

Ne vous fiez pas à la validation de l'extension de fichier pour déterminer le type d'un fichier. Les fichiers peuvent être renommés pour avoir n'importe quelle extension, quelles que soient les données qu'ils contiennent.

Il y a aussi de nouveaux validate_image_file_extension ( https://docs.djangoproject.com/en/dev/ref/validators/#validate-image-file-extension ) pour valider les extensions d'image (à l'aide de Pillow).

47
illagrenan

Il y a un extrait de Django qui fait ceci:

import os

from Django import forms

class ExtFileField(forms.FileField):
    """
    Same as forms.FileField, but you can specify a file extension whitelist.

    >>> from Django.core.files.uploadedfile import SimpleUploadedFile
    >>>
    >>> t = ExtFileField(ext_whitelist=(".pdf", ".txt"))
    >>>
    >>> t.clean(SimpleUploadedFile('filename.pdf', 'Some File Content'))
    >>> t.clean(SimpleUploadedFile('filename.txt', 'Some File Content'))
    >>>
    >>> t.clean(SimpleUploadedFile('filename.exe', 'Some File Content'))
    Traceback (most recent call last):
    ...
    ValidationError: [u'Not allowed filetype!']
    """
    def __init__(self, *args, **kwargs):
        ext_whitelist = kwargs.pop("ext_whitelist")
        self.ext_whitelist = [i.lower() for i in ext_whitelist]

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

    def clean(self, *args, **kwargs):
        data = super(ExtFileField, self).clean(*args, **kwargs)
        filename = data.name
        ext = os.path.splitext(filename)[1]
        ext = ext.lower()
        if ext not in self.ext_whitelist:
            raise forms.ValidationError("Not allowed filetype!")

#-------------------------------------------------------------------------

if __== "__main__":
    import doctest, datetime
    doctest.testmod()
10
Dominic Rodger

Premier. Créez un fichier nommé formatChecker.py dans l'application où vous avez le modèle qui a le FileField que vous souhaitez accepter un certain type de fichier.

Voici votre formatChecker.py:

from Django.db.models import FileField
from Django.forms import forms
from Django.template.defaultfilters import filesizeformat
from Django.utils.translation import ugettext_lazy as _

class ContentTypeRestrictedFileField(FileField):
    """
    Same as FileField, but you can specify:
        * content_types - list containing allowed content_types. Example: ['application/pdf', 'image/jpeg']
        * max_upload_size - a number indicating the maximum file size allowed for upload.
            2.5MB - 2621440
            5MB - 5242880
            10MB - 10485760
            20MB - 20971520
            50MB - 5242880
            100MB 104857600
            250MB - 214958080
            500MB - 429916160
"""
def __init__(self, *args, **kwargs):
    self.content_types = kwargs.pop("content_types")
    self.max_upload_size = kwargs.pop("max_upload_size")

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

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

    file = data.file
    try:
        content_type = file.content_type
        if content_type in self.content_types:
            if file._size > self.max_upload_size:
                raise forms.ValidationError(_('Please keep filesize under %s. Current filesize %s') % (filesizeformat(self.max_upload_size), filesizeformat(file._size)))
        else:
            raise forms.ValidationError(_('Filetype not supported.'))
    except AttributeError:
        pass        

    return data

Seconde. Dans votre models.py, ajoutez ceci:

from formatChecker import ContentTypeRestrictedFileField

Ensuite, au lieu d'utiliser "FileField", utilisez ce "ContentTypeRestrictedFileField".

Exemple:

class Stuff(models.Model):
    title = models.CharField(max_length=245)
    handout = ContentTypeRestrictedFileField(upload_to='uploads/', content_types=['video/x-msvideo', 'application/pdf', 'video/mp4', 'audio/mpeg', ],max_upload_size=5242880,blank=True, null=True)

Ce sont les choses que vous devez faire lorsque vous souhaitez accepter uniquement un certain type de fichier dans FileField.

7
Amazing Angelo

Vous pouvez utiliser ce qui suit pour restreindre les types de fichiers dans votre formulaire

file = forms.FileField(widget=forms.FileInput(attrs={'accept':'application/pdf'}))
3
savp

Quelques personnes ont suggéré d'utiliser python-magic pour valider que le fichier est réellement du type que vous attendez. Cela peut être incorporé dans le validator suggéré dans la réponse acceptée:

import os
import magic
from Django.core.exceptions import ValidationError

def validate_is_pdf(file):
    valid_mime_types = ['application/pdf']
    file_mime_type = magic.from_buffer(file.read(1024), mime=True)
    if file_mime_type not in valid_mime_types:
        raise ValidationError('Unsupported file type.')
    valid_file_extensions = ['.pdf']
    ext = os.path.splitext(file.name)[1]
    if ext.lower() not in valid_file_extensions:
        raise ValidationError('Unacceptable file extension.')

Cet exemple ne valide qu'un pdf, mais un nombre illimité de types MIME et d'extensions de fichiers peuvent être ajoutés aux tableaux.

En supposant que vous avez enregistré ce qui précède dans validators.py vous pouvez incorporer ceci dans votre modèle comme ceci:

from myapp.validators import validate_is_pdf

class PdfFile(models.Model):
    file = models.FileField(upload_to='pdfs/', validators=(validate_is_pdf,))
3
Thismatters

Je pense que vous seriez mieux adapté en utilisant le ExtFileField que Dominic Rodger a spécifié dans sa réponse et python-magic que Daniel Quinn a mentionné est la meilleure façon de procéder. Si quelqu'un est assez intelligent pour changer l'extension, au moins, vous l'attraperez avec les en-têtes.

2
man2xxl

Vous pouvez définir une liste de types MIME acceptés dans les paramètres, puis définir un validateur qui utilise python-magic pour détecter le type MIME et déclenche ValidationError si le type MIME n'est pas accepté. Définissez ce validateur dans le champ du formulaire de fichier.

Le seul problème est que parfois le type MIME est application/octet-stream, ce qui peut correspondre à différents formats de fichiers. Est-ce que quelqu'un d'entre vous a surmonté ce problème?

1
sabrina

De plus, je vais étendre cette classe avec un comportement supplémentaire.

class ContentTypeRestrictedFileField(forms.FileField):
    ...
    widget = None
    ...
    def __init__(self, *args, **kwargs):
        ...
        self.widget = forms.ClearableFileInput(attrs={'accept':kwargs.pop('accept', None)})
        super(ContentTypeRestrictedFileField, self).__init__(*args, **kwargs)

Lorsque nous créons une instance avec param accept = ". Pdf, .txt", dans une fenêtre contextuelle avec une structure de fichier par défaut, nous verrons les fichiers avec l'extension passée.

1
gauee