Imposta FileField di Django su un file esistente


89

Ho un file esistente su disco (ad esempio /folder/file.txt) e un campo modello FileField in Django.

Quando io faccio

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

salva nuovamente il file come file_1.txt(la prossima volta _2, ecc.).

Capisco perché, ma non voglio questo comportamento: so che il file a cui voglio associare il campo è davvero lì che mi aspetta e voglio solo che Django lo punti.

Come?


1
Non sono sicuro di poter ottenere ciò che desideri senza modificare Django o sottoclassi FileField. Ogni volta che FileFieldviene salvato un, viene creata una nuova copia del file. Sarebbe abbastanza semplice aggiungere un'opzione per evitarlo.
Michael Mior

beh sì, sembra che devo sottoclassare e aggiungere un parametro. Non voglio creare tabelle extra per questo semplice compito
Guardia

1
Metti il ​​file in una posizione diversa, crea il tuo campo con questo percorso, salvalo e poi avrai il file nella destinazione upload_to.
benjaoming il

Risposte:


22

Se vuoi farlo in modo permanente, devi creare la tua 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

Ora nel tuo modello, usi il tuo MyFileStorage modificato

from mystuff.customs import MyFileStorage

mfs = MyFileStorage()

class SomeModel(model.Model):
   my_file = model.FileField(storage=mfs)

oh, sembra promettente. perché il codice di FileField è un po 'non intuitivo
Guard

ma ... è possibile cambiare lo spazio di archiviazione in base alla richiesta, come: instance.field.storage = mfs; instance.field.save (nome, file); ma non farlo in un ramo diverso del mio codice
Guard

2
No, poiché lo storage engine è legato al modello. È possibile evitare tutto ciò semplicemente memorizzando il percorso del file in a FilePathFieldo semplicemente come testo normale.
Burhan Khalid

Non puoi semplicemente restituire un nome. Devi prima rimuovere il file esistente.
Alexander Shpindler

124

basta impostare instance.field.nameil percorso del file

per esempio

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>

15
Il percorso relativo dal tuo MEDIA_ROOT, cioè.
mgalgs

7
In questo esempio, penso che tu possa anche faredoc.file = 'path/to/file'
Andrew Swihart

13

prova questo ( doc ):

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

5

È giusto scrivere la propria classe di archiviazione. Tuttavia get_available_namenon è il metodo giusto per eseguire l'override.

get_available_nameviene chiamato quando Django vede un file con lo stesso nome e cerca di ottenere un nuovo nome di file disponibile. Non è il metodo che causa la ridenominazione. il metodo ha causato quello è _save. Commenti in _saveè abbastanza buono e puoi facilmente trovare che apre il file per la scrittura con flag os.O_EXCLche genererà un errore OS se lo stesso nome di file esiste già. Django rileva questo errore, quindi chiama get_available_nameper ottenere un nuovo nome.

Quindi penso che il modo corretto sia sovrascrivere _savee chiamare os.open () senza flag os.O_EXCL. La modifica è abbastanza semplice, tuttavia il metodo è un po 'lungo, quindi non lo incollo qui. Dimmi se hai bisogno di ulteriore aiuto :)


sono 50 righe di codice che devi copiare, il che è piuttosto brutto. L'override di get_available_name sembra essere più isolato, più breve e molto più sicuro per, ad esempio, l'aggiornamento alle versioni più recenti di Django in futuro
Michael Gendin

2
Il problema dell'override è sologet_available_name quando carichi un file con lo stesso nome, il server entrerà in un ciclo infinito. Poiché _savecontrolla il nome del file e decide di ottenerne uno nuovo, get_available_namerestituisce comunque quello duplicato. Quindi è necessario sovrascrivere entrambi.
x1a0

1
Oops, stiamo discutendo di due domande, ma solo ora ho notato che sono leggermente diverse) Quindi ho ragione in quella domanda, e tu sei in questo)
Michael Gendin

1

Ho avuto esattamente lo stesso problema! poi mi rendo conto che i miei modelli lo stavano causando. esempio ho i miei modelli in questo modo:

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

Quindi, volevo avere più una tessera che fa riferimento allo stesso file nel disco! Il modo in cui ho trovato per risolverlo è stato cambiare la struttura del mio modello in questo:

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

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

Il che dopo ho capito che ha più senso, perché se voglio che lo stesso file venga salvato più di uno nel mio DB devo creare un'altra tabella per questo!

Immagino che tu possa risolvere anche il tuo problema in questo modo, sperando solo che tu possa cambiare i modelli!

MODIFICARE

Inoltre immagino che tu possa utilizzare un archivio diverso, come questo ad esempio: SymlinkOrCopyStorage

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


ha senso nel tuo caso, non nel mio. Non voglio che venga referenziato più volte. Creo un oggetto facendo riferimento a un file, poi mi accorgo che ci sono errori in altri attributi e riapro il form di creazione. Al suo reinvio non voglio perdere il file che è già salvato su disco
Guardia

quindi immagino che tu possa usare il mio approccio! perché avrai una tabella FormFile che conterrà il file solo allora! quindi nella tua tabella Form avrai un FK per quel file! quindi puoi modificare / creare nuovi moduli per lo stesso file! (btw sto cambiando l'ordine dell'FK nel mio esempio principale)
Arthur Neves

Se vuoi inserire il tuo dominio (modelli) nel tuo post! anche io posso avere un'ideia migliore!
Arthur Neves

il dominio in realtà non ha importanza: ho un modello a cui è associata una foto e ho una schermata di modifica personalizzata. una volta caricata, voglio che la foto rimanga sul server, ma in realtà non mi piace generare un modello separato, una tabella e una ricerca FK solo perché sembra essere una limitazione del framework
Guard

La limitazione qui credo sia dovuta al fatto che quando salvi un FileField in django, passa sempre attraverso Django Storages! quindi non avrà senso forzare il percorso di un file! anche come Django dovrebbe sapere che il file esiste già nel percorso? un altro approccio che puoi usare è usare invece FilePathField! quindi puoi semplicemente impostare il percorso nel tuo DB e fare la ricerca nel modo che ritieni migliore!
Arthur Neves

1

È necessario definire il proprio archivio, ereditarlo da FileSystemStorage e sovrascrivere l' OS_OPEN_FLAGSattributo e il get_available_name()metodo di classe :

Versione Django: 3.1.0

Progetto / core / file / archivi / backend / 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

Nel tuo modello, usa il tuo OverwriteStorage personalizzato

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())
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.