如何在Django中更改上传文件的文件名?

44

在Django中,是否可以更改上传文件的文件名?我搜索了一下,但是没有找到任何答案。

我的要求是每当有文件上传时,文件名都应按以下格式更改。

format = userid + transaction_uuid + file_extension

非常感谢你...

9个回答

63
你是如何上传文件的? 我猜是用的 FileFieldFileField.upload_to 的文档显示,upload_to 字段可以是一个可调用对象,比如函数,它将被调用以获取上传路径(包括文件名)。这个可调用对象必须能够接受两个参数,并返回一个 Unix 风格的路径(使用正斜杠)以传递给存储系统。将传入的两个参数分别是: "instance":定义 FileField 的模型实例。更具体地说,这是当前正在附加当前文件的特定实例。 "filename":最初给定的文件名。在确定最终目标路径时可能会考虑此名称,也可能不会考虑。
因此,看起来你只需要编写一个处理名称的函数并返回路径即可。
def update_filename(instance, filename):
    path = "upload/path/"
    format = instance.userid + instance.transaction_uuid + instance.file_extension
    return os.path.join(path, format)

2
除非文件已存在,否则存储会破坏您的创建以获得唯一名称吗? - John Mee
4
请注意,您需要将路径更改为 path = "upload/path/",而不是 path = "/upload/path/"。如果路径以斜杠开头,Django 会出现“可疑操作”错误。 - Seb Ashton
请注意,如果您在表单中创建自定义验证函数,例如 def clean_my_audio_file_field(self):,那么此函数的结果将覆盖您在 upload_to=参数 中执行的任何操作。 - Olivier Pons
如果您希望文件名包括下拉菜单中的值,该怎么办?您如何使该值在models.py中可用? - Renel Chesak
@SoftwareEnthusiastic,你能告诉我在views.py文件中哪里写了这个函数def update_file_name()吗? - Cyley Simon
显示剩余2条评论

13

你需要一个带有 upload_to 属性的 FileField,这个属性调用一个回调函数,参见 [1]。

你的回调函数应该调用一个包装方法,这个方法将实例作为其中一个参数,文件名作为另一个参数。[2]

按照你喜欢的方式进行更改并返回新路径。[3]

1. 逻辑

FileField(..., upload_to=method_call(params),....)

2. 定义方法

def method_call(params):
    return u'abc'

3. 包装器:

def wrapper(instance, filename):
    return method

这是获取实例所需的rapper方法。

def wrapper(instance, filename):
... Your logic
...
return wrapper

完整代码

def path_and_rename(path, prefix):
    def wrapper(instance, filename):
        ext = filename.split('.')[-1]
        project = "pid_%s" % (instance.project.id,)
        # get filename
        if instance.pk:
            complaint_id = "cid_%s" % (instance.pk,)
            filename = '{}.{}.{}.{}'.format(prefix, project, complaint_id, ext)
        else:
            # set filename as random string
            random_id = "rid_%s" % (uuid4().hex,)
            filename = '{}.{}.{}.{}'.format(prefix, project, random_id, ext)
            # return the whole path to the file
        return os.path.join(path, filename)

    return wrapper

调用方法

sales_attach = models.FileField("Attachment", upload_to=path_and_rename("complaint_files", 'sales'), max_length=500,
                                help_text="Browse a file")

希望这可以帮到你。 谢谢。


我在项目和UUID中遇到了错误,因此我创建了自己的函数,作为另一个答案给出。 - The EasyLearn Academy

11

如果您希望使您的函数可重用:

import hashlib
import datetime
import os
from functools import partial

def _update_filename(instance, filename, path):
    path = path

    filename = "..."

    return os.path.join(path, filename)

def upload_to(path):
    return partial(_update_filename, path=path)

你只需要按照这种方式使用它:

document = models.FileField(upload_to=upload_to("my/path"))

2
如果这有助于任何人。
import os
import uuid
import random

from datetime import datetime 

def user_directory_path(instance, filename):
    # Get Current Date
    todays_date = datetime.now()

    path = "uploads/{}/{}/{}/".format(todays_date.year, todays_date.month, todays_date.day)
    extension = "." + filename.split('.')[-1]
    stringId = str(uuid.uuid4())
    randInt = str(random.randint(10, 99))

    # Filename reformat
    filename_reformat = stringId + randInt + extension

    return os.path.join(path, filename_reformat)


class MyModel(models.Model):
    upload = models.FileField(upload_to=user_directory_path)

或者路径可以是 path="uploads/",用于单个目录。 - Haykins

2
import random
import os
def generate_unique_name(path):
    def wrapper(instance, filename):
        extension = "." + filename.split('.')[-1]
        filename = str(random.randint(10,99)) + str(random.randint(10,99)) + str(random.randint(10,99)) + str(random.randint(10,99))  + extension
        return os.path.join(path, filename)
    return wrapper

您只需要按照以下方式使用它:

 photo = models.FileField("Attachment", upload_to=generate_unique_name("pics"),max_length=500,help_text="Browse a photo")

1

不需要那么多代码,你只需使用单行代码 fille._name=userid + transaction_uuid + file_extension

就像这样

class xyz(models.Model):
   file = models.FileField(upload_to="notice/")

   def add(request):
      file = request.POST['file']
      file._name = request.user.id + transaction_uuid +"."+ file._name.split('.')[1]

你可以通过覆盖文件对象的 _name 值来覆盖文件名。


请将代码部分插入代码格式而不是文本格式。 - Khaled

0

你也可以尝试这个方法,name_of_submission 可以是你想要附加文件的任何列。

import os

def update_filename(instance, filename):
    path = "upload/path/"
    ext = filename.split('.')[-1]
    format = instance.name_of_submission +    str(instance.id_application) + "." + ext
    print(format)
    return os.path.join(path, format)

-1

基本方法是

import os

os.rename('a.txt', 'b.html')

对于您的情况,它可能看起来像这样

os.rename ("a.txt", "{id}{uuid}.{ext}".format(id=userid, uuid=transaction_uuid, ext=file_extension))

-1

Hi, i check all the answers, but someone are not updated, this is how in 2022 works whith clean code and following the Django Documentation Here, remember that you need to make a MIGRATION to make this work:

 def AvatarSave(instance, filename):
    #this line changes the name of the file to the user name and put the file extension at the end after the point
    return 'users/avatars/{0}.{1}'.format(instance.id,filename.split('.')[-1])

 avatar = models.ImageField(_("avatar"),upload_to=AvatarSave)

这只是@monkut接受答案的劣化版本。你甚至链接到相同的旧版本3.2的Django文档。与你的回答相比,被接受的答案实际上涉及到OP的具体情况,即他们的文件应该如何命名。你使用了一些头像示例,其中包含与OP无关的ImageField。 - Daniil Fajnberg
抱歉,这份工作需要使用最新版本的链接(https://docs.djangoproject.com/en/4.1/ref/models/fields/#django.db.models.FileField.upload_to),我不记得为什么其他代码对我无效。 - Oliver Sitán
最后我使用了这段代码,因为我需要每次都使用完全相同的名称来保存图片,并且我使用其他方法在我的应用程序中缓存这些图片。欢迎提出评论: def AvatarSave(instance, filename): file = 'users/avatars/{0}.{1}'.format(instance.id, filename.split('.')[-1]) fullFile = os.path.join(settings.MEDIA_ROOT, file) if exists(fullFile): os.remove(os.path.join(settings.MEDIA_ROOT, fullFile)) return file - Oliver Sitán

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接