Establecer FileField de Django en un archivo existente

89

Tengo un archivo existente en el disco (digamos /folder/file.txt) y un campo de modelo FileField en Django.

Cuando lo hago

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

vuelve a guardar el archivo como file_1.txt(la próxima vez _2, etc.).

Entiendo por qué, pero no quiero este comportamiento: sé que el archivo con el que quiero que se asocie el campo está realmente allí esperándome, y solo quiero que Django lo señale.

¿Cómo?

Guardia
fuente
1
No estoy seguro de poder obtener lo que desea sin modificar Django o subclasificar FileField. Siempre que FileFieldse guarda un, se crea una nueva copia del archivo. Sería bastante sencillo agregar una opción para evitar esto.
Michael Mior
bueno, sí, parece que tengo que crear una subclase y agregar un parámetro. No quiero crear tablas adicionales para esta simple tarea
Guard
1
Coloque el archivo en una ubicación diferente, cree su campo con esta ruta, guárdelo y luego tendrá el archivo en el destino upload_to.
benjaoming

Respuestas:

22

Si desea hacer esto de forma permanente, debe crear su propia clase 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

Ahora en su modelo, usa su MyFileStorage modificado

from mystuff.customs import MyFileStorage

mfs = MyFileStorage()

class SomeModel(model.Model):
   my_file = model.FileField(storage=mfs)
Burhan Khalid
fuente
oh, parece prometedor. cuase el código de FileField es un poco no intuitivo
Guardia
pero ... ¿es posible cambiar el almacenamiento por solicitud, como: instance.field.storage = mfs; instance.field.save (nombre, archivo); pero no hacerlo en una rama diferente de mi código
Guardia
2
No, ya que el motor de almacenamiento está vinculado al modelo. Puede evitar todo esto simplemente almacenando la ruta de su archivo en un FilePathFieldo simplemente como texto sin formato.
Burhan Khalid
No puedes simplemente devolver un nombre. Primero debe eliminar el archivo existente.
Alexander Shpindler
124

solo establezca instance.field.namela ruta de su archivo

p.ej

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
fuente
15
El camino relativo desde tu MEDIA_ROOT, eso es.
mgalgs
7
En este ejemplo, creo que también puede hacerlodoc.file = 'path/to/file'
Andrew Swihart
13

prueba esto ( doc ):

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

Es correcto escribir su propia clase de almacenamiento. Sin embargo, get_available_nameno es el método correcto para anular.

get_available_namese llama cuando Django ve un archivo con el mismo nombre e intenta obtener un nuevo nombre de archivo disponible. No es el método el que provoca el cambio de nombre. el método provocó eso _save. Los comentarios _saveson bastante buenos y puede encontrar fácilmente que abre un archivo para escribir con una bandera os.O_EXCLque arrojará un error de OS si ya existe el mismo nombre de archivo. Django detecta este error y luego llama get_available_namepara obtener un nuevo nombre.

Entonces creo que la forma correcta es anular _savey llamar a os.open () sin bandera os.O_EXCL. La modificación es bastante simple, sin embargo, el método es un poco largo, así que no lo pego aquí. Dime si necesitas más ayuda :)

x1a0
fuente
son 50 líneas de código que tienes que copiar, lo cual es bastante malo. Anular get_available_name parece más aislado, más corto y mucho más seguro para, por ejemplo, actualizar a las versiones más nuevas de Django en el futuro
Michael Gendin
2
El problema de solo anular get_available_namees cuando carga un archivo con el mismo nombre, el servidor entrará en un bucle sin fin. Dado que _saveverifica el nombre del archivo y decide obtener uno nuevo, sin embargo, get_available_nameaún devuelve el duplicado. Entonces necesitas anular ambos.
x1a0
1
Vaya, estamos teniendo esta discusión en dos preguntas, pero solo ahora me di cuenta de que son ligeramente diferentes) Así que tengo razón en esa pregunta, y tú estás en esto)
Michael Gendin
1

¡Tuve exactamente el mismo problema! luego me doy cuenta de que mis modelos estaban causando eso. ejemplo, tengo mis modelos así:

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

¡Entonces, quería tener más de un mosaico que hiciera referencia al mismo archivo en el disco! La forma en que encontré para resolver eso fue cambiar la estructura de mi modelo a esto:

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

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

Lo cual después me doy cuenta de que tiene más sentido, porque si quiero que el mismo archivo se guarde más de uno en mi base de datos, ¡tengo que crear otra tabla para él!

Supongo que también puedes resolver tu problema así, ¡solo esperando que puedas cambiar los modelos!

EDITAR

También supongo que puede usar un almacenamiento diferente, como este, por ejemplo: SymlinkOrCopyStorage

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

Arthur Neves
fuente
tiene sentido en tu caso, no en el mío. No quiero que se haga referencia a él varias veces. Creo un objeto que hace referencia a un archivo, luego me doy cuenta de que hay errores en otros atributos y vuelvo a abrir el formulario de creación. En su reenvío, no quiero perder el archivo que ya está guardado en el disco
Guard
¡así que supongo que puedes usar mi enfoque! ¡porque tendrá un FormFile de tabla que contendrá el archivo solo entonces! ¡Entonces en su tabla de formularios tendrá un FK para ese archivo! para que pueda cambiar / crear nuevos formularios para el mismo archivo. (por cierto, estoy cambiando el orden del FK en mi ejemplo principal)
Arthur Neves
¡Si desea publicar su dominio (modelos) en su publicación! ¡Yo también puedo tener una mejor idea!
Arthur Neves
el dominio en realidad no importa: tengo un modelo con una foto asociada y tengo una pantalla de edición personalizada. una vez cargada, quiero que la foto permanezca en el servidor, pero en realidad no me gusta generar un modelo, una tabla y una búsqueda de FK por separado solo porque parece ser una limitación del marco
Guard
La limitación aquí, supongo, se debe a que cuando guarda un FileField en django, ¡siempre pasa por Django Storages! por lo que no tendrá sentido forzar una ruta de archivo. también, ¿cómo debería saber Django que el archivo ya existe en la ruta? ¡Otro enfoque que puede usar es usar FilePathField en su lugar! para que pueda establecer la ruta en su base de datos y realizar la búsqueda de la manera que crea que es mejor.
Arthur Neves
1

Debe definir su propio almacenamiento, heredarlo de FileSystemStorage y anular el método OS_OPEN_FLAGSy el atributo de clase get_available_name():

Versión de Django: 3.1

Proyecto / 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

En su modelo, use su 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án
fuente