Django文件存储(二)定制存储系统
要自己写一个存储系统,可以依照以下步骤:
1.写一个继承自django.core.files.storage.Storage的子类。
from django.core.files.storage import Storage class MyStorage(Storage):
...
2.Django必须可以在无任何参数的情况下实例化MyStorage,所以任何环境设置必须来自django.conf.settings。
from django.conf import settings
from django.core.files.storage import Storage class MyStorage(Storage):
def __init__(self, option=None):
if not option:
option = settings.CUSTOM_STORAGE_OPTIONS
...
3.根据Storage的open和save方法源码:
def open(self, name, mode='rb'):
"""
Retrieves the specified file from storage.
"""
return self._open(name, mode) def save(self, name, content, max_length=None):
"""
Saves new content to the file specified by name. The content should be
a proper File object or any python file-like object, ready to be read
from the beginning.
"""
# Get the proper name for the file, as it will actually be saved.
if name is None:
name = content.name if not hasattr(content, 'chunks'):
content = File(content, name) name = self.get_available_name(name, max_length=max_length)
return self._save(name, content)
MyStorage需要实现_open和_save方法。
如果写的是个本地存储系统,还要重写path方法。
4.使用django.utils.deconstruct.deconstructible装饰器,以便在migration可以序列化。
还有,Storage.delete()、Storage.exists()、Storage.listdir()、Storage.size()、Storage.url()方法都会报NotImplementedError,也需要重写。
Django Qiniu Storage
七牛云有自己的django storage系统,可以看下是怎么运作的,地址https://github.com/glasslion/django-qiniu-storage。
先在环境变量或者settings中配置QINIU_ACCESS_KEY、QINIU_SECRET_KEY、QINIU_BUCKET_NAME、QINIU_BUCKET_DOMAIN、QINIU_SECURE_URL。
使用七牛云托管用户上传的文件,在 settings.py 里设置DEFAULT_FILE_STORAGE:
DEFAULT_FILE_STORAGE = 'qiniustorage.backends.QiniuStorage'
使用七牛托管动态生成的文件以及站点自身的静态文件,设置:
STATICFILES_STORAGE = 'qiniustorage.backends.QiniuStaticStorage'
运行python manage.py collectstatic,静态文件就会被统一上传到七牛。
QiniuStorage代码如下:
@deconstructible
class QiniuStorage(Storage):
"""
Qiniu Storage Service
"""
location = "" def __init__(
self,
access_key=QINIU_ACCESS_KEY,
secret_key=QINIU_SECRET_KEY,
bucket_name=QINIU_BUCKET_NAME,
bucket_domain=QINIU_BUCKET_DOMAIN,
secure_url=QINIU_SECURE_URL): self.auth = Auth(access_key, secret_key)
self.bucket_name = bucket_name
self.bucket_domain = bucket_domain
self.bucket_manager = BucketManager(self.auth)
self.secure_url = secure_url def _clean_name(self, name):
"""
Cleans the name so that Windows style paths work
"""
# Normalize Windows style paths
clean_name = posixpath.normpath(name).replace('\\', '/') # os.path.normpath() can strip trailing slashes so we implement
# a workaround here.
if name.endswith('/') and not clean_name.endswith('/'):
# Add a trailing slash as it was stripped.
return clean_name + '/'
else:
return clean_name def _normalize_name(self, name):
"""
Normalizes the name so that paths like /path/to/ignored/../foo.txt
work. We check to make sure that the path pointed to is not outside
the directory specified by the LOCATION setting.
""" base_path = force_text(self.location)
base_path = base_path.rstrip('/') final_path = urljoin(base_path.rstrip('/') + "/", name) base_path_len = len(base_path)
if (not final_path.startswith(base_path) or
final_path[base_path_len:base_path_len + 1] not in ('', '/')):
raise SuspiciousOperation("Attempted access to '%s' denied." %
name)
return final_path.lstrip('/') def _open(self, name, mode='rb'):
return QiniuFile(name, self, mode) def _save(self, name, content):
cleaned_name = self._clean_name(name)
name = self._normalize_name(cleaned_name) if hasattr(content, 'chunks'):
content_str = b''.join(chunk for chunk in content.chunks())
else:
content_str = content.read() self._put_file(name, content_str)
return cleaned_name def _put_file(self, name, content):
token = self.auth.upload_token(self.bucket_name)
ret, info = put_data(token, name, content)
if ret is None or ret['key'] != name:
raise QiniuError(info) def _read(self, name):
return requests.get(self.url(name)).content def delete(self, name):
name = self._normalize_name(self._clean_name(name))
if six.PY2:
name = name.encode('utf-8')
ret, info = self.bucket_manager.delete(self.bucket_name, name) if ret is None or info.status_code == 612:
raise QiniuError(info) def _file_stat(self, name, silent=False):
name = self._normalize_name(self._clean_name(name))
if six.PY2:
name = name.encode('utf-8')
ret, info = self.bucket_manager.stat(self.bucket_name, name)
if ret is None and not silent:
raise QiniuError(info)
return ret def exists(self, name):
stats = self._file_stat(name, silent=True)
return True if stats else False def size(self, name):
stats = self._file_stat(name)
return stats['fsize'] def modified_time(self, name):
stats = self._file_stat(name)
time_stamp = float(stats['putTime']) / 10000000
return datetime.datetime.fromtimestamp(time_stamp) def listdir(self, name):
name = self._normalize_name(self._clean_name(name))
if name and not name.endswith('/'):
name += '/' dirlist = bucket_lister(self.bucket_manager, self.bucket_name,
prefix=name)
files = []
dirs = set()
base_parts = name.split("/")[:-1]
for item in dirlist:
parts = item['key'].split("/")
parts = parts[len(base_parts):]
if len(parts) == 1:
# File
files.append(parts[0])
elif len(parts) > 1:
# Directory
dirs.add(parts[0])
return list(dirs), files def url(self, name):
name = self._normalize_name(self._clean_name(name))
name = filepath_to_uri(name)
protocol = u'https://' if self.secure_url else u'http://'
return urljoin(protocol + self.bucket_domain, name)
配置是从环境变量或者settings.py中获得的:
def get_qiniu_config(name, default=None):
"""
Get configuration variable from environment variable
or django setting.py
"""
config = os.environ.get(name, getattr(settings, name, default))
if config is not None:
if isinstance(config, six.string_types):
return config.strip()
else:
return config
else:
raise ImproperlyConfigured(
"Can't find config for '%s' either in environment"
"variable or in setting.py" % name) QINIU_ACCESS_KEY = get_qiniu_config('QINIU_ACCESS_KEY')
QINIU_SECRET_KEY = get_qiniu_config('QINIU_SECRET_KEY')
QINIU_BUCKET_NAME = get_qiniu_config('QINIU_BUCKET_NAME')
QINIU_BUCKET_DOMAIN = get_qiniu_config('QINIU_BUCKET_DOMAIN', '').rstrip('/')
QINIU_SECURE_URL = get_qiniu_config('QINIU_SECURE_URL', 'False')
重写了_open和_save方法:
def _open(self, name, mode='rb'):
return QiniuFile(name, self, mode) def _save(self, name, content):
cleaned_name = self._clean_name(name)
name = self._normalize_name(cleaned_name) if hasattr(content, 'chunks'):
content_str = b''.join(chunk for chunk in content.chunks())
else:
content_str = content.read() self._put_file(name, content_str)
return cleaned_name
使用的put_data方法上传文件,相关代码如下:
def put_data(
up_token, key, data, params=None, mime_type='application/octet-stream', check_crc=False, progress_handler=None,
fname=None):
"""上传二进制流到七牛 Args:
up_token: 上传凭证
key: 上传文件名
data: 上传二进制流
params: 自定义变量,规格参考 http://developer.qiniu.com/docs/v6/api/overview/up/response/vars.html#xvar
mime_type: 上传数据的mimeType
check_crc: 是否校验crc32
progress_handler: 上传进度 Returns:
一个dict变量,类似 {"hash": "<Hash string>", "key": "<Key string>"}
一个ResponseInfo对象
"""
crc = crc32(data) if check_crc else None
return _form_put(up_token, key, data, params, mime_type, crc, progress_handler, fname) def _form_put(up_token, key, data, params, mime_type, crc, progress_handler=None, file_name=None):
fields = {}
if params:
for k, v in params.items():
fields[k] = str(v)
if crc:
fields['crc32'] = crc
if key is not None:
fields['key'] = key fields['token'] = up_token
url = config.get_default('default_zone').get_up_host_by_token(up_token) + '/'
# name = key if key else file_name fname = file_name
if not fname or not fname.strip():
fname = 'file_name' r, info = http._post_file(url, data=fields, files={'file': (fname, data, mime_type)})
if r is None and info.need_retry():
if info.connect_failed:
url = config.get_default('default_zone').get_up_host_backup_by_token(up_token) + '/'
if hasattr(data, 'read') is False:
pass
elif hasattr(data, 'seek') and (not hasattr(data, 'seekable') or data.seekable()):
data.seek(0)
else:
return r, info
r, info = http._post_file(url, data=fields, files={'file': (fname, data, mime_type)}) return r, info def _post_file(url, data, files):
return _post(url, data, files, None) def _post(url, data, files, auth, headers=None):
if _session is None:
_init()
try:
post_headers = _headers.copy()
if headers is not None:
for k, v in headers.items():
post_headers.update({k: v})
r = _session.post(
url, data=data, files=files, auth=auth, headers=post_headers,
timeout=config.get_default('connection_timeout'))
except Exception as e:
return None, ResponseInfo(None, e)
return __return_wrapper(r) def _init():
session = requests.Session()
adapter = requests.adapters.HTTPAdapter(
pool_connections=config.get_default('connection_pool'), pool_maxsize=config.get_default('connection_pool'),
max_retries=config.get_default('connection_retries'))
session.mount('http://', adapter)
global _session
_session = session
最终使用的是requests库上传文件的,统一适配了链接池个数、链接重试次数。
Django文件存储(二)定制存储系统的更多相关文章
- Django文件存储(一)默认存储系统
Django默认使用的文件存储系统'django.core.files.storage.FileSystemStorage'是一个本地存储系统,由settings中的DEFAULT_FILE_STOR ...
- IOS开发--数据持久化篇文件存储(二)
前言:个人觉得开发人员最大的悲哀莫过于懂得使用却不明白其中的原理.在代码之前我觉得还是有必要简单阐述下相关的一些知识点. 因为文章或深或浅总有适合的人群.若有朋友发现了其中不正确的观点还望多多指出,不 ...
- PostgreSQL的存储系统二:REDOLOG文件存储结构二
REDOLOG文件里的用户数据和数据文件里的用户数据存储结构相同 几个月前同事给台湾一家公司培训<pg9 ad admin>时,有个学员提及WAL里记录的内容为Query时的SQL语句(比 ...
- django 自定义存储上传文件的文件名
一.需求: Django实现自定义文件名存储文件 使文件名看起来统一 避免收到中文文件导致传输.存储等问题 相同的文件也需要使用不同的文件名 二.实现思路: 思路: 生成14位随机字母加数字.后10位 ...
- django中云存储静态文件
Django自带文件存储系统存储在本地文件夹,如果我们将文件存储在云端,需要自定义文件存储系统. 自定义文件存储系统需要继承django.core.files.storage.Storage from ...
- Android开发手记(17) 数据存储二 文件存储数据
Android为数据存储提供了五种方式: 1.SharedPreferences 2.文件存储 3.SQLite数据库 4.ContentProvider 5.网络存储 本文主要介绍如何使用文件来存储 ...
- Android中的数据存储(二):文件存储 2017-05-25 08:16 35人阅读 评论(0) 收藏
文件存储 这是本人(菜鸟)学习android数据存储时接触的有关文件存储的知识以及本人自己写的简单地demo,为初学者学习和使用文件存储提供一些帮助.. 如果有需要查看SharedPreference ...
- [ Android 五种数据存储方式之二 ] —— 文件存储数据
关于文件存储,Activity提供了openFileOutput()方法可以用于把数据输出到文件中,具体的实现过程与在J2SE环境中保存数据到文件中是一样的. 文件可用来存放大量数据,如文本.图片.音 ...
- Python3编写网络爬虫09-数据存储方式二-JSON文件存储
2.JSON文件存储 全称为JavaScript Object Notation 通过对象和数组的组合来表示数据,构造简洁且结构化程度非常高.是一种轻量级的数据交换格式 2.1 对象和数组 在Java ...
随机推荐
- 34.js----JS 开发者必须知道的十个 ES6 新特性
JS 开发者必须知道的十个 ES6 新特性 这是为忙碌的开发者准备的ES6中最棒的十个特性(无特定顺序): 默认参数 模版表达式 多行字符串 拆包表达式 改进的对象表达式 箭头函数 =&> ...
- RocketMQ的使用
1 在resources目录下创建config目录,新建文件rocketmq.properties文件 # 指定namesrv地址 suning.rocketmq.namesrvAddr=localh ...
- c++ 常用的数据结构
set // constructing sets #include <iostream> #include <set> void checkin(std::set<int ...
- pycharm python3.5 神奇的导入问题
说明:pycharm目录中没有同名.py文件,则可以直接用import util导入: 若有同名.py文件,则导入的时候需要加入所在文件夹名称
- CNN超参数优化和可视化技巧详解
https://zhuanlan.zhihu.com/p/27905191 在深度学习中,有许多不同的深度网络结构,包括卷积神经网络(CNN或convnet).长短期记忆网络(LSTM)和生成对抗网络 ...
- Pycharm学习python路
import 模块之后是灰色的表明没有被引用过 lxml找不到的话用anaconda prompt :pip uninstall lxml 重新安装 用request时,写的reg无法正确解析网页,先 ...
- 【CDH学习之一】CDH简介
环境 虚拟机:VMware 10 Linux版本:CentOS-6.5-x86_64 客户端:Xshell4 FTP:Xftp4 jdk8 zookeeper-3.4.11 一.CDH在商业应用中,对 ...
- python 将字节写入文本文件
想在文本模式打开的文件中写入原始的字节数据 将字节数据直接写入文件的缓冲区即可 >>> import sys >>> sys.stdout.write(b'Hell ...
- Win7 Python开发环境搭建
1. 下载Anaconda并安装 地址: https://www.anaconda.com/download/ Anaconda包括Python基础包与一系列科学计算包,安装后不用再单独安装Pyth ...
- IO多路复用 IO异步
一.概念说明 同步IO和异步IO,阻塞IO和非阻塞IO分别是什么,到底有什么区别?不同的人在不同的环境给出的答案是不同的.所以先限定一下本文的环境.本文讨论的背景是Linux环境下的network I ...