关于django:如何更新FileField中的文件位置?

How to update a file location in a FileField?

我在模型中有一个FileField。 对于该模型的每个实例,我希望磁盘上的文件名保持更新为该模型的另一个字段(称为label)的值。

目前,我使用自定义upload_to()函数,该函数在首次上传新文件时生成正确的文件名。 但是,如果更改label的值,则保存模型时文件名不会更新。

在模型的save()函数中,我可以(a)从label计算新文件名(还要检查新名称是否与磁盘上的另一个现有文件相对应),(b)重命名磁盘上的文件 (c)在FileField中设置新文件位置。 但是,没有更简单的方法可以做到这一点吗?


这里发布的所有解决方案,以及我在网上看到的所有解决方案都涉及使用第三方应用程序或您已经拥有的解决方案。

我同意@Phillip,没有简单的方法可以完成您想要的事情,即使使用第三方应用程序,也需要一些工作才能使其适应您的目的。

如果您有许多需要此行为的模型,则只需实现pre_save信号并只编写一次该代码即可。

我也建议您阅读Django Signals,我相信您会发现它非常有趣。

很简单的例子:

1
2
3
4
5
6
7
8
9
10
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都会处理文件名。

值得注意的是,只有在使用FileSystemStorage的情况下,才使用file_move_safe重命名文件,因为@Phillip提到您不想使用Cloud文件存储,因为通常这些后端不支持文件重命名。

还几个笔记。您不必使用UploadTo类,常规函数即可。如果不指定keep_orphans,则每当删除模型实例时,文件就会被删除。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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信号的方法还可以使您简单地重新使用该方法。

有关pre_save的更多信息:https://docs.djangoproject.com/en/1.8/ref/signals/#pre-save


好的,它不能是lambda,因为lambda由于某些原因无法序列化,但这是简单的答案。

1
2
3
4
5
6
7
8
9
10
11
12
13
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 ='new-thing'将//myexample.com/MEDIA_ROOT/1/new-thing/mything.png

1
<img src="{{ obj.image.url }}">

这是假设您已设置MEDIA_ROOT,因为上载会转到媒体和媒体url。如在生产中一样处理静态文件,则以MEDIA_URL命名。

upload_to将对象实例和文件名都传递给您的函数,从那里您可以对其进行操作。

要更改实际的文件名,您需要在save()方法中做一些额外的工作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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)

如果文件扩展名很重要(很可能很重要),请在创建标签时将其添加到标签中,或者我们将旧文件扩展名作为字符串的一部分:

1
2
3
4
5
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的一部分

检查文件是否存在很简单

1
2
3
if os.path.isfile(rename):
    # you can also do this before renaming,
    # maybe raise an error if the file already exists