根據需求定義“任務”是一個完整的業務搬運流程,整個流程涉及到多個機構(設備)分別動作執行多個步驟,所以依據前面的模型設計,需要把任務分解到多個連續的子任務(作業),未來通過順序串聯下達執行的方式來分步驟的完成任務的執行。
1.1. 倉庫規划
同樣,依據需求我們先來做幾個倉庫的規划設計,101位置是AGV的入庫起始站台,102是提升機門口的入庫工位,104是提升機1樓轎廂工位,同理504是提升機在5樓的轎廂工位。左邊是5樓的貨位區編碼規則,完整好的貨位號加上樓層編碼05-01-01表示五樓的01-01貨位,如下圖:
有了圖上的基本倉庫規划設計,我們才能把一個任務依據設計進行分解,例如:任務編碼100的搬運任務是把貨物從101 入庫工位搬運到05-01-01貨位存放。
設計約定:
① 102、502為進提升機工位,103、503為出提升機工位,出和入的工位分開來解決任務沖突的問題(當然也可以先規划一個工位,隨着程序迭代改進)。
② 提升機控制邏輯與電梯類似,到達指定樓層后會自動開門。
③ 電梯只有關上廊門才能提升/下降。
這樣根據約定(前置條件)現在我們針對這個任務逐步分解成以下子任務(作業)如下表,對照早期的需求會發現有增加作業,實際項目中也是如此,早期的需求定義到實際編碼的時候,需要根據實際的設備接口協議,規定等調整早期的需求定義。
序號 |
作業描述 |
執行設備 |
1 |
調度AGV從101站台搬運托盤到102站台 |
1樓AGV |
2 |
調度提升機到1樓並打開門 |
提升機 |
3 |
調度AGV從102工位搬運到104工位並卸貨 |
1樓AGV |
4 |
調度空AGV從104工位到103工位 |
1樓AGV |
5 |
調度提升機關門 |
提升機 |
6 |
調度提升機1樓提升到5樓並開門 |
提升機 |
7 |
調度空AGV到504工位並載貨 |
5樓AGV |
8 |
調度AGV從504工位到503工位 |
5樓AGV |
9 |
調度提升機關門 |
提升機 |
10 |
調度AGV從503工位搬運到05-01-01貨位並卸貨 |
5樓AGV |
上面的分解比需求表增加了兩個子任務“調度空AGV從104工位到102工位”和“調度AGV從504工位到502工位”,是與實際的AGV設備對接通信協議后增加的,AGV小車的控制系統不能識別“調度AGV從提升機1樓門口工位到提升機並卸貨,返回提升機門口工位”需求里“返回”提升機門口工位。依據設備的接口邏輯,只能把這個步驟再分解成兩個步驟來執行。
上述分解提供一個簡化分解的模型和思路,實際項目可能更為復雜或簡單。
1.2. 分解代碼實現
我們在后台任務管理里添加任務編碼100、源地址101、目標地址05-01-01的任務,如下圖:
同時admin.py里增加新增任務默認狀態設置為未處理的代碼,我們通過pk值是否為None來判斷model是否是新增還是修改。
#Task模型的管理器 class TaskAdmin(admin.ModelAdmin): ... def save_model(self, request, obj, form, change): #新增任務默認狀態設置為 未處理 if obj.pk==None: obj.State=1 obj.User=request.user return super().save_model(request, obj, form, change)
接下來,我們演示如何依據上面的分解邏輯用代碼來實現把任務分解成子任務並保存到Job對應的表里,同時,把任務的狀態從“未處理”更新到“處理完成”狀態。這里需要注意的就是任務的分解和狀態變更必須在一個事務里完成,系統不能存在任務狀態已經變更到處理完成,但是沒有對應的作業,也不能存在已有對應的作業任務狀態仍然是未處理狀態的情形,否則就會造成系統業務上的混亂,前面章節提到的事務應用就非常重要!
#Task模型的管理器 class TaskAdmin(admin.ModelAdmin): ... @atomic def task_decompose_action(self, request, queryset): for obj in queryset: #只處理狀態等於未處理的任務 if obj.State==1: result=self.task_decompose(request,obj) if result: self.message_user(request, str(obj.TaskNum) + " 處理成功.") else: self.message_user(request, str(obj.TaskNum) + " 處理成功.") task_decompose_action.short_description = '處理所選的' + ' 任務' def task_decompose(self,request,obj): success=True try: job1=Job(**{"Task":obj,"TaskNum":obj.TaskNum,"OrderNo":1,"Source":obj.Source,"Target":'102',"Executor":"AGV01","State":1,\ "Priority":obj.Priority,"Barcode":obj.Barcode,"User":request.user,}) job1.save() job2=Job(**{"Task":obj,"TaskNum":obj.TaskNum,"OrderNo":2,"Source":None,"Target":'1',"Executor":"ELEVATOR","State":1,\ "Priority":obj.Priority,"Barcode":obj.Barcode,"User":request.user,}) job2.save() job3=Job(**{"Task":obj,"TaskNum":obj.TaskNum,"OrderNo":3,"Source":"102","Target":'104',"Executor":"AGV01","State":1,\ "Priority":obj.Priority,"Barcode":obj.Barcode,"User":request.user,}) job3.save() job4=Job(**{"Task":obj,"TaskNum":obj.TaskNum,"OrderNo":4,"Source":'104',"Target":'103',"Executor":"AGV01","State":1,\ "Priority":obj.Priority,"Barcode":obj.Barcode,"User":request.user,}) job4.save() job5=Job(**{"Task":obj,"TaskNum":obj.TaskNum,"OrderNo":5,"Source":'1',"Target":'5',"Executor":"ELEVATOR","State":1,\ "Priority":obj.Priority,"Barcode":obj.Barcode,"User":request.user,}) job5.save() job6=Job(**{"Task":obj,"TaskNum":obj.TaskNum,"OrderNo":6,"Source":'504',"Target":'503',"Executor":"AGV05","State":1,\ "Priority":obj.Priority,"Barcode":obj.Barcode,"User":request.user,}) job6.save() job7=Job(**{"Task":obj,"TaskNum":obj.TaskNum,"OrderNo":7,"Source":'5',"Target":None,"Executor":"ELEVATOR","State":1,\ "Priority":obj.Priority,"Barcode":obj.Barcode,"User":request.user,}) job7.save() job8=Job(**{"Task":obj,"TaskNum":obj.TaskNum,"OrderNo":8,"Source":'503',"Target":'05-01-01',"Executor":"AGV05","State":1,\ "Priority":obj.Priority,"Barcode":obj.Barcode,"User":request.user,}) job8.save() #子任務分解完成,並提交數據庫 #更新任務的狀態到“處理完成” obj.State=5 obj.save() except Exception: success = False return success
執行效果如下圖:
1.3. 列表增加“作業數量”
任務分解完成后,列表除了顯示狀態變化之外,能夠查看作業的分解數量,為此在Task Admin里增加job_count函數用來顯示作業數量。
#Task模型的管理器 class TaskAdmin(admin.ModelAdmin): #listdisplay設置要顯示在列表中的字段(TaskId字段是Django模型的主鍵) list_display = ('TaskId','TaskNum', 'Source', 'Target', 'Barcode','State','PriorityColor','BeginDate','EndDate','job_count','task_operate',) ... def job_count(self, obj): return obj.job_set.count() job_count.short_description = '作業數量'
1.4. 編輯頁面查看作業數據
Job模型外鍵管理Task模型,我就可以采用TabularInline的方式來顯示任務的作業信息,通過編輯界面直接查看對應任務的Job列表。
class JobInline(admin.TabularInline): model = Job fields = ('TaskNum','OrderNo','Source', 'Target','Executor','State','BeginDate','EndDate',) extra = 0 #默認所有條目 # 只讀的字段 readonly_fields = ('TaskNum','OrderNo','Source', 'Target','Executor','State','BeginDate','EndDate',) def has_delete_permission(self, request, obj=None): return False #不允許刪除 def has_add_permission(self, request, obj=None): return False #不允許添加 #Task模型的管理器 class TaskAdmin(admin.ModelAdmin): #listdisplay設置要顯示在列表中的字段(TaskId字段是Django模型的主鍵) list_display = ('TaskId','TaskNum', 'Source', 'Target', 'Barcode','State','PriorityColor','BeginDate','EndDate','job_count','task_operate',) inlines=[JobInline] fieldsets = (("任務", {'fields': ['TaskNum', ('Source', 'Target'), 'Barcode','Priority','State','BeginDate','EndDate','User']}),)
1.5. 魔法數(mogic number)
任務的狀態賦值和變更我們采用了直接obj.State=1或obj.State=5的直接賦值數字的方式,這個就是程序的mogic number它們有特殊的含義,編程人員得知道這個數字對應的含義,當狀態變多時間拉長,編程人員就得去回憶或來回查看數字對應的狀態。好的編程習慣就是用常量來代替魔法數字。重構models.py里的Task和Job代碼用常量替換mogic number.
class Task(models.Model): STATE_NEW =1 STATE_PROCESSED=4 STATE_RUNNING=5 STATE_COMPLETED=99 STATE_CANCEL=-1 TASK_STATE=((STATE_NEW,u'未處理'),(STATE_PROCESSED,u'處理成功'),(STATE_RUNNING,u'執行中'),(STATE_COMPLETED,u'完成'),(STATE_CANCEL,u'已取消')) TaskId = models.AutoField(u'ID',primary_key=True, db_column='task_id') TaskNum = models.IntegerField(u'任務號', null=False, db_column='task_num') Source = models.CharField(u'源地址', null=False, max_length=50, db_column='source') Target = models.CharField(u'目標地址', null=False, max_length=50, db_column='target') Barcode = models.CharField(u'容器條碼', null=False, max_length=50, db_column='barcode') State = models.IntegerField(u'狀態', choices=TASK_STATE, null=False, db_column='state') Priority = models.IntegerField(u'優先級', choices=PRIORITY, null=True, db_column='priority') BeginDate = models.DateTimeField(u'開始時間',null=True, db_column='begin_date') EndDate = models.DateTimeField(u'結束時間',null=True, db_column='end_date') SystemDate = models.DateTimeField(u'系統時間', null=False, auto_now_add=True, db_column='system_date') User = models.ForeignKey(User, verbose_name="操作員",null=True, on_delete=models.CASCADE,db_column='user_id') class Meta: db_table = 'task_task' ordering = ['-Priority','TaskId'] verbose_name = verbose_name_plural = "任務" ... class Job(models.Model): STATE_NEW =1 STATE_START=2 STATE_COMPLETED=99 STATE_CANCEL=-1 JOB_STATE=((STATE_NEW,u'新作業'),(STATE_START,u'下達執行'), (STATE_COMPLETED,u'完成'),(STATE_CANCEL,u'已取消'))
凡是有魔法數字的地方都盡量替換成可以閱讀的常量,采用中式英語都是可以推薦的方式:)。
class TaskBiz(object): """description of class""" def task_start(self,obj): success=False if obj.State==Task.STATE_PROCESSED: obj.State=Task.STATE_RUNNING try: obj.save() success = True except Exception: success = False return success ... def task_decompose(self,request,obj): success=True try: job1=Job(**{"Task":obj,"TaskNum":obj.TaskNum,"OrderNo":1,"Source":obj.Source,"Target":'102',"Executor":"AGV01","State":Job.STATE_NEW,\ "Priority":obj.Priority,"Barcode":obj.Barcode,"User":request.user,}) job1.save() job2=Job(**{"Task":obj,"TaskNum":obj.TaskNum,"OrderNo":2,"Source":None,"Target":'1',"Executor":"ELEVATOR","State":Job.STATE_NEW,\ "Priority":obj.Priority,"Barcode":obj.Barcode,"User":request.user,}) job2.save() job3=Job(**{"Task":obj,"TaskNum":obj.TaskNum,"OrderNo":3,"Source":"102","Target":'104',"Executor":"AGV01","State":Job.STATE_NEW,\ "Priority":obj.Priority,"Barcode":obj.Barcode,"User":request.user,}) job3.save() job4=Job(**{"Task":obj,"TaskNum":obj.TaskNum,"OrderNo":4,"Source":'104',"Target":'103',"Executor":"AGV01","State":Job.STATE_NEW,\ "Priority":obj.Priority,"Barcode":obj.Barcode,"User":request.user,}) job4.save() job5=Job(**{"Task":obj,"TaskNum":obj.TaskNum,"OrderNo":5,"Source":'1',"Target":'5',"Executor":"ELEVATOR","State":Job.STATE_NEW,\ "Priority":obj.Priority,"Barcode":obj.Barcode,"User":request.user,}) job5.save() job6=Job(**{"Task":obj,"TaskNum":obj.TaskNum,"OrderNo":6,"Source":'504',"Target":'503',"Executor":"AGV05","State":Job.STATE_NEW,\ "Priority":obj.Priority,"Barcode":obj.Barcode,"User":request.user,}) job6.save() job7=Job(**{"Task":obj,"TaskNum":obj.TaskNum,"OrderNo":7,"Source":'5',"Target":None,"Executor":"ELEVATOR","State":Job.STATE_NEW,\ "Priority":obj.Priority,"Barcode":obj.Barcode,"User":request.user,}) job7.save() job8=Job(**{"Task":obj,"TaskNum":obj.TaskNum,"OrderNo":8,"Source":'503',"Target":'05-01-01',"Executor":"AGV05","State":Job.STATE_NEW,\ "Priority":obj.Priority,"Barcode":obj.Barcode,"User":request.user,}) job8.save() #子任務分解完成,並提交數據庫 #更新任務的狀態到“處理完成” obj.State=Task.STATE_PROCESSED obj.save() except Exception: success = False return success
1.6. 小結
本章節我們講述了如何通過admin.py來快速的完成頁面功能的構建,並通過自定義action快速的實現了任務分解功能,並根據業務進展也逐步的完善了查看頁面以內聯表的方式顯示作業詳情。隨着業務功能的增加admin.py的代碼逐步增多和變得復雜,下一章我們演示如何通過功能內聚和重構代碼,增加代碼可讀性和可維護性。