【问题标题】:Django Import - Export: IntegrittyError when trying to insert duplicate record in field(s) with unique or unique_together constraintsDjango 导入 - 导出:尝试在具有唯一性或唯一性约束的字段中插入重复记录时出现 IntegrittyError
【发布时间】:2016-04-17 01:56:51
【问题描述】:

更新

我已提交feature request。思路是当uniqueunique_together拒绝数据库中已经存在的记录时,数据库产生的IntegrittyError上的pass


我有以下型号:

class Compositions(models.Model):
    composer_key = models.ForeignKey(
        Composer,
        )
    composition = models.CharField(
        max_length=383,
        )

    class Meta(object):
        unique_together = (('composer_key', 'composition'), )

在管理界面中使用 django-import-export,不为 csv 文件中的每个条目提供id,...如果一对 csv 文件已经存在,则程序将因完整性错误而中断

duplicate key value violates unique constraint "data_compositions_composer_key_id_12f91ce7dbac16bf_uniq"
DETAIL:  Key (composer_key_id, composition)=(2, Star Wars) already exists.

CSV 文件如下:

id  composer_key    composition
        1           Hot Stuff
        2           Star Wars

The idea 是使用skip_row 并在管理员中实现它。

admin.py:

class CompositionsResource(resources.ModelResource):

    class Meta:
        model = Compositions
        skip_unchanged = True
        report_skipped = True


class CompositionsAdmin(ImportExportModelAdmin):
    resource_class = CompositionsResource


admin.site.register(Compositions, CompositionsAdmin)

然而,这并不能解决问题,因为skip_row 期望 csv 文件中有一个 id,以便检查每一行是否与非常特定的数据库条目相同。

考虑到在使用unique(_together) 时可以由数据库执行此控制,捕获此错误然后在此错误上返回skip_row = Truepass 不是有效吗?

【问题讨论】:

  • default implementation 看起来很实用,应该可以开箱即用。你能展示你的CSV吗?也许,pk 属性有什么不同?
  • @AlexMorozov 我已经用相关的 csv 数据更新了我的答案。我想知道删除unique_together 是否会产生任何影响,因为实现是正确的。我下班后试试看。
  • 您的id 列是空的吗?如果是这样,在检查重复项时,模块可能会自欺欺人,因为新对象的 pk 与数据库中的 pk 不同。所以它尝试添加另一条记录,是的,由于unique_together 约束而失败。我建议您不要删除约束,但首先尝试为您的 csv 记录分配一个永久的ids。不管怎样,让我知道进展如何)
  • @Paolo 实际上,我确实编写了一个脚本来完成这项工作。我把它贴在下面,虽然它是“即时”写的。
  • @Paolo 查看有关此问题的最新答案,非常酷!

标签: python django django-import-export


【解决方案1】:

models.py:

class Compositions(models.Model):
    composer_key = models.ForeignKey(
        Composer,
        )
    composition = models.CharField(
        max_length=383,
        unique=False
        )
    date_created = models.DateTimeField(default=timezone.now)

    class Meta(object):
        unique_together = (('composer_key','composition'),)

这是我为上述模型“即时”编写的脚本,以便自动丢弃重复条目。当我用数据填充文件duc.csv 的相关列时,我已将其保存到./project_name/csv.py 并从shell 导入它。列不应包含标题。只有数据。

$./manage.py shell
>>> from project_name import csv

csv.py:

from data.models import Composer, Compositions
import csv
import sys, traceback
from django.utils import timezone

filename = '/path/to/duc.csv'

with open(filename, newline='') as csvfile:
    all_lines = csv.reader(csvfile, delimiter=',', quotechar='"')
    for each_line in all_lines:
        print (each_line)
        try:
            instance = Compositions(
                id=None,
                date_created=timezone.now(),
                composer_key=Composer.objects.get(id=each_line[2]),
                composition=each_line[3]
            )
            instance.save()
            print ("Saved composition: {0}".format(each_line[3]))
        except:  // exception type must be inserted here
            exc_type, exc_value, exc_traceback = sys.exc_info()  //debugging mostly
            print (exc_value)

【讨论】:

    【解决方案2】:

    只需要一个改变。你可以使用 django-import-export

    models.py

        class Compositions(models.Model):
            composer_key = models.ForeignKey(
                Composer,
                )
            composition = models.CharField(
                max_length=383,
                unique=False
                )
            date_created = models.DateTimeField(default=timezone.now)
    
            class Meta(object):
                unique_together = (('composer_key','composition'),)
    

    用 try 覆盖 save_instance。失败时忽略错误。 admin.py

            class CompositionsResource(resources.ModelResource):
    
                class Meta:
                    model = Compositions
                    skip_unchanged = True
                    report_skipped = True
    
                def save_instance(self, instance, using_transactions=True, dry_run=False):
                    try:
                        super(CompositionsResource, self).save_instance(instance, using_transactions, dry_run)
                    except IntegrityError:
                        pass
    
            class CompositionsAdmin(ImportExportModelAdmin):
                resource_class = CompositionsResource
    
            admin.site.register(Compositions, CompositionsAdmin)
    

    并导入这个

    from django.db import IntegrityError
    

    【讨论】:

    • How to write an answer。添加更多详细信息以证明您的回答是正确的。
    • 谢谢你,很好的回答。最好致电the parent save_instance 以确保您不会破坏某些东西。而不是instance.save(),您可以尝试super().save_instance(self,instance, using_transactions=True, dry_run=False),如果可行,请将其添加到帖子中。喜欢the code here
    • usign super().save_instance(self,instance, using_transactions=True, dry_run=False) 不起作用。看到这个django-import-export.readthedocs.io/en/latest/…
    • 请记住,应该避免使用general error catching,而且您还有一个错字:不是self,instance,而是self.instance。除此之外,此解决方案存在一个问题:如果正在进行试运行,则不会继续保存实例。这会干扰进出口核心功能。你有一个很好的答案,但它不能被标记为一个解决方案。
    • 好的!打电话给super()怎么样?为什么它不起作用?
    【解决方案3】:

    关于已接受答案的说明:它会给出所需的结果,但会在大文件时占用磁盘空间和时间。

    我一直在使用的一种更有效的方法(在花费大量时间浏览文档之后)是覆盖skip_row,并使用一组元组作为类的一部分作为唯一约束。我仍然覆盖save_instance,因为另一个答案建议处理通过的 IntegrityErrors,当然。

    Python sets 不会创建重复条目,因此它们似乎适合这种唯一索引。

    class CompositionsResource(resources.ModelResource):
      set_unique = set()
    
      class Meta:
        model = Composers
        skip_unchanged = True
        report_skipped = True
    
      def before_import(self, dataset, using_transactions, dry_run, **kwargs):
        # Clear out anything that may be there from a dry_run,
        #  such as the admin mixin preview
        self.set_unique = set()
    
      def skip_row(self, instance, original):
        composer_key = instance.composer_key  # Could also use composer_key_id
        composition = instance.composition
        tuple_unique = (composer_key, composition)
    
        if tuple_unique in self.set_unique:
          return true
        else:
          self.set_unique.add(tuple_unique)
        return super(CompositionsResource, self).skip_row(instance, original)
    
        # save_instance override should still go here to pass on IntegrityError
    

    这种方法至少会减少在同一数据集中遇到的重复项。我用它来处理多个平面文件,每个文件大约 60000 行,但有很多重复/嵌套的外键。这使得初始数据导入速度更快。

    【讨论】:

      猜你喜欢
      • 2016-11-15
      • 1970-01-01
      • 1970-01-01
      • 2018-08-14
      • 2015-11-14
      • 1970-01-01
      • 2011-11-12
      • 1970-01-01
      • 2017-08-13
      相关资源
      最近更新 更多