我在模型中有一个FileField
。对于模型的每个实例,我希望磁盘上的文件名保持与模型的另一个字段(我们称之为label
)的值更新。
目前,我使用了一个自定义的upload_to()
函数,该函数在首次上传新文件时生成正确的文件名。但是,如果我更改label
的值,则在保存模型时不会更新文件名。
在模型的save()
函数中,我可以(a)从label
计算新文件名(同时检查新名称是否与磁盘上的另一个现有文件对应),(b)重命名磁盘上的文件,以及(c)在FileField
中设置新文件位置。但是没有比这更简单的方法了吗?
这里发布的所有解决方案,以及我在网上看到的所有解决方法都涉及使用第三方应用程序或您现有的解决方案。
我同意@Phillip的观点,没有比这更简单的方法可以做你想做的事了,即使使用第三方应用程序,也需要一些工作才能使其适应你的目的。
如果有许多模型需要这种行为,只需实现一个pre_save信号并只写一次代码。
我建议你也阅读Django Signals,我相信你会发现它非常有趣。
非常简单的例子:
from django.db.models.signals import pre_save
from django.dispatch import receiver
@receiver(pre_save, sender=Product)
def my_signal_handler(sender, instance, **kwargs):
"""
Sender here would be the model, Product in your case.
Instance is the product instance being saved.
"""
# Write your solution here.
这里有一个应用程序可以为您处理django smartfields。我添加了一个特殊的处理器就是为了这个目的,只是因为它看起来是一个有用的功能。
工作原理:
- 更改
upload_to
中指定的文件名,以及 - 每当
label
字段发生更改时,FileDependency
都会处理文件名
值得注意的是,文件将使用file_move_safe进行重命名,但仅在FileSystemStorage的情况下,正如@Phillip所提到的,您不想使用云文件存储进行重命名,因为这些后端通常不支持文件重命名。
还有情侣笔记。您不必使用UploadTo
类,常规函数即可。如果不指定keep_orphans
,则每当删除模型实例时,文件都会被删除。
from django.db import models
from smartfields import fields, processors
from smartfields.dependencies import FileDependency
from smartfields.utils import UploadTo
def name_getter(name, instance):
return instance.label
class TestModel(models.Model):
label = fields.CharField(max_length=32, dependencies=[
FileDependency(attname='dynamic_file', keep_orphans=True,
processor=processors.RenameFileProcessor())
])
dynamic_file = models.FileField(
upload_to=UploadTo(name=name_getter, add_pk=False))
我认为您使用save()方法的方法是正确且"简单"的方法,除了我会使用pre_save信号而不是覆盖save方法(这通常是个坏主意)。
如果你想在其他模型上重复这种行为,那么使用连接到pre_save信号的方法也可以简单地重复使用该方法。
有关预保存的详细信息(_S):https://docs.djangoproject.com/en/1.8/ref/signals/#pre-保存
好的,它不能是lambda,因为lambda由于某种原因不可序列化,但这里有一个简单的答案。
def pic_loc(instance, filename):
"""
:param instance: Product Instance
:param filename: name of image being uploaded
:return: image location for the upload
"""
return '/'.join([str(instance.pk), str(instance.slug), filename])
Class Product(models.Model):
image = models.ImageField(upload_to=pic_loc)
slug = models.SlugField()
user = models.ForeignKey(User, related_name="products")
然后找到说pk=1与:
slug="新事物"应该是//myexample.com/MEDIA_ROOT/1/newthing/mything.png
<img src="{{ obj.image.url }}">
这是假设您设置了MEDIA_ROOT,因为上传到媒体和媒体url。如果在生产中使用静态文件,请以MEDIA_URL命名。
upload_to将对象实例和文件名都传递给您的函数,您可以从中操作它
要更改实际的文件名,您需要在save()方法中做一些额外的工作。
from django.core.files import File
class Product(models.Model):
label = CharField(max_length=255)
...
def save(self, **kwargs):
# here we use os.rename then change the name of the file
# add condition to do this, I suggest requerying the model
# and checking if label is different
if self.pk: # Need this to mitigate error using self.pk
if Product.objects.get(pk=self.pk).label != self.label:
path = self.image.path
rename = '/'.join(path.split('/')[:-1]) + '/' + self.label
os.rename(path, rename)
file = File(open(rename))
self.image.save(self.label, file)
return super(Product, self).save(**kwargs)
如果文件扩展名很重要,那么在创建标签时将其添加到标签中,或者我们将旧的文件扩展名作为字符串的一部分:
filename, file_extention = os.splitext(path)
rename += file_extension # before renaming and add to label too
os.rename(path, rename)
self.image.save(self.label + file_extension, file)
实际上,我建议在app_label.utils 中编写一个重命名函数
检查文件是否存在只是
if os.path.isfile(rename):
# you can also do this before renaming,
# maybe raise an error if the file already exists