我在磁盘上有一个现有文件(例如/folder/file.txt)和Django中的FileField模型字段。
当我这样做时
instance.field = File(file('/folder/file.txt'))
instance.save()
它将文件重新保存为file_1.txt
(下次是_2
等)。
我理解为什么,但我不想要这种行为 - 我知道我希望该字段与之关联的文件真的在那里等着我,我只想让Django指向它。
如何?
答案 0 :(得分:98)
只需将instance.field.name
设置为文件的路径
e.g。
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>
答案 1 :(得分:18)
如果您想永久执行此操作,则需要创建自己的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)
答案 2 :(得分:9)
试试这个(doc):
instance.field.name = <PATH RELATIVE TO MEDIA_ROOT>
instance.save()
答案 3 :(得分:4)
编写自己的存储类是正确的。但是get_available_name
不是覆盖的正确方法。
get_available_name
。这不是导致重命名的方法。导致的方法是_save
。 _save
中的注释相当不错,您可以轻松找到它打开文件,使用标记os.O_EXCL
进行写入,如果已存在相同的文件名,则会抛出OSError。 Django捕获此错误,然后调用get_available_name
获取新名称。
所以我认为正确的方法是覆盖_save
并调用不带标记os.O_EXCL
的os.open()。修改很简单但是方法有点长,所以我不在这里粘贴它。如果您需要更多帮助,请告诉我:)
答案 4 :(得分: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 django.db import models
from core.files.storages.backends.local import OverwriteStorage
class MyModel(models.Model):
my_file = models.FileField(storage=OverwriteStorage())
答案 5 :(得分:0)
我有完全相同的问题!然后我意识到我的模型造成了这种情况。例如,我喜欢这样的模型:
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
答案 6 :(得分:0)
如果您使用应用程序的文件系统来存储您的文件,这些答案就可以正常工作。但是,如果您使用 boto3 并上传到诸如 AWS S3 之类的东西,并且您可能想将一个存在于 S3 存储桶中 的文件设置为模型的 FileField,那么这是你需要什么。
我们有一个带有文件字段的简单模型类:
class Image(models.Model):
img = models.FileField()
owner = models.ForeignKey(get_user_model(), on_delete=models.CASCADE, related_name='images')
date_added = models.DateTimeField(editable=False)
date_modified = models.DateTimeField(editable=True)
from botocore.exceptions import ClientError
import boto3
s3 = boto3.client(
's3',
aws_access_key_id=os.getenv("AWS_ACCESS_KEY_ID"),
aws_secret_access_key=os.getenv("AWS_SECRET_ACCESS_KEY")
)
s3_key = S3_DIR + '/' + filename
bucket_name = os.getenv("AWS_STORAGE_BUCKET_NAME")
try:
s3.upload_file(local_file_path, bucket_name, s3_key)
# we want to store it to our db model called **Image** after s3 upload is complete so,
image_data = Image()
image_data.img.name = s3_key # this does it !!
image_data.owner = get_user_model().objects.get(id=owner_id)
image_data.save()
except ClientError as e:
print(f"failed uploading to s3 {e}")
将 S3 KEY 设置到 FileField 的 name 字段中即可。我已经按预期测试了所有相关的工作,例如在 django admin 中预览图像文件。从 db 获取图像也会将根 s3 存储桶前缀(或 cloudfront cdn 前缀)附加到文件的 s3 键。当然,鉴于此,我已经为 boto 和 s3 设置了 django settings.py。