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库上传文件的,统一适配了链接池个数、链接重试次数。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • django之状态保持-使用redis存储session的例子

    关于redis安装,pip install django-redis-sessions,按照提示进行安装相关的服务端 和客户端. django版本1.8.2, Python版本2.7.12 1 进入虚拟环境h1 workon h1 2 创建一个项目test应用booktest django-admin startproject test 3 创建应用booktest 进入项目test目录,创建应用booktest,这个booktest应用目录和manage.py在 同级目录下 python ma

  • Django文件存储 默认存储系统解析

    Django默认使用的文件存储系统'django.core.files.storage.FileSystemStorage'是一个本地存储系统,由settings中的DEFAULT_FILE_STORAGE值确定. class FileSystemStorage(location=None, base_url=None, file_permissions_mode=None, directory_permissions_mode=None) FileSystemStorage类继承自Storag

  • Python的Django中将文件上传至七牛云存储的代码分享

    最近在写的一个django小项目需要实现用户上传图片的功能,使用到了七牛云存储,特此记录下来.这里我使用的七牛python SDK 版本是7.0.3,函数使用上可能会与旧版有些不同. 原本文件上传需要先把文件上传到自己的业务服务器,再从业务服务器上传到云存储.现在七牛的表单上传可以直接把文件上传到七牛,不再需要业务服务器的中转,节省了流量成本,降低了业务服务器的压力.而且通过设置,还可以在文件上传完成后让客户端自动重定向到一个上传成功的结果页面.这里我就是使用了七牛的表单上传. 表单上传 用户上

  • django自定义Field实现一个字段存储以逗号分隔的字符串

    实现了在一个字段存储以逗号分隔的字符串,返回一个相应的列表 复制代码 代码如下: from django import formsfrom django.db import modelsfrom django.utils.text import capfirstfrom django.core import exceptions class MultiSelectFormField(forms.MultipleChoiceField):    widget = forms.CheckboxSel

  • Django中的文件的上传的几种方式

    PS:这段时间有点不在状态,刚刚找回那个状态,那么我们继续曾经的梦想 今天我们来补充一下文件的上传的几种方式: 首先我们先补充的一个知识点: 一.请求头ContentType: ContentType 指的是请求体的编码类型,常见的类型共有三种: 1.application/x-www-form-urlencoded 这应该是最常见的POST提交数据的方式.浏览器的原生 <form> 表单,如果不设置 enctype 属性,那么最终就会以 application/x-www-form-urle

  • Django后台获取前端post上传的文件方法

    由于初学Django所以对于其特有的表单模板和models中的filefiled并不是特别熟悉.因此在本次开发中还是依照以往自己在html中写好图片提交表单: 注意在<form>表单的定义中一定要加上enctype= "multipart/form-data"属性,否则后台可能会取不到文件. 在form表单后一定要有{% csrf_token %} 我们在控制页面可以看到,当我们通过这样的表单进行提交后,FILES一栏中会有一个文件. 我们可以在views.py中通过 im

  • 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

  • Android文件存储SharedPreferences源码解析

    1.我们都知道SharedPreferences 是android可以用来存放key value的的文件. SharedPreferences sp = getSharedPreferences("fileName", Context.MODE_PRIVATE); SharedPreferences.Editor editor = sp.edit(); editor.putString("key","value"); editor.commit(

  • android数据存储之文件存储方法

    文件存储是 Android 中最基本的一种数据存储方式,它不对存储的内容进行任何的格式化处理,所有数据都是原封不动的保存到文件当中的. 概述 文件存取的核心就是输入流和输出流. Android文件的操作模式 文件的相关操作方法 文件读写的实现 openFileOutput和openFileInput方法 /** * openFIleOutput ,openFileInput * 这两种方法同sp一样只能讲文件保存到手机内存固定的路径中, * 默认为 /data/data/<packageName

  • python+django加载静态网页模板解析

    接着前面Django入门使用示例 今天我们来看看Django是如何加载静态html的? 我们首先来看一看什么是静态HTML,什么是动态的HTML?二者有什么区别? 静态HTML指的是使用单纯的HTML或者结合CSS制作的包括图片.文字等的只供用户浏览但不包含任何脚本.不含有任何交互功能的网页! 动态的HTML指的是网页不仅提供给用户浏览,网页本身还有交互功能,存在着在脚本如JAVASCRIPT,并利用某种服务器端语言如PHP等实现如用户注册,用户登录,上传文件,下载文件等功能 接下来,了解下加载

  • Django中Forms的使用代码解析

    本文研究的主要是Django中Forms的使用,具体如下. 创建文件do.html {% extends 'base.html' %} {% block mainbody %} <form action="/app1/do/" method="post"> {% csrf_token %} {{ form.as_p }} <input type="submit" value="Submit"> <

  • Django数据库表反向生成实例解析

    本文我们研究下如何在django中反向生成mysql model代码,接下来我们看看具体介绍. 我们在展示django ORM反向生成之前,我们先说一下怎么样正向生成代码. 正向生成,指的是先创建model.py文件,然后通过django内置的编译器,在数据库如mysql中创建出符合model.py的表. 反向生成,指的是先在数据库中create table,然后通过django内置的编译器,生成model代码. 1.准备工作 创建django工程以及app 创建django工程,名字是hell

  • django admin后管定制-显示字段的实例

    先说需求: 1.django 自带了admin后管,如果我们需要使用,只需把我们定义的models注册即可: 2.但如果只是简单注册,那显示的很简单,根本看不到每行记录的描述信息,全部以model object 形式显示,必须点击进去才能看到信息,太不方便,如下图: 1.显示关键信息 效果如下图,这样就好看多啦. 2.配置 在 admin.py 文件中配置一下就行. admin.py # Underwriter admin model class UnderwriterAdmin(admin.M

  • C++使用cjson操作Json格式文件(创建、插入、解析、修改、删除)

    目录 为什么要学习解析Json文件? 一.准备cJSON开源库 二.cJSON介绍 三.封装Json 四.解析Json 五.修改Json 六.删除Json 七.全部代码 八.总结 为什么要学习解析Json文件? 工作需要呗! 最近在工作项目中,有需求是需要进行解析Json字符串的,但是我只会使用QT去解析Json,且主管规定要使用C/C++语言去解析,说是为了方便移植到其他项目中进行使用… 没办法,只能硬着头皮,在网上找找有没有什么解析Json的开源库是C/C++可以使用的.找了许多,网上也提供

  • Android数据存储方式操作模式解析

    目录 SharedPreferences 基本用法 操作模式 ContentProvider 基本概念 统一资源标识符(URI) 基本使用 文件存储 基本使用 总结 SharedPreferences 在开发过程中,数据存取是较为频繁的,今天我们来了解下android几种常见的数据存取方式. 在Android中,sharePreferences是一种轻量级的数据存储方式,采用键值对的存储方式,存储少量数据,支持基本类型的简单数据存储. 基本用法 根据Context获取SharedPreferen

随机推荐