2010-09-06 14 views
39

¿Cómo puedo restringir FileField para aceptar solo un cierto tipo de archivo (video, audio, pdf, etc.) de una manera elegante, en el lado del servidor?Solo acepta cierto tipo de archivo en FileField, lado del servidor

+1

Para obtener el diálogo abierto para restringir los archivos a ciertos tipos de cliente, [ver esta pregunta] (http: //stackoverflow.com/a/40847649/247696). – Flimm

Respuesta

44

Una manera muy fácil es usar un validador personalizado.

En validators.py de su aplicación:

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.') 

Luego, en su models.py:

from .validators import validate_file_extension 

... y utilizar el validador para el campo de formulario:

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

Ver también: How to limit file types on file uploads for ModelForms with FileFields?.

+1

Esto debería ser aceptado como respuesta. ¡Muchas gracias! – vabada

+2

@dabad utilizando solo la extensión no es bueno para la validación de archivos, este tipo de respuestas crea lagunas de seguridad; Por favor, chicos que vean estas respuestas también verifiquen CVE relacionado a diferentes formatos y validadores, tome el caso de pillow/PIL por ejemplo: O –

10

Hay una Django snippet que hace esto:

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 __name__ == "__main__": 
    import doctest, datetime 
    doctest.testmod() 
+2

Este es un filtro basado en la extensión, que no es confiable en absoluto. Estaba pensando en analizar el archivo después de subir los acabados. – maroxe

+0

@maroxe ¿Encontró una solución a esto? Solo tengo el mismo problema para determinar si los archivos son audiofiles o no. – marue

+3

Pruebe este enlace: http://nemesisdesign.net/blog/coding/django-filefield-content-type-size-validation/ – PhoebeB

7

Primera. Cree un archivo llamado formatChecker.py dentro de la aplicación donde tiene el modelo que tiene FileField y desea aceptar un determinado tipo de archivo.

Esta es su 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 

Segundo. En su models.py, añadir lo siguiente:

from formatChecker import ContentTypeRestrictedFileField 

A continuación, en lugar de utilizar 'FileField', utilizar este 'ContentTypeRestrictedFileField'.

Ejemplo:

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) 

Esas son las cosas que tiene que cuando se quiere aceptar sólo un determinado tipo de archivo en FileField.

+0

content-type es fácil de engañar –

+3

Agregando esta línea antes de la super llamada: self.widget = ClearableFileInput (attrs = {' accept ':', '. join (self.content_types)}) solo hará que los tipos de contenido aceptados sean seleccionables en la ventana modal. – laffuste

+0

@laffuste Después de haber enviado su comentario votando arriba, esto no parece funcionar en Mac (es decir, los archivos pueden seleccionarse). Sin embargo, no han probado en Windows. – Erve1879

2

Creo que sería más adecuado usar el ExtFileField que Dominic Rodger especificó en su respuesta y python-magic que Daniel Quinn mencionó es la mejor manera de hacerlo. Si alguien es lo suficientemente inteligente como para cambiar la extensión, al menos los atrapará con los encabezados.

+0

Dado que ha determinado que este es el mejor método, ¿por qué no publicar un código para demostrarlo al resto de nosotros? – Thismatters

1

Además extenderé esta clase con algún comportamiento adicional.

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) 

Cuando creamos instancia con el parámetro accept = "pdf, txt", en la ventana emergente con la estructura de archivos por defecto veremos los archivos con extensión pasado.

1

Puede definir una lista de tipos de mime aceptados en la configuración y luego definir un validador que use python-magic para detectar el tipo mime y provoca ValidationError si no se acepta el tipo mime. Establezca ese validador en el campo de formulario de archivo.

El único problema es que a veces el tipo mime es application/octet-stream, que podría corresponder a diferentes formatos de archivo. ¿Alguien de ustedes superó este problema?

11

Django en la versión 1.11 tiene un recién agregado FileExtensionValidator para los campos del modelo, el documento está aquí: https://docs.djangoproject.com/en/dev/ref/validators/#fileextensionvalidator.

Un ejemplo de cómo validar una extensión de archivo:

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'])]) 

Tenga en cuenta que este método es no es seguro. Cita de documentos de Django:

No confíe en la validación de la extensión de archivo para determinar el tipo de un archivo . Los archivos se pueden renombrar para tener cualquier extensión sin importar qué datos contienen .

También es nueva validate_image_file_extension (https://docs.djangoproject.com/en/dev/ref/validators/#validate-image-file-extension) para la validación de las extensiones de imagen (que usa la almohadilla).

2

Usted puede utilizar el siguiente para restringir los tipos de archivos en su Formulario

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

Algunas personas han sugerido el uso de python-magic para validar que el archivo es en realidad del tipo que está esperando recibir. Esto puede ser incorporado en el validator se sugiere en la respuesta aceptada:

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.') 

Este ejemplo sólo valida un pdf, pero cualquier número de tipos MIME y extensiones de archivo se puede agregar a las matrices.

Asumiendo que salvó a la anterior en validators.py puede incorporar esto en su modelo de este modo:

from myapp.validators import validate_is_pdf 

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