Django - como criar um arquivo e salvá-lo no FileField de um model?

110

Aqui está meu modelo. O que desejo fazer é gerar um novo arquivo e substituir o existente sempre que uma instância de modelo for salva:

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)

Vejo muita documentação sobre como fazer upload de um arquivo. Mas como faço para gerar um arquivo, atribuí-lo a um campo de modelo e fazer com que o Django o armazene no lugar certo?

Greg
fonte

Respostas:

152

Você quer dar uma olhada em FileField e FieldFile nos documentos do Django, e especialmente FieldFile.save () .

Basicamente, um campo declarado como a FileField, quando acessado, fornece uma instância de classe FieldFile, que fornece vários métodos para interagir com o arquivo subjacente. Então, o que você precisa fazer é:

self.license_file.save(new_name, new_contents)

onde new_nameé o nome do arquivo que você deseja atribuir e new_contentsé o conteúdo do arquivo. Observe que new_contentsdeve ser uma instância de django.core.files.Fileou django.core.files.base.ContentFile(consulte os links fornecidos para o manual para obter os detalhes). As duas opções se resumem a:

# 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
fonte
1
Ok, acho que vai funcionar, mas estou entrando em algum tipo de loop recursivo chamando isso no método save. Ele simplesmente continua criando arquivos para sempre.
Greg,
11
Para o problema recursivo, tenho que chamar self.license_file.save com arg save = False.
Greg,
1
Este (ContentFile) funciona perfeitamente com a string do arquivo retornada pelo convert_to_pdfcomando django-wkhtmltopdf . Obrigado!!
Nostalg.io
Além disso, recebo um erro se não especificar o modo de arquivo ao abrir o arquivo. Portanto, f = open('/path/to/file', 'r')para o tipo de arquivo ZIP,f = open('/path/to/file.zip', 'rb')
rajagopalx
1
No meu caso, acima não foi salvar o arquivo na pasta. Acontece que o problema é que estou usando docker-compose para executar meu aplicativo django junto com um trabalhador de aipo. O volume do app django para MEDIA_ROOTnão foi compartilhado com o mesmo volume no trabalhador de aipo. Compartilhar o volume nomeado corrigiu ( ref ).
shadi de
28

A resposta aceita é certamente uma boa solução, mas aqui está como fiz para gerar um CSV e exibi-lo a partir de uma visualização.

Achei que valia a pena colocar isso aqui, pois precisei mexer um pouco para obter todo o comportamento desejável (sobrescrever o arquivo existente, armazenar no local certo, não criar arquivos duplicados 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
marcadores
fonte
1

É uma boa prática usar um gerenciador de contexto ou chamada close()em caso de exceções durante o processo de salvamento do arquivo. Pode acontecer se o back-end de armazenamento estiver inativo etc.

Qualquer comportamento de substituição deve ser configurado em seu back-end de armazenamento. Por exemplo, S3Boto3Storage tem uma configuração AWS_S3_FILE_OVERWRITE. Se você estiver usando, FileSystemStoragepode escrever um mixin personalizado .

Você também pode querer chamar o método save do modelo em vez do método save do FileField se quiser que qualquer efeito colateral personalizado aconteça, como os carimbos de data / hora da última atualização. Se for esse o caso, você também pode definir o atributo name do arquivo como o nome do arquivo - que é relativo a MEDIA_ROOT. O padrão é o caminho completo do arquivo, o que pode causar problemas se você não configurá-lo - veja File .__ init __ () e File.name .

Aqui está um exemplo onde selfestá a instância do modelo, onde my_fileestá o FileField / ImageFile, chamando save()toda a instância do modelo em vez de apenas 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
fonte