将Django的FileField设置为现有文件


89

我在磁盘上有一个现有文件(例如/folder/file.txt),在Django中有一个FileField模型字段。

当我做

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

它将文件另存为file_1.txt(下次是_2,等等)。

我知道为什么,但是我不想要这种行为-我知道我想要与该字段关联的文件确实在那里等着我,我只想让Django指向它。

怎么样?


1
不知道无需修改Django或子类化就可以得到想要的东西FileField。每当FileField保存a时,都会创建该文件的新副本。添加一个选项来避免这种情况非常简单。
Michael Mior

好吧,看起来我必须继承并添加参数。我不会为这个简单的任务创建额外的表
Guard

1
将文件放置在其他位置,使用此路径创建字段,保存,然后将文件保存在upload_to目标中。
即将

Answers:


22

如果要永久执行此操作,则需要创建自己的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

现在在模型中,使用修改后的MyFileStorage

from mystuff.customs import MyFileStorage

mfs = MyFileStorage()

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

哦,看起来很有前途。cuase FileField或代码是有点不直观
卫队

但是...可以根据每个请求更改存储,例如:instance.field.storage = mfs; instance.field.save(名称,文件); 但不能在我的代码的其他分支中执行此操作
Guard

2
否,因为存储引擎与模型绑定。您可以通过简单地以aFilePathField或纯文本格式存储文件路径来避免所有这些情况。
Burhan Khalid

您不能只返回名字。您需要先删除现有文件。
Alexander Shpindler

124

只需设置instance.field.name为文件的路径

例如

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
MEDIA_ROOT就是您的相对路径。
mgalgs 2012年

7
在此示例中,我认为您也可以这样做doc.file = 'path/to/file'
Andrew Swihart

13

试试这个(doc):

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

5

编写自己的存储类是正确的。但是,get_available_name这不是重写的正确方法。

get_available_name当Django看到具有相同名称的文件并尝试获取新的可用文件名时,将调用。不是导致重命名的方法。造成的方法是_save。中的注释_save非常好,您可以轻松地找到带有标志的打开待写入文件,os.O_EXCL如果已经存在相同的文件名,则会引发OSError。Django捕获此错误,然后调用get_available_name以获取新名称。

所以我认为正确的方法是_save不带flag覆盖并调用os.open()os.O_EXCL。修改很简单,但是方法有点长,所以我不在这里粘贴。告诉我您是否需要更多帮助:)


您必须复制50行代码,这非常糟糕。覆盖get_available_name似乎更孤立,更短并且更安全,例如,将来升级到较新版本的Django
Michael Gendin 2012年

2
问题覆盖get_available_name是当你上传同名的文件时,服务器将进入一个死循环。由于_save检查文件名并决定获取一个新文件,因此get_available_name仍然返回重复的文件名。因此,您需要同时覆盖两者。
x1a0 2012年

1
糟糕,我们在两个问题中进行了讨论,但直到现在我才注意到它们略有不同)所以我在这个问题上是正确的,而您在这个问题中)
Michael Gendin 2012年

1

我有完全一样的问题!然后我意识到我的模型是造成这种情况的原因。例如,我有这样的模型:

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

然后,我希望在磁盘上有更多的磁贴引用相同的文件!我找到的解决方法是将我的模型结构更改为:

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

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

在意识到这一点之后,这更有意义,因为如果我希望将同一文件保存得更多,那么在数据库中就必须为其创建另一个表!

我想您也可以解决这种问题,只是希望您可以更改模型!

编辑

另外,我猜您可以使用其他存储,例如:SymlinkOrCopyStorage

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


在你的情况下是有意义的,而不是我的。我不希望它被多次引用。我创建了一个引用文件的对象,然后我意识到其他attrs中存在错误,因此我重新打开了创建表单。在其重新提交我不想失去这已经保存在磁盘上的文件
卫队

所以我想您可以使用我的方法!因为您将拥有一个表格FormFile,它将仅在您拥有表格的情况下才保存该文件!然后在表格表中,您将获得该文件的FK!因此您可以为同一文件更改/创建新表单!(顺便说一句,在我的主要示例中,我正在更改FK的顺序)
Arthur Neves

如果您想在您的帖子中发布您的域(模型)!我也可以有一个更好的想法!
亚瑟·内维斯

域实际上并不重要-我有一个带有照片的模型,并且有自定义编辑屏幕。上载后,我希望将照片保留在服务器上,但实际上我不喜欢生成单独的模型,表格和FK查找,只是因为这看起来是框架限制
Guard

我想这里的限制是因为当您在Django中保存FileField时,它总是通过Django存储传递!因此,仅强制使用文件路径就没有意义了!Django还应该如何知道该文件已存在于路径中?您可以使用的另一种方法是使用FilePathField!这样您就可以在数据库中设置路径并以您认为最好的方式进行查找!
亚瑟·内维斯

1

您应该定义自己的存储,从FileSystemStorage继承它,并覆盖OS_OPEN_FLAGS类属性和get_available_name()方法:

Django版本: 3.1

项目/核心/文件/存储/后端/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

在模型中,使用自定义的OverwriteStorage

myapp / models.py

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


class MyModel(model.Model):
   my_file = model.FileField(storage=OverwriteStorage)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.