Django - как создать файл и сохранить его в FileField модели?

110

Вот моя модель. Я хочу создать новый файл и перезаписать существующий при каждом сохранении экземпляра модели:

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)

Я вижу много документации о том, как загрузить файл. Но как мне создать файл, назначить его полю модели и заставить Django сохранить его в нужном месте?

Грег
источник

Ответы:

152

Вы хотите взглянуть на FileField и FieldFile в документации Django, и особенно на FieldFile.save () .

По сути, поле, объявленное как a FileField, при доступе дает вам экземпляр класса FieldFile, который дает вам несколько методов для взаимодействия с базовым файлом. Итак, что вам нужно сделать:

self.license_file.save(new_name, new_contents)

где new_nameимя файла, которое вы хотите назначить, и new_contentsего содержимое. Обратите внимание, что это new_contentsдолжен быть экземпляр либо django.core.files.Fileили django.core.files.base.ContentFile(подробности см. В указанных ссылках на руководство). Два варианта сводятся к следующему:

# 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
Хорошо, я думаю, что это сработает, но я вхожу в какой-то рекурсивный цикл, вызывающий его в методе сохранения. Он просто продолжает создавать файлы вечно.
Грег
11
Для рекурсивной проблемы мне нужно вызвать self.license_file.save с arg save = False.
Грег
1
Этот (ContentFile) отлично работает со строкой файла, возвращаемой командой django-wkhtmltopdf convert_to_pdf. Спасибо!!
Nostalg.io 01
Вдобавок к этому я получал ошибку, если не указывал режим файла при открытии файла. Итак, f = open('/path/to/file', 'r')для f = open('/path/to/file.zip', 'rb')
формата
1
В моем случае вышеупомянутый файл не сохранялся в папке. Оказывается, проблема в том, что я использую docker-compose для запуска приложения django вместе с работником сельдерея. Том приложения django для MEDIA_ROOTне использовался совместно с тем же томом в celery worker. Совместное использование названного тома исправило это ( ref ).
шади
28

Принятый ответ, безусловно, хорошее решение, но вот как я создал CSV и обслужил его из представления.

Думал, что стоит поместить это здесь, поскольку мне потребовалось немного возиться, чтобы получить все желаемое поведение (перезаписать существующий файл, сохранить в нужном месте, не создавать дублирующиеся файлы и т. Д.).

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
источник
1

Хорошей практикой является использование диспетчера контекста или вызова close()в случае исключений во время процесса сохранения файла. Это может произойти, если серверная часть хранилища не работает и т. Д.

Любое поведение перезаписи должно быть настроено в вашем хранилище. Например, у S3Boto3Storage есть настройка AWS_S3_FILE_OVERWRITE. Если вы используете, FileSystemStorageвы можете написать собственный миксин .

Вы также можете вызвать метод save модели вместо метода save FileField, если хотите, чтобы произошли какие-либо пользовательские побочные эффекты, такие как отметки времени последнего обновления. В этом случае вы также можете установить атрибут имени файла равным имени файла, относящемуся к MEDIA_ROOT. По умолчанию используется полный путь к файлу, что может вызвать проблемы, если вы его не зададите - см. File .__ init __ () и File.name .

Вот пример, где selfнаходится экземпляр модели, где my_fileFileField / ImageFile, вызывающий save()весь экземпляр модели, а не только 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
источник