Django不调用模型的clean方法

39

我有一个视图,它从CSV文件创建模型。我已经在模型类定义中添加了"clean"方法,但是当模型被创建时,该方法并没有被调用。

以下是models.py的示例:

class Run(models.Model):
    name = models.CharField(max_length=120)
    tested_build = models.ForeignKey('apps.Build')
    timestamp_start = models.DateTimeField()
    timestamp_end = models.DateTimeField()

class CommonMeasurement(models.Model):
    timestamp = models.DateTimeField()
    value = models.FloatField()
    run = models.ForeignKey(Run)

    def clean(self):
        super(CommonMeasurement, self).clean()
        print 'here we go'
        if self.timestamp < self.run.timestamp_start or self.timestamp > self.run.timestamp_end:
            raise django_excetions.ValidationError('Measurement is outside the run')


class ClientMeasurement(CommonMeasurement):
    metric = models.ForeignKey(ClientMetric)
    account = models.CharField(max_length=120, blank=True)

这里是我的表单视图代码示例:

class BaseMeasurementsUpload(generic_views.FormView):
    template_name = 'upload.html'
    models_to_upload = None   

    def get_success_url(self):
        return self.request.get_full_path()

    def form_valid(self, form):
        uploader = getattr(importers, form.cleaned_data['uploader'])
        try:
            the_uploader = uploader(form.cleaned_data, self.models_to_upload)
            upload_results = the_uploader.get_result_info()
        except django_exceptions.ValidationError as e:
            custom_errors = e
        return render_to_response(self.template_name,
                                  {'upload_results': upload_results,
                                   'custom_errors': custom_errors},
                                  context_instance=RequestContext(self.request))


class ClientMeasurementsUploadView(BaseMeasurementsUpload):
    form_class = forms.ClientMeasurementsUploadForm
    models_to_upload = models.ClientMeasurement

    def get_form(self, form_class):
        uploaders = (('MeasurementsSimpleCsv', importers.MeasurementsSimpleCsv.__doc__),
                     ('ClientMeasurementsBulkCsv', importers.ClientMeasurementsBulkCsv.__doc__,))
        if self.request.POST:
            # get bound form
            return self.form_class(uploaders,
                                   self.request.POST,
                                   self.request.FILES)
        else:
            return forms.ClientMeasurementsUploadForm(uploaders)

导入程序执行实际验证,并为每个模型调用创建方法。


您可以发布您的视图代码吗?也许您没有在ModelForm上调用is_valid函数? - Fiver
1
clean 方法适用于 ModelFormForm 对象,而不是模型。它永远不会自动调用。 - karthikr
1
@karthikr -- 虽然你说的 clean() 不会自动调用是正确的,但你错误地认为它 "不适用于模型" -- 请参阅文档上的说明。 - Paul Bissex
4个回答

53

为了调用模型的清理方法,我们需要重写保存方法。请查看链接:https://docs.djangoproject.com/en/2.0/ref/models/instances/#django.db.models.Model.clean

class CommonMeasurement(models.Model):
    timestamp = models.DateTimeField()
    value = models.FloatField()
    run = models.ForeignKey(Run)

    def clean(self):
        if self.timestamp < self.run.timestamp_start or self.timestamp > self.run.timestamp_end:
            raise django_excetions.ValidationError('Measurement is outside the run')

    def save(self, *args, **kwargs):
        self.full_clean()
        return super(CommonMeasurement, self).save(*args, **kwargs)

谢谢。在djangorestframework中,使用这个方法解决了我的问题,因为clean方法没有被调用。 - SirSaleh
@SirSaleh,你是否成功将错误发送回REST API,还是只显示了调试器错误? - aviya.developer

13

我已经找到了覆盖方法的解决方案:

class CommonMeasurement(models.Model):
    timestamp = models.DateTimeField()
    value = models.FloatField()
    run = models.ForeignKey(Run)

    objects = models.Manager()
    analyzes = managers.MeasureStatManager()

    def save(self, **kwargs):
        self.clean()
        return super(CommonMeasurement, self).save(**kwargs)

    def clean(self):
        super(CommonMeasurement, self).clean()
        print 'here we go'
        if self.timestamp < self.run.timestamp_start or self.timestamp > self.run.timestamp_end:
            raise django_excetions.ValidationError('Measurement is outside the run')

但我不确定这是否是一个好决定。


8
我认为如果在保存时调用"clean"方法,将会防止"ValidationError"传递到视图/模板中,这样用户就不会看到错误消息,而是看到一个包含跟踪信息的调试消息(当debug=True时),或者看到一个服务器错误(当debug=False时)。 - Fiver
3
实际上,验证错误已经成功传递给了上层视图层。 - Nikolai Golub
3
这段代码可以运行,但是Django在调用save()方法时不支持ValidationError异常,会返回一个(糟糕的)500服务器错误,而不是400错误请求。我仍在寻找解决方案。 - John Pang
我正在使用Django==2.2.5和djangorestframework==3.10.3,但仍然遇到这个烦人的500错误!有什么解决方案吗? - mhyousefi
如果有人遇到了Django==3.2中出现500调试消息而不是正常的管理员错误消息的问题,最好从django.core.exceptions.ValidationError中调用ValidationError - Damir Nafikov
显示剩余2条评论

7

使用我的解决方案是一个不好的做法吗?如果从save()方法自动调用clean()方法,那么我会破坏哪些兼容性? - Nikolai Golub
  1. 我的解决方法是将所有我原本要放在clean()中的代码移到save()函数中。在这方面,我认为你的解决方案更加干净。
  2. 我不知道它应该怎么破坏。
- Thomas Maurin

2

4
是的,但是 save 方法也不会调用 full_clean - Flimm
2
请点击此链接 https://github.com/django/django/blob/14e2b1b065085c1d2d3e94ebaeefe25e12595a00/django/forms/models.py#L403,以便在未来不会出现混淆无效的情况。 - Adam Barnes
1
谢谢@AdamBarnes,我已经在答案中更新了链接 ;) - Ariel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接