Envoi de fichiers

Lorsque Django gère un téléversement de fichier, les données du fichier aboutissent dans request.FILES (plus de détails sur l’objet request se trouvent dans la documentation des objets request et response). Ce document explique comment les fichiers sont stockés sur disque et en mémoire, et comment personnaliser le comportement par défaut.

Avertissement

Il existe des risques de sécurité si vous acceptez du contenu téléversé de la part d’utilisateurs non fiables. Consultez le sujet Contenu envoyé par les utilisateurs du guide de sécurité pour des détails sur les mesures de réduction des risques.

Envoi simple de fichiers

Considérez ce simple formulaire contenant un champ FileField:

# In forms.py...
from django import forms

class UploadFileForm(forms.Form):
    title = forms.CharField(max_length=50)
    file  = forms.FileField()

Une vue gérant ce formulaire recevra les données de fichier dans request.FILES qui est un dictionnaire contenant une clé pour chaque FileField (ou ImageField, ou toute autre sous-classe de FileField) du formulaire. Ainsi, les données du formulaire ci-dessus seraient accessibles dans request.FILES['file'].

Notez que request.FILES ne contient les données que lorsque la méthode de requête est POST et que le formulaire``<form>`` à l’origine de la requête possède l’attribut enctype="multipart/form-data". Sinon, request.FILES est vide.

La plupart du temps, les données de fichiers seront simplement transmises de request au formulaire comme expliqué dans Liaison de fichiers téléversés avec un formulaire. Voici ce que cela peut donner :

from django.http import HttpResponseRedirect
from django.shortcuts import render_to_response
from .forms import UploadFileForm

# Imaginary function to handle an uploaded file.
from somewhere import handle_uploaded_file

def upload_file(request):
    if request.method == 'POST':
        form = UploadFileForm(request.POST, request.FILES)
        if form.is_valid():
            handle_uploaded_file(request.FILES['file'])
            return HttpResponseRedirect('/success/url/')
    else:
        form = UploadFileForm()
    return render_to_response('upload.html', {'form': form})

Remarquez que nous devons transmettre request.FILES au constructeur du formulaire ; c’est comme cela que les données de fichiers sont liées à un formulaire.

Gestion des fichiers téléversés

class UploadedFile[source]

La pièce finale du puzzle consiste à gérer les données de fichiers réelles dans request.FILES. Chaque élément de ce dictionnaire est un objet UploadedFile, un simple adaptateur autour d’un fichier téléversé. En général, c’est par l’une de ces méthodes que l’on accède au contenu téléversé :

read()

Lit toutes les données du fichier téléversé. Soyez prudent avec cette méthode : si le fichier concerné est très gros, il pourrait dépasser les capacités de votre système si vous essayez de le lire en mémoire. Il est plutôt recommandé d’utiliser chunks(), voir ci-dessous.

multiple_chunks()

Renvoie True si le fichier téléversé est assez gros pour avoir besoin d’être lu en plusieurs segments. Par défaut, cela concerne tous les fichiers plus gros que 2,5 mébioctets, mais cela peut être configuré ; voir ci-dessous.

chunks()

Un générateur renvoyant des segments du fichier. Si multiple_chunks() vaut True, il est conseillé d’utiliser cette méthode avec une boucle au lieu d’utiliser read().

En pratique, il est souvent plus simple de toujours utiliser chunks(). Voir l’exemple ci-dessous.

name

Le nom du fichier téléversé (par ex. mon_fichier.txt).

size

La taille en octets du fichier téléversé.

Il existe quelque autres méthodes et attributs accessibles pour les objets UploadedFile. Voir objets UploadedFile pour une référence complète.

En rassemblant tous les morceaux, voici une manière habituelle de gérer un fichier téléversé :

def handle_uploaded_file(f):
    with open('some/file/name.txt', 'wb+') as destination:
        for chunk in f.chunks():
            destination.write(chunk)

En bouclant sur UploadedFile.chunks() au lieu d’appeler read(), on peut s’assurer que les gros fichiers ne saturent pas la mémoire du système.

Emplacement de stockage des données

Avant d’enregistrer des fichiers téléversés, les données doivent bien être stockées quelque part.

Par défaut, si un fichier téléversé est plus petit que 2,5 Mio, Django place la totalité du fichier en mémoire. Cela signifie que l’enregistrement du fichier correspond à une simple lecture en mémoire et à une écriture sur le disque, ce qui est très rapide.

Cependant, si un fichier téléversé est trop gros, Django écrit le fichier dans un fichier temporaire stocké dans le répertoire des fichiers temporaires du système. Sur une plate-forme de type Unix, Django va en principe générer un fichier dont le chemin correspond à quelque chose comme /tmp/tmpzfp6I6.upload. Si le fichier est suffisamment gros, vous pouvez même voir la taille du fichier augmenter au fur et à mesure de son flux d’enregistrement vers le disque.

Ces paramètres spécifiques, 2,5 Mio, /tmp, etc. ne sont que des valeurs par défaut raisonnables. Lisez la suite pour obtenir des détails sur la manière de personnaliser ou de remplacer entièrement le comportement d’envoi de fichiers.

Modification de la gestion des téléversements

Trois réglages contrôlent le comportement de téléversement de fichiers par Django :

FILE_UPLOAD_MAX_MEMORY_SIZE

La taille maximale en octets des fichiers téléversés conservés en mémoire. Les fichiers plus gros que FILE_UPLOAD_MAX_MEMORY_SIZE sont stockés temporairement sur le disque.

La valeur par défaut est de 2,5 mébioctets.

FILE_UPLOAD_TEMP_DIR

Le répertoire dans lequel sont stockés les fichiers plus gros que FILE_UPLOAD_MAX_MEMORY_SIZE.

Contient par défaut le répertoire temporaire standard du système (c’est-à-dire /tmp sur la plupart des systèmes de type Unix).

FILE_UPLOAD_PERMISSIONS

The numeric mode (i.e. 0o644) to set newly uploaded files to. For more information about what these modes mean, see the documentation for os.chmod().

If this isn’t given or is None, you’ll get operating-system dependent behavior. On most platforms, temporary files will have a mode of 0o600, and files saved from memory will be saved using the system’s standard umask.

Pour des raisons de sécurité, ces permissions ne sont pas appliquées aux fichiers temporaires qui sont stockés dans FILE_UPLOAD_TEMP_DIR.

Avertissement

Si vous n’êtes pas à l’aise avec les modes de fichiers, sachez que le premier 0 est très important : il indique un nombre octal, ce qui est la bonne manière d’indiquer les modes. Si vous essayez d’utiliser 644, vous obtiendrez un comportement totalement incorrect.

Toujours précéder le mode d’un 0.

FILE_UPLOAD_HANDLERS

Les gestionnaires effectifs qui vont traiter les fichiers téléversés. Ce réglage permet de personnaliser complètement (même remplacer) le processus de gestion des téléversements par Django. Voir gestionnaires de téléversement ci-dessous pour plus de détails.

Valeur par défaut :

("django.core.files.uploadhandler.MemoryFileUploadHandler",
 "django.core.files.uploadhandler.TemporaryFileUploadHandler",)

Ce qui signifie : « essayer d’abord de placer les fichiers reçus en mémoire, puis se rabattre sur les fichiers temporaires ».

Téléversement de fichiers liés à un modèle

Si vous enregistrez un fichier dans un Model contenant un champ FileField, l’emploi d’un formulaire ModelForm simplifie le processus. L’objet fichier sera enregistré à l’emplacement indiqué par le paramètre upload_to du champ FileField correspondant lors de l’appel à form.save():

from django.http import HttpResponseRedirect
from django.shortcuts import render
from .forms import ModelFormWithFileField

def upload_file(request):
    if request.method == 'POST':
        form = ModelFormWithFileField(request.POST, request.FILES)
        if form.is_valid():
            # file is saved
            form.save()
            return HttpResponseRedirect('/success/url/')
    else:
        form = ModelFormWithFileField()
    return render(request, 'upload.html', {'form': form})

Si vous construisez manuellement un objet, vous pouvez simplement attribuer l’objet fichier provenant de request.FILES au champ de fichier du modèle :

from django.http import HttpResponseRedirect
from django.shortcuts import render
from .forms import UploadFileForm
from .models import ModelWithFileField

def upload_file(request):
    if request.method == 'POST':
        form = UploadFileForm(request.POST, request.FILES)
        if form.is_valid():
            instance = ModelWithFileField(file_field=request.FILES['file'])
            instance.save()
            return HttpResponseRedirect('/success/url/')
    else:
        form = UploadFileForm()
    return render(request, 'upload.html', {'form': form})

Objets UploadedFile

En plus de ceux hérités de File, tous les objets UploadedFile définissent les méthodes/attributs suivants :

UploadedFile.content_type

L’en-tête content-type associé au fichier téléversé (par ex. text/plain ou application/pdf). Comme toute donnée en provenance d’un utilisateur, on ne peut pas être certain que le fichier soit vraiment de ce type. Il est toujours nécessaire de valider que le contenu du fichier corresponde bien au type de contenu prétendu par l’en-tête, « avoir confiance mais contrôler ».

UploadedFile.charset

Pour les types de contenu text/*, le codage de caractères (par ex. utf8) indiqué par le navigateur. Encore une fois, « avoir confiance, mais contrôler » est la stratégie la plus sage ici.

UploadedFile.temporary_file_path

Seuls les fichiers déversés sur le disque possèdent cette méthode ; elle renvoie le chemin complet du fichier téléversé temporaire.

Note

Comme pour les fichiers Python normaux, vous pouvez lire le fichier ligne par ligne en bouclant simplement sur le fichier téléversé :

for line in uploadedfile:
    do_something_with(line)

Cependant, au contraire des fichiers Python normaux, UploadedFile ne gère que les fins de ligne \n (connues aussi sous le nom de « style Unix »). Si vous savez que vous aurez à gérer des fichiers téléversés avec des fins de ligne différentes, vous devrez le faire dans la vue.

Gestionnaires de téléversement

Lorsqu’un utilisateur envoie un fichier, Django transmet les données du fichier à un gestionnaire de téléversement, une petite classe qui gère les données du fichier reçu. Les gestionnaires de téléversement sont définis initialement dans le réglage FILE_UPLOAD_HANDLERS dont le contenu par défaut est :

("django.core.files.uploadhandler.MemoryFileUploadHandler",
 "django.core.files.uploadhandler.TemporaryFileUploadHandler",)

Conjointement, MemoryFileUploadHandler et TemporaryFileUploadHandler définissent le comportement Django par défaut de téléversement de fichier en plaçant les petits fichiers en mémoire et les plus gros sur le disque.

Vous pouvez écrire des gestionnaires personnalisée qui modifient la façon de gérer les fichiers. Vous pourriez par exemple utiliser des gestionnaires personnalisés pour limiter la quantité de données par utilisateur, compresser les données à la volée, afficher des barres de progression ou même envoyer directement des données vers un autre emplacement de stockage sans les stocker localement.

Modification des gestionnaires de téléversement à la volée

Parfois, certaines vues ont besoin d’un comportement de téléversement différent. Dans ces situations, il est possible de surcharger les gestionnaires de téléversement par requête en modifiant request.upload_handlers. Par défaut, celle liste contient les gestionnaires de téléversement énumérés dans FILE_UPLOAD_HANDLERS, mais cette liste est modifiable comme n’importe quelle autre liste.

Par exemple, supposons que vous ayez écrit un gestionnaire ProgressBarUploadHandler qui renvoie des informations de progression de téléversement d’un composant AJAX quelconque. Voici comment ajouter ce gestionnaire à la liste des gestionnaires de téléversement :

request.upload_handlers.insert(0, ProgressBarUploadHandler())

Dans ce cas, list.insert() peut être plus approprié que append() parce qu’un gestionnaire de barre de progression devrait être exécuté avant tout autre gestionnaire. Souvenez-vous que les gestionnaires de téléversement sont appelés dans l’ordre.

Si vous vouliez totalement remplacer les gestionnaires de téléversement, il suffirait d’attribuer une nouvelle liste :

request.upload_handlers = [ProgressBarUploadHandler()]

Note

Il n’est possible de modifier les gestionnaires de téléversement qu’avant d’accéder à request.POST ou request.FILES, car ça n’aurait pas de sens de modifier les gestionnaires de téléversement après que la gestion des téléversements a déjà démarré. Si vous le faites tout de même, Django générera une erreur.

Ceci dit, la modification des gestionnaires de téléversement devrait toujours intervenir aussi tôt que possible dans les vues.

De plus, CsrfViewMiddleware qui est un intergiciel activé par défaut accède à request.POST. Cela signifie qu’il est nécessaire de décorer avec csrf_exempt() les vues dans lesquelles vous souhaitez changer les gestionnaires de téléversement. Il faut ensuite utiliser csrf_protect() sur la fonction qui traite effectivement la requête. Remarquez qu’il se pourrait que les gestionnaires commencent à recevoir un fichier envoyé avant que les contrôles CSRF aient été effectués. Exemple de code :

from django.views.decorators.csrf import csrf_exempt, csrf_protect

@csrf_exempt
def upload_file_view(request):
    request.upload_handlers.insert(0, ProgressBarUploadHandler())
    return _upload_file_view(request)

@csrf_protect
def _upload_file_view(request):
    ... # Process request

Écriture de gestionnaires de téléversement personnalisés

Tous les gestionnaires de téléversement de fichiers doivent hériter de django.core.files.uploadhandler.FileUploadHandler. Ils peuvent être placés à n’importe quel endroit.

Méthodes obligatoires

Les gestionnaires de téléversement personnalisés doivent définir les méthodes suivantes :

FileUploadHandler.receive_data_chunk(raw_data, start)

Reçoit un « segment » de données du fichier téléversé.

raw_data est une chaîne d’octets contenant les données envoyées.

start est la position dans le fichier correspondant au début de ce segment raw_data.

Les données que vous renvoyez seront retransmises aux méthodes receive_data_chunk des gestionnaires de téléversement suivants. De cette façon, un gestionnaire peut être un « filtre » pour d’autres gestionnaires.

Renvoyez None depuis receive_data_chunk pour empêcher les gestionnaires de téléversement restants de recevoir ce segment. Cela peut être utile si vous stockez vous-même les données reçues et que vous ne souhaitez pas que les gestionnaires suivants stockent une copie des données.

Si vous générez une exception StopUpload ou SkipFile, le téléversement s’interrompt ou le fichier est complètement ignoré.

FileUploadHandler.file_complete(file_size)

Appelée lorsque le téléversement d’un fichier est terminé.

Le gestionnaire doit renvoyer un objet UploadedFile qui sera stocké dans request.FILES. Les gestionnaires peuvent aussi renvoyer None pour indiquer que l’objet UploadedFile doit provenir d’un des gestionnaires de téléversement suivants.

Méthodes facultatives

Les gestionnaires de téléversement peuvent aussi définir l’une des méthodes ou des attributs facultatifs suivants :

FileUploadHandler.chunk_size

Taille, en octets, des segments que Django stocke en mémoire et transmet au gestionnaire. En clair, cet attribut contrôle la taille des segments qui sont envoyés à FileUploadHandler.receive_data_chunk.

Pour des performances optimales, la taille des segments devrait être divisible par 4 sans excéder 2 Gio (231 octets). Lorsque différentes tailles de segment sont indiquées par différents gestionnaires, Django utilise la taille de segment la plus petite d’entre elles.

La valeur par défaut est 64*210 octets ou 64 Kio.

FileUploadHandler.new_file(field_name, file_name, content_type, content_length, charset)

Fonction de rappel signalant qu’un nouvel envoi de fichier démarre. Elle est appelée avant que les données commencent à être envoyées à un gestionnaire de téléversement.

field_name est le nom (chaîne) du champ de fichier <input>.

file_name est le nom de fichier unicode qui a été indiqué par le navigateur.

content_type est le type MIME indiqué par le navigateur, par exemple 'image/jpeg'.

content_length est la taille de l’image indiquée par le navigateur. Il arrive que cette valeur ne soit pas indiquée et contienne None.

charset est le jeu de caractères (par ex. utf8) indiqué par le navigateur. Comme content_length, il arrive que cette valeur ne soit pas donnée.

Cette méthode peut générer une exception StopFutureHandlers pour empêcher d’autres gestionnaires de gérer ce fichier après celui-ci.

FileUploadHandler.upload_complete()

Fonction de rappel signalant que tout le téléversement est terminé (tous les fichiers).

FileUploadHandler.handle_raw_input(input_data, META, content_length, boundary, encoding)

Permet au gestionnaire de surcharger complètement l’analyse de l’entrée HTTP brute.

input_data est un objet de type fichier prenant en charge la lecture (read()).

META est le même objet que request.META.

content_length est la taille des données dans input_data. Ne lisez pas plus de content_length octets à partir de input_data.

boundary est la frontière MIME de cette requête.

encoding est le codage de cette requête.

Renvoyez None si vous voulez que la gestion du téléversement se poursuive, ou un tuple (POST, FILES) si vous voulez renvoyer directement les nouvelles structures de données prêtes à l’emploi par la requête.