跨多个字段使用 UniqueConstraint 时,如何在 Django 中捕获 IntegrityError?

How do I catch IntegrityError in Django when using UniqueConstraint across multiple fields?

我有一个模型,其中用户作为 1 个字段(外键)和另一个字段 skill_group。我需要确保用户不会添加重复的技能组,所以我添加了一个 UniqueConstraint。这是在 /skillgroup/create/ 处出现 IntegrityError 系统错误时起作用 重复键值违反唯一约束 "unique_skillgroup" - 如何捕获此异常并在重复时通知用户;否则保存它?

Django/Python/Postgres 的新手,我认为我可以通过覆盖 save() 函数来处理它,但是无法访问用户,这是检查的一部分,我已经阅读过这不应该在这里处理.我应该雇用 try/save catch/message 吗?我尝试了一些没有运气的事情。我在这里看到过类似的问题,但没有帮助。感谢任何帮助。

models.py
class SkillGroup(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    skill_group = models.CharField(max_length=35)
    sequence = models.IntegerField(default=999)

    class Meta:
        constraints = [
            models.UniqueConstraint(fields=['user', 'skill_group'], name='unique_skillgroup'),
        ]

    def __str__(self):
        return self.skill_group

    def get_absolute_url(self):
        return reverse('skillgroup-list')
views.py
class SkillGroupCreateView(LoginRequiredMixin, CreateView):
    model = SkillGroup
    fields = ['skill_group']

    def form_valid(self, form):
        form.instance.user = self.request.user
        form.instance.sequence = SkillGroup.objects.filter(user=self.request.user).order_by('sequence').last().sequence + 1
        return super().form_valid(form)
skillgroup_form.html
{% extends "recruiter/baseskills.html" %}
{% load crispy_forms_tags %}

{% block content%}
  <div class="content-section">
      <form method="post">
        {% csrf_token %}
        <fieldset class="form-group">
          <legend class="border-bottom mb-4">Skill Group</legend>
          {{ form|crispy }}
        </fieldset>
        <div class="form-group">
          <button class="btn btn-outline-info" type="submit">Add Skill Group</button>
        </div>
      </form>
  </div>
{% endblock content%}

我想要么捕获异常并保存记录(如果不是重复的),要么在屏幕上显示消息说 "Skill Group already exists" 并让用户留在创建页面上。另外,如果这是最好的解决方案,我可以删除 UniqueConstraint 并使用代码处理。

你在这里无意中绕过了 Django 的表单验证,然后试图将无效输入保存到数据库中,这就是为什么 Django 从数据库中反馈一个丑陋的 IntegrityError 而不是优雅地处理错误。

如果您在表单中提交了重复的用户和技能组,您的 CreateView 将有助于 return 将错误消息返回到您的表单模板中:

  • "Skill group with this User and Skill group already exists."

但只有当您在表单中包含 User 字段时,它才能执行此操作。我假设您已排除 User 以保持表单模板整洁,但这会阻止 Django 的表单验证检查组合是否已存在。

要解决此问题,请将 User 作为隐藏输入添加到您的表单字段中。我认为使用 CreateView 的幕后魔法是不可能的,因此您需要创建一个 SkillGroupForm 来处理它。

# forms.py
from django import forms
from .models import SkillGroup

class SkillGroupForm(forms.ModelForm):
    class Meta:
        model = SkillGroup
        fields = ('user', 'skill_group')
        widgets = {
            'user': forms.HiddenInput,
        }

# views.py
from .forms import SkillGroupForm

class SkillGroupCreateView(LoginRequiredMixin, CreateView):
    model = SkillGroup
    form_class = SkillGroupForm

    def get_initial(self):
        return {'user': self.request.user}

    def form_valid(self, form):
        form.instance.sequence = SkillGroup.objects.filter(user=self.request.user).order_by('sequence').last().sequence + 1
        return super().form_valid(form)

get_initial 方法将 request.user 作为初始值传递给隐藏的表单字段,因此不需要用户输入。

尝试验证表单中的 skill_group 字段 class。定义clean_skill_group方法like in docs.There you can get queryset of all SkillGroup objects related to your User (like here) 然后比较技巧。但是你需要以某种方式将你的 User 对象或 user_id (然后得到 User 对象)推到表单之前 form.is_valid() 将被调用(或调用一个更多时间 form.is_valid() 之后)。然后在您的 html 模板中显示表单错误。

class YourForm(forms.ModelForm):
    ....some fields, Meta....
    def clean_skill_group(self):
        your_user_object = ....
        previously_created_skills = your_user_object.skill_group_set.all()
        skill_input = self.cleaned_data["skill_group"]
        if skill_input in previously_created_skills:
            raise forms.ValidationError(("This skill group is already exist"), code="invalid") # I suppose you are using model form