You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python3.6+Django2.0中Admin内联唯一约束重复键值错误求助

解决Django通用内联中"duplicate key value violates unique constraint"错误

嘿,我帮你分析下这个问题,大概率是通用内联的表单处理或者模型定义的小疏漏导致的,咱们一步步来解决:

首先检查模型定义的完整性

你提供的ToolResult模型代码里缺少了通用外键必须的字段!因为要使用GenericRelation和GenericForeignKey,ToolResult必须包含content_type、object_id字段以及对应的content_object属性,否则Django无法正确关联到Domain实例,这很可能是触发唯一约束错误的根源之一。

修正后的ToolResult模型应该是这样的:

from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.fields import GenericForeignKey
from django.utils.six import python_2_unicode_compatible

@python_2_unicode_compatible
class ToolResult(models.Model):
    tool = models.ForeignKey('projects.Tool', on_delete=models.CASCADE)
    # 新增通用外键所需字段
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')

    class Meta:
        unique_together = ('content_type', 'object_id', 'tool')

    def __str__(self):
        return f"{self.tool} - {self.content_object}"

方案一:自定义通用内联的表单集

如果模型定义没问题,那可能是Django的通用内联表单集在编辑场景下没有正确识别已有的关联记录,导致重复提交相同的(content_type, object_id, tool)组合。我们可以通过重写内联类的get_formset方法来修复:

from django.contrib.contenttypes.admin import GenericTabularInline
from .models import ToolResult

class ToolResultGenericTabularInline(GenericTabularInline):
    model = ToolResult
    extra = 1  # 控制默认显示的空白表单数量,避免不必要的重复提交
    can_delete = True

    def get_formset(self, request, obj=None, **kwargs):
        formset = super().get_formset(request, obj, **kwargs)
        # 编辑对象时,确保表单集只加载当前Domain关联的ToolResult记录
        if obj is not None:
            formset.queryset = obj.tool_result.all()
        return formset

方案二:在模型层面添加重复校验

为了在表单提交前就拦截重复的记录,我们可以给ToolResult模型添加clean方法,提前验证是否存在重复的关联:

from django.core.exceptions import ValidationError

class ToolResult(models.Model):
    # ... 其他字段定义 ...

    def clean(self):
        super().clean()
        # 排除当前实例(编辑场景下),检查是否存在相同的关联记录
        existing_records = ToolResult.objects.filter(
            content_type=self.content_type,
            object_id=self.object_id,
            tool=self.tool
        ).exclude(pk=self.pk)
        
        if existing_records.exists():
            raise ValidationError(f"该工具 {self.tool} 已经关联到当前对象了,请勿重复添加!")

这个方法会在表单验证阶段触发,避免错误传到数据库层面才抛出异常,用户体验也更好。

方案三:重写内联类的保存逻辑

如果上面的方法还没解决问题,我们可以直接控制内联对象的保存过程,用get_or_create来避免重复创建:

from django.contrib.contenttypes.models import ContentType

class ToolResultGenericTabularInline(GenericTabularInline):
    model = ToolResult
    extra = 1

    def save_formset(self, request, form, formset, change):
        # 获取当前正在编辑的Domain实例
        domain_obj = form.instance
        # 遍历表单集中的每个表单
        for tool_form in formset:
            if tool_form.cleaned_data and not tool_form.cleaned_data.get('DELETE', False):
                tool = tool_form.cleaned_data.get('tool')
                if tool:
                    # 用get_or_create确保不会重复创建
                    tool_result, created = ToolResult.objects.get_or_create(
                        content_type=ContentType.objects.get_for_model(domain_obj),
                        object_id=domain_obj.pk,
                        tool=tool,
                        defaults=tool_form.cleaned_data
                    )
                    # 如果是已存在的记录,更新其他字段(如果有需要的话)
                    if not created:
                        for field, value in tool_form.cleaned_data.items():
                            setattr(tool_result, field, value)
                        tool_result.save()
            else:
                # 处理删除操作
                if tool_form.instance.pk:
                    tool_form.instance.delete()

最后检查Admin的注册

确保你正确注册了Domain的Admin,并添加了这个内联类:

from django.contrib import admin
from .models import Domain

@admin.register(Domain)
class DomainAdmin(admin.ModelAdmin):
    inlines = [ToolResultGenericTabularInline]

按照上面的步骤排查和修改,应该就能解决这个重复键约束的问题了。

内容的提问来源于stack exchange,提问作者Arti

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 06:32:03