【问题标题】:Django: validating unique_together constraints in a ModelForm with excluded fieldsDjango:使用排除字段验证 ModelForm 中的 unique_together 约束
【发布时间】:2015-11-22 12:13:31
【问题描述】:

我有一个表格:

class CourseStudentForm(forms.ModelForm):

    class Meta:
        model = CourseStudent
        exclude = ['user']

对于有一些复杂要求的模型:

class CourseStudent(models.Model):

    user = models.ForeignKey(settings.AUTH_USER_MODEL)
    semester = models.ForeignKey(Semester)
    block = models.ForeignKey(Block)
    course = models.ForeignKey(Course)
    grade = models.PositiveIntegerField()

    class Meta:
        unique_together = (
            ('semester', 'block', 'user'), 
            ('user','course','grade'),
        )

我希望新对象使用 CourseStudent.user 的当前登录用户:

class CourseStudentCreate(CreateView):
    model = CourseStudent
    form_class = CourseStudentForm
    success_url = reverse_lazy('quests:quests')


    def form_valid(self, form):
        form.instance.user = self.request.user
        return super(CourseStudentCreate, self).form_valid(form)

然而,这可行,因为用户不是表单的一部分,它错过了 Django 否则会使用 unique_together 约束进行的验证。

如何让我的表单和视图使用 Django 对这些约束的验证,而不必自己编写?

我虽然在表单的隐藏字段中传递用户(而不是排除它),但这似乎不安全(即用户值可以更改)?

【问题讨论】:

    标签: django django-forms django-views


    【解决方案1】:

    form_valid 中设置form.instance.user 为时已晚,因为那时表单已经通过验证。由于这是您的 form_valid 方法所做的唯一自定义操作,因此您应该删除它。

    您可以覆盖get_form_kwargs,并在已设置用户的情况下传入CourseStudent 实例:

    class CourseStudentCreate(CreateView):
        model = CourseStudent
        form_class = CourseStudentForm
        success_url = reverse_lazy('quests:quests')
    
        def get_form_kwargs(self):
            kwargs = super(CreateView, self).get_form_kwargs()
            kwargs['instance'] = CourseStudent(user=self.request.user)
            return kwargs
    

    这还不足以让它工作,因为表单验证会跳过引用user 字段的唯一共同约束。解决方法是覆盖模型表单的full_clean()方法,在模型上显式调用validate_unique()。覆盖 clean 方法(如您通常所做的那样)不起作用,因为此时尚未使用表单中的值填充实例。

    class CourseStudentForm(forms.ModelForm):
    
        class Meta:
            model = CourseStudent
            exclude = ['user']
    
        def full_clean(self):
            super(CourseStudentForm, self).full_clean()
            try:
                self.instance.validate_unique()
            except forms.ValidationError as e:
                self._update_errors(e)
    

    【讨论】:

    • 使用此代码,我收到此错误:TypeError at /courses/create/ ModelFormMetaclass object argument after ** must be a mapping, not NoneType
    • 我错过了get_form_kwargs的退货声明,请您再试一次。如果您仍然遇到错误,请使用完整的回溯更新您的问题 - 一行通常不足以查看发生了什么。
    • (感谢您的帮助)表单有效,但无法验证模型:IntegrityError at /courses/create/ UNIQUE constraint failed: courses_coursestudent.semester_id, courses_coursestudent.block_id, courses_coursestudent.user_id
    • 很好的答案。而不是使用“私有”方法self._update_errors(e),调用self.add_error(field=None, error=e)不是更合适吗? (field=None 会变成non_field_error
    • @djvg 是的,使用self.add_error() 可能会更好。我看不出为什么我在写答案时使用 self._update_errors(e) 的充分理由,因为 add_error 是 2014 年在 Django 1.7 中添加的。
    【解决方案2】:

    这对我有用,请检查。请求反馈/建议。 (基于this SO 帖子。)

    1) 修改 POST 请求以发送 exclude_field。

    def post(self, request, *args, **kwargs):
      obj = get_object_or_404(Model, id=id)
      request.POST = request.POST.copy()
      request.POST['excluded_field'] = obj
      return super(Model, self).post(request, *args, **kwargs)
    

    2) 使用所需的验证更新表单的 clean 方法

    def clean(self):
        cleaned_data = self.cleaned_data
        product = cleaned_data.get('included_field')
        component = self.data['excluded_field']
    
        if Model.objects.filter(included_field=included_field, excluded_field=excluded_field).count() > 0:
            del cleaned_data['included_field']
            self.add_error('included_field', 'Combination already exists.')
    
        return cleaned_data
    

    【讨论】:

      猜你喜欢
      • 2011-01-09
      • 2017-04-26
      • 2016-12-20
      • 2018-10-10
      • 1970-01-01
      • 2011-08-08
      • 2022-01-10
      • 2012-08-13
      相关资源
      最近更新 更多