Django结合七牛云实现对象云储存
目标
- 掌握
storage
类的导入方式 - 掌握
storage
类的操作模式 - 学会 查看七牛的文档,并为自己所用
- 掌握七牛的空间的创建、秘钥的获取
文章目录
- 目标
- 前言
-
-
- 1.1 思路一
- 1.2 思路二
-
- 学习 Storage 类的使用
-
-
- 2.1 创建存储类
- 2.2 添加装饰器
- 2.3 了解Storage类
- 2.4 获取参数
- 2.5 完整代码
-
前言
在Django中结合第三方实现图片、文件上传的功能,现在能够实现的思路有两个,分析如下
:
1.1 思路一
思路:
利用七牛现有的api,单独实现一个功能模块,来完成文件对象存储。
使用方式:
在视图中,将前端传递过来的文件数据,上传七牛,然后保存七牛返回的文件地址。
优缺点:
优点:耦合性低,易修改功能代码。逻辑简单,易操作。
1.2 思路二
思路:
继承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 类的使用
Storage类提供了用于存储文件的标准化API,以及所有其他存储系统可以根据需要继承或覆盖的一组默认行为。
如果需要提供自定义文件存储(一个常见的示例是在某个远程系统上存储文件),则可以通过定义自定义存储类来实现。
2.1 创建存储类
您的自定义存储系统必须是 django.core.files.storage.Storage
的子类:
from django.core.files.storage import Storageclass QiNiuStorage(Storage): pass
2.2 添加装饰器
自定义的存储类必须是 deconstructible
,以便在迁移中的字段上使用它时可以序列化。 只要你的字段有自己的参数 serializable
,你可以使用 django.utils.deconstruct.deconstructible
类装饰器。
from django.utils.deconstruct import deconstructiblefrom django.core.files.storage import Storage@deconstructibleclass QiNiuStorage(Storage): pass
2.3 了解Storage类
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 deconstructiblefrom django.core.files.storage import Storage@deconstructibleclass 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
2.4 获取参数
Django必须能够在没有任何参数的情况下实例化您的存储系统。 这意味着任何设置都应来自 django.conf.settings
:
from django.core.files.storage import Storagefrom django.conf import settings@deconstructibleclass 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
2.5 完整代码
import osimport datetimeimport timefrom django.core.files.storage import Storagefrom django.utils.deconstruct import deconstructiblefrom django.conf import settingsfrom qiniu import Auth, put_data, BucketManager@deconstructibleclass 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