目标
- 掌握
storage 类的导入方式 - 掌握
storage 类的操作模式 - 学会 查看七牛的文档,并为自己所用
- 掌握七牛的空间的创建、秘钥的获取
前言
在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 Storage
class QiNiuStorage(Storage):
pass
2.2 添加装饰器
自定义的存储类必须是 deconstructible ,以便在迁移中的字段上使用它时可以序列化。 只要你的字段有自己的参数 serializable ,你可以使用 django.utils.deconstruct.deconstructible 类装饰器。
from django.utils.deconstruct import deconstructible
from django.core.files.storage import Storage
@deconstructible
class 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 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
2.4 获取参数
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
self.__access_key = settings.QINIU_ACCESS_KEY
self.__secret_key = settings.QINIU_SECRET_KEY
2.5 完整代码
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
self.__access_key = settings.QINIU_ACCESS_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 = self.qiniu_server.upload_token(self.__backet_name)
file_data = content.file
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
|