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
相关产品推荐
相关产品推荐

