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()

另外,這裏可能有個小問題就是:我目前有圖片處理的地方並不多, 所以也比較簡單, 如果處理圖片的模塊不止兩三個, 各自展示如果還有其他條件, 可能就需要做更多的邏輯判斷

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