Peupler un FileField Django par programmation

Ce billet date de plusieurs années, ses informations peuvent être devenues obsolètes.

Dans beaucoup de cas les fichiers media sont téléversés par les utilisateurs et Django fait le job presque tout seul.

Mais il m'arrive d'avoir à déplacer des fichiers par l'intermédiaire d'une commande ou de devoir récupérer des ressources distantes en provenance d'une API et d'avoir à les enregistrer dans un FileField par programmation.

Le code Py3k

Un modèle d'exemple :

from django.db import models
from django.utils.translation import ugettext_lazy as _

class StoredMedia(models.Model):
    name = models.CharField(verbose_name=_("Nom"), max_length=255)
    file = models.FileField(verbose_name=_("Fichier"), max_length=255)

    def __str__(self):
        return self.name

Le code pour enregistrer un fichier distant :

import os
import tempfile
import urllib
from django.core.files import File
from my.app.models import StoredMedia

# Retrieve the file.
response = urllib.request.urlopen('https://marcarea.com/media/blog/djangocong.2012.png')

# Get the file name.
split_url = urllib.parse.urlsplit(response.url).path
file_name = os.path.basename(split_url)

tmp_file = '{tmp_dir}{file_name}'.format(
    tmp_dir=tempfile.gettempdir(),
    file_name=file_name
)

# Create a temporary file locally.
with open(tmp_file, 'wb') as f:
    f.write(response.read())

# Open the temporary file as a normal Python File object.
with open(tmp_file, 'rb') as f:
    # Convert it to a Django File.
    django_file = File(f)
    # Create a StoredMedia instance.
    stored_media = StoredMedia()
    stored_media.name = "Beautiful picture"
    # Attach the temporary file to the StoredMedia instance.
    # 'save=True' means that we want to save the StoredMedia instance after the image is saved.
    stored_media.file.save(file_name, django_file, save=True)

os.remove(tmp_file)

Plus concis avec django.core.files.temp.NamedTemporaryFile ?

C'est possible :

import os
import urllib
from django.core.files import File
from django.core.files.temp import NamedTemporaryFile
from my.app.models import StoredMedia

response = urllib.request.urlopen('https://marcarea.com/media/blog/djangocong.2012.png')

split_url = urllib.parse.urlsplit(response.url).path
file_name = os.path.basename(split_url)

tmp_file = NamedTemporaryFile(delete=True)
tmp_file.write(response.read())
tmp_file.flush()

stored_media = StoredMedia()
stored_media.name = "Beautiful picture"
stored_media.file.save(file_name, File(tmp_file), save=True)

tmp_file.close()

C'est plus beau, mais c'est à vos risques et périls !

D'une part parce que cette classe est marquée for Django's internal use et certains suggèrent même de la supprimer, d'autre part parce qu'elle est basée sur la fonction Python du même nom qualifiée de not particularly useful on Windows !

À propos de FileField

Une solution quick-and-dirty pour prévisualiser les images dans l'admin

En premier lieu j'ajoute une méthode à mon modèle (avec un style en ligne bien sale) :

class StoredMedia(models.Model):

    [...]

    def file_preview(self):
        return '<img src="{0}" style="max-width: 150px;">'.format(self.file.url)
    file_preview.short_description = _("Image")
    file_preview.allow_tags = True

Et puis je me fais plaisir dans l'admin :

from django.contrib import admin
from my.app.models import StoredMedia

class StoredMediaAdmin(admin.ModelAdmin):
    list_display = ('id', 'name', 'file', 'file_preview',)

admin.site.register(StoredMedia, StoredMediaAdmin)

Les enfants, ne faites pas ça chez vous avec un FileField ! Faites le avec un ImageField si vous avez des images de taille raisonnable.

L'instruction with en Python avec open

D'après la documentation Python :

It is good practice to use the with keyword when dealing with file objects. This has the advantage that the file is properly closed after its suite finishes, even if an exception is raised on the way. It is also much shorter than writing equivalent try-finally blocks

Concrètement, l'objet File créé et retourné par la fonction open() expose ses propres méthodes __enter__ et __exit__ et se comporte comme un context manager avec l'instruction with. Le fichier est toujours fermé à la fin.

Ressources

Avant Django et le pouvoir du Q Après Django avec CloudFront et S3, PEP 476

A Kemar Joint