vue表單中批量匯入功能_idcops 中批量匯入線上裝置
技術標籤:vue表單中批量匯入功能
django-idcops 平臺之前一直都沒開發線上裝置匯入的功能,剛好一些定製的系統有這個功能的需求,所以便把批量匯入線上裝置的功能完善了。
Django 框架中資料的匯入和匯出有很大的區別,匯出的時候可以利用框架model已有的資料解析方法將資料按照一定的規則匯出。
而匯入則需要根據具體model開發相關的資料解耦方法才能繼續進行,idcops 中大量使用了必填外來鍵,必填字串,多對多這種model欄位型別。因此,在開發批量匯入線上裝置這一功能時就需要寫更多的耦合函式。
面向物件,把idcops中Online這個模型的欄位型別瞭解一波先:
(env)PSC:\Users\shoutian\Desktop\django-idcops>python.\manage.pyshell>>> from idcops.models import Online>>> for f in Online._meta.get_fields(): f, "{} 必填:{}".format(f.verbose_name, not f.blank)... (, 'ID 必填:False')(, '系統標記 必填:False')(, '建立人 必填:True')(, '修改人 必填:False')(, '建立日期 必填:True')(, '修改日期 必填:False')(, '已啟用 必填:False')(, '已刪除 必填:False')(, '所屬機房 必填:False')(, '裝置編號 必填:True')(, '所屬機櫃 必填:True')(, '所屬客戶 必填:True')(, '裝置SN號 必填:True')(, 'IP地址 必填:False')(, '裝置型號 必填:True')(, '裝置型別 必填:True')(, '狀態 必填:True')(, '裝置U位 必填:False')(, 'PDU介面 必填:False')(, '裝置標籤 必填:False')(, '備註資訊 必填:False')>>>
進一步提取必須填寫的欄位如下:
>>> for f in Online._meta.get_fields(): (f, f.verbose_name) if not f.blank else None ... (, '建立人')(, '建立日期')(, '裝置編號')(, '所屬機櫃')(, '所屬客戶')(, '裝置SN號')(, '裝置型號')(, '裝置型別')(, '狀態')>>>
因線上裝置涉及到機櫃中展示具體所在位置,所以,裝置U位也是必填的,它是一個多對多欄位。另外還有pdus和tags是可選填的多對多欄位。最終整理的欄位如下:
>>> must_fields = [f.name for f in Online._meta.get_fields() if not f.blank] >>> extra = ['units', 'pdus', 'tags']>>> must_fields + extra['creator', 'created', 'name', 'rack', 'client', 'sn', 'model', 'style', 'status', 'units', 'pdus', 'tags']>>>
通過上面的分析,我們做一個excel表格模板,供使用者按照格式填寫,模板截圖如下:
注:
1、第1行是欄位的中文說明和欄位填寫格式註釋
2、第2行對應上面得出的填寫欄位的英文標識,這樣做可以讓使用者的模板自己隨意組合順序,和在表格中填寫以下其他的內容(如Excel第M列)
下面開始分析程式碼:
1、根據表格提供的上架人字串獲取系統使用者例項,非嚴格查詢,匹配以下3個欄位中的值均可,沒查到直接返回pk最小的一個使用者。【外來鍵型別】
def get_creator(username): fields = ['first_name', 'username', 'mobile'] query = [Q(**{k: username.strip()}) for k in fields] query_str = reduce(operator.or_, query) user = User.objects.filter(query_str) if user.exists(): return user.first() else: return User.objects.filter().order_by('pk').first()
2、獲取當前機房裝置型別例項,沒有則新建。【外來鍵型別】
def get_or_create_style(name, onidc_id): f = dict( onidc_id=onidc_id, flag='Device-Style', text=name.strip() ) qs = shared_queryset(Option.objects.filter(**f), onidc_id) if qs.exists(): instance = qs.first() else: extra = dict( description=name.strip(), creator_id=CreatorId ) f.update(**extra) instance = Option.objects.create(**f) return instance
注:shared_queryset 是一個過濾查詢的函式,主要獲得其他機房共享出來的對應資料條目。
3、獲取客戶例項,沒有則按照相關條件新建。【外來鍵型別】
def get_or_create_client(name, onidc_id): qs = Client.objects.filter(name=name.strip()) if qs.exists(): instance = qs.first() else: types = Option.objects.filter( onidc_id=onidc_id, flag='Client-Style' ) if types.exists(): default = types.filter(master=True) if default.exists(): style = default.first() else: style = types.first() else: return None, "客戶型別不能為空" instance = Client.objects.create( onidc_id=onidc_id, creator_id=CreatorId, name=name.strip(), style=style ) return instance, None
注:style 是一個Option例項,有動態共享和預設的屬性。這裡可能不太好理解,但不影響理解整段程式碼。作用就是能獲取該機房預設的客戶型別,那就返回它,沒有獲取到任何客戶型別就返回None和提示字串,供迴圈程式作進一步判斷處理。
4、獲取機房已分配的機櫃例項。【外來鍵型別】
def get_rack(name, onidc_id): """ Return: (instance, error) """ qs = Rack.objects.filter(name=name.strip(), onidc_id=onidc_id) if qs.filter(actived=True).exists(): return qs.first(), None elif qs.filter(actived=False).exists(): return None, "該機櫃未分配使用" else: return None, "找不到該機櫃"
5、下面程式碼是處理裝置U位,Pdu位,tags的程式碼【多對多型別】
def clean_units(data, rack_id): units = sorted([int(i) for i in data.split('|') if len(i) != 0]) units_list = [ str(x).zfill(2) for x in range(units[0], units[-1]+1) ] instances = Unit.objects.filter(rack_id=rack_id, name__in=units_list) if instances.exists(): used = instances.filter(actived=False) if used.count() > 0: return None, "有U位被佔用中" return instances, None else: return None, "找不到U位資訊"def clean_pdus(data, rack_id): pdus = re.split('[, |]', data) pdus_list = [x.strip() for x in pdus if x] instances = Pdu.objects.filter(rack_id=rack_id, name__in=pdus_list) if instances.exists(): used = instances.filter(actived=False) if used.count() > 0: return instances.filter(actived=True), "部分PDU位被佔用中" return instances, None else: return None, "找不到PDU位資訊"def clean_tags(tags, onidc_id, creator_id): tags = re.split('[, |]', tags) tags_list = [x.strip() for x in tags if x] default = dict(onidc_id=onidc_id, flag='Device-Tags') instances = [] for tag in tags_list: default.update(**dict(text=tag)) verify = Option.objects.filter(**default) if verify.exists(): instance = verify.first() else: default.update(**dict(creator_id=creator_id)) instance = Option.objects.create(**default) instances.append(instance) return instances
注:跟外來鍵型別的處理方式差不多,先查出例項,在返回。這邊都是返回模型的例項列表。
Python中使用xlrd讀取Excel資料內容
是不是感覺跟django表單的清理資料有點大同小異,是的,匯入的時候我們主要的工作就是做資料的清理和校驗。下面是讀取excel表格資料的具體處理程式碼:
def import_online(path, onidc_id): fileds = [ 'name', 'creator', 'rack', 'client', 'created', 'onidc', 'sn', 'model', 'ipaddr', 'style', 'units', 'pdus', 'tags' ] workbook = xlrd.open_workbook(path) sheets = workbook.sheet_names() worksheet = workbook.sheet_by_name(sheets[0])#設定匯入錯誤日誌記錄到各種列表中 handler_error = [] handler_warning = [] handler_success = [] for index, row in enumerate(worksheet.get_rows(), 1): # header = index if index == 1: # 跳過表頭 continue if index == 2: # 獲取欄位名稱 headers = [h.value for h in row] continue if index > 503: # 每次只處理500條資料 msg = "一次最多匯入500條資料" handler_error.append(msg) break data = dict(zip(headers, [k.value for k in row])) raw = {k: data.get(k) for k in fileds} created = datetime.strptime(data.get('created'), '%Y-%m-%d') raw.update(**dict(created=created)) verify = Online.objects.filter(name=raw.get('name')) if verify.exists(): msg = "第{}行:{}裝置已存在".format(index, raw.get('name')) handler_error.append(msg) continue else: style = get_or_create_style(raw.get('style'), onidc_id) creator = get_creator(raw.get('creator')) # 獲取機櫃資訊 rack, err = get_rack(raw.get('rack'), onidc_id) if not rack: msg = "第{}行:{}".format(index, err) handler_error.append(msg) continue # 獲取客戶資訊 client, err = get_or_create_client(raw.get('client'), onidc_id) if not client: msg = "第{}行:{}".format(index, err) handler_error.append(msg) continue # 例項化線上裝置 instance = Online( created=created, style=style, creator=creator, rack=rack, client=client, name=raw.get('name'), sn=raw.get('sn'), ipaddr=raw.get('ipaddr'), model=raw.get('model'), onidc_id=onidc_id ) instance.save() # 儲存U位 units, err = clean_units(raw.get('units'), rack.pk) if units: for u in units: instance.units.add(u) units.update(actived=False) instance.save() else: msg = "第{}行:{}".format(index, err) handler_error.append(msg) # U位不對,刪除本例項 instance.delete() continue handler_success.append(instance.name) # 儲存PDU pdus, err = clean_pdus(raw.get('pdus'), rack.pk) if pdus: for p in pdus: instance.pdus.add(p) pdus.update(actived=False) instance.save() else: msg = "第{}行:{}".format(index, err) handler_warning.append(msg) # 儲存TAGS tags = clean_tags(raw.get('tags'), onidc_id, creator.pk) if tags: for t in tags: instance.tags.add(t) instance.save() total = (index-2) return handler_error, handler_warning, handler_success, total
注:
1、表頭處理的 index=1 或 index=2 ,從 index=3 開始手機我們要的資料
2、巧妙的使用 fields 限制從使用者表格中獲取我們真正需要的欄位內容
3、匯入出錯的時候要銷燬 instance.save() 儲存的例項
4、匯入的日誌要記錄清除,精確到使用者excel第幾行第幾列
總體來說,批量匯入是另一種提交資料內容的方式,我們主要的工作就是做資料的清理和校驗,需針對到具體模型。並沒有像匯出資料那樣有很多的通用方法可實現。
文中程式碼不能直接套用,有興趣的朋友可以檢視本次功能開發的Gitee提交:
https://gitee.com/wenvki/django-idcops/commit/b9822ee543f541fef5325ccf075a7aefb726fa07
IDC運維管理平臺會盡力更新有關django-idcops開發或使用相關文章。如果文章中的內容對你有幫助,可以通過右下角點選在看支援一下,謝謝。