【问题标题】:Handling Exceptions in Python Django Crispy Forms处理 Python Django Crispy 表单中的异常
【发布时间】:2021-02-02 20:43:46
【问题描述】:

我是 Python 3.8.2/Django 3.1.1 的新手,我正在尝试找出向模型添加规则的正确方法,以便可以从表单和 Web 服务中调用它们,我遇到的问题是有些错误没有被正确捕获,它们正在生成 500 错误。

这是我的初始模型:

class TotalPoints(TimeStampedModel):
    course = models.ForeignKey(Course, on_delete=models.CASCADE, related_name='TotalPoints_Course', null=False, blank=False, verbose_name='Curso')
    kit = models.ForeignKey(Kit, on_delete=models.CASCADE, related_name='TotalPoints_Kit', null=False, blank=False, verbose_name='Kit')
    bought = models.PositiveIntegerField(default=0, null=False, blank=False, verbose_name='Comprados')
    assigned = models.PositiveIntegerField(default=0, null=False, blank=False, verbose_name='Asignados')
    available = models.PositiveIntegerField(default=0, null=False, blank=False, verbose_name='Disponibles')
    class Meta:
        db_table = 'app_totalpoints'
        constraints = [
            models.UniqueConstraint(fields=['course','kit'], name='app_totalpoints.course-kit')
        ]

字段被声明为 PositiveIntegerField 以避免负值

这是我的表单和 CreateView

class TotalPointsForm(forms.ModelForm):
    class Meta:
        model = TotalPoints
        fields = ['id', 'course', 'kit', 'available', 'bought', 'assigned']

class TotalPointsCreateView(CreateView):
    model = TotalPoints
    form_class = TotalPointsForm
    template_name = 'app/form_base.html'
    success_url='../list/'

这是我的模板:

{# app/templates/app/form_base.html #}
{% load crispy_forms_tags %}
<!doctype html>
<html lang="en">
    <head lang="es">
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
        <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.10.22/css/jquery.dataTables.css">
        <title>{% block page_title %}{% endblock %}</title>
    </head>
    <body>
        <div class="container-fluid">
            <div class="jumbotron">
                <h1 class="display-4">{% block form_title %}{% endblock %}</h1>
                <p class="lead">{% block form_subtitle %}{% endblock %}
                {% if user.is_authenticated %}
                    ( Usuario: {{ user.get_username }} )
                {% else %}
                    ( Usuario no registrado. )
                {% endif %}
                </p>
                <hr class="my-4">
                <div id='form-errors'>{{ form_errors }}</div>
                <form method="post">
                    {% csrf_token %}
                    {{ form|crispy }}
                    <button type="submit" class="btn btn-success">Grabar</button>
                    <!-- <a class="btn btn-primary" href="../list" role="button">Cancelar</a> -->
                    <a class="btn btn-primary" href="#" onclick="location.href = document.referrer; return false;" role="button">Cancelar</a>
                </form>
            </div>
        </div>
        <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
        <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
        <script type="text/javascript" charset="utf8" src="https://cdn.datatables.net/1.10.22/js/jquery.dataTables.js"></script>
    </body>
</html>

到目前为止一切正常,保存错误的值会在表单上返回一条消息:

要实施的规则是: 可用 = 已购买 - 已分配 (Disponibles = Comprados - Asignados)

这是我尝试的第一个版本,修改了Model:

class TotalPoints(TimeStampedModel):
    course = models.ForeignKey(Course, on_delete=models.CASCADE, related_name='TotalPoints_Course', null=False, blank=False, verbose_name='Curso')
    kit = models.ForeignKey(Kit, on_delete=models.CASCADE, related_name='TotalPoints_Kit', null=False, blank=False, verbose_name='Kit')
    bought = models.PositiveIntegerField(default=0, null=False, blank=False, verbose_name='Comprados')
    assigned = models.PositiveIntegerField(default=0, null=False, blank=False, verbose_name='Asignados')
    available = models.PositiveIntegerField(default=0, null=False, blank=False, verbose_name='Disponibles')
    class Meta:
        db_table = 'app_totalpoints'
        constraints = [
            models.UniqueConstraint(fields=['course','kit'], name='app_totalpoints.course-kit')
        ]
    def save(self, *args, **kwargs):
        #AVAILABLE = BOUGHT - ASSIGNED
        self.available = self.bought - self.assigned
        super(TotalPoints, self).save(*args, **kwargs)

如果所有值都是正数,则该规则有效并正确计算可用 如果购买是否定的,则正确的错误会出现在表单上,​​但如果可用变为否定(购买 追踪:

Traceback (most recent call last):
  File "/home/egarza/projects/python/DjangoRESTABC/django-rest-abc-env/lib/python3.8/site-packages/django/db/backends/utils.py", line 84, in _execute
    return self.cursor.execute(sql, params)
psycopg2.errors.CheckViolation: el nuevo registro para la relación «app_totalpoints» viola la restricción «check» «app_totalpoints_available_check»
DETAIL:  La fila que falla contiene (36, 2020-10-19 21:16:05.356574+00, 2020-10-20 15:57:39.007548+00, 10, 12, 23, 1, -2).


The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/home/egarza/projects/python/DjangoRESTABC/django-rest-abc-env/lib/python3.8/site-packages/django/core/handlers/exception.py", line 47, in inner
    response = get_response(request)
  File "/home/egarza/projects/python/DjangoRESTABC/django-rest-abc-env/lib/python3.8/site-packages/django/core/handlers/base.py", line 179, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/home/egarza/projects/python/DjangoRESTABC/django-rest-abc-env/lib/python3.8/site-packages/django/contrib/auth/decorators.py", line 21, in _wrapped_view
    return view_func(request, *args, **kwargs)
  File "/home/egarza/projects/python/DjangoRESTABC/django-rest-abc-env/lib/python3.8/site-packages/django/views/generic/base.py", line 70, in view
    return self.dispatch(request, *args, **kwargs)
  File "/home/egarza/projects/python/DjangoRESTABC/django-rest-abc-env/lib/python3.8/site-packages/django/views/generic/base.py", line 98, in dispatch
    return handler(request, *args, **kwargs)
  File "/home/egarza/projects/python/DjangoRESTABC/django-rest-abc-env/lib/python3.8/site-packages/django/views/generic/edit.py", line 194, in post
    return super().post(request, *args, **kwargs)
  File "/home/egarza/projects/python/DjangoRESTABC/django-rest-abc-env/lib/python3.8/site-packages/django/views/generic/edit.py", line 142, in post
    return self.form_valid(form)
  File "/home/egarza/projects/python/DjangoRESTABC/django-rest-abc-env/lib/python3.8/site-packages/django/views/generic/edit.py", line 125, in form_valid
    self.object = form.save()
  File "/home/egarza/projects/python/DjangoRESTABC/django-rest-abc-env/lib/python3.8/site-packages/django/forms/models.py", line 460, in save
    self.instance.save()
  File "/home/egarza/projects/python/DjangoRESTABC/abc_project/app/models.py", line 138, in save
    super(TotalPoints, self).save(*args, **kwargs)
  File "/home/egarza/projects/python/DjangoRESTABC/django-rest-abc-env/lib/python3.8/site-packages/django/db/models/base.py", line 753, in save
    self.save_base(using=using, force_insert=force_insert,
  File "/home/egarza/projects/python/DjangoRESTABC/django-rest-abc-env/lib/python3.8/site-packages/django/db/models/base.py", line 790, in save_base
    updated = self._save_table(
  File "/home/egarza/projects/python/DjangoRESTABC/django-rest-abc-env/lib/python3.8/site-packages/django/db/models/base.py", line 872, in _save_table
    updated = self._do_update(base_qs, using, pk_val, values, update_fields,
  File "/home/egarza/projects/python/DjangoRESTABC/django-rest-abc-env/lib/python3.8/site-packages/django/db/models/base.py", line 926, in _do_update
    return filtered._update(values) > 0
  File "/home/egarza/projects/python/DjangoRESTABC/django-rest-abc-env/lib/python3.8/site-packages/django/db/models/query.py", line 803, in _update
    return query.get_compiler(self.db).execute_sql(CURSOR)
  File "/home/egarza/projects/python/DjangoRESTABC/django-rest-abc-env/lib/python3.8/site-packages/django/db/models/sql/compiler.py", line 1522, in execute_sql
    cursor = super().execute_sql(result_type)
  File "/home/egarza/projects/python/DjangoRESTABC/django-rest-abc-env/lib/python3.8/site-packages/django/db/models/sql/compiler.py", line 1156, in execute_sql
    cursor.execute(sql, params)
  File "/home/egarza/projects/python/DjangoRESTABC/django-rest-abc-env/lib/python3.8/site-packages/django/db/backends/utils.py", line 98, in execute
    return super().execute(sql, params)
  File "/home/egarza/projects/python/DjangoRESTABC/django-rest-abc-env/lib/python3.8/site-packages/django/db/backends/utils.py", line 66, in execute
    return self._execute_with_wrappers(sql, params, many=False, executor=self._execute)
  File "/home/egarza/projects/python/DjangoRESTABC/django-rest-abc-env/lib/python3.8/site-packages/django/db/backends/utils.py", line 75, in _execute_with_wrappers
    return executor(sql, params, many, context)
  File "/home/egarza/projects/python/DjangoRESTABC/django-rest-abc-env/lib/python3.8/site-packages/django/db/backends/utils.py", line 84, in _execute
    return self.cursor.execute(sql, params)
  File "/home/egarza/projects/python/DjangoRESTABC/django-rest-abc-env/lib/python3.8/site-packages/django/db/utils.py", line 90, in __exit__
    raise dj_exc_value.with_traceback(traceback) from exc_value
  File "/home/egarza/projects/python/DjangoRESTABC/django-rest-abc-env/lib/python3.8/site-packages/django/db/backends/utils.py", line 84, in _execute
    return self.cursor.execute(sql, params)
django.db.utils.IntegrityError: el nuevo registro para la relación «app_totalpoints» viola la restricción «check» «app_totalpoints_available_check»
DETAIL:  La fila que falla contiene (36, 2020-10-19 21:16:05.356574+00, 2020-10-20 15:57:39.007548+00, 10, 12, 23, 1, -2).

我使用信号切换到 pre_save 中的规则,但结果是一样的: 版本 02 使用信号:

class TotalPoints(TimeStampedModel):
    course = models.ForeignKey(Course, on_delete=models.CASCADE, related_name='TotalPoints_Course', null=False, blank=False, verbose_name='Curso')
    kit = models.ForeignKey(Kit, on_delete=models.CASCADE, related_name='TotalPoints_Kit', null=False, blank=False, verbose_name='Kit')
    bought = models.PositiveIntegerField(default=0, null=False, blank=False, verbose_name='Comprados')
    assigned = models.PositiveIntegerField(default=0, null=False, blank=False, verbose_name='Asignados')
    available = models.PositiveIntegerField(default=0, null=False, blank=False, verbose_name='Disponibles')
    class Meta:
        db_table = 'app_totalpoints'
        constraints = [
            models.UniqueConstraint(fields=['course','kit'], name='app_totalpoints.course-kit')
        ]

@receiver(pre_save, sender=TotalPoints, dispatch_uid="totalpoints_presave")
def totalpoints_presave(sender, instance, **kwargs):
    instance.available = instance.bought - instance.assigned

那么这些规则应该在哪里实现如此清晰的表单并且 Django 可以正确处理错误?

添加:

我知道我正在提前进行计算以使表单捕获错误,这是否意味着我必须在表单上复制规则并始终进行重复验证?

模型上是否没有应该执行此计算的标准事件? (我也通过网络服务调用模型,所以我需要规则是通用的)

我知道我可以使用@property,但我需要数据库中的字段,因为稍后它将被另一个进程(非 Python One)使用

我和老 C#/Java 程序员正在被回收,我可能会犯非常明显的错误,所以请教育我,不要以为我理解完整的上下文。

提前谢谢你。

部分回答

我修改了 Melvyns 的评论并覆盖了 clean 方法:

class TotalPoints(TimeStampedModel):
    course = models.ForeignKey(Course, on_delete=models.CASCADE, related_name='TotalPoints_Course', null=False, blank=False, verbose_name='Curso')
    kit = models.ForeignKey(Kit, on_delete=models.CASCADE, related_name='TotalPoints_Kit', null=False, blank=False, verbose_name='Kit')
    bought = models.PositiveIntegerField(default=0, null=False, blank=False, verbose_name='Comprados')
    assigned = models.PositiveIntegerField(default=0, null=False, blank=False, verbose_name='Asignados')
    available = models.PositiveIntegerField(default=0, null=False, blank=False, verbose_name='Disponibles')
    class Meta:
        db_table = 'app_totalpoints'
        constraints = [
            models.UniqueConstraint(fields=['course','kit'], name='app_totalpoints.course-kit')
        ]
    def clean(self):
        super().clean()
        if self.assigned > self.bought:
            #OPTION 1 Raise on the Form
            #raise ValidationError('Asignados debe ser menor o igual a Comprados')
            #OPTION 2 Raise on the field
            raise ValidationError({'assigned': 'Asignados no debe ser mayor a Comprados.'})

现在我在表单上得到了正确的错误消息,但这是验证数据的一种迂回方式:

【问题讨论】:

  • 请将您的完整错误回溯作为文本而不是图像添加到问题中。另外,请确保内容应该是英文,因为我们中的许多人可能不知道非英语语言
  • 我在文本中添加了跟踪,我理解部分内容是英文的,但是我在翻译示例时遇到了一些问题,因为它还没有国际化。

标签: python python-3.x django django-models django-crispy-forms


【解决方案1】:

当您保存数据库由于限制而认为错误的内容时会发生完整性错误。表单捕捉到它为时已晚。

ModelForms 在模型上调用 full_clean() 并将捕获任何要处理的验证错误,就好像它是一个表单错误一样。调用顺序为:

  1. forms.forms.Form.is_valid()
  2. forms.forms.Form.full_clean()
  3. forms.models.ModelForm._post_clean()
  4. instance.full_clean() => ValidationErrors 转到 form.errors
    1. 干净的字段
    2. instance.clean()
    3. 验证唯一性
  5. instance.save() => 太低了,无法再起泡

因此您可以在 instance.clean() 处进行验证,并确保引发 ValidationErrors。相关阅读:

Form validation:

表单验证在数据被清理时发生。如果您想自定义此过程,可以在多个地方进行更改,每个地方都有不同的用途。在表单处理过程中运行三种类型的清洁方法

Model validation

验证模型涉及三个步骤:

验证模型字段 - Model.clean_fields() 整体验证模型 - Model.clean() 验证字段唯一性 - Model.validate_unique()

当您调用模型的 full_clean() 方法时,会执行所有三个步骤。

【讨论】:

  • 谢谢,我知道我正在为表单进行计算以捕获错误,这是否意味着我必须在表单上复制规则并始终进行重复验证?模型上没有应该执行此计算的标准事件吗? (我还通过 Web 服务调用模型,所以我需要通用规则)我知道我可以使用 @property,但我需要数据库上的字段,因为稍后它将被另一个进程(非 Python One )
  • 在上面的模型验证中,尤其是ModelInstance.clean(),应该有所有信息来确定可用的将是负面的,并且如前所述,可以将其冒泡。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-09-16
  • 1970-01-01
  • 1970-01-01
  • 2014-02-22
  • 2020-07-30
  • 2020-08-04
  • 2012-06-05
相关资源
最近更新 更多