web-dev-qa-db-fra.com

Django Limite de taille de téléchargement de fichier

J'ai un formulaire dans mon Django où les utilisateurs peuvent télécharger des fichiers.
Comment puis-je définir une limite à la taille du fichier téléchargé afin que si un utilisateur télécharge un fichier plus grand que ma limite, le formulaire ne sera pas valide et générera une erreur?

57
daniels

Ce code pourrait aider:

# Add to your settings file
CONTENT_TYPES = ['image', 'video']
# 2.5MB - 2621440
# 5MB - 5242880
# 10MB - 10485760
# 20MB - 20971520
# 50MB - 5242880
# 100MB 104857600
# 250MB - 214958080
# 500MB - 429916160
MAX_UPLOAD_SIZE = "5242880"

#Add to a form containing a FileField and change the field names accordingly.
from Django.template.defaultfilters import filesizeformat
from Django.utils.translation import ugettext_lazy as _
from Django.conf import settings
def clean_content(self):
    content = self.cleaned_data['content']
    content_type = content.content_type.split('/')[0]
    if content_type in settings.CONTENT_TYPES:
        if content._size > settings.MAX_UPLOAD_SIZE:
            raise forms.ValidationError(_('Please keep filesize under %s. Current filesize %s') % (filesizeformat(settings.MAX_UPLOAD_SIZE), filesizeformat(content._size)))
    else:
        raise forms.ValidationError(_('File type is not supported'))
    return content

Extrait de: Django Snippets - Validate by file content type and size

46
Nacho

Vous pouvez utiliser ce format d'extrait Checker. Ce qu'il fait c'est

  • il vous permet de spécifier les formats de fichiers autorisés à être téléchargés.

  • et vous permet de définir la taille limite du fichier à télécharger.

Première. 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)

Vous pouvez modifier la valeur de "max_upload_size" à la limite de taille de fichier que vous souhaitez. Vous pouvez également modifier les valeurs dans la liste des 'content_types' en types de fichiers que vous souhaitez accepter.

68
Amazing Angelo

une autre solution utilise des validateurs

from Django.core.exceptions import ValidationError

def file_size(value): # add this to some file where you can import it from
    limit = 2 * 1024 * 1024
    if value.size > limit:
        raise ValidationError('File too large. Size should not exceed 2 MiB.')

puis dans votre formulaire avec le champ Fichier, vous avez quelque chose comme ça

image = forms.FileField(required=False, validators=[file_size])
39
ifedapo olarewaju

Je crois que Django ne reçoit le fichier qu'après avoir été complètement téléchargé.C'est pourquoi si quelqu'un télécharge un fichier 2 Go, vous êtes beaucoup mieux avec un serveur Web qui vérifie la taille à la volée.

Voir ceci fil de discussion pour plus d'informations.

19
Dmitry Shevchenko

Juste une petite note sur l'extrait de code qui était inclus dans ce fil:

Jetez un œil à cet extrait: http://www.djangosnippets.org/snippets/1303/

C'était très utile, mais cela inclut quelques erreurs mineures. Un code plus robuste devrait ressembler à ceci:

# Add to your settings file
CONTENT_TYPES = ['image', 'video']
# 2.5MB - 2621440
# 5MB - 5242880
# 10MB - 10485760
# 20MB - 20971520
# 50MB - 5242880
# 100MB - 104857600
# 250MB - 214958080
# 500MB - 429916160
MAX_UPLOAD_SIZE = "5242880"

#Add to a form containing a FileField and change the field names accordingly.
from Django.template.defaultfilters import filesizeformat
from Django.utils.translation import ugettext_lazy as _
from Django.conf import settings
def clean_content(self):
    if content != None:
        content = self.cleaned_data['content']
        content_type = content.content_type.split('/')[0]
        if content_type in settings.CONTENT_TYPES:
            if content._size > int(settings.MAX_UPLOAD_SIZE):
                raise forms.ValidationError(_(u'Please keep filesize under %s. Current filesize %s') % (filesizeformat(settings.MAX_UPLOAD_SIZE), filesizeformat(content._size)))
        else:
            raise forms.ValidationError(_(u'File type is not supported'))
        return content

Il y a juste quelques améliorations:

Tout d'abord, je détecte si le champ du fichier est vide (Aucun) - sans lui, Django générera une exception dans le navigateur Web.

Ensuite, tapez le transtypage en int (settings.MAX_UPLOAD_SIZE), car cette valeur de paramètre est une chaîne. Les chaînes ne peuvent pas être utilisées pour comparer avec des nombres.

Enfin et surtout, le préfixe unicode 'u' dans la fonction ValidationError.

Merci beaucoup pour cet extrait!

9
Jaro

Si quelqu'un recherche une variante de formulaire FileField de la solution @angelo, alors la voici

from Django import forms
from Django.template.defaultfilters import filesizeformat
from Django.utils.translation import ugettext_lazy as _
from Django.core.exceptions import ValidationError

class RestrictedFileField(forms.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(RestrictedFileField, self).__init__(*args, **kwargs)

    def clean(self, data, initial=None):
        file = super(RestrictedFileField, self).clean(data, initial)

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

        return data

Créez ensuite un formulaire

class ImageUploadForm(forms.Form):
    """Image upload form."""
    db_image = RestrictedFileField(content_types=['image/png', 'image/jpeg'],
                                   max_upload_size=5242880)
8
Hemant_Negi

Du côté serveur

Ma méthode préférée pour vérifier si un fichier est trop volumineux côté serveur est réponse de ifedapo olarewaj en utilisant un validateur.

Côté client

Le problème lié à la validation côté serveur uniquement est que la validation ne se produit qu'une fois le téléchargement terminé. Imaginez, en train de télécharger un fichier énorme, en attendant des âges, pour être ensuite informé que le fichier est trop volumineux. Ne serait-il pas plus agréable que le navigateur me prévienne à l'avance que le fichier est trop volumineux?

Eh bien, il existe un moyen pour cela côté client , en utilisant API de fichier HTML5 !

Voici le Javascript requis (selon JQuery):

$("form").submit(function() {
  if (window.File && window.FileReader && window.FileList && window.Blob) {
    var file = $('#id_file')[0].files[0];

    if (file && file.size > 2 * 1024 * 1024) {
      alert("File " + file.name + " of type " + file.type + " is too big");
      return false;
    }
  }
});

Bien sûr, vous avez toujours besoin d'une validation côté serveur pour vous protéger contre les entrées malveillantes et les utilisateurs qui n'ont pas activé Javascript.

8
Flimm

Une autre solution élégante avec des validateurs qui ne codent pas en dur la taille de fichier maximale consiste à utiliser un validateur basé sur une classe:

from Django.core.exceptions import ValidationError
from Django.core.validators import MaxValueValidator
from Django.utils.translation import ugettext as _

class MaxSizeValidator(MaxValueValidator):
message = _('The file exceed the maximum size of %(limit_value)s MB.')

def __call__(self, value):
    # get the file size as cleaned value
    cleaned = self.clean(value.size)
    params = {'limit_value': self.limit_value, 'show_value': cleaned, 'value': value}
    if self.compare(cleaned, self.limit_value * 1024 * 1024): # convert limit_value from MB to Bytes
        raise ValidationError(self.message, code=self.code, params=params)

puis, dans votre modèle, par exemple:

image = models.ImageField(verbose_name='Image', upload_to='images/', validators=[MaxSizeValidator(1)])

EDIT: ici est le code source de MaxValueValidator pour plus de détails sur ces travaux.

3
Gianpaolo

Je tiens à remercier toutes les personnes qui ont fourni différentes solutions différentes à ce problème. J'avais des exigences supplémentaires lorsque je voulais (a) faire la validation de la longueur des fichiers en JavaScript avant la soumission, (b) faire une deuxième ligne de défense sur le serveur dans le forms.py, (c) conserver tous les bits codés en dur, y compris les messages d'utilisateur final dans forms.py, (d) je voulais mon views.py a le moins de code possible sur les fichiers, et (d) télécharge les informations sur le fichier dans ma base de données car ce sont de petits fichiers que je souhaite uniquement servir aux utilisateurs connectés et supprimer instantanément lorsque le modèle Meal les éléments sont supprimés (c'est-à-dire qu'il ne suffit pas de les déposer dans/media /).

D'abord le modèle:

class Meal(models.Model) :
    title = models.CharField(max_length=200)
    text = models.TextField()

    # Picture (you need content type to serve it properly)
    picture = models.BinaryField(null=True, editable=True)
    content_type = models.CharField(max_length=256, null=True, help_text='The MIMEType of the file')

    # Shows up in the admin list
    def __str__(self):
        return self.title

Ensuite, vous avez besoin d'un formulaire qui effectue à la fois la validation sur le serveur et la conversion de pré-sauvegarde de InMemoryUploadedFile en bytes et en saisissant le Content-Type pour servir plus tard.

class CreateForm(forms.ModelForm):
    max_upload_limit = 2 * 1024 * 1024
    max_upload_limit_text = str(max_upload_limit) # A more natural size would be Nice
    upload_field_name = 'picture'
    # Call this 'picture' so it gets copied from the form to the in-memory model
    picture = forms.FileField(required=False, label='File to Upload <= '+max_upload_limit_text)

    class Meta:
        model = Meal
        fields = ['title', 'text', 'picture']

    def clean(self) :  # Reject if the file is too large
        cleaned_data = super().clean()
        pic = cleaned_data.get('picture')
        if pic is None : return
        if len(pic) > self.max_upload_limit:
            self.add_error('picture', "File must be < "+self.max_upload_limit_text+" bytes")

    def save(self, commit=True) : # Convert uploaded files to bytes
        instance = super(CreateForm, self).save(commit=False)
        f = instance.picture   # Make a copy
        if isinstance(f, InMemoryUploadedFile):
            bytearr = f.read();
            instance.content_type = f.content_type
            instance.picture = bytearr  # Overwrite with the actual image data

        if commit:
            instance.save()
        return instance

Dans le modèle, ajoutez ce code (adapté d'une réponse précédente):

<script>
$("#upload_form").submit(function() {
  if (window.File && window.FileReader && window.FileList && window.Blob) {
      var file = $('#id_{{ form.upload_field_name }}')[0].files[0];
      if (file && file.size > {{ form.max_upload_limit }} ) {
          alert("File " + file.name + " of type " + file.type + " must be < {{ form.max_upload_limit_text }}");
      return false;
    }
  }
});
</script>

Voici le code d'affichage qui gère à la fois la création et la mise à jour:

class MealFormView(LoginRequiredMixin, View):
    template = 'meal_form.html'
    success_url = reverse_lazy('meals')
    def get(self, request, pk=None) :
        if not pk :
            form = CreateForm()
        else:
            meal = get_object_or_404(Meal, id=pk, owner=self.request.user)
            form = CreateForm(instance=meal)
        ctx = { 'form': form }
        return render(request, self.template, ctx)

    def post(self, request, pk=None) :
        if not pk:
            form = CreateForm(request.POST, request.FILES or None)
        else:
            meal = get_object_or_404(Meal, id=pk, owner=self.request.user)
            form = CreateForm(request.POST, request.FILES or None, instance=meal)

        if not form.is_valid() :
            ctx = {'form' : form}
            return render(request, self.template, ctx)

        form.save()
        return redirect(self.success_url)

Il s'agit d'une vue très simple qui garantit que cette requête.FILES est transmise lors de la création de l'instance. Vous pourriez presque utiliser le CreateView générique s'il (a) utilisait mon formulaire et (b) passait request.files lors de la création de l'instance de modèle.

Juste pour terminer l'effort, j'ai la vue simple suivante pour diffuser le fichier:

def stream_file(request, pk) :
    meal = get_object_or_404(Meal, id=pk)
    response = HttpResponse()
    response['Content-Type'] = meal.content_type
    response['Content-Length'] = len(meal.picture)
    response.write(meal.picture)
    return response

Cela n'oblige pas les utilisateurs à se connecter, mais je l'ai omis car cette réponse est déjà trop longue.

2
drchuck

Dans mon cas, Django limite la taille du fichier de téléchargement. Ajouter les paramètres suivants supprimera la restriction.

# allow upload big file
DATA_UPLOAD_MAX_MEMORY_SIZE = 1024 * 1024 * 15  # 15M
FILE_UPLOAD_MAX_MEMORY_SIZE = DATA_UPLOAD_MAX_MEMORY_SIZE
0
weaming
from Django.forms.utils import ErrorList

class Mymodelform(forms.ModelForm):
    class Meta:
        model = Mymodel
        fields = '__all__'

    def clean(self):image = self.cleaned_data.get('image')
        # 5MB - 5242880
        if org_image._size > 5242880:            
            self._errors["image"] = ErrorList([u"Image too heavy."])
0
Nids Barthwal

Vous pouvez étendre le MaxValueValidator de Django et écraser son clean() pour retourner la taille du fichier:

from Django.core.validators import MaxValueValidator
from Django.utils.deconstruct import deconstructible
from Django.utils.translation import ugettext_lazy as _


@deconstructible
class MaxKibFileSizeValidator(MaxValueValidator):
    message = _('File size %(show_value)d KiB exceeds maximum file size of %(limit_value)d KiB.')

    def clean(self, filefield) -> float:
        return filefield.file.size / 1024

0
Ciaran Courtney