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. 數據保存時自動登記操作員

  我們希望操作員是由系統自動登記的,不能人為修改,通過重寫ModelAdminsave_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我們快速的構建了一個任務的管理系統原型,下一章節我們將進一步增加功能講述如何分解任務到作業(子任務),並通過程式碼重構改進程式碼的組織結構。