Django - wie erstelle ich eine Datei und speichere sie im FileField eines Modells?

108

Hier ist mein Modell. Ich möchte eine neue Datei generieren und die vorhandene überschreiben, wenn eine Modellinstanz gespeichert wird:

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)

Ich sehe viele Dokumentationen zum Hochladen einer Datei. Aber wie generiere ich eine Datei, ordne sie einem Modellfeld zu und lasse sie von Django am richtigen Ort speichern?

Greg
quelle

Antworten:

151

Sie möchten einen Blick auf FileField und FieldFile in den Django-Dokumenten werfen , insbesondere auf FieldFile.save () .

Grundsätzlich FileFieldgibt Ihnen ein Feld , das beim Zugriff als deklariert wird , eine Klasseninstanz FieldFile, die Ihnen verschiedene Methoden zur Interaktion mit der zugrunde liegenden Datei bietet. Was Sie also tun müssen, ist:

self.license_file.save(new_name, new_contents)

Wo new_nameist der Dateiname, den Sie zuweisen möchten, und wo new_contentsist der Inhalt der Datei? Beachten Sie, dass new_contentsdies eine Instanz von entweder django.core.files.Fileoder sein muss django.core.files.base.ContentFile(Einzelheiten finden Sie unter den angegebenen Links zum Handbuch). Die beiden Möglichkeiten beschränken sich auf:

# 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
quelle
1
Ok, ich denke, das wird funktionieren, aber ich gerate in eine Art rekursive Schleife, die das in der Speichermethode aufruft. Es werden einfach immer Dateien für immer erstellt.
Greg
11
Für das rekursive Problem muss ich self.license_file.save mit dem Argument save = False aufrufen.
Greg
1
Diese (ContentFile) funktioniert perfekt mit der vom Befehl django-wkhtmltopdf zurückgegebenen Dateizeichenfolge convert_to_pdf. Danke dir!!
Nostalg.io
Außerdem wurde eine Fehlermeldung angezeigt, wenn ich beim Öffnen der Datei den Dateimodus nicht angegeben habe. Also, f = open('/path/to/file', 'r')für ZIP Art von Datei,f = open('/path/to/file.zip', 'rb')
Rajagopalx
1
In meinem Fall wurde die Datei oben nicht im Ordner gespeichert. Es stellt sich heraus, dass ich Docker-Compose verwende , um meine Django-App zusammen mit einem Sellerie-Arbeiter auszuführen. Das Django-App-Volume für MEDIA_ROOTwurde im Sellerie-Worker nicht mit demselben Volume geteilt. Durch das Teilen des benannten Volumes wurde das Problem behoben ( Ref ).
Shadi
28

Akzeptierte Antworten sind sicherlich eine gute Lösung, aber hier ist die Art und Weise, wie ich eine CSV generiert und aus einer Sicht bereitgestellt habe.

Ich dachte, es hat sich gelohnt, dies hier zu platzieren, da ich ein wenig herumgespielt habe, um das gewünschte Verhalten zu erreichen (vorhandene Datei überschreiben, an der richtigen Stelle speichern, keine doppelten Dateien erstellen usw.).

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
quelle
1

Es close()wird empfohlen, beim Speichern von Dateien einen Kontextmanager zu verwenden oder bei Ausnahmen aufzurufen . Kann passieren, wenn Ihr Speicher-Backend nicht verfügbar ist usw.

Jedes Überschreibungsverhalten sollte in Ihrem Speicher-Backend konfiguriert werden. Zum Beispiel hat S3Boto3Storage eine Einstellung AWS_S3_FILE_OVERWRITE. Wenn Sie verwenden FileSystemStorage, können Sie ein benutzerdefiniertes Mixin schreiben .

Möglicherweise möchten Sie auch die Speichermethode des Modells anstelle der Speichermethode von FileField aufrufen, wenn benutzerdefinierte Nebenwirkungen wie zuletzt aktualisierte Zeitstempel auftreten sollen. In diesem Fall können Sie auch das Namensattribut der Datei auf den Namen der Datei setzen - relativ zu MEDIA_ROOT. Standardmäßig wird der vollständige Pfad der Datei verwendet, was zu Problemen führen kann, wenn Sie ihn nicht festlegen - siehe Datei .__ init __ () und Dateiname .

Hier ist ein Beispiel, in dem selfsich die Modellinstanz my_filebefindet, in der sich FileField / ImageFile befindet und save()die gesamte Modellinstanz anstelle von nur FileField aufruft:

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
quelle