Django-Import-Export外掛控制資料匯入流程

2022-08-29 18:02:00

前言

之前寫過兩篇跟這個外掛有關的文章,可以回顧一下:

最近有個朋友留言問我一個關於django-import-export外掛的問題

為了形象表達這個問題,我舉個書籍管理的例子來描述一下

資料庫表

id name price
1 book1 10
2 book2 20
3 book3 30

要匯入的Excel表

id name price tax
4 book4 40 5
5 book5 50 6
6 book6 60 7

可以看到,Excel裡每本書都有價格和稅兩個屬性,但資料庫只有價格一個屬性

匯入的時候,需要把每本書的價格+稅,才是要存入資料的最終價格

在以前,這種問題場景我會建議直接用pandas來處理資料然後匯入,django-import-export外掛只用來做資料匯出,因為它的檔案很簡陋,給的例子很難解決實際問題,往往某個需求用pandas手動處理只需要很少時間,用這個外掛還得去啃原始碼和簡陋的檔案,效率太低了。

不過本著折騰的精神,還是來研究一下這個用django-import-export到底能不能實現這個功能。(結果當然是可以的,不然也沒有這篇文章了)

分析

首先是看官網檔案,有一個節點叫import data workflow

地址:https://django-import-export.readthedocs.io/en/latest/import_workflow.html

import_data(dataset, dry_run=False, raise_errors=False)

The import_data() method of Resource is responsible for importing data from a given dataset.

dataset is required and expected to be a tablib.Dataset with a header row.

dry_run is a Boolean which determines if changes to the database are made or if the import is only simulated. It defaults to False.

raise_errors is a Boolean. If True, import should raise errors. The default is False, which means that eventual errors and traceback will be saved in Result instance.

根據檔案,在匯入資料的時候,我們可以通過import_data這個hook來對要匯入的資料進行處理

然後這個hook有個引數,dataset,這個是tablib的東西

關於這個tablib,我之前沒用過,查了一下,是requests作者做的庫,那想來應該不會差

官網檔案是:https://tablib.readthedocs.io/en/stable/tutorial.html

寫程式碼

直接把官方的程式碼例子拿來用

程式碼倉庫:https://github.com/django-import-export/django-import-export

同樣是這個書籍管理的

Models程式碼

來看看它的model設計

class Book(models.Model):
    name = models.CharField('Book name', max_length=100)
    author = models.ForeignKey(Author, blank=True, null=True, on_delete=models.CASCADE)
    author_email = models.EmailField('Author email', max_length=75, blank=True)
    imported = models.BooleanField(default=False)
    published = models.DateField('Published', blank=True, null=True)
    published_time = models.TimeField('Time published', blank=True, null=True)
    price = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True)
    added = models.DateTimeField(blank=True, null=True)

    categories = models.ManyToManyField(Category, blank=True)

    def __str__(self):
        return self.name

很多欄位

要匯入的資料

依然是官方提供的,各種格式都有,我選csv的,比較方便

id,name,author_email
1,Some book,[email protected]

轉換成表格長這樣

id name author_email
1 Some book [email protected]

可以看到欄位比model定義的少很多

我們要在匯入的時候,給dataset加上價格(price)屬性

Resources程式碼

這是原本的程式碼

class BookResource(ModelResource):
    class Meta:
        model = Book

    def for_delete(self, row, instance):
        return self.fields['name'].clean(row) == ''

        return super(BookResource, self).import_data(
            dataset, dry_run, raise_errors, use_transactions,
            collect_failed_rows, rollback_on_validation_errors, **kwargs

現在我們要加一個hook來處理匯入的資料

程式碼如下

def import_data(self, dataset: tablib.Dataset, dry_run=False, raise_errors=False,
                use_transactions=None, collect_failed_rows=False,
                rollback_on_validation_errors=False, **kwargs):
    cols = []
    for item in dataset['id']:
        cols.append(int(item) * 99)

        dataset.append_col(cols, header='price')
        print(dataset)
		
        return super(BookResource, self).import_data(
            dataset, dry_run, raise_errors, use_transactions,
            collect_failed_rows, rollback_on_validation_errors, **kwargs
        )

使用DataSetappend_col方法來新增一個新的列

關於這個DataSet的更多操作請參考Tablib的檔案

這部分的具體操作可以根據實際需求來做修改,這裡我直接簡單粗暴的把ID乘以99

處理完DataSet之後記得要執行父類別的import_data,完成資料匯入的操作。

效果

在admin後臺執行匯入,可以得到以下的結果

可以看到price屬性變成99

ID NAME AUTHOR AUTHOR_EMAIL IMPORTED PUBLISHED PUBLISHED_TIME PRICE ADDED CATEGORIES
New 1 Some book [email protected] 0 99

就OK了,搞定~

其實還挺簡單的,只是官方檔案太簡陋了,連個例子的沒有,只能自己摸索一下

參考資料

雖然前面都有連結,這裡再總結一下吧