中秋快樂!新鮮出爐一篇DjangoAdmin使用合集,DjangoAdmin的功能比你想像的強大!

DjangoAdmin

DjangoAdmin本身就是一套大而全的系統,官方文檔中介紹了很多配置方法,但仍然有大量的騷操作是文檔中沒有的,所以遇到特殊需求的時候,求助文檔不一定有用。

在我看來 DjangoAdmin 雖然能快速生成一套管理後台,但如果要做大量特殊需求的訂製,其成本不亞於用 Vue/React 重新開發一套,簡單的使用成本不高,但深入訂製的話需要對 DjangoAdmin 的工作流程比較熟悉,把源碼啃熟了(有些 Python 源碼沒有類型註解是很難讀懂的),才能在原有基礎上雕花,有時候還存在著後續維護的問題。

不過還是瑕不掩瑜了,誰能拒絕配置了幾行程式碼就可以用的管理後台呢?

而且還不需要你做出一套 RESTFul API 來實現前後端分離,直接一把梭,起飛~

一些參考資料

介面主題

GitHub 上有很多 DjangoAdmin 的替換主題,所以不要抱怨 DjangoAdmin 的介面丑啦,好看的主題很多!

我最先使用的是 adminx,但這個侵入性太強了,需要對 admin 的配置程式碼做大量修改,實在是不划算,可能官方也意識到這個問題,後續應該是停更了。

到了 Django 2.x 時代後面,admin 的介面主題多了起來,有個國產的 SimpleUI 很不錯,基於 Vue + ElementUI 實現的,star很多,算是比較成熟的一類,我願稱之為國產之光。

其他的我也大多有測試,但用起來總有一些兼容的問題,所以目前還是比較推薦國產之光。

SimpleUI

已經在多個產品中使用,使用 vue+elementUI(非單頁應用),支援多標籤頁

一些相關的參考資料

django-jazzmin

地址://github.com/farridav/django-jazzmin

這個是偶然發現的,對於看膩了 ElementUI 的人來說,會有眼前一亮的感覺

使用 Bootstrap+AdminLTE 重寫,效果還不錯

(就是偶爾會莫名卡死

而且細節方面做得不如 SimpleUI,比如搜索框沒有 placeholder 的提示之類的。

訂製案例

本文只記錄特殊需求的實現,對於 DjangoAdmin 的常規配置就不複製粘貼了,網上隨便一搜都有很多。

我之前已經寫過不少 DjangoAdmin 的訂製案例文章了,最近也做了不少訂製,不過我不想寫新文章來單獨記錄某個需求的實現過程了,直接在本文里記錄,同時保持本文更新~

添加自定義列

本案例基於 SimpleUI

效果圖

image

實現過程

這裡使用的是 ElementUI 的 Tag 組件,文檔://element.eleme.cn/#/zh-CN/component/tag

前面提到過 SimpleUI 不是單頁應用,是直接在網頁上使用 vue 和 elementUI,並沒有webpack環境

所以要加入 elementUI 的組件不能直接簡單的

<el-tag type="success">標籤</el-tag>

而是要用 webpack 生成出來的

<div class="el-tag el-tag--success el-tag--light">標籤</div>

ok,開始上Python程式碼

假設有個 model 叫 Invoice,中文名發票,定義如下

class Invoice(models.Model):
  invoice_type = models.CharField('發票類型')

在需要自定義的 ModelAdmin 中,增加一個方法

# 發票類型顏色
@admin.display(description='發票類別')
def invoice_type_tag(self, obj: Invoice):
  def el_tag(color_type, content):
    """
    生成 ElementUI 的 tag 組件
		:param color_type: success, info, warning, danger
		:param content:
    :return:
    """
    from django.utils.safestring import mark_safe
    type_class = '' if len(color_type) == 0 else f'el-tag--{color_type}'
    return mark_safe(f'<div class="el-tag el-tag--small {type_class} el-tag--light">{content}</div>')

  if obj.invoice_type.startswith('普通'):
    return el_tag('', obj.invoice_type)
  if obj.invoice_type.startswith('專用'):
    return el_tag('danger', obj.invoice_type)
  if obj.invoice_type.startswith('電子專票'):
    return el_tag('info', obj.invoice_type)
  if obj.invoice_type.startswith('電子普票'):
    return el_tag('warning', obj.invoice_type)

然後把這個 invoice_type_tag 加到 list_display 中即可

PS:這裡的 @admin.display() 裝飾器是Django3.2版本之後新增的,很方便,相當於以前的

invoice_type_tag.short_description = '發票類別'

PS:注意HTML程式碼需要用 mark_safe 方法包裝起來,才能正常渲染,不然會被轉義!

顯示進度條

效果圖

image

實現過程

原理同上面的添加自定義列

程式碼如下

# 進度條
@admin.display(description='進度條')
def progress_bar(self, obj):
    html = f'''
        <div role="progressbar" aria-valuenow="{obj.progress}" aria-valuemin="0" aria-valuemax="100"
            class="el-progress el-progress--line is-light el-progress--text-inside">
            <div class="el-progress-bar">
                <div class="el-progress-bar__outer" style="height: 22px;">
                    <div class="el-progress-bar__inner" style="width: {obj.progress}%;">
                        <div class="el-progress-bar__innerText">{obj.progress}%</div>
                    </div>
                </div>
            </div>
        </div>
    '''
    from django.utils.safestring import mark_safe
    return mark_safe(html)

頁面上顯示合計數額

效果圖

image

實現過程

這個功能比較麻煩,因為需要魔改 template

首先我們要知道,這個列表對應的是哪個 template,在 admin 包的 templates 目錄下面的找了半天,最終發現這個頁面是 change_list ,而且因為頁面比較複雜,被分成了好幾部分

我們只需要修改 change_list.html 這個文件就行了。

admin.py

OK,模板程式碼先不管,我們來寫Python程式碼計算總金額。

要實現將數據放在 context 里傳給 template,得重寫個 ChangeList 對象

from django.db.models import Sum
from django.contrib.admin.views.main import ChangeList

class InvoiceChangeList(ChangeList):
    def get_results(self, request):
        super(InvoiceChangeList, self).get_results(request)
        totals = self.result_list.aggregate(Sum('amount'))
        self.total_amount = totals['amount__sum']

使用 Sum 這個聚合方法,計算總金額。

通過Python語言的動態特性,加 total_amount 這個屬性添加到 ChangeList 對象中

這樣在 template 里就能通過 {{ cl.total_amount }} 的方式拿到這個屬性。

然後改一下 ModelAdmin :

class InvoiceAdmin(ImportExportModelAdmin):
  # 如果你改了 template 的名稱,這裡可以對應修改,否則默認即可
  change_list_template = 'change_list.html'
  
  # 添加這個程式碼
  def get_changelist(self, request, **kwargs):
    return InvoiceChangeList

後端部分搞定了,接下來是前端的模板部分。

template

為了在頁面上添加新元素,我們來修改 change_list.html 文件。

注意,不要直接複製這個文件來修改!原因是你修改完的 template 會覆蓋其他組件,這樣以後換 admin 主題,或者使用 import-export 這類會修改 admin 頁面的插件時無法生效,也就是所謂的兼容問題。

Django也想到了這種情況,這些 template 都是組件化的,我們寫一個擴展 template 就可以了。

在項目的 templates/admin 目錄下新建 change_list.html 文件,程式碼如下

{% extends "admin/import_export/change_list.html" %}

{% block result_list %}
    {{ block.super }}
    <div style="text-align: right; margin: 20px 5px; font-size: 20px;">
        總金額:{{ cl.total_amount }} 元
    </div>
{% endblock %}

注意:如果用了 django-import-export 插件,則要根據使用到的功能來添加 object-tools-items block。

比如你的 ModelAdmin 繼承自 ImportExportModelAdmin,那我們轉到源碼,可以看到它重寫了 template

class ImportExportMixin(ImportMixin, ExportMixin):
    """
    Import and export mixin.
    """
    #: template for change_list view
    change_list_template = 'admin/import_export/change_list_import_export.html'

class ImportExportModelAdmin(ImportExportMixin, admin.ModelAdmin):
    """
    Subclass of ModelAdmin with import/export functionality.
    """

然後再看看 admin/import_export/change_list_import_export.html 這個文件

{% extends "admin/import_export/change_list.html" %}

{% block object-tools-items %}
  {% include "admin/import_export/change_list_import_item.html" %}
  {% include "admin/import_export/change_list_export_item.html" %}
  {{ block.super }}
{% endblock %}

可以看到它在 object-tools-items 中添加了倆組件,把這一塊 block 的程式碼複製到我們的 change_list.html 中即可。

參考資料

擴展工具

這部分記錄我在逛GitHub時發現的比較有意思的擴展庫,記錄一下

Django AdminPlus

地址://github.com/jsocol/django-adminplus

可以方便的給admin增加新頁面

django-adminactions

地址://github.com/saxix/django-adminactions

可以給admin添加一系列的actions

  • Export as CSV
  • Export as Excel
  • Export as fixture
  • Export delete tree
  • Mass update records
  • Graph queryset
  • Merge records
  • Find Duplicates