js制定一个单选按钮_【下】每个月整理发票太头疼?手把手教你快速开发一个工具解决!...
"NightTeam",一個值得加星標的公眾號。
在上篇中,我們已經將我們的發票管理工具開發到了能一鍵導入發票、能看到效果、能仍然不太方便地管理的狀態,接下來我們來繼續將還沒有加進去的那些方便的功能給加上,以將管理發票的方便性提升到一個更高的程度。
先回顧一下上篇的最終狀態:
現在我們再來給這個頁面加上兩個非常重要的功能,一個是設定發票的分類,一個是勾選發票后自動計算已選發票的總金額,這能大幅提高我們處理發票時的效率。
設定發票分類的話,Django Admin有個功能叫action,可以用于在admin頁面上添加一個“快捷動作”,我們可以用它來實現。用法很簡單,只需要寫一個符合Django參數要求的函數并將它扔進一個列表中,再把這個列表設置給admin類的actions配置項即可。
這個有個小問題,action沒有辦法便捷地處理需要攜帶參數的情況,官方給出的解決方案是單獨寫一個中間頁,在中間頁中進行處理,但我們這種參數可選項不多的情況,如果單獨寫一個中間頁的話好像有點麻煩?而如果復制粘貼直接寫一堆函數的話好像又太長了,改起來麻煩、代碼也很冗余?
沒關系,我們可以用個騷操作來比較方便地解決,畢竟這只是個用來管理發票的工具而已,稍微用用騷操作也不是什么大問題。Python作為一個動態語言,可以很輕松地做到動態創建函數這種騷操作,只需要像這樣就可以在代碼運行時動態地創建一個函數了:
func = FunctionType(compile( (f"def replace_category_to_{category}(modeladmin, request, queryset):\n" f" queryset.update(category='{category}')\n" f" return "), "", "exec").co_consts[0], globals())然后我們就可以基于這個騷操作,把actions自動地創建出來,像這樣:
注:這里面的short_description是用來設置它在頁面上顯示的名稱的,如果沒有設置的話就會是函數名。
現在我們再刷新頁面,就能得到這樣的幾個按鈕了:
注:按鈕是simpleui庫帶來的效果,原始的Django Admin會展示成一個下拉框和一個執行按鈕。
接著是勾選發票后自動計算已選發票的總金額,這個要做的話也很簡單,我們只需要用JS直接監聽復選框勾選后對頁面DOM的修改即可。
打開瀏覽器開發者工具定位到復選框附近的元素并操作幾下復選框就會發現:全選時id為action-toggle的那個元素會被修改、單選時class為action-select的那個元素會被修改。所以我們可以基于這兩個變化,寫一段這樣的JS代碼:
$("#action-toggle,input[class='action-select']").bind("change", function (e){ let sum_price = 0; $("#result_list > tbody > tr[class*='selected'] > td[class='field-price']").each(function(){ sum_price += parseFloat($(this).text()); }) console.log(sum_price)})這段代碼用了一個叫jQuery的庫,在前端領域里很常見,代碼的意思是先對那兩個元素設置個監聽器,一旦change事件發生時就運行后面那個函數里的代碼;后面那個函數里的代碼其實就是取出每一個被選中項價格部分的值,轉成float之后往sum_price變量里加。
我們可以在瀏覽器開發者工具的Console中進行測試,結果大致如下,每次點擊時都會自動把當前選中的發票計算個總和:
那么我們需要怎么把這段JS代碼放進頁面里呢?只需要用Django的模板功能就好了。
注:由于我們用了simpleui庫,所以有些被simpleui修改過的頁面需要以simpleui的頁面為基準進行操作,具體請參考simpleui的文檔和對應的模板文件源碼。
我們直接從admin.py導入django.contrib.admin的地方按鼠標中鍵點進去,然后在Pycharm上方的路徑欄中找到admin目錄下的templates/admin目錄,在這里我們能看到一堆的.html文件。
接著,我們在瀏覽器中通過瀏覽器開發者工具隨便選中一個列表中的發票的任意一個DOM元素,并挑一個特征出來(比如前面提到的ID),再用Pycharm的“在文件中查找”功能搜索,就可以找到頁面中對應的那部分HTML了。
當然,也可以直接在Django的文檔中查看admin的模板部分,里面也有說明每個.html文件分別對應哪個部分。
我這里是想把合計的金額展示在按鈕那一排,所以最終找到的是actions.html這個文件。
找到對應的模板文件后,我們在manager目錄下新建一個templates目錄,然后再在這里面新建一個admin目錄,最后再在admin目錄里面創建一個manager目錄,并新建與前面那個模板文件同名的文件。這么做的目的是為了限定這個修改的適用范圍,避免后續我們需要弄別的admin頁面或將這個manager應用集成到其他Django項目中時,把修改也帶到其他admin頁面上。
然后我們可以通過Django的模板語法中的include標簽來引用原始的actions.html,接著我們在后面寫的HTML就會直接在加載頁面時被添加到原始模板文件的最后面了。
現在需要的就是寫HTML了,我們加個span標簽,給它設置個ID和喜歡的樣式;
再加個script標簽,把前面寫好的JS代碼放進去,并添加上一行用于修改那個span的text值的代碼(比如$("#sum_price_val").text("共計:" + sum_price + "元"));
噢對了,由于這個模板文件的所處位置實際上是在列表頁的開頭部分,所以其中的JS代碼會在列表中的內容加載完成前就執行,所以我們還需要讓這段代碼能在頁面完全加載后才執行,這里可以使用jQuery庫的$(document).ready來實現。
最后我們這個actions.html的內容差不多長這樣:
然后就可以得到這樣的效果:
再回顧一下上篇開頭定下的需求列表,功能其實已經完成得差不多了,發票自動識別、分類、總額計算、去重都已經完成,現在剩下的主要功能就是批量打印和導出發票了。
批量打印的話需要先對PDF進行合并,將多個發票PDF合并到一張,這樣我們就可以只調用一次打印機但打印多張發票了。
寫起來也很簡單,直接用一個名為PyPDF4的庫來實現即可,使用它只需要像這樣就能快速實現合并一個目錄下所有發票的效果:
from pathlib import Pathimport PyPDF4 as pdfinvoices_path = Path("./invoices")merger = pdf.merger.PdfFileMerger()for path in invoices_path.iterdir(): if path.suffix != ".pdf": continue merger.append(path.open("rb"))merger.write("output.pdf")導出發票的話就直接用Python自帶的zipfile就好了,也很簡單,這里就不演示了。
那么這兩個功能在admin頁面里應該如何實現呢?還是一樣,添加一個action即可,只不過這里我們需要讓action被觸發時先跳到一個中間頁面,然后中間頁面中通過JS來請求兩個接口分別做到返回合并后的PDF文件以及返回打包好的壓縮包,接著再自己跳回原頁面。
具體實現起來就像這樣:
首先,我們先弄這個中間頁面,這個中間頁面我們可以用到Django的模板渲染功能,這樣我們就能方便地將被選中的發票ID傳給JS,讓JS再來進行后續的步驟。
和前面弄分類設置一樣,在manager/templates目錄下直接新建一個HTML文件,這里我新建的文件名為use_invoices.html:
在模板文件中,我們可以通過{{ invoice_ids }}這樣的語法來表示這里需要填充進一個值作為模板中的文本,效果類似于Python的format;還可以通過{% url "manager:merge_invoices" %}這樣的語法來表示這里需要填充進這個Django項目下manager應用中名為merge_invoices的View(視圖/頁面)的URL。
然后我們可以通過用JS的window.open來打開一個新頁面(標簽)、window.location.href來設置當前頁(標簽)的URL并刷新、document.referrer來取到上一頁的URL。
所以最終這個模板文件的代碼會像這樣:
這樣我們就可以在action函數中調用模板渲染器渲染這個模板頁面,并傳入選中的發票ID以將發票ID傳給JS了。
JS中會把ID賦值給params參數,并做個URL中請求參數部分的拼接,接著分別打開兩個頁面,再將當前頁跳回上一頁并刷新。這樣我們就可以做到觸發action之后自動打開兩個新頁面并刷新發票列表頁的效果了。
然后我們回到admin.py,導入render函數from django.shortcuts import render用來渲染模板。接著再和前面一樣,寫一個函數用來做action具體的事務。
action函數的queryset參數是可以用來取到被選中對象以及其具體屬性的,只需要調用values_list方法并傳入屬性名就可以直接取到屬性列表了,這里我們像這樣取出所有被選中的發票的id:queryset.values_list('id', flat=True)。
注:flat參數表示返回的結果是單個值,而不是一個元組,設為True之后我們處理起id這種每個對象只有一個值的屬性時就會方便些。
所以最終這個action函數會像這樣 :
先取到被選中發票對象的ID列表,然后再用Django的ORM一一取到它們的對象實例,然后將used修改為True以標識已使用,并調用save函數保存到數據庫中;接著通過str.join來拼接成一個1,2,3這樣的字符串,再傳給render函數作為invoice_ids參數對前面寫好的模板文件進行渲染。
注:還是和之前的action一樣,可以通過use_invoices.short_description = "使用這些發票"這樣的操作來為這個action命名,在網頁上看起來會舒服一些。
寫好action函數后記得將它加到Admin類下的actions列表中,接下來我們需要寫一下前面在JS中定下的那兩個接口的View。
View是Django中的視圖的概念,它能接收HTTP請求并且將return的內容作為響應返回,只需要創建一個HttpResponse(或其他Response,具體參考Django文檔)實例填好對應的參數并return就可以了,搞不明白的話可以簡單理解為一個View就是一個頁面、返回的Response就是頁面的內容。
打開manager/views.py定義兩個對應名字的函數,并給它們加入名為request的參數,就創建好了兩個View。
在View中,我們可以通過request.GET["ids"]來取到URL中名為ids的參數,然后接下來就是對這些發票進行處理了。
先寫合并發票PDF的View,根據前面所說的方法取到id后調用PyPDF4庫進行處理即可,差不多像這樣:
這里我把之前寫的那個導入發票的腳本中用到的INVOICES_PATH移到了invoice_manager/settings.py中,以避免無意義的重復代碼;
然后用了io.BytesIO創建了一個虛擬的IO對象,這個IO對象就類似于open(file)后得到的那個一樣,只不過它的內容是存在內存中的,這樣可以避免不必要的磁盤讀寫;
接著就是取了id之后用str.split轉成了列表,并拼接出對應的發票文件路徑,再調用PyPDF4庫進行合并;
最后,創建一個HttpResponse,把IO對象中的內容(bytes)放進去,并設置響應頭中的content_type為PDF文件對應的那個,這樣可以讓瀏覽器打開后直接顯示出一個預覽頁面,而不是作為普通的文件被自動下載。
接著寫打包發票的View,和前面這個差不多,只不過是換成了用zipfile庫打包,并且把content_type改成了ZIP文件對應的,然后設置了一下返回的文件名以便瀏覽器在自動下載、保存時能保存成正確的名字。代碼差不多像這樣:
那么現在我們兩個接口的View就都寫好了,只需要再做兩個簡單的配置就可以讓它跑起來看效果了。
我們在manager目錄下新建一個名為url.py的文件,然后在里面像這樣配置一下urlpatterns:
上面導入的東西就不用多說了吧?大同小異,直接看下面給path函數提供的參數。
第一個參數的意思是這個View在URL中的路徑,這里寫的是manager應用下的相對路徑,Django會按照層級,依次將每個應用中定義的path一級一級拼接下去;
第二個參數就是這個View的函數了,沒什么好說的;
第三個參數是這個View的名字,這個需要與前面在模板文件中使用的那個名字對應上,否則Django會因為找不到對應的View而報錯。
接著打開invoice_manager/url.py,在urlpatterns里加上引用manager應用的URL配置,像這樣:
注:這里的第一個參數也是相對路徑,如果這是最上一級的路徑的話,那么最后拼接出來的就是像manager/merge_invoices這樣的路徑;而如果上面還有層級的話,最后拼接出來的就會是balabala/.../manager/merge_invoices這樣的。
現在可以跑起來看一下了,如果不出意外的話你會看到這樣的效果:
點擊“使用這些發票”按鈕后,會瞬間打開兩個頁面,一個開始了自動下載,另一個則是發票PDF的預覽:
在點擊預覽頁右上角的打印機按鈕后,就會彈出打印設置了,直接選擇打印機打印,就可以一次性打出多張發票了。
而那個壓縮包,打開后也可以看到,里面是整整齊齊的發票PDF文件,都是按照指定規范命名好的,直接整個壓縮包扔給財務就好了,很方便。
那么最后剩下的抬頭和稅號檢測和自動排除已使用的發票這兩個功能,我就不再廢話了。畢竟前者直接在導入發票的腳本中判斷一下即可,后者則是在選擇發票時直接通過Django Admin的篩選功能來篩選未使用發票就可以了,非常簡單。如果你不會做的話,可以直接參考我放在GitHub上的源碼。
發送消息「發票管理工具」到公眾號「NightTeam」即可獲取項目開源地址,歡迎提出自己的需求或自行開發自己所需的功能并合并進來,讓我們一起將這個工具變得更方便吧~
總結
以上是生活随笔為你收集整理的js制定一个单选按钮_【下】每个月整理发票太头疼?手把手教你快速开发一个工具解决!...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Oracle中表连接的方式有哪些
- 下一篇: 云服务器的发展历程,盘点微软Azure云