django2.0 + xadmin2.0 一次性选择多张图片并存储的一个方法, 以及详情页面多图展示

写在开头: 建议使用源码安装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()

另外,这里可能有个小问题就是:我目前有图片处理的地方并不多, 所以也比较简单, 如果处理图片的模块不止两三个, 各自展示如果还有其他条件, 可能就需要做更多的逻辑判断

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章