Django - jak stworzyć plik i zapisać go w FileField modelu?


110

Oto mój model. To, co chcę zrobić, to wygenerować nowy plik i nadpisać istniejący za każdym razem, gdy zapisywana jest instancja modelu:

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)

Widzę dużo dokumentacji o tym, jak przesłać plik. Ale jak wygenerować plik, przypisać go do pola modelu i sprawić, by Django przechowało go we właściwym miejscu?

Odpowiedzi:


152

Chcesz rzucić okiem na FileField i FieldFile w dokumentach Django, a zwłaszcza na FieldFile.save () .

Zasadniczo pole zadeklarowane jako a FileField, po uzyskaniu do niego dostępu, daje instancję klasy FieldFile, która udostępnia kilka metod interakcji z plikiem źródłowym. Więc co musisz zrobić, to:

self.license_file.save(new_name, new_contents)

gdzie new_namejest nazwą pliku, który chcesz przypisać i new_contentsjest zawartością pliku. Zauważ, że new_contentsmusi to być instancja albo django.core.files.Filelub django.core.files.base.ContentFile(szczegóły znajdziesz w podanych odnośnikach do instrukcji). Te dwie możliwości sprowadzają się do:

# 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'))

1
Ok, myślę, że to zadziała, ale wchodzę w coś w rodzaju pętli rekurencyjnej, wywołującej to w metodzie save. Po prostu tworzy pliki na zawsze.
Greg,

11
W przypadku problemu rekurencyjnego muszę wywołać self.license_file.save z arg save = False.
Greg,

1
Ten (ContentFile) działa doskonale z łańcuchem pliku zwróconym przez polecenie django-wkhtmltopdf convert_to_pdf. Dziękuję Ci!!
Nostalg.io

Oprócz tego otrzymałem błąd, jeśli nie określę trybu pliku podczas otwierania pliku. Tak więc, f = open('/path/to/file', 'r')dla pliku typu ZIP,f = open('/path/to/file.zip', 'rb')
rajagopalx

1
W moim przypadku powyższe nie zapisywało pliku do folderu. Okazuje się, że problem polega na tym, że używam docker-compose do uruchamiania mojej aplikacji django razem z selerem. Wolumin aplikacji django dla MEDIA_ROOTnie został udostępniony z tym samym woluminem w programie selerowym. Udostępnienie nazwanego woluminu naprawiło to ( ref ).
shadi

28

Zaakceptowana odpowiedź jest z pewnością dobrym rozwiązaniem, ale oto sposób, w jaki zabrałem się za generowanie pliku CSV i podawanie go z perspektywy.

Pomyślałem, że warto umieścić to tutaj, ponieważ zajęło mi trochę majstrowania, aby uzyskać wszystkie pożądane zachowanie (nadpisanie istniejącego pliku, przechowywanie we właściwym miejscu, brak tworzenia duplikatów plików itp.).

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

1

Dobrą praktyką jest korzystanie z menedżera kontekstu lub połączenia telefonicznego close() w przypadku wyjątków podczas procesu zapisywania pliku. Może się to zdarzyć, jeśli Twoja pamięć masowa nie działa itp.

Wszelkie zachowanie związane z zastępowaniem należy skonfigurować w zapleczu magazynu. Na przykład S3Boto3Storage ma ustawienie AWS_S3_FILE_OVERWRITE. Jeśli używasz FileSystemStorage, możesz napisać własny mixin .

Możesz także chcieć wywołać metodę save modelu zamiast metody save FileField, jeśli chcesz, aby wystąpiły jakiekolwiek niestandardowe efekty uboczne, takie jak ostatnio zaktualizowane znaczniki czasu. W takim przypadku możesz również ustawić atrybut nazwy pliku na nazwę pliku - która jest względna do MEDIA_ROOT. Domyślnie jest to pełna ścieżka do pliku, która może powodować problemy, jeśli jej nie ustawisz - zobacz File .__ init __ () i File.name .

Oto przykład, w którym selfjest instancja modelu, gdzie my_filejest FileField / ImageFile, wywołując save()całą instancję modelu zamiast tylko 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()
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.