storage
类的导入方式storage
类的操作模式在Django中结合第三方实现图片、文件上传的功能,现在能够实现的思路有两个,分析如下
:
思路:
利用七牛现有的api,单独实现一个功能模块,来完成文件对象存储。
使用方式:
在视图中,将前端传递过来的文件数据,上传七牛,然后保存七牛返回的文件地址。
优缺点:
优点:耦合性低,易修改功能代码。逻辑简单,易操作。
思路:
继承Django的 Storage
存储类,然后在自定义的存储类中,完成上传七牛,获取文件地址的逻辑,然后以及保存的逻辑。
使用方式:
学习继承 Storage
类的要点,通过自定义逻辑封装成类,在 ImageFiled
中通过 storage
参数指向自定的类
优缺点:
能够很好的利用Django的组件,完成自定逻辑。通过Django的模型类来实现图片的增删改查,例如:自带的admin管理系统,DRF的序列化操作等。
七牛官网有写好的api,大家可以参考官网api的使用完成上述逻辑。
官方网站注册地址 https://developer.qiniu.com/
官方SDK文档 https://developer.qiniu.com/kodo/sdk/1242/python
Storage类提供了用于存储文件的标准化API,以及所有其他存储系统可以根据需要继承或覆盖的一组默认行为。
如果需要提供自定义文件存储(一个常见的示例是在某个远程系统上存储文件),则可以通过定义自定义存储类来实现。
您的自定义存储系统必须是 django.core.files.storage.Storage
的子类:
from django.core.files.storage import Storage
class QiNiuStorage(Storage):
pass
自定义的存储类必须是 deconstructible
,以便在迁移中的字段上使用它时可以序列化。 只要你的字段有自己的参数 serializable
,你可以使用 django.utils.deconstruct.deconstructible
类装饰器。
from django.utils.deconstruct import deconstructible
from django.core.files.storage import Storage
@deconstructible
class QiNiuStorage(Storage):
pass
Storage模型类
作为一个基本存储类,给我们提供一些存储系统默认拥有的方法,这些方法可以被继承或重写。
在Storage模型类
提供的方法中,save()
方法 和 open()
方法中使用了未定义的 _open()
以及 _save()
。因此在自定义模型类时,除了自定义的方法外,我们必须继承或重写 _open()
以及 _save()
方法。
其他的方法如果不重写,在默认情况下,会引发 NotImplementedError
异常,因此在使用时,需要将其覆盖:
Storage.delete()
Storage.exists()
Storage.listdir()
Storage.size()
Storage.url()
注意点:
这些方法并非都是必需的,因此可以有意省略。 碰巧的是,有可能使每个方法都未实现,而仍然可以使用存储。
举例说明
如果列出某些存储后端的内容确实很昂贵,则可以决定不实现
Storage.listdir
。仅处理写入文件的后端。 在这种情况下,您将不需要实现任何上述方法。
最终,由您决定采用哪种方法。 保留一些未实现的方法将导致部分(可能已损坏)接口。
通常,您还需要使用专门为自定义存储对象设计的挂钩
from django.utils.deconstruct import deconstructible
from django.core.files.storage import Storage
@deconstructible
class QiNiuStorage(Storage):
def __init__(self):
"""初始化参数"""
pass
def open(self):
"""打开"""
pass
def save(self):
"""保存"""
pass
def exists(self):
"""判断文件是否存在"""
pass
def delete(self):
"""删除"""
pass
def url(self):
"""获取url地址"""
pass
Django必须能够在没有任何参数的情况下实例化您的存储系统。 这意味着任何设置都应来自 django.conf.settings
:
from django.core.files.storage import Storage
from django.conf import settings
@deconstructible
class QiNiuStorage(Storage):
"""七牛云存储"""
def __init__(self, child_name):
# 访问图片的根地址
self.__base_url = settings.QINIU_BASE_URL
# 存储图片的空间名
self.__backet_name = settings.QINIU_BACKET_NAME
# 七牛 access_key
self.__access_key = settings.QINIU_ACCESS_KEY
# 七牛 secret_key
self.__secret_key = settings.QINIU_SECRET_KEY
import os
import datetime
import time
from django.core.files.storage import Storage
from django.utils.deconstruct import deconstructible
from django.conf import settings
from qiniu import Auth, put_data, BucketManager
@deconstructible
class QiNiuStorage(Storage):
"""七牛云存储"""
def __init__(self, child_name):
# 访问图片的根地址
self.__base_url = settings.QINIU_BASE_URL
# 存储图片的空间名
self.__backet_name = settings.QINIU_BACKET_NAME
# 七牛 access_key
self.__access_key = settings.QINIU_ACCESS_KEY
# 七牛 secret_key
self.__secret_key = settings.QINIU_SECRET_KEY
# 七牛云-构建鉴权对象
self.qiniu_server = Auth(self.__access_key, self.__secret_key)
# 存储图片的子空间名
self.child_name = child_name
def _open(self, name, mode='rb'):
"""不需要打开文件,所以直接忽略"""
pass
def _save(self, name, content):
"""
存储函数
:param name: 文件名
:param content: 文件
:return:
"""
# 七牛云-生成上传 Token,可以指定过期时间等
token = self.qiniu_server.upload_token(self.__backet_name)
# 获取文件二进制内容
file_data = content.file
# 利用七牛的put_data方法上传文件内容
ret, info = put_data(
token,
self.__new_name(name, self.child_name),
file_data if isinstance(file_data, bytes) else file_data.read(),
)
# 根据七牛的返回结果中的响应状态码,判断是否上传成功
if info.status_code == 200:
return ret.get("key")
else:
raise Exception("上传七牛失败")
def exists(self, name):
"""
判断文件是否存在,7牛云可以自动判断文件名是否以存在
所以此处返回false,告诉django上传的文件都是新的
:param name: 文件名
:return: False
"""
return False
def url(self, name):
"""
返回文件的完整URL路径
:param name: 数据库中保存的文件名
:return: 完整的URL
"""
return os.path.join(self.__base_url, name)
def delete(self, name):
bucket = BucketManager(self.qiniu_server)
ret, info = bucket.delete(self.__backet_name, name)
if ret == {} and info.status_code == 200:
return True
else:
raise Exception('对象存储异常!')
@staticmethod
def __new_name(name, child_name='article'):
"""
将上传的文件重新命名
:param name: 文件名
:param child_name: 子空间域名
:return: 新的文件名
"""
# 获取文件后缀
file_extension = name.split('.').pop()
# 获取当前的时间:年_月_日,作为二级文件夹的名字
now_time = datetime.datetime.now().strftime("%Y_%m_%d")
# 因为业务量级不大,所以以时间戳为文件名字
name = int(time.time())
# 整理路径,并返回
new_name = f"file/bigevent/{child_name}/{now_time}/{name}.{file_extension}.png"
return new_name