import os import hashlib from datetime import datetime def _get_file_md5(file_obj): """ 通用辅助函数:计算任意文件对象的 MD5 """ md5 = hashlib.md5() # 关键步骤:重置文件指针到开头 # 因为 Django 在处理上传时可能已经读取了一部分文件头 try: file_obj.seek(0) except Exception: pass # 分块读取计算,防止大文件撑爆内存 for chunk in file_obj.chunks(): md5.update(chunk) # 计算完后再次重置指针,确保不影响 Django 后续的保存操作 try: file_obj.seek(0) except Exception: pass return md5.hexdigest() def upload_image_and_rename(instance, filename): """ 通用图片上传处理函数 逻辑:存入 images/年/月/日/md5.ext """ # 1. 获取后缀 ext = os.path.splitext(filename)[1].lower() # 2. 生成日期路径 date_str = datetime.now().strftime('%Y/%m/%d') # 3. 计算 MD5 # 这里直接利用传入的 filename 对应的文件对象(在内存或临时文件中) # 注意:这里假设 filename 是有效的 UploadedFile 对象名称, # 但最稳妥的方式其实是直接操作 file_obj。 # 由于 upload_to 签名限制,我们通常通过 instance 获取字段值, # 但为了通用性,我们尝试从 instance 的 __dict__ 中寻找 FileField 对象。 file_obj = None # 遍历 instance 的所有属性,找到第一个 FileField 类型的值 for field in instance._meta.get_fields(): if hasattr(field, 'upload_to') and hasattr(instance, field.title): val = getattr(instance, field.title) if val and hasattr(val, 'chunks'): # 确认是文件对象 file_obj = val break # 如果找不到文件对象(极少见情况),使用随机字符串防止报错 if not file_obj: import uuid new_filename = f"{uuid.uuid4().hex}{ext}" else: md5_hash = _get_file_md5(file_obj) new_filename = f"{md5_hash}{ext}" # 4. 返回路径 return os.path.join('images', date_str, new_filename) def upload_file_and_rename(instance, filename): """ 通用文件上传处理函数 逻辑:存入 uploads/年/月/日/md5.ext """ ext = os.path.splitext(filename)[1].lower() date_str = datetime.now().strftime('%Y/%m/%d') file_obj = None # 同样的通用查找逻辑 for field in instance._meta.get_fields(): if hasattr(field, 'upload_to') and hasattr(instance, field.title): val = getattr(instance, field.title) if val and hasattr(val, 'chunks'): file_obj = val break if not file_obj: import uuid new_filename = f"{uuid.uuid4().hex}{ext}" else: md5_hash = _get_file_md5(file_obj) new_filename = f"{md5_hash}{ext}" return os.path.join('uploads', date_str, new_filename)