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

当我们需要用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的表格,会报错。例如,如果导入以下表格:

需导入的表格

会出现如下报错:

导入报错

修改后:


修改后

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,580评论 18 139
  • 关于Mongodb的全面总结 MongoDB的内部构造《MongoDB The Definitive Guide》...
    中v中阅读 31,893评论 2 89
  • 深沉厚重是第一等资质,磊落豪雄是第二等资质,聪明才辩是第三等资质。领导者首要资质是具备时常深入思考事务本质的厚...
    徐晓琳111阅读 109评论 0 1
  • 2017.11.22 文/琴音 今天是一个好日子。小樱桃奶奶的生日,又是小樱桃代表班级参...
    王燕惠阅读 517评论 1 2
  • 我们后天学习的东西,都是理性,理性是把人往回拉的力量。但是驱动一个人的,其实是他的内在感受,他的情绪。 知识的调用...
    ProblemSolver阅读 225评论 0 0