如何更新FileField中的文件位置?

时间:2014-08-26 21:18:47

标签: django filefield

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

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

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

4 个答案:

答案 0 :(得分:4)

此处发布的所有解决方案以及我在网络上看到的所有解决方案都涉及使用第三方应用或您已有的解决方案。

我同意@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.

答案 1 :(得分:2)

这是一款可以为您处理django-smartfields的应用。 我为此添加了一个特殊的处理器,因为它看起来像是一个有用的功能。

这是如何运作的:

  • 更改upload_to
  • 中指定的文件名称 只要FileDependency字段发生更改,
  • label就会处理文件名。

值得注意的是,该文件将使用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))

答案 2 :(得分:1)

好吧,它不能是一个lambda,因为lambdas由于某种原因不可序列化,但这是简单的答案。

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

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

这假设您已设置MEDIA_ROOT,因为上传会转到媒体和媒体网址。像生产中的静态文件一样提供服务,在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

答案 3 :(得分:1)

我认为你使用save()方法的方法是正确的,简单的&#34;一个除了我会使用pre_save信号而不是覆盖保存方法(这通常是一个坏主意)。

如果这是您想在其他模型上重复的行为,使用连接到pre_save信号的方法也可以让您简单地重复使用该方法。

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