【问题标题】:Django import-export duplicating rows when importing same file multiple times多次导入同一文件时,Django导入导出重复行
【发布时间】:2021-03-11 19:08:33
【问题描述】:

我正在构建一个工具,允许用户使用django-import-export 工具上传 CSV 文件以更新数据库。我上传了包含一个数据行的测试 CSV 文件,然后再次上传并得到了一个重复的行(具有新的主键,但所有其他值都相同)。 row.import_type 值是“更新的”,但唯一更新的是 id。

然后我第三次上传同一个文件,得到一个错误:

app.models.Role.MultipleObjectsReturned: get() returned more than one Role -- it returned 2!

(顺便说一句,我非常感谢该错误消息中的感叹号。)

理想情况下,我会在文件的第二次导入和第三次导入时跳过一行。我想我会接受一个错误。文件内容为:

    Sales Role,System Role,System Plan,id
    Sales Rep,Account Executive,951M-NA,

这是用户在导出 csv 数据集时获得的格式。理想情况下,他们会导出一个文件,更改几列(除了 import_id_field 的名称),然后重新上传数据。

在 app/resources.py 中:

    class RoleResourec(resources.ModelResource):
        name = Field(attribute='name', column_name="Sales Role")
        default_role = Field(attribute='default_role', column_name="System Role")
        default_plan = Field(attribute='default_plan', column_name="System Plan")
    
        class Meta:
            models=Role
            fields= ('id', 'name', 'default_role', 'default_plan')
            import_id_fields = ('name',)
            skip_unchanged = True

据我所知,在第二次导入时,get_or_init_instance() 方法没有从第一次导入中找到对象,但在第三次导入时确实找到了它们。如Import data workflow 页面中所述,我没有对资源进行任何自定义导入工作流。

这里出了什么问题?我是否需要自定义导入工作流,还是我错过了资源中的另一个必需属性?

【问题讨论】:

    标签: django django-import-export


    【解决方案1】:

    如果所有声明的字段在导入行和持久对象中都相同,则逻辑只会跳过该行。如果任何字段不同,则将执行更新。

    为此,您在import_id_fields 中声明的字段必须是唯一匹配行,否则您将获得MultipleObjectsReturned

    在您的情况下,如果正在创建重复行,那么它必须意味着 name 在第二次运行时不存在于数据库中。我假设您没有覆盖 ModelInstanceLoader 或在批量模式下运行,因为这会破坏跳行逻辑。

    默认情况下,import_id_fields 设置为行 ID,因此如果您可以将其包含在导出中,则可以保证您拥有唯一的行。显然用户不应该改变这个字段,否则你会得到重复。

    MultipleObjectsReturned 错误来自here,它只是对Role.objects.get(name=<n>) 的调用。

    【讨论】:

    • 我在 SO 中手动输入所有内容,所以这是拼写错误的来源(我不记得如何从 vim 复制到系统剪贴板)。在努力弄清楚如何对实例加载器进行子类化以便我可以放入断点之后,我确实找到了错误。从 Meta 中的字段列表中删除“id”解决了这个问题。我会把它写下来作为答案。
    • 如果您使用的是 PyCharm,那么很容易在库代码中添加断点 - 无需子类
    • 我在虚拟环境中的 vim 中,我确实开始挖掘源代码以找出在哪里放置断点,然后在我的系统上找到它,yadda yadda。简而言之,我害怕弄乱别人的库,这就是我考虑子类化的原因。
    【解决方案2】:

    这是一个非常简单的解决方案:

    
        class RoleResourec(resources.ModelResource):
                name = Field(attribute='name', column_name="Sales Role")
                default_role = Field(attribute='default_role', column_name="System Role")
                default_plan = Field(attribute='default_plan', column_name="System Plan")
            
                class Meta:
                    models=Role
                    fields= ('name', 'default_role', 'default_plan')
                    import_id_fields = ('name',)
                    skip_unchanged = True
    
    

    我所要做的就是从 Meta 类的字段列表中删除 'id',现在我得到了预期的行为。

    我可以将此文件导出为 CSV(id 列不出现),编辑列表,然后重新上传,系统会跳过和更新,甚至根据需要添加新内容。

    【讨论】:

    • 我不明白这是如何解决它的,所以你可能掩盖了真正的问题。如果 'name' 确实是唯一的,那么它应该匹配该唯一行,然后根据其他字段是否未更改来跳过或更新。我已经更新了我的答案以添加更多细节。
    • 我通过运行python manage.py migrate <app> zeropython manage.py migrate <app> 清空应用程序的表格,这让我对所有事情有了新的开始。我上传了一个带有标题行和数据行的 csv 文件。我确认该表只有一排。然后我导入完全相同的文件,得到两行相同的数据(只有id 是新的)。
    猜你喜欢
    • 2021-11-16
    • 1970-01-01
    • 2020-05-02
    • 1970-01-01
    • 2013-05-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多