django.db.utils.IntegrityError 尝试删除重复图像时
django.db.utils.IntegrityError when trying to delete duplicate images
我有以下代码可以从我计算的感知哈希中删除重复图像。
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: insert or update on table
"crawlers_image_tags" violates foreign key constraint
"crawlers_image_t_image_id_72a28d1d54e11b5f_fk_crawlers_image_id"
DETAIL: Key (image_id)=(5675) is not present in table
"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
让我们逐步检查您的代码。
比如说,你的数据库中有 3 张图像(为简单起见,我跳过了不相关的字段):
Image(pk=1, hash="d2ffacb...e3')
Image(pk=2, hash="afcbdee...77')
Image(pk=3, hash="d2ffacb...e3')
正如我们所见,第一张和第三张图片具有完全相同的哈希值。假设您所有的图像都有一些标签。现在回到您的代码。让我们检查一下第一次迭代会发生什么:
- 将从数据库中获取具有相同哈希值的所有图像,这将是唯一的图像
pk=3
- 遍历该图像会将所有标签从该副本复制到原始标签。没有错。
- 迭代这些图像也会删除它们。
所以在第一次迭代后,带有 pk=3
的图像不再存在。
下一次迭代,图像 pk=2
。什么都不会发生,因为没有重复。
下一次迭代,图像 pk=3
。
- 将从数据库中获取具有相同哈希值的所有图像,这将是唯一的图像
pk=1
- 遍历该图像会将所有标签从该副本复制到原始标签。但是等等...数据库中没有图像
pk=3
,我们无法为其分配任何标签。那会抛出你的 IntegrityError
.
为避免这种情况,您应该在外部 for 循环中仅从数据库中获取原始数据。为此,您可以这样做:
images = Image.objects.distinct('hash')
您还可以在此处添加一些排序,因此始终会获取例如 ID 较低的图像作为原始图像:
images = Image.objects.order_by('id').distinct('hash')
这与查询集的评估策略有关。
Image.objects.all()
returns thunk - 也就是说,一种可迭代图像序列的承诺。 SQL 查询在此阶段未执行。
当您开始对其进行迭代时 - for image in images
- 将评估 SQL 查询。您现在在内存中有一个图像对象列表。
现在,假设数据库中有四张图片 - ID 0、1、2 和 3。0 和 3 是重复的。第一张图像被处理,出现 3 个副本。您删除了 3。但是 Image 3 仍在 images
迭代器 中。到达那里后,您将尝试将图像 0 中的标签添加到图像 3 的标签集合中。这将触发完整性错误,因为图像 3 已被删除。
简单的解决方法是保留一个要删除的图像的累加器,并在最后删除它们。
images = Image.objects.all()
images_to_delete = []
for image in images:
if image.pk in images_to_delete:
pass
else:
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)
images_to_delete.append(duplicate.pk)
print(len(images_to_delete))
for pk in images_to_delete:
Image.objects.get(pk=pk).delete()
编辑: 更正了错误的近因,正如 GwynBleidD 所指出的那样。
我有以下代码可以从我计算的感知哈希中删除重复图像。
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: insert or update on table "crawlers_image_tags" violates foreign key constraint "crawlers_image_t_image_id_72a28d1d54e11b5f_fk_crawlers_image_id"
DETAIL: Key (image_id)=(5675) is not present in table "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
让我们逐步检查您的代码。
比如说,你的数据库中有 3 张图像(为简单起见,我跳过了不相关的字段):
Image(pk=1, hash="d2ffacb...e3')
Image(pk=2, hash="afcbdee...77')
Image(pk=3, hash="d2ffacb...e3')
正如我们所见,第一张和第三张图片具有完全相同的哈希值。假设您所有的图像都有一些标签。现在回到您的代码。让我们检查一下第一次迭代会发生什么:
- 将从数据库中获取具有相同哈希值的所有图像,这将是唯一的图像
pk=3
- 遍历该图像会将所有标签从该副本复制到原始标签。没有错。
- 迭代这些图像也会删除它们。
所以在第一次迭代后,带有 pk=3
的图像不再存在。
下一次迭代,图像 pk=2
。什么都不会发生,因为没有重复。
下一次迭代,图像 pk=3
。
- 将从数据库中获取具有相同哈希值的所有图像,这将是唯一的图像
pk=1
- 遍历该图像会将所有标签从该副本复制到原始标签。但是等等...数据库中没有图像
pk=3
,我们无法为其分配任何标签。那会抛出你的IntegrityError
.
为避免这种情况,您应该在外部 for 循环中仅从数据库中获取原始数据。为此,您可以这样做:
images = Image.objects.distinct('hash')
您还可以在此处添加一些排序,因此始终会获取例如 ID 较低的图像作为原始图像:
images = Image.objects.order_by('id').distinct('hash')
这与查询集的评估策略有关。
Image.objects.all()
returns thunk - 也就是说,一种可迭代图像序列的承诺。 SQL 查询在此阶段未执行。
当您开始对其进行迭代时 - for image in images
- 将评估 SQL 查询。您现在在内存中有一个图像对象列表。
现在,假设数据库中有四张图片 - ID 0、1、2 和 3。0 和 3 是重复的。第一张图像被处理,出现 3 个副本。您删除了 3。但是 Image 3 仍在 images
迭代器 中。到达那里后,您将尝试将图像 0 中的标签添加到图像 3 的标签集合中。这将触发完整性错误,因为图像 3 已被删除。
简单的解决方法是保留一个要删除的图像的累加器,并在最后删除它们。
images = Image.objects.all()
images_to_delete = []
for image in images:
if image.pk in images_to_delete:
pass
else:
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)
images_to_delete.append(duplicate.pk)
print(len(images_to_delete))
for pk in images_to_delete:
Image.objects.get(pk=pk).delete()
编辑: 更正了错误的近因,正如 GwynBleidD 所指出的那样。