django-import-export修改记

环境

Django==2.0.4
django-import-export==1.0.1

需求:

  1. 导入需要修改主键,由另外一个字段来决定唯一性(替代主键);
  2. 一定要先预览再修改,即:不点【确认导入】就不修改数据库;

解决方案

需求1

使用import_id_fields配置即可,把这个设置为另外一个字段:

class TestModifyProductResource(resources.ModelResource):
    """
    以 test_field 为唯一值的导入。p_id 为数据库主键
    """
    class Meta:
        model = models.TestProduct
        skip_unchanged = True
        import_id_fields = ('test_field',)
        fields = ('p_id', 'field_2', 'field_3', 'field_4', 'test_field')
        use_transactions = False
        instance_loader_class = TestModifyProductInstanceLoader

    def import_row(self, row, *args, **kwargs):
        row['dry_run'] = kwargs.get('dry_run')
        return super().import_row(row, *args, **kwargs)

    def save_instance(self, instance, using_transactions=True, dry_run=False):
        if dry_run:
            # 如果是导入第一阶段,就不存储。
            return
        super().save_instance(instance, using_transactions, dry_run)

问题1:

  • 在mysql中,因为主键无法修改,所以在导入时必须先新增(新数据)后删除(老数据)。解决办法是自定义InstanceLoader,代码如下:
class TestModifyProductInstanceLoader(ModelInstanceLoader):
    """
    导入会出现更新主键的情况,如果已经存在相同的主键就需要先增加再删除。
    """
    def get_instance(self, row):
        # dry_run表示导入阶段,因为不同的阶段行为不一样所以需要把这个信息传递过来。
        dry_run = row.pop('dry_run', True)
        qs = self.get_queryset().filter(test_field=row['test_field'])
        if qs.count() <= 1:
            if dry_run:
                return qs.first()
            instance = super().get_instance(row)
            # 删除p_id和test_field一样的记录,不然后续程序会报错。
            qs.exclude(p_id=row['p_id']).delete()
            return instance
        else:
            # 下面备份p_id是为了提示正确:能够正确的显示更新数量。
            # 如果不需要提示正确,直接对tqs执行delete就行。
            zqs = qs.exclude(p_id=row['p_id'])
            old_p_id = zqs.first().p_id
            zqs.delete()
            instance = super().get_instance(row)
            instance.p_id = old_p_id
            return instance

需求2

按照官方的说法,只需要设置use_transactions = False即可,实际上不行,因官方实现数据缓存使用了事务来实现的。源码如下:

    def import_data(self, dataset, dry_run=False, raise_errors=False,
                    use_transactions=None, collect_failed_rows=False, **kwargs):
        #....
        if use_transactions is None:
            use_transactions = self.get_use_transactions()

        connection = connections[DEFAULT_DB_ALIAS]
        supports_transactions = getattr(connection.features, "supports_transactions", False)

        if use_transactions and not supports_transactions:
            raise ImproperlyConfigured

        using_transactions = (use_transactions or dry_run) and supports_transactions
        #....

我重写save_instance函数,强制不存储即可:

def save_instance(self, instance, using_transactions=True, dry_run=False):
    if dry_run:
        # 如果是导入第一阶段,就不存储。
        return
    super().save_instance(instance, using_transactions, dry_run)

备注: Django是根据主键是否存在来判定是使用insert还是update的,使用sql是可以修改主键的。

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值