Größenbeschränkung für das Hochladen von Django-Dateien

74

Ich habe ein Formular in meiner Django-App, in das Benutzer Dateien hochladen können.
Wie kann ich ein Limit für die Größe der hochgeladenen Datei festlegen, damit das Formular nicht gültig ist und ein Fehler auftritt, wenn ein Benutzer eine Datei hochlädt, die größer als mein Limit ist?

Daniels
quelle
1
Ähnliche Frage mit Antwort: stackoverflow.com/questions/2894914/…
Dave
1
@ DaveGallagher: Die Verwendung eines Upload-Handlers zeigt dem Benutzer keine hübsche Fehlermeldung an, sondern trennt nur die Verbindung.
Emil Stenström
Mögliches Duplikat der maximalen Bildgröße beim Hochladen der Datei
Zulu

Antworten:

56

Dieser Code könnte helfen:

# 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

Entnommen aus: Django-Snippets - Validierung nach Art und Größe des Dateiinhalts

Nacho
quelle
Wissen Sie, warum solche Werte verwendet werden, sehen Sie aus wie 10 * (eine Potenz von 2)?
Lajarre
@AnthonyLozano Eigentlich MiBist immer immer immer 1048576 Bytes, es ist keine Zehnerpotenz. Was MBist es nicht eindeutig, kann es entweder mittlere 1000000 Bytes , wenn Sie IEC - Normen sind folgende oder 1048576 Bytes , wenn Sie Windows und dergleichen verwenden. Der Wikipedia-Artikel, auf den Sie verlinkt haben, ist ein Beweis.
Flimm
2
Ich bin mir ziemlich sicher, dass Sie am Ende von "5242880" eine 0 verloren haben. Sollte "52428800" sein
Anthony Lozano
7
In Django 1.10 verwenden content.size(kein Unterstrich)
Rick Westera
3
Setzen Sie keine MAX_UPLOAD_SIZEZeichenfolge. Es sollte eine Zahl sein - dieser Code ermöglicht das Hochladen jeder Größe, da der erste ValidationError nicht erreicht werden kann.
Jeremy Hert
83

Sie können dieses Snippet formatChecker verwenden. Was es tut ist

  • Hier können Sie angeben, welche Dateiformate hochgeladen werden dürfen.

  • und lässt Sie das Limit der Dateigröße der hochzuladenden Datei festlegen.

Zuerst. Erstellen Sie eine Datei mit dem Namen formatChecker.py in der App, in der Sie das Modell mit dem FileField haben, das Sie für einen bestimmten Dateityp akzeptieren möchten.

Dies ist Ihr 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", 0)

        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

Zweite. Fügen Sie in Ihrer models.py Folgendes hinzu:

from formatChecker import ContentTypeRestrictedFileField

Verwenden Sie dann anstelle von 'FileField' dieses 'ContentTypeRestrictedFileField'.

Beispiel:

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)

Sie können den Wert von 'max_upload_size' auf das gewünschte Limit der Dateigröße ändern. Sie können auch die Werte in der Liste der 'content_types' in die Dateitypen ändern, die Sie akzeptieren möchten.

Erstaunlicher Angelo
quelle
5
Was für eine großartige unterschätzte Antwort! Vollständiger und etwas besser als die validierte.
Geoffroy CALA
4
gibt einen Fehler __init __ () bekam ein unerwartetes Schlüsselwortargument content_types beim Erstellen einer Datenbank
Dinesh Goel
5
Zeile 23 in init self.content_types = kwargs.pop ("content_types") KeyError: 'content_types' - gibt mir immer wieder diesen Fehler
luiscvalmeida
3
Dies sollte wirklich in Django eingebaut werden.
Shacker
3
Diejenigen, die darauf stoßen, sollten sich aus der Dokumentation kwargs.popdaran erinnern, dass sie einen Standard haben sollten. Ändern Sie zu diesemself.content_types = kwargs.pop("content_types", []) self.max_upload_size = kwargs.pop("max_upload_size", [])
Ngatia Frankline
63

Eine andere Lösung ist die Verwendung von Validatoren

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

dann haben Sie in Ihrem Formular mit dem Feld Datei so etwas

image = forms.FileField(required=False, validators=[file_size])
ifedapo olarewaju
quelle
1
Dies ist mein Favorit, da die anderen auf eine private Variable zugreifen _sizeund diese nicht.
Flimm
11
Dies ist so elegant, dass Sie diesen Code mit Anzug und Krawatte schreiben sollten.
Camilo Nova
5
Vorsichtig! Validierungen werden nur aufgerufen, wenn Sie ein Formular zum Speichern von Daten verwenden. Andernfalls müssen Sie Validierungen manuell aufrufen, z. 'instance.full_clean ()' vor dem Speichern in db.
Hemant_Negi
1
@Hemant_Negi Ich glaube, die Frage zeigt an, dass die Datei über Formulare empfangen wird, also keine Sorge.
ifedapo olarewaju
Das hat bei mir nicht funktioniert, der Validator wird nicht angerufen
Leman Kirme
20

Ich glaube, dass das Django-Formular eine Datei erst empfängt, nachdem es vollständig hochgeladen wurde. Wenn jemand eine 2-GB-Datei hochlädt, ist es daher viel besser, wenn der Webserver die Größe im laufenden Betrieb überprüft.

Weitere Informationen finden Sie in diesem Mail-Thread .

Dmitry Shevchenko
quelle
1
Ich stimme Ihnen darin zu, aber in meinem Fall brauche ich das Limit, um in Django zu sein.
Daniels
1
Zum Zeitpunkt des Schreibens (vor 2 Jahren) hat Django einfach DoS mit starkem Datei-Upload durchgeführt. Im Moment sind die Dinge anders und je nach Zweck der Einschränkung könnte es in beide Richtungen gehen
Dmitry Shevchenko
9

Nur eine kurze Anmerkung zu dem Snippet, das in diesem Thread enthalten war:

Schauen Sie sich dieses Snippet an: http://www.djangosnippets.org/snippets/1303/

Es war sehr nützlich, enthält jedoch einige kleinere Fehler. Robusterer Code sollte folgendermaßen aussehen:

# 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

Es gibt nur ein paar Verbesserungen:

Zuerst stelle ich fest, ob das Dateifeld leer ist (keine) - ohne es wird Django eine Ausnahme im Webbrowser auslösen.

Als nächstes geben Sie Casting in int (settings.MAX_UPLOAD_SIZE) ein, da dieser Einstellungswert eine Zeichenfolge ist. Zeichenfolgen können nicht zum Vergleichen mit Zahlen verwendet werden.

Last but not least das Unicode-Präfix 'u' in der ValidationError-Funktion.

Vielen Dank für diesen Ausschnitt!

Jaro
quelle
Ich verwende eine ähnliche Methode, verwende nur Python-Magie, anstatt das Feld django content_type zu lesen, aber ich hatte ein Problem. Ich akzeptiere PDF-Dateien (MIME-Typ 'Anwendung / PDF'). Das Problem ist, dass der MIME-Typ manchmal "application / octet-stream" zu sein scheint, selbst für PDF-Dateien. Ich möchte diesen MIME-Typ nicht zu meiner Liste der akzeptierten Typen hinzufügen, da sonst auch andere Dokumenttypen akzeptiert würden (z. B. Excel). Weiß jemand, wie man dieses Problem behebt?
Sabrina
Versuchen Sie, die Dateierweiterungen zu überprüfen, '.pdf'anstatt zu content_typeprüfen. Dies ist einfacher und gleichzeitig robuster.
Mikhail Geyer
8

Wenn jemand nach einer Formularvariante FileFieldder @angelo-Lösung sucht, dann ist sie hier

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

Dann erstellen Sie ein Formular als

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

Serverseite

Meine bevorzugte Methode, um zu überprüfen, ob eine Datei serverseitig zu groß ist, ist die Antwort von ifedapo olarewaju mithilfe eines Validators.

Client-Seite

Das Problem, nur eine serverseitige Validierung durchzuführen, besteht darin, dass die Validierung erst nach Abschluss des Uploads erfolgt. Stellen Sie sich vor, Sie laden eine riesige Datei hoch und warten ewig, um anschließend zu erfahren, dass die Datei zu groß ist. Wäre es nicht schöner, wenn der Browser mich vorher darüber informieren könnte, dass die Datei zu groß ist?

Nun, es gibt einen Weg zu dieser Client-Seite , indem Sie die HTML5-Datei-API verwenden !

Hier ist das erforderliche Javascript (abhängig von 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;
    }
  }
});

Natürlich benötigen Sie noch eine serverseitige Überprüfung, um sich vor böswilligen Eingaben zu schützen, und Benutzer, für die Javascript nicht aktiviert ist.

Flimm
quelle
3

Eine andere elegante Lösung mit Validatoren, die die maximale Dateigröße nicht fest codiert, ist die Verwendung eines klassenbasierten Validators:

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)

und dann in Ihrem Modell zum Beispiel:

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

BEARBEITEN: Hier ist der Quellcode von MaxValueValidatorfür weitere Details zu dieser Arbeit.

Gianpaolo
quelle
2

Ich möchte allen Leuten danken, die verschiedene Lösungen für dieses Problem bereitgestellt haben. Ich hatte zusätzliche Anforderungen, bei denen ich (a) vor der Übermittlung eine Überprüfung der Dateilänge in JavaScript durchführen wollte, (b) eine zweite Verteidigungslinie auf dem Server durchführen wollte forms.py, (c) alle fest codierten Bits einschließlich Endbenutzernachrichten beibehalten wollte in forms.py, (d) Ich wollte, dass ich views.pyso wenig dateibezogenen Code wie möglich habe, und (d) lade die Dateiinformationen in meine Datenbank hoch, da dies kleine Dateien sind, die ich nur für angemeldete Benutzer bereitstellen und beim MealModell sofort löschen möchte Elemente werden gelöscht (dh es reicht nicht aus, sie nur in / media / abzulegen).

Zuerst das Modell:

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

Dann benötigen Sie ein Formular, das sowohl die In-Server-Validierung als auch die Konvertierung vor InMemoryUploadedFiledem Speichern von nach bytesausführt und das Content-Typefür die spätere Bereitstellung abruft.

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

Fügen Sie in der Vorlage diesen Code hinzu (angepasst an eine vorherige Antwort):

<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>

Hier ist der Ansichtscode, der sowohl Erstellen als auch Aktualisieren behandelt:

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)

Dies ist eine sehr einfache Ansicht, die sicherstellt, dass request.FILES während der Erstellung der Instanz übergeben wird. Sie könnten fast die generische CreateView verwenden, wenn sie (a) mein Formular verwenden und (b) request.files beim Erstellen der Modellinstanz übergeben würde.

Um den Aufwand abzuschließen, habe ich die folgende einfache Ansicht, um die Datei zu streamen:

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

Dies erzwingt nicht, dass Benutzer angemeldet werden, aber ich habe dies weggelassen, da diese Antwort bereits zu lang ist.

drchuck
quelle
2

In meinem Fall begrenzt Django die Größe der Upload-Datei. Wenn Sie die folgenden Einstellungen hinzufügen, werden die Einschränkungen aufgehoben.

# allow upload big file
DATA_UPLOAD_MAX_MEMORY_SIZE = 1024 * 1024 * 15  # 15M
FILE_UPLOAD_MAX_MEMORY_SIZE = DATA_UPLOAD_MAX_MEMORY_SIZE
Weaming
quelle
Wenn ich versuche, große Bilder in meine Django-App hochzuladen, werden sie nicht an den Server gesendet. Vielen Dank für diese Hilfe.
CY23
0
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."])
Nids Barthwal
quelle
0

Sie können Djangos erweitern MaxValueValidatorund überschreiben clean(), um die Dateigröße zurückzugeben:

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

Ciaran Courtney
quelle