Defina o FileField do Django para um arquivo existente

90

Eu tenho um arquivo existente no disco (digamos / pasta / arquivo.txt) e um campo de modelo FileField no Django.

Quando eu faço

instance.field = File(file('/folder/file.txt'))
instance.save()

ele salva o arquivo novamente como file_1.txt(na próxima vez _2, etc.).

Eu entendo o porquê, mas não quero esse comportamento - eu sei que o arquivo ao qual quero que o campo seja associado está realmente lá, esperando por mim, e só quero que o Django aponte para ele.

Quão?

Guarda
fonte
1
Não tenho certeza se você pode obter o que deseja sem modificar o Django ou criar subclasses FileField. Sempre que um FileFieldé salvo, uma nova cópia do arquivo é criada. Seria bastante simples adicionar uma opção para evitar isso.
Michael Mior
bem, sim, parece que tenho que criar uma subclasse e adicionar um parâmetro. Não pretendo criar tabelas extras para esta tarefa simples
Guarda
1
Coloque o arquivo em um local diferente, crie seu campo com este caminho, salve-o e você terá o arquivo no destino upload_to.
benjaoming

Respostas:

22

Se você quiser fazer isso permanentemente, você precisa criar sua própria classe FileStorage

import os
from django.conf import settings
from django.core.files.storage import FileSystemStorage

class MyFileStorage(FileSystemStorage):

    # This method is actually defined in Storage
    def get_available_name(self, name):
        if self.exists(name):
            os.remove(os.path.join(settings.MEDIA_ROOT, name))
        return name # simply returns the name passed

Agora em seu modelo, você usa seu MyFileStorage modificado

from mystuff.customs import MyFileStorage

mfs = MyFileStorage()

class SomeModel(model.Model):
   my_file = model.FileField(storage=mfs)
Burhan Khalid
fonte
oh, parece promissor. porque o código do FileField é meio não intuitivo
Guard
mas ... é possível alterar o armazenamento por solicitação, como: instance.field.storage = mfs; instance.field.save (nome, arquivo); mas não fazendo isso em um branch diferente do meu código
Guarda
2
Não, já que o mecanismo de armazenamento está vinculado ao modelo. Você pode evitar tudo isso simplesmente armazenando o caminho do arquivo em um FilePathFieldou simplesmente como texto simples.
Burhan Khalid
Você não pode simplesmente retornar um nome. Você precisa remover o arquivo existente primeiro.
Alexander Shpindler
124

apenas defina instance.field.nameo caminho do seu arquivo

por exemplo

class Document(models.Model):
    file = FileField(upload_to=get_document_path)
    description = CharField(max_length=100)


doc = Document()
doc.file.name = 'path/to/file'  # must be relative to MEDIA_ROOT
doc.file
<FieldFile: path/to/file>
bara
fonte
15
O caminho relativo do seu MEDIA_ROOT, isto é.
mgalgs
7
Neste exemplo, acho que você também pode fazerdoc.file = 'path/to/file'
Andrew Swihart
14

tente isto ( doc ):

instance.field.name = <PATH RELATIVE TO MEDIA_ROOT> 
instance.save()
uNmAnNeR
fonte
5

É certo escrever sua própria classe de armazenamento. No entanto, get_available_namenão é o método certo para substituir.

get_available_nameé chamado quando o Django vê um arquivo com o mesmo nome e tenta obter um novo nome de arquivo disponível. Não é o método que causa a renomeação. o método causou isso _save. Os comentários _savesão muito bons e você pode facilmente descobrir que ele abre um arquivo para escrever com o sinalizador os.O_EXCLque lançará um OSError se o mesmo nome de arquivo já existir. O Django detecta esse erro e então chama get_available_namepara obter um novo nome.

Portanto, acho que a maneira correta é substituir _savee chamar os.open () sem sinalizar os.O_EXCL. A modificação é bastante simples, porém o método é um pouco demorado, então não colo aqui. Diga-me se precisar de mais ajuda :)

x1a0
fonte
são 50 linhas de código que você precisa copiar, o que é muito ruim. Substituir get_available_name parece ser mais isolado, mais curto e muito mais seguro para, digamos, atualizar para as versões mais novas do Django no futuro
Michael Gendin
2
O problema de apenas substituir get_available_nameé quando você carrega um arquivo com o mesmo nome, o servidor entrará em um loop infinito. Desde _saveverifica o nome do arquivo e decide obter um novo, porém get_available_nameainda retorna o duplicado. Portanto, você precisa substituir ambos.
x1a0
1
Ops, estamos tendo essa discussão em duas perguntas, mas só agora percebi que elas são um pouco diferentes) Então, estou certo nessa pergunta e você está nisso)
Michael Gendin
1

Eu tive exatamente o mesmo problema! então eu percebo que meus modelos estavam causando isso. exemplo, eu tenho meus modelos assim:

class Tile(models.Model):
  image = models.ImageField()

Então, eu queria ter mais um bloco referenciando o mesmo arquivo no disco! A maneira que encontrei de resolver isso foi mudar minha estrutura de modelo para esta:

class Tile(models.Model):
  image = models.ForeignKey(TileImage)

class TileImage(models.Model):
  image = models.ImageField()

Que depois de perceber que faz mais sentido, pois se eu quiser que o mesmo arquivo seja salvo mais de um no meu BD tenho que criar outra tabela para ele!

Eu acho que você pode resolver seu problema assim também, apenas esperando que você possa mudar os modelos!

EDITAR

Além disso, acho que você pode usar um armazenamento diferente, como este, por exemplo: SymlinkOrCopyStorage

http://code.welldev.org/django-storages/src/11bef0c2a410/storages/backends/symlinkorcopy.py

Arthur neves
fonte
faz sentido no seu caso, não no meu. Não quero que seja referenciado várias vezes. Crio um objeto que faz referência a um arquivo, então percebo que há erros em outros atributos e reabra o formulário de criação. No seu reenvio não quero perder o arquivo que já está salvo no disco
Guarda
então acho que você pode usar minha abordagem! porque você terá uma tabela FormFile que armazenará o arquivo somente quando você tiver! então em sua tabela de formulário você terá um FK para aquele arquivo! para que você possa alterar / criar novos formulários para o mesmo arquivo! (a propósito, estou mudando a ordem do FK no meu exemplo principal)
Arthur Neves
Se você quiser postar seu domínio (modelos) em sua postagem! posso ter uma ideia melhor também!
Arthur Neves
na verdade, o domínio não importa - tenho um modelo com uma foto associada a ele e tenho uma tela de edição personalizada. depois de carregado, quero que a foto permaneça no servidor, mas na verdade não gosto de gerar um modelo, uma tabela e uma consulta FK separados só porque parecem ser uma limitação do framework
Guarda
A limitação aqui eu acho que é por causa de quando você salva um FileField no django, ele sempre passa pelo Django Storages! então não fará sentido você apenas forçar um caminho de arquivo! também como o Django deve saber que o arquivo já existe no caminho? outra abordagem que você pode usar é usar o FilePathField! então você pode apenas definir o caminho em seu banco de dados e fazer a pesquisa da maneira que achar melhor!
Arthur Neves
1

Você deve definir seu próprio armazenamento, herdá-lo de FileSystemStorage e substituir o OS_OPEN_FLAGSatributo e o get_available_name()método de classe :

Versão Django: 3.1

Projeto / core / files / storages / backends / local.py

import os

from django.core.files.storage import FileSystemStorage


class OverwriteStorage(FileSystemStorage):
    """
    FileSystemStorage subclass that allows overwrite the already existing
    files.
    
    Be careful using this class, as user-uploaded files will overwrite
    already existing files.
    """

    # The combination that don't makes os.open() raise OSError if the
    # file already exists before it's opened.
    OS_OPEN_FLAGS = os.O_WRONLY | os.O_TRUNC | os.O_CREAT | getattr(os, 'O_BINARY', 0)

    def get_available_name(self, name, max_length=None):
        """
        This method will be called before starting the save process.
        """
        return name

Em seu modelo, use seu OverwriteStorage personalizado

myapp / models.py

from django.db import models

from core.files.storages.backends.local import OverwriteStorage


class MyModel(models.Model):
   my_file = models.FileField(storage=OverwriteStorage())
Sultão
fonte