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
upload_to
n'est plus requis à partir de Django 1.7- attention à la valeur de
max_length
, un test lors dusave()
n'est pas superflu sinon gare à laDatabaseError
- l'argument
content
deFieldFile.save(name, content, save=True)
doit être une instance dedjango.core.files.File
- depuis Django 1.3 les fichiers stockés ne sont plus supprimés, on peut le faire à la main dans le
delete()
du modèle mais attention à la suppression en masse dans l'admin !
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 equivalenttry-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.
Concernant le
with
, les objets retournés parfile/open
seront (presque) toujours fermés dans tous les cas, même sans lewith
.En effet, les fichiers sont "fermés" lors de la destruction de l'objet
file
. Donc dans la majorité des cas lorsque l'ont sort de la fonction.On peut éventuellement leaker une référence (et donc garder un fichier ouvert) si une exception référençant le fichier est levée