Django - comment créer un fichier et l'enregistrer dans le FileField d'un modèle?

110

Voici mon modèle. Ce que je veux faire, c'est générer un nouveau fichier et écraser l'existant chaque fois qu'une instance de modèle est enregistrée:

class Kitten(models.Model):
    claw_size = ...
    license_file = models.FileField(blank=True, upload_to='license')

    def save(self, *args, **kwargs):
        #Generate a new license file overwriting any previous version
        #and update file path
        self.license_file = ???
        super(Request,self).save(*args, **kwargs)

Je vois beaucoup de documentation sur la façon de télécharger un fichier. Mais comment générer un fichier, l'assigner à un champ de modèle et faire en sorte que Django le stocke au bon endroit?

Greg
la source

Réponses:

152

Vous voulez jeter un œil à FileField et FieldFile dans la documentation Django, et en particulier FieldFile.save () .

Fondamentalement, un champ déclaré en tant que FileField, lors de l'accès, vous donne une instance de classe FieldFile, qui vous donne plusieurs méthodes pour interagir avec le fichier sous-jacent. Donc, ce que vous devez faire est:

self.license_file.save(new_name, new_contents)

new_nameest le nom de fichier que vous souhaitez attribuer et new_contentsest le contenu du fichier. Notez qu'il new_contentsdoit s'agir d'une instance de l'un django.core.files.Fileou l' autre django.core.files.base.ContentFile(voir les liens vers le manuel pour plus de détails). Les deux choix se résument à:

# Using File
f = open('/path/to/file')
self.license_file.save(new_name, File(f))
# Using ContentFile
self.license_file.save(new_name, ContentFile('A string with the file content'))
tawmas
la source
1
Ok, je pense que cela fonctionnera mais je me lance dans une sorte de boucle récursive en appelant cela dans la méthode save. Il ne cesse de créer des fichiers pour toujours.
Greg
11
Pour le problème récursif, je dois appeler self.license_file.save avec l'arg save = False.
Greg du
1
Ce (ContentFile) fonctionne parfaitement avec la chaîne de fichier renvoyée par la convert_to_pdfcommande de django-wkhtmltopdf . Je vous remercie!!
Nostalg.io
En plus de cela, j'ai une erreur si je ne spécifie pas le mode de fichier lors de l'ouverture du fichier. Donc, f = open('/path/to/file', 'r')pour le type de fichier ZIP,f = open('/path/to/file.zip', 'rb')
rajagopalx
1
Dans mon cas, ce qui précède n'a pas enregistré le fichier dans le dossier. Il s'avère que le problème est que j'utilise docker-compose pour exécuter mon application django avec un ouvrier céleri. Le volume de l'application django pour MEDIA_ROOTn'a pas été partagé avec le même volume dans le céleri worker. Partager le volume nommé l'a corrigé ( ref ).
shadi
28

La réponse acceptée est certainement une bonne solution, mais voici comment j'ai procédé pour générer un CSV et le servir à partir d'une vue.

J'ai pensé que cela valait la peine de mettre ceci ici car il m'a fallu un peu de bidouillage pour obtenir tout le comportement souhaitable (écraser le fichier existant, stocker au bon endroit, ne pas créer de fichiers en double, etc.).

Django 1.4.1

Python 2.7.3

#Model
class MonthEnd(models.Model):
    report = models.FileField(db_index=True, upload_to='not_used')

import csv
from os.path import join

#build and store the file
def write_csv():
    path = join(settings.MEDIA_ROOT, 'files', 'month_end', 'report.csv')
    f = open(path, "w+b")

    #wipe the existing content
    f.truncate()

    csv_writer = csv.writer(f)
    csv_writer.writerow(('col1'))

    for num in range(3):
        csv_writer.writerow((num, ))

    month_end_file = MonthEnd()
    month_end_file.report.name = path
    month_end_file.save()

from my_app.models import MonthEnd

#serve it up as a download
def get_report(request):
    month_end = MonthEnd.objects.get(file_criteria=criteria)

    response = HttpResponse(month_end.report, content_type='text/plain')
    response['Content-Disposition'] = 'attachment; filename=report.csv'

    return response
Markdsievers
la source
1

Il est recommandé d'utiliser un gestionnaire de contexte ou un appel close()en cas d'exceptions pendant le processus d'enregistrement du fichier. Cela peut se produire si votre backend de stockage est en panne, etc.

Tout comportement d'écrasement doit être configuré dans votre backend de stockage. Par exemple, S3Boto3Storage a un paramètre AWS_S3_FILE_OVERWRITE. Si vous utilisez, FileSystemStoragevous pouvez écrire un mixin personnalisé .

Vous pouvez également appeler la méthode de sauvegarde du modèle au lieu de la méthode de sauvegarde du FileField si vous souhaitez que des effets secondaires personnalisés se produisent, comme les horodatages de la dernière mise à jour. Si tel est le cas, vous pouvez également définir l'attribut name du fichier sur le nom du fichier - qui est relatif à MEDIA_ROOT. Il utilise par défaut le chemin complet du fichier, ce qui peut causer des problèmes si vous ne le définissez pas - voir File .__ init __ () et File.name .

Voici un exemple où se selftrouve l'instance de modèle où se my_filetrouve le FileField / ImageFile, appelant save()l'ensemble de l'instance de modèle au lieu de seulement FileField:

import os
from django.core.files import File

with open(filepath, 'rb') as fi:
    self.my_file = File(fi, name=os.path.basename(fi.name))
    self.save()
whp
la source