python工業互聯網應用實戰5—Django Admin 編輯介面和操作
1.1. 編輯介面
默認任務的編輯介面,對於model屬性包含「choices」會自動顯示下來列表供選擇,「datetime」數據類型也默認提供時間選擇組件,如下圖:
注意:「auto_now_add=True」的屬性默認不會顯示在編輯介面,外鍵欄位會自動載入關聯表數據,如上圖操作員屬性。
1.1.1. 設置要顯示的模型屬性
我們可以通過設置不顯示操作員選項,程式碼如下:
fields=('TaskNum','Source','Target',Barcode','State','Priority','BeginDate','EndDate')
也可以採用exclude 屬性設置排除不打算顯示的模型屬性。
exclude =('User',)
1.1.2. 設置一行顯示多個屬性
admin欄位都是一個欄位佔一行,若想兩個欄位放在同一行顯示,設置程式碼如下:
fields=('TaskNum',('Source','Target'),'Barcode','State','Priority','BeginDate','EndDate')
1.2. 編輯欄位集合
model欄位比較多的可以採用fieldsets,該設置可以對欄位分塊,讓編輯介面看起來比較整潔和統一,程式碼如下:
fieldsets = ( ("任務", {'fields': ['TaskNum', ('Source', 'Target'), 'Barcode','Priority',]}), ("摘要", {'fields':['State','BeginDate','EndDate']}) )
1.3. 設置只讀欄位
我們使用admin編輯介面的時候,會有些欄位是不希望用戶直接編輯的,那麼通過重寫get_readonly_fields()函數來實現這一功能,程式碼如下:
def get_readonly_fields(self, request, obj=None): """ 重寫此函數,設置只讀欄位 """ readonly_fields = ('State','BeginDate','EndDate') return readonly_fields
1.4. 數據保存時自動登記操作員
我們希望操作員是由系統自動登記的,不能人為修改,通過重寫ModelAdmin的save_model方法來實現。
def save_model(self, request, obj, form, change): obj.User=request.user return super().save_model(request, obj, form, change)
1.5. 任務下達操作
現在我們增加一個「下達」操作來變更任務的狀態,把「處理成功」狀態的任務變成修改成「執行中」狀態。
# 增加自定義按鈕 actions = ['task_start_action',] def task_start_action(self, request, queryset): for obj in queryset: if obj.State==4: obj.State=5 try: obj.save() self.message_user(request, str(queryset[0].TaskNum) + " 下達成功.") except Exception: self.message_user(request, str(queryset[0].TaskNum) + " 下達失敗.") else: self.message_user(request, str(queryset[0].TaskNum) + " 下達失敗.") task_start_action.short_description = '下達所選的' + ' 任務'
到這裡,我們通過自定義按鈕實現了對model的操作,把任務的狀態從「處理完成」變更成「執行中」,本文我們將遵照「敏捷編程」中從簡的業務宗旨來推進我們的功能實現。並演示程式碼如何通過重構來保證業務的不斷迭代變更。
任務下達到「執行中」或「執行完成」後任務數據應該不允許再進行修改,需要把編輯頁面變成數據瀏覽/查看頁面。
1.6. 執行中和完成的任務狀態全部欄位只讀設置
這裡我們重構一下前面的get_readonly_fields函數,同時,通過資料庫工具把其中1條數據狀態設置成5,測試一下效果。
def get_readonly_fields(self, request, obj=None): readonly_fields = ('State','BeginDate','EndDate','User') if hasattr(obj, 'State'): if obj.State in(5,99): readonly_fields = ('TaskNum', 'Source', 'Target', 'Barcode','State','Priority','BeginDate','EndDate','User') return readonly_fields
1.7. 重構任務下達操作函數
1.6實現任務「下達」自定義按鈕功能,操作起來需要先選擇需要下達的行,然後再選擇「下達所選的 任務」,最後點擊執行操起起來過於繁瑣,多行選擇操作還好,單行選擇操作就極度不符合用戶的使用習慣。
通過重構任務下達函數,支援列錶行操作「下達」功能,這樣對於單行操作來說用戶通過直接點擊行的「操作」列下的「下達」鏈接即可以直接對數據行進行「下達」操作,提供人機介面的易用性。
1.7.1. 增加列操作功能
首先,我們增加列表操作功能列和行下達操作鏈接,程式碼和實現效果如下:
#Task模型的管理器 class TaskAdmin(admin.ModelAdmin): ... def task_operate(self,obj): dest = 'taskStart/{}'.format(obj.pk) title = '下達' return format_html('<a href="{}">{}</a>'.format(dest, title)) task_operate.short_description = '操作'
1.7.2. 重構task_start_action函數,增加業務功能函數task_start()
我們task_start()函數把model的業務邏輯程式碼進行封裝,通過這個函數的重用來保證,無論是通過action按鈕還是功能列下達的業務邏輯是一致的。
#Task模型的管理器 class TaskAdmin(admin.ModelAdmin): ... # 增加自定義按鈕 actions = ['task_start_action',] def task_start_action(self, request, queryset): for obj in queryset: result=self.task_start(obj) if result: self.message_user(request, str(obj.TaskNum) + " 下達成功.") else: self.message_user(request, str(obj.TaskNum) + " 下達失敗.") task_start_action.short_description = '下達所選的' + ' 任務' def task_start(self,obj): success=False if obj.State==4: obj.State=5 try: obj.save() success = True except Exception: success = False return success
程式碼重構要點之一就是先確保滿足原有業務功能的前提下,調整程式碼的結構,上面的程式碼增加的task_start並沒有改變原先的業務邏輯,所以通過原來的action仍然能夠正常下達選中的任務。
#Task模型的管理器 class TaskAdmin(admin.ModelAdmin): ... def task_operate(self,obj): url = '{}/taskStart/'.format(obj.pk) oprName = '下達' return format_html('<a href="{}">{}</a>'.format(url, oprName)) task_operate.short_description = '操作'
接下來我們先增加操作列的下達鏈接操作,刷新列表頁我們就能看到操作按鈕鏈接了。然後,我們依據修改的url規則來構建taskStart url 「/admin/Task/task/1/taskStart /」用來響應點擊下達鏈接響應事件,程式碼如下:
#Task模型的管理器 class TaskAdmin(admin.ModelAdmin): ... def task_operate(self,obj): url = '{}/taskStart/'.format(obj.pk) oprName = '下達' return format_html('<a href="{}">{}</a>'.format(url, oprName)) task_operate.short_description = '操作' #task_operate.allow_tags = True def get_urls(self): """添加一個url,指向任務下達功能的函數taskStart()""" from django.conf.urls import url urls = [ re_path('(?P<pk>\d+)/taskStart/', self.admin_site.admin_view(self.task_start_view), name='task_start_view'), ] return urls + super(TaskAdmin, self).get_urls() def task_start_view(self, request, *args, **kwargs): obj = get_object_or_404(Task, pk=kwargs['pk']) self.task_start(obj) #數據更新成功後,重新刷新列表介面 co_path = request.path.split('/') new_path=co_path[0:4] new_path='/'.join(new_path) return redirect(new_path)
現在我們在列表上點擊「下達」鏈接,測試下達操作效果,效果如下圖:
1.8. 模擬異常初窺事務
本章節我們演示了通過「下達」事件修改對應model的狀態值,從而實現「任務」業務從一個狀態到另一個狀態的轉換,現在我們通過一個模擬異常來演示也是事務的完整性問題,在一個業務操作里相關的業務數據變化要保持一致性。當出現異常需要回滾時,必須回滾所有涉及的對象(表)數據。
#Task模型的管理器 class TaskAdmin(admin.ModelAdmin): ... def task_start_view(self, request, *args, **kwargs): obj = get_object_or_404(Task, pk=kwargs['pk']) self.task_start(obj) raise Exception('模擬拋出異常!') #重新刷新列表介面 co_path = request.path.split('/') new_path=co_path[0:4] new_path='/'.join(new_path) request.path = new_path return redirect(new_path)
上面的程式碼我們模擬調用self.task_start(obj)後,模擬出現異常,出現異常後應該需要回滾obj的狀態,從而確保事務的一致性,尤其操作涉及到多個對象時,避免出現事務不一致的情況。由於現在程式碼沒有事務約束機制,所以異常拋出後我們會發現obj的狀態還是變更成「執行中」了。
現在增加事務約束來保證事務的一致性,再測試一遍發現變更的狀態回滾回去了。
#Task模型的管理器 class TaskAdmin(admin.ModelAdmin): ... from django.db.transaction import atomic @atomic def task_start_view(self, request, *args, **kwargs): obj = get_object_or_404(Task, pk=kwargs['pk']) self.task_start(obj) raise Exception('模擬拋出異常!') #重新刷新列表介面 co_path = request.path.split('/') new_path=co_path[0:4] new_path='/'.join(new_path) request.path = new_path return redirect(new_path)
1.9. 小結
本章節我們主要介紹了admin後台管理的編輯介面設置,通過兩個章節完成了admin的初步介紹和設置,後面我們會根據內容的穿插admin的其它一些配置項。本章我們還簡要的模擬演示了業務事務的一致性問題,當某一個業務操作過程中出現異常時,需要回滾當前的所有操作,事務的部分完成在企業的開發中是不能被允許的!
通過django admin我們快速的構建了一個任務的管理系統原型,下一章節我們將進一步增加功能講述如何分解任務到作業(子任務),並通過程式碼重構改進程式碼的組織結構。