Django:使用排除字段验证ModelForm中的unique_together约束

时间:2015-08-27 23:03:12

标签: django django-forms django-views

我有一张表格:

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的验证,而不是自己编写?

我虽然将用户传递给表单中的隐藏字段(而不是将其排除),但这看起来不安全(即用户值可能会被更改)?

2 个答案:

答案 0 :(得分:4)

form.instance.user中设置form_valid为时已晚,因为此时表单已经过验证。由于这是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)

答案 1 :(得分:0)

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

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)使用所需的验证更新表单的清理方法

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