写在开头: 建议使用源码安装xadmin, 这样可以根据自己实际需求做更灵活的处理
在xadmin
中图片选择按钮默认是只能单选
,如图:
并不能一次性选择多张图片并存储, 这里只需要修改一点xadmin源代码
, 就可以实现一次性选择多张图片并存储.
在xadmin源码目录xadmin/plugins/images.py
里找到第38行AdminImageWidget
这个类:
# xadmin/plugins/images.py 第38行
class AdminImageWidget(forms.FileInput):
"""
A ImageField Widget that shows its current value if it has one.
"""
def __init__(self, attrs={}):
super(AdminImageWidget, self).__init__(attrs)
def render(self, name, value, attrs=None):
output = []
if value and hasattr(value, "url"):
label = self.attrs.get('label', name)
output.append('<a href="%s" target="_blank" title="%s" data-gallery="gallery"><img src="%s" class="field_img"/></a><br/>%s ' %
(value.url, label, value.url, _('Change:')))
output.append(super(AdminImageWidget, self).render(name, value, attrs))
return mark_safe(u''.join(output))
AdminImageWidget
的注释就说明了它就是xadmin处理ImageField的插件,如果有图片, 就显示当前图片,下面的方法render
返回的就包含前面支付截图选项(xadmin是在这里根据加载的数据生成前端页面所需要的a标签并返回-----xadmin管理页面有很多都是这中处理方式), 而选择图片也只需要在return mark_safe
前面加上几行代码就可以把单选改为多选:
class AdminImageWidget(forms.FileInput):
"""
...
"""
def render(self, name, value, attrs=None):
output = []
if value and hasattr(value, "url"):
label = self.attrs.get('label', name)
output.append('<a href="%s" target="_blank" title="%s" data-gallery="gallery"><img src="%s" class="field_img"/></a><br/>%s ' %
(value.url, label, value.url, _('Change:')))
output.append(super(AdminImageWidget, self).render(name, value, attrs))
"""
因为前端页面中选择按钮变多选,需要在对应标签里添加上multiple="multiple"
而这里的output就是xadmin后端生成的html所需要的标签的文本流
所以我们只需要在output的对应位置用代码添加上这一条件
"""
if self.attrs['label'] and self.attrs['label'] == '访问截图':
if len(output) > 1:
output[-1] = output[-1][:-2] + ' multiple="multiple">'
else:
output[0] = output[0][:-2] + ' multiple="multiple">'
return mark_safe(u''.join(output))
这里我自己的逻辑是只给访问截图添加多张图片,支付截图没有这个需求,所以对标签做了筛选, 支付截图保持单选, 其中 self.attrs['label'] == '访问截图'
中的访问截图
, 就是我们页面中对应选项的文字描述:
保存刷新, 此时测试页面点击选择文件, 在选择的时候按住ctrl
就能够同时选择多张图片了:
现在能够选择多张图片, 但是xadmin在存储的时候还是只会存储一张, 需要在自己的admin.py
文件里面的插件下添加方法save_models
来处理自己的存储逻辑, 我这里处理的很简单, visit_img是多张图, 另建了一张表来存路径+图片名, 另外由于没有巨量图片的存储需求, 也没有用到云存储, 直接在服务器上把图片写入一个文件夹:
# admin.py
import xadmin
from .models import TestBasci, VisitImg
class TestBasicView(object):
list_display = ['name', 'url', 'record_num', 'record_company', 'ip', 'addr', 'client_type', 'pay_type', 'order', 'image_data', 'vi_img_data', ]
"""
...
"""
def save_models(self):
obj = self.new_obj
files = self.request.FILES.getlist('visit_image')
if files:
for f in files:
fname = ''
if VisitImg.objects.filter(image='visit_imgs/%s' % f.name).first():
fname = str(datetime.timestamp(datetime.now())).split('.')[0] + '_' + f.name
else:
# 如果图片名不存在,则使用当前图片名
fname = f.name
with open('%s/visit_imgs/%s' % (settings.MEDIA_ROOT, fname), 'wb+') as pic:
for chunk in f.chunks():
pic.write(chunk)
# 图片名信息写入数据库
# 这里数据库多加了一个字段 name, 是为了下面详情页多图展示里把相同归属的访问图拿到
img_obj = VisitImg(name=self.new_obj.name,
image='visit_imgs/%s' % fname,)
img_obj.save()
obj.save()
# 注册插件
xadmin.site.register(TestBasic, TestBasicView)
再保存刷新一次, 选择多张图片后就可以在后台数据库中看到成功保存所选图片
之前忽略了添加多图后详情页面展示的问题,这里补充一下
还是要回到xadmin源码目录xadmin/plugins/images.py
里的AdminImageWidget
;
其实这个跟前面修改选择按钮单选
为多选
差不多的, 只是需要自己去分析一下这里output
的输出格式(如果对html5
有一点了解的话,就比较容易知道应该怎么去修改output
的输出结果了);
详情页面多图展示的代码, 只需要根据自己的逻辑, 添加到render
里多选框之前就可以了:
# xadmin/plugins/images.py
from BasicInfo.models import VisitImg
class AdminImageWidget(forms.FileInput):
"""
A ImageField Widget that shows its current value if it has one.
"""
def __init__(self, attrs={}):
super(AdminImageWidget, self).__init__(attrs)
def render(self, name, value, attrs=None, renderer=None):
output = []
if value and hasattr(value, "url"):
label = self.attrs.get('label', name)
# fixme code: 访问截图多图展示
if label == '访问截图':
# 具体要怎么展示, 根据自己的逻辑, 按照 output 的格式适当修改就可以了
# 导航栏-访问截图 详情页面 只显示单张
if value.name.split('/')[0] == 'visit_imgs':
output.append(
'<a href="%s" target="_blank" title="%s" data-gallery="gallery"><img src="%s" class="field_img"/></a><br/>%s ' %
(value.url, label, value.url, _('Change:')))
else:
# 导航栏-基础信息 详情页面-访问截图 多图展示
# 根据上面提到的存储访问图时增加的 name 字段,找出相同归属的访问图
img_names = VisitImg.objects.filter(name=value.instance.name).values('image')
end = ''
if img_names:
for img in img_names:
# 这个判断可要可不要, 我自己给最后一张图片后边加上了 '添加' 两个字
if img['image'][11:] == value.name[10:]:
# end = '<a href="/media/%s" target="_blank" title="%s" data-gallery="gallery"><img src="/media/%s" class="field_img"/></a><br/>%s ' %\
# (jsq["image"], label, jsq["image"], _('Change:'))
end = '<a href="%s" target="_blank" title="%s" data-gallery="gallery"><img src="%s" class="field_img"/></a>' \
'<br/>%s ' %\
(value.url, label, value.url, '添加')
else:
# output.append('<a href="%s" target="_blank" title="%s" data-gallery="gallery"><img src="%s" class="field_img"/></a><br/>%s ' %
# (value.url, label, value.url, _('Change:')))
output.append('<a href="/media/%s" target="_blank" title="访问截图" data-gallery="gallery"> '
'<img src="/media/%s" class="field_img">'
'</a>' % (img["image"], img["image"]))
output.append(end)
else:
# 其他图片单张显示(目前只有 基础信息-支付截图)
output.append('<a href="%s" target="_blank" title="%s" data-gallery="gallery"><img src="%s" class="field_img"/></a><br/>%s ' %
(value.url, label, value.url, _('Change:')))
output.append(super(AdminImageWidget, self).render(name, value, attrs, renderer))
# fixme code: 访问截图图片选择框改为多选
if self.attrs['label'] and self.attrs['label'] == '访问截图':
if len(output) > 1:
output[-1] = output[-1][:-2] + ' multiple="multiple">'
else:
output[0] = output[0][:-2] + ' multiple="multiple">'
return mark_safe(u''.join(output))
我的导航栏里的基础信息
和访问截图
,在上面的注释里有说到,只给基础信息
的详情页面里做做图展示:
我自己纯属无聊,画蛇添足弄的添加
两个字:
而访问截图
自己本身的详情页面, 就仍然只显示一张图片:
还有一点之前忘记了,这里一并添加上
既然做了多图存储,我这种直接把图片写入服务器的操作,要记得自定义 delete_model
在做删除操作的时候,把相关的图片信息都要删掉,否则它们就永远存在服务器上了
当然,如果不想在做删除操作的时候直接删除对应图片,而是有其他处理方式,也是可以的
# admin.py
import xadmin
from .models import TestBasci, VisitImg
class TestBasicView(object):
"""
...
"""
def delete_model(self):
# 删除 TestBasicView 自己本身的图片
delete_image(self.obj.image.name)
delete_image(self.obj.image.thumbnail.name)
delete_image(self.obj.visit_image.name)
delete_image(self.obj.visit_image.thumbnail.name)
# 与 TestBasicView 相关联的 VisitImg 也跟着删除
visit_images = VisitImg.objects.filter(name=self.obj.name)
if visit_images:
for img in visit_images:
# 删除服务器上对应的图片文件
delete_image(img.image.name)
# 删除数据库记录
VisitImg.objects.filter(name=self.obj.name).delete()
self.obj.delete()
另外,这里可能有个小问题就是:我目前有图片处理的地方并不多, 所以也比较简单, 如果处理图片的模块不止两三个, 各自展示如果还有其他条件, 可能就需要做更多的逻辑判断