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?
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
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.
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])
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.
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!
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)
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.
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.
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.
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.
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
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."])
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