美文网首页
使用py3fdfs - 踩坑实录 __str__ return

使用py3fdfs - 踩坑实录 __str__ return

作者: 花括弧 | 来源:发表于2019-07-30 00:42 被阅读0次
django上传图片 和 用户获得html页面后请求图片 流程
  • 后台运营人员 通过djangoadmin页面,进行(图片)文件的上传
  • django使用 自定义的storage类,把文件上传到fastdfs
  • fastdfs根据文件内容,得到文件名(/group1/M00...)。并 返回文件名(/group1/M00...)django
  • django文件名(/group1/M00...) 存储到数据库表的某个字段内。
  • 网站用户访问某个页面(比如/goods页面),django返回 经过渲染的页面(相关标签 被替换成真实的 数据) 给用户。
  • 用户用经过渲染的页面中的url地址 向 远端的nginx(nginxfastdfs的storage服务器 是部署在一起的) 请求资源文件。
  • nginx返回 资源文件

fastdfs的优点:

  • 使得存储容量的扩展 很方便。
  • 解决了 上传文件时,文件名相同 而文件内容不同 带来的问题。因为fastdfs是根据文件内容 生成 文件名的。
  • fastdfs和nginx结合使用,可提高 网站提供资源的 效率。

在pycharm中导入py3fdfs

# 根据pypi中py3fdfs的示例: 在python3命令行执行下面两句
>>> from fdfs_client.client import *
>>> client = Fdfs_client('/etc/fdfs/client.conf')

在执行client = Fdfs_client('/etc/fdfs/client.conf')时,会报错:TypeError: type object argument after ** must be a mapping, not str
解决方法:
1)根据报错位置,定位到PycharmProjects/dailyfresh/venv/lib/python3.6/site-packages/fdfs_client/client.py
观察到 如下的代码:

class Fdfs_client(object):
    '''
    Class Fdfs_client implemented Fastdfs client protol ver 3.08.

    It's useful upload, download, delete file to or from fdfs server, etc. It's uses
    connection pool to manage connection to server.
    '''

    def __init__(self, trackers, poolclass=ConnectionPool):
        self.trackers = trackers
        self.tracker_pool = poolclass(**self.trackers)
        self.timeout = self.trackers['timeout']
        return None

发觉Fdfs_client的初始化要传递trackers, 而不是'/etc/fdfs/client.conf'字符串
接着观察到 文件顶部 有如下代码:

def get_tracker_conf(conf_path='client.conf'):
    cf = Fdfs_ConfigParser()
    tracker = {}
    try:
        cf.read(conf_path)
        timeout = cf.getint('__config__', 'connect_timeout')
        tracker_list = cf.get('__config__', 'tracker_server')
        if isinstance(tracker_list, str):
            tracker_list = [tracker_list]
        tracker_ip_list = []
        for tr in tracker_list:
            tracker_ip, tracker_port = tr.split(':')
            tracker_ip_list.append(tracker_ip)
        tracker['host_tuple'] = tuple(tracker_ip_list)
        tracker['port'] = int(tracker_port)
        tracker['timeout'] = timeout
        tracker['name'] = 'Tracker Pool'
    except:
        raise
    return tracker

def get_tracker_conf(conf_path='client.conf'):不就是 返回一个tracker么,而且其接收的参数是client.conf配置文件的路径
def get_tracker_conf(conf_path='client.conf'):函数的作用是:把配置文件client.conf中信息,提取到一个字典tracker中,并返回 该字典tracker

那么我们可以使用如下的代码 来代替(同时也是正确的):

# 由配置文件中的信息 得到 字典trackers 
trackers = get_tracker_conf('/Users/leesam/PycharmProjects/dailyfresh/utils/fdfs/client.conf')
# 把
client = Fdfs_client(trackers)

别急,填完这个坑,路的前方 还有好多坑呢。哈哈哈哈啊哈哈。

自定义了 文件存储类 用来将admin管理页面 添加的一条记录 保存到远端fdfs,点击保存按钮时,出现了 如下错误

__str__ return non-string (type bytes)
报错的意思大概是:返回了非字符串的bytes类型
由于之前 在项目中 只添加了如下的代码,而且 只有2个方法(只有2个返回值)。一个明确返回False,那么 错误 大概是出在 return filename这行。
况且, filename = res.get('Remote file_id')的确是 返回的bytes类型。那么,我们要把其从字节类型转换到字符串类型
使用decode()函数,把字节类型的 filename转换到字符串类型
return filename修改为return filename.decode()即可。 str(value), the type of value is bytes

原因分析:
自己写的文件存储类,返回的是字节型类型的文件名。执行的时候,在django内部的get_prep_value模块 接收到了 该文件名参数,并使用了str(value)进行了封装。所以, 才会报错__str__ returned non-string (type bytes).
由于,报错位置 跟 实际问题的位置 不在一个地方,所以 问题藏得比较隐蔽。

文件存储类的代码如下(注意_save的返回值: 返回字符串类型):

from django.core.files.storage import Storage

from fdfs_client.client import *

class FDFSStorage(Storage):
    '''fastdfs文件存储类'''

    def _open(self, name, mode='rb'):
        '''打开文件时 调用该函数'''
        pass

    # 通过后台管理页面,选文件 并 上传时
    # django会调用_save方法(并给_save方法传递2个参数:name: 所要上传文件的名字,content: (包含文件内容的)File类的实例对象)
    def _save(self, name, content):
        '''保存文件时 调用该函数'''
        # name: 所要上传文件的名字
        # content: File类的实例(包含上传文件内容的File实例对象)

        # 创建一个Fdfs_client对象
        # client = Fdfs_client('./utils/fdfs/client.conf')    #会根据./utils/fdfs/client.conf文件的配置,传给远端的tracker
        trackers = get_tracker_conf('/Users/leesam/PycharmProjects/dailyfresh/utils/fdfs/client.conf')
        client = Fdfs_client(trackers)

        # 上传文件到 fastdfs文件系统 中
        # content.read() 可以从File的实例对象content中 读取 文件内容
        # upload_by_buffer返回内容为 字典。格式如下 注释部分
        res = client.upload_by_buffer(content.read()) # upload_by_buffer 根据文件内容 上传文件

        # dict {
        #
        #     'Group name': group_name,
        #     'Remote file_id': remote_file_id,
        #     'Status': 'Upload successed.',
        #     'Local file name': '',
        #     'Uploaded size': upload_size,
        #     'Storage IP': storage_ip
        #
        # }

        if res.get('Status') != 'Upload successed.':
            # 上传失败
            raise Exception('upload file to fastdfs failed.')

        # 获取 返回的 文件id
        filename = res.get('Remote file_id')

        return filename.decode()

    # django在调用_save之前,会先调用_exists
    # _exists 根据 文件的name,判断 文件 是否存在于 文件系统中。存在:返回True,不存在:返回False
    def exists(self, name):
        '''django 判断 文件名 是否可用'''
        # 因为 文件是存储在 fastdfs文件系统中的,所以 对于django来说:不存在 文件名不可用 的情况
        return False

改进方法:

在setting.py增加以下内容

# 设置django的文件存储类,上传文件时 django会调用 该文件存储类的相关方法
DEFAULT_FILE_STORAGE = 'utils.fdfs.storage.FDFSStorage'

# 设置 fastdfs文件系统 使用的 client.conf文件路径
FDFS_CLIENT_CONF = './utils/fdfs/client.conf'
# 设置 fastdfs存储服务器上 nginx使用的IP和端口号
FDFS_STORAGE_URL = 'http://10.211.55.15:8888/'

定义自己的storage类:

from django.core.files.storage import Storage

from fdfs_client.client import *

from django.conf import settings

class FDFSStorage(Storage):
    '''fastdfs文件存储类'''
    def  __init__(self, client_conf=None, base_url=None):
        if client_conf == None:
            client_conf = settings.FDFS_CLIENT_CONF
        self.client_conf = client_conf

        if base_url == None:
            base_url = settings.FDFS_STORAGE_URL
        self.base_url = base_url


    def _open(self, name, mode='rb'):
        '''打开文件时 调用该函数'''
        # 用不到 打开文件,所以省略
        pass

    # 通过后台管理页面,选文件 并 上传时
    # django会调用_save方法(并给_save方法传递2个参数:name: 所要上传文件的名字,content: (包含文件内容的)File类的实例对象)
    def _save(self, name, content):
        '''保存文件时 调用该函数'''
        # name: 所要上传文件的名字
        # content: File类的实例(包含上传文件内容的File实例对象)
        # 返回值: fastdfs中 存储文件时 使用的文件名(被保存到 数据库的表 中)

        # 创建一个Fdfs_client对象
        # client = Fdfs_client('./utils/fdfs/client.conf')    #会根据./utils/fdfs/client.conf文件的配置,传给远端的tracker
        # trackers = get_tracker_conf('/Users/leesam/PycharmProjects/dailyfresh/utils/fdfs/client.conf')
        trackers = get_tracker_conf(self.client_conf)
        client = Fdfs_client(trackers)

        # 上传文件到 fastdfs文件系统 中
        # content.read() 可以从File的实例对象content中 读取 文件内容
        # upload_by_buffer返回内容为 字典。格式如下 注释部分
        res = client.upload_by_buffer(content.read()) # upload_by_buffer 根据文件内容 上传文件

        # dict {
        #
        #     'Group name': group_name,
        #     'Remote file_id': remote_file_id,
        #     'Status': 'Upload successed.',
        #     'Local file name': '',
        #     'Uploaded size': upload_size,
        #     'Storage IP': storage_ip
        #
        # }

        if res.get('Status') != 'Upload successed.':
            # 上传失败
            raise Exception('upload file to fastdfs failed.')

        # 获取 返回的 文件id
        filename = res.get('Remote file_id')
        # 只能返回str类型, filename为bytes类型(需要转换类型,不然会报错)
        return filename.decode()

    # django在调用_save之前,会先调用_exists
    # _exists 根据 文件的name,判断 文件 是否存在于 文件系统中。存在:返回True,不存在:返回False
    def exists(self, name):
        '''django 判断 文件名 是否可用'''
        # 因为 文件是存储在 fastdfs文件系统中的,所以 对于django来说:不存在 文件名不可用 的情况
        # 因为 fastdfs是根据文件内容 得到文件名的(不存在文件名相同 文件内容不同,因而 无法存储的问题)
        return False

    def url(self, name):
        '''返回 访问文件name 所需的url路径'''
        # django调用url方法时,所传递的 name参数:数据库 表中所存的 文件名字符串(即是,fastdfs中存储文件时 使用的文件名)
        return self.base_url + name

compare:

# 存储类必须是:deconstructible,以便在迁移中的字段上使用它时可以序列化。 
# 只要你的字段有自己的参数:serializable,
#你可以使用django.utils.deconstruct.deconstructible类装饰器(这是Django在FileSystemStorage上使用的)
@deconstructible
class FdfsStorage(Storage):
    def __init__(self, option=None):
        if not option:
            self.option = settings.CUSTOM_STORAGE_OPTIONS
        else:
            self.option = option

相关文章

  • 使用py3fdfs - 踩坑实录 __str__ return

    后台运营人员 通过django的admin页面,进行(图片)文件的上传 django使用 自定义的storage类...

  • 自定义类的内置函数unbound method

    """ 使用print打印的时候,class调用该函数""" def __str__(self): return...

  • 小甲鱼044.魔法方法:简单定制

    def __str__(self) 使用return来返回结果 当用户需要打印字符时自动调用 def __repr...

  • Vue踩坑实录(二)

    在上一篇中说了一下踩过的前三个坑,剩下的坑就在这篇中全部搞定吧。Vue踩坑实录(一) Vue-cli .js?.V...

  • 注意forEach不能使用return跳出循环

    一直混淆了概念—关于跳出循环或终止方法,认为一般循环return就可以终止,踩坑的是for Each使用retur...

  • python3 FastDFS客户端连接

    安装 py3fdfs源于fdfs-client,但在使用过程中, 和旧版略有不同.(py3fdfs官网示例有误) ...

  • fastlane 踩坑实录

    这个世界是懒人创造的。 人懒了就会发明各种各样的工具,或者寻找各种各样能够给自己偷懒机会的工具,当然我还停留在使用...

  • Mongo踩坑实录

    1.更新数据时,js脚本中没有指定数据类型,int数据被更新成了double,导致线上问题。 原因,js是弱类型,...

  • gitattribute踩坑实录

    工欲善其事,必先利其器。 前一阵子,公司的版本控制从svn迁移到了git,不得不说,git确实比svn要强大好多,...

  • Weex踩坑实录

    1.新组件无法与配合使用。目前遇到的情况主要是loading没法正常...

网友评论

      本文标题:使用py3fdfs - 踩坑实录 __str__ return

      本文链接:https://www.haomeiwen.com/subject/uphmrctx.html