【问题标题】:How do I catch IntegrityError in Django when using UniqueConstraint across multiple fields?跨多个字段使用 UniqueConstraint 时如何在 Django 中捕获 IntegrityError?
【发布时间】:2019-04-15 19:29:53
【问题描述】:

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

Django/Python/Postgres 的新手,我认为我可以通过覆盖 save() 函数来处理它,但是无法访问用户,这是检查的一部分,我已经阅读过这不应该在这里处理。是否有我应该使用的尝试/保存捕获/消息?我尝试了一些没有运气的事情。我在这里看到过类似的问题,但它们没有帮助。任何帮助表示赞赏。

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%}

我想捕获异常并保存记录(如果不是重复的),或者在屏幕上显示“技能组已存在”的消息并将用户留在创建页面上。另外,如果这是最好的解决方案,我可以删除 UniqueConstraint 并使用代码处理。

【问题讨论】:

    标签: django duplicates unique-constraint


    【解决方案1】:

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

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

    • “具有此用户和技能组的技能组已经存在。”

    但只有在表单中包含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 作为初始值传递到隐藏表单字段中,因此不需要用户输入。

    【讨论】:

    • 非常感谢!我在许多网站上阅读了很多解决方案,但在任何地方都没有看到任何关于将用户添加到表单的信息。
    • 很好的答案,谢谢!似乎在 UpdateView 的情况下,不需要 (view.)get_initial() 方法,将 HiddenInput 添加到 (form.)Meta 中就足够了。
    • 这修复了它,但如果我没记错的话,它使客户端可以修改隐藏的输入并随之更改用户,这应该是不可能的。为了防止这种情况发生,您需要通过添加 form.instance.user = self.request.user 来覆盖 form_valid 中来自该输入的任何内容。
    【解决方案2】:

    尝试验证表单类中的 skill_group 字段。定义 clean_skill_group 方法like in docs。在那里您可以获取与您的用户相关的所有 SkillGroup 对象的查询集(如here),然后比较技能。但是您需要在调用 form.is_valid() 之前以某种方式将您的 User 对象或 user_id (以获取 User 对象)推送到表单(或再调用一次 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 
    

    【讨论】:

    • 谢谢。第一个解决方案有效,但这是在不使用 UniqueConstraint 的情况下解决它的方法。感谢您的意见!
    猜你喜欢
    • 1970-01-01
    • 2021-11-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-04-02
    • 2021-04-28
    • 1970-01-01
    相关资源
    最近更新 更多