Django-10-导入导出功能优化

作者: JOooo_ | 来源:发表于2018-07-05 10:57 被阅读11次

当我们需要用django的中文界面时,可以设置LANGUAGE_CODE = 'zh-hans'
在admin页面需要展示列表字段为中文名时,可以在模型中为各字段添加verbose_name。
而在使用导出功能中发现,导出的excel,csv表格中列名会默认成模型中的英文字段名。

为了更易读,现在将表格中的列名改为字段的verbose_name。

1. 导出表格中的列名显示

首先,我们先在DeviceResource的init中新建一个全局字段vname_dict,获取Device模型中的所有字段对应的verbose_name。

#myproject/admin.py
class DeviceResource(resources.ModelResource):
    ...
    def __init__(self):
        super(DeviceResource, self).__init__()
        # 获取所以字段的verbose_name并存放在字典
        field_list = apps.get_model('deviceManager', 'Device')._meta.fields
        self.vname_dict = {}
        for i in field_list:
            self.vname_dict[i.name] = i.verbose_name

然后,查看resource.py源码,可以发现导出数据前,是通过get_export_fields方法获取要导出的数据,而该方法其实只是直接调用了get_fields方法:获取数据并按export_order的顺序排列,返回一个列表。

# import-export/resource.py
...
def get_export_fields(self):
    return self.get_fields()

我们直接重写get_export_fields方法,在这里修改导出数据的列名就可以了。

# myapp/admin.py
# 默认导入导出field的column_name为字段的名称,这里修改为字段的verbose_name
def get_export_fields(self):
    fields = self.get_fields()
    for field in fields:
        field_name = self.get_field_name(field)
        # 如果我们设置过verbose_name,则将column_name替换为verbose_name。否则维持原有的字段名
        if field_name in self.vname_dict.keys():  
            field.column_name = self.vname_dict[field_name]
    return fields

再使用导入导出时,列名就变为我们为模型中字段设置的verbose_name啦!

列名为verbose_name
2. 导出表格中的外键显示

接着发现一个问题:我们导出的表格中,所有的外键字段会显示外键的id,而不是我们想看到的文字。查看resource.py源文件,发现有一个before_export方法,官方注释写着可以重写这个方法去自定义一些导出前的操作。但是,我发现before_export中只能获取到queryset,也是就从数据库查询后的一个列表,是不可更改的,我根本没法在这里修改将要导出的数据。于是只能重写export方法了。

首先,我们需要知道哪些字段是外键。在DeviceResource的init方法中再新建一个全局变量self.fkey。现在,init变为:

#myproject/admin.py
class DeviceResource(resources.ModelResource):
    ...
    def __init__(self):
        super(DeviceResource, self).__init__()
        #获取deviceManager应用下Device模型中的所有字段
        field_list = apps.get_model('deviceManager', 'Device')._meta.fields  
        self.vname_dict = {}
        self.fkey = []
        for i in field_list:
            self.vname_dict[i.name] = i.verbose_name    # 获取所有字段的verbose_name并存放在字典
            if(isinstance(i, ForeignKey)):
                self.fkey.append(i.name)    # 获取所有ForeignKey字段的name存放在列表

然后,把resource.py中的export方法全部拷贝过来后进行修改,如下。
备注: # ---------- #之间的是自己添加的代码,其它为export原本的代码。

#myproject/admin.py
class DeviceResource(resources.ModelResource):
    ...
    # 重载resources.py的export方法,修改将要导出的data的某些外键相关数据。默认导出外键id,这里修改为导出外键对应的值
    def export(self, queryset=None, *args, **kwargs):
        self.before_export(queryset, *args, **kwargs)

        if queryset is None:
            queryset = self.get_queryset()
        headers = self.get_export_headers()
        data = tablib.Dataset(headers=headers)

        # --------------------- #
        # 获取所有外键名称在headers中的位置
        fk_index = {}
        for fk in self.fkey:
            fk_index[fk] = headers.index(self.vname_dict[fk])
        # --------------------- #

        if isinstance(queryset, QuerySet):
            # Iterate without the queryset cache, to avoid wasting memory when
            # exporting large datasets.
            iterable = queryset.iterator()
        else:
            iterable = queryset
        for obj in iterable:
            # --------------------- #
            # 获取将要导出的源数据,这里export_resource返回的是列表,便于更改。替换到外键的值
            res = self.export_resource(obj)
            res[fk_index['area_company']] = Group.objects.get(id=res[fk_index['area_company']]).name
            res[fk_index['site']] = Site.objects.get(id=res[fk_index['site']]).site
            res[fk_index['device_type']] = DeviceType.objects.get(id=res[fk_index['device_type']]).type
            res[fk_index['device_model']] = Model.objects.get(id=res[fk_index['device_model']]).model
            res[fk_index['supplier']] = Supplier.objects.get(id=res[fk_index['supplier']]).supplier
            data.append(res)
            # --------------------- #
        self.after_export(queryset, data, *args, **kwargs)
        return data

这样,导出表格中的外键字段就不会再显示为id啦。对比如下:

修改前:

外键字段显示的全是数字

修改后:

外键字段显示为我们想看到的文字信息了
3. 导入时外键字段的转换

对于外键的处理,导入时存在同样的问题。我们自己填写表格时,肯定不会填写这个值对应在另一个数据表里的id。所以执行导入操作前,我们也需要将数据源中实际输入的值转换为对应的外键id。

查看resource.py源码,发现我们可以直接重写before_import方法去修改数据源dataset,代码如下。

#myproject/admin.py
class DeviceResource(resources.ModelResource):
    ...
    # import相关。将导入的表格中的一些文字通过数据库查询一些外键field对应的id。也可通过设置use_transactions=True,重写before_save_instance等方法
    def before_import(self, dataset, using_transactions, dry_run, **kwargs):
        dict = []
        id = Device.objects.latest('id').id+1
        for row in dataset.dict:
            tmp = OrderedDict()
            tmp['ID'] = id
            for item in row:
                if item == self.vname_dict['area_company']:
                    tmp[item] = Group.objects.get(name=row[self.vname_dict['area_company']]).id
                elif item == self.vname_dict['site']:
                    tmp[item] = Site.objects.get(site=row[self.vname_dict['site']]).id
                elif item == self.vname_dict['device_type']:
                    tmp[item] = DeviceType.objects.get(type=row[self.vname_dict['device_type']]).id
                elif item == self.vname_dict['device_model']:
                    tmp[item] = Model.objects.get(model=row[self.vname_dict['device_model']]).id
                elif item == self.vname_dict['supplier']:
                    tmp[item] = Supplier.objects.get(supplier=row[self.vname_dict['supplier']]).id
                else:
                    tmp[item] = row[item]
            id = id+1
            dict.append(tmp)
        dataset.dict = dict

修改前,如果导入外键字段不是填int的表格,会报错。例如,如果导入以下表格:

需导入的表格

会出现如下报错:

导入报错

修改后:


修改后

导入导出功能的优化就搞定啦!

相关文章

网友评论

    本文标题:Django-10-导入导出功能优化

    本文链接:https://www.haomeiwen.com/subject/zjioyftx.html