django上传图片 和 用户获得html页面后请求图片 流程
admin
页面,进行(图片)文件的上传storage
类,把文件上传到fastdfs
fastdfs
根据文件内容,得到文件名(/group1/M00...)
。并 返回文件名(/group1/M00...)
给django。文件名(/group1/M00...)
存储到数据库表的某个字段内。url地址
向 远端的nginx(nginx
和fastdfs的storage服务器
是部署在一起的) 请求资源文件。fastdfs的优点:
上传文件时,文件名相同 而文件内容不同
带来的问题。因为fastdfs是根据文件内容 生成 文件名的。在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