尝试删除重复图像时出现django.db.utils.IntegrityError

时间:2015-10-04 08:52:13

标签: sql django error-handling exception-handling django-orm

我有以下代码从我计算的感知哈希中删除重复的图像。

images = Image.objects.all()
images_deleted = 0
for image in images:
    duplicates = Image.objects.filter(hash=image.hash).exclude(pk=image.pk).exclude(hash="ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
    for duplicate in duplicates:
        duplicate_tags = duplicate.tags.all()
        image.tags.add(*duplicate_tags)
        duplicate.delete()
        images_deleted+=1
        print(str(images_deleted))

运行它我得到以下异常:

  

django.db.utils.IntegrityError:在表格上插入或更新   “crawlers_image_tags”违反了外键约束   “crawlers_image_t_image_id_72a28d1d54e11b5f_fk_crawlers_image_id”

     

详细信息:表中不存在键(image_id)=(5675)   “crawlers_image”。

任何人都可以解释究竟是什么问题?

编辑:

模型:

class Tag(models.Model):
    name = models.CharField(max_length=100)

    def __str__(self):
        return self.name

class Image(models.Model):

    origins = (
        ('PX', 'Pexels'),
        ('MG', 'Magdeleine'),
        ('FC', 'FancyCrave'),
        ('SS', 'StockSnap'),
        ('PB', 'PixaBay'),
        ('TP', 'tookapic'),
        ('KP', 'kaboompics'),
        ('PJ', 'picjumbo'),
        ('LS', 'LibreShot')  
    )

    source_url = models.URLField(max_length=400)
    page_url = models.URLField(unique=True, max_length=400)
    thumbnail = models.ImageField(upload_to='thumbs', null=True)
    origin = models.CharField(choices=origins, max_length=2)
    tags = models.ManyToManyField(Tag)
    hash = models.CharField(max_length=200)

    def __str__(self):
        return self.page_url

    def create_hash(self):
        thumbnail = Imagelib.open(self.thumbnail.path)
        thumbnail = thumbnail.convert('RGB')
        self.hash = blockhash(thumbnail, 24)
        self.save(update_fields=["hash"])

    def create_thumbnail(self, image_url):
        if not self.thumbnail:
            if  not image_url:
                image_url = self.source_url
            headers = {
                'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36',
            }
            for i in range(5):
                r = requests.get(image_url, stream=True, headers=headers)
                if r.status_code != 200 and r.status_code!= 304:
                    print("error loading image url status code: {}".format(r.status_code))
                    time.sleep(2)
                else:
                    break

            if r.status_code != 200 and r.status_code!= 304:
                    print("giving up on this image, final status code: {}".format(r.status_code))
                    return False

            # Create the thumbnail of dimension size
            size = 500, 500
            img = Imagelib.open(r.raw)
            thumb = ImageOps.fit(img, size, Imagelib.ANTIALIAS)

            # Get the image name from the url
            img_name = os.path.basename(image_url.split('?', 1)[0])


            file_path = os.path.join(djangoSettings.MEDIA_ROOT, "thumb" + img_name)
            thumb.save(file_path, 'JPEG')

            # Save the thumbnail in the media directory, prepend thumb
            self.thumbnail.save(
                img_name,
                File(open(file_path, 'rb')))

            os.remove(file_path)
            return True

2 个答案:

答案 0 :(得分:2)

让我们一步一步检查你的代码。

说,你的数据库中有3张图片(为简单起见,我跳过了不相关的字段):

Image(pk=1, hash="d2ffacb...e3')
Image(pk=2, hash="afcbdee...77')
Image(pk=3, hash="d2ffacb...e3')

正如我们所看到的,第一和第三个图像具有完全相同的哈希值。我们假设您的所有图片都有一些标签。现在回到你的代码。让我们检查第一次迭代会发生什么:

  1. 将从数据库中获取具有相同散列的所有图像,这将只是图像pk=3
  2. 遍历该图像会将所有标记从该副本复制到原始标记。没有错。
  3. 遍历该图像也会删除它们。
  4. 因此,在第一次迭代后,pk=3的图像不再存在。

    下一次迭代,图片pk=2。什么都不会发生,因为没有重复。

    下一次迭代,图片pk=3

    1. 将从数据库中获取具有相同散列的所有图像,这将只是图像pk=1
    2. 遍历该图像会将所有标记从该副本复制到原始标记。但是等等......数据库中没有图像pk=3,我们无法为其分配任何标记。这会抛出你的IntegrityError
    3. 为了避免这种情况,你应该只从数据库中获取外部for循环中的原始数据库。要做到这一点,你可以这样做:

      images = Image.objects.distinct('hash')
      

      你也可以在这里添加一些顺序,所以总会有一个具有较低ID的图像作为原始图像被提取:

      images = Image.objects.order_by('id').distinct('hash')
      

答案 1 :(得分:2)

这与查询集的评估策略有关。

job.tasks.work.each do |job| 返回一个thunk - 即一种可迭代图像序列的承诺。在此阶段不执行SQL查询。

当你开始迭代它时 - Image.objects.all() - 评估SQL查询。您现在在内存中有一个图像对象列表。

现在,假设您在数据库中有四个图像 - ids 0,1,2和3. 0和3是重复的。处理第一张图片,将3张作为副本。您删除3. 图像3仍然在for image in images迭代器中。当你到达那里时,你将尝试添加从图像0到图像3的标签集合的标签。这将触发完整性错误,因为图像3已被删除。

简单的解决方法是保留要删除的图像累加器,并在最后完成所有操作。

images

编辑:更正了错误的原因,正如GwynBleidD所指出的那样。