【问题标题】:How to show database errors to user in Django Admin如何在 Django Admin 中向用户显示数据库错误
【发布时间】:2013-11-29 07:27:38
【问题描述】:

背景:我的 Django 应用程序位于预先存在的 Postgresql 数据库之上。这个数据库有一个非常复杂的触发器和约束网络。

问题:在 Django Admin 中,如果用户在保存时导致 DatabaseError,我想以用户友好的格式将错误显示给他们,类似于内置 forms.ValidationError。

示例(这不起作用,它会导致 500)

def save_model(self, request, obj, form, change):
    try:
        obj.save()
    except DatabaseError as e:
        raise forms.ValidationError(e)

预期结果:

在管理员中向用户显示“Database Error: ID 58574 - Price is outside customers requested range. Cannot add or update a child row: a foreign key constraint fails”。

【问题讨论】:

  • Price is outside customers requested range : 你能展示模型(以及你指定的约束)
  • 我正在寻找适用于任何模型/约束的更通用的东西。

标签: python django postgresql


【解决方案1】:

如果可能的话,你需要稍微改变你的逻辑。您需要的是自定义AdminModel.form。所有的验证都应该在那里完成。请参阅save_model() 的注释:

ModelAdmin.save_model() 和 ModelAdmin.delete_model() 必须保存/删除 对象,它们不是用于否决目的,而是允许您 执行额外的操作。

但是,如果您的情况是您无法在表单中进行所有验证,我将继承 ModelAdmin 并覆盖 def add_view()def change_view()def changelist_view(),如下所示:

from django.contrib import admin
from django import forms
from django.contrib.admin import helpers
from django.contrib.admin.options import csrf_protect_m, IS_POPUP_VAR
from django.utils.translation import ugettext as _
from django.utils.encoding import force_text

# for nonfield errors to show correctly
from django.forms.forms import NON_FIELD_ERRORS

from .models import TestModel


class TestModelAdmin(admin.ModelAdmin):

    def save_model(self, request, obj, form, change):

        raise Exception('test exception')

    @csrf_protect_m
    def add_view(self, request, form_url='', extra_context=None):
        try:
            return super(TestModelAdmin, self).add_view(request, form_url, extra_context)
        except Exception as e:
            pass

        # mimic parent class on error

        model = self.model
        opts = model._meta

        ModelForm = self.get_form(request)
        formsets = []
        inline_instances = self.get_inline_instances(request, None)
        form = ModelForm(request.POST, request.FILES)
        form.is_valid()

        # make faked nonfield error
        # see http://stackoverflow.com/questions/8598247/how-to-append-error-message-to-form-non-field-errors-in-django
        form._errors[NON_FIELD_ERRORS] = form.error_class([e.message])

        # We may handle exception here (just to save indentation)
        adminForm = helpers.AdminForm(form, list(self.get_fieldsets(request)),
            self.get_prepopulated_fields(request),
            self.get_readonly_fields(request),
            model_admin=self)
        media = self.media + adminForm.media

        inline_admin_formsets = []
        for inline, formset in zip(inline_instances, formsets):
            fieldsets = list(inline.get_fieldsets(request))
            readonly = list(inline.get_readonly_fields(request))
            prepopulated = dict(inline.get_prepopulated_fields(request))
            inline_admin_formset = helpers.InlineAdminFormSet(inline, formset,
                fieldsets, prepopulated, readonly, model_admin=self)
            inline_admin_formsets.append(inline_admin_formset)
            media = media + inline_admin_formset.media

        context = {
            'title': _('Add %s') % force_text(opts.verbose_name),
            'adminform': adminForm,
            'is_popup': IS_POPUP_VAR in request.REQUEST,
            'media': media,
            'inline_admin_formsets': inline_admin_formsets,
            'errors': helpers.AdminErrorList(form, formsets),
            'app_label': opts.app_label,
            'preserved_filters': self.get_preserved_filters(request),
        }
        context.update(extra_context or {})
        return self.render_change_form(request, context, form_url=form_url, add=True)

admin.site.register(TestModel, TestModelAdmin)

我的models.py

from django.db import models

class TestModel(models.Model):

    text = models.TextField()

您知道,在save_model() 中挂钩并不容易,因此您必须复制粘贴部分表单准备代码。

【讨论】:

    【解决方案2】:

    @twil -- 感谢您的帮助。你让我走上正轨。非常感谢您的帮助。但是,该解决方案并非开箱即用。实际上并没有在我的测试用例中显示错误或使用 change_view。这是我最终与之合作的愿望。

    from django.contrib.admin import ModelAdmin
    from django.db import DatabaseError, IntegrityError
    from django.contrib import messages
    
    
    class ShowValidationAdmin(ModelAdmin):
    
        def add_view(self, request, form_url='', extra_context=None):
            try:
                return super(ShowValidationAdmin, self).add_view(request, form_url, extra_context)
            except (IntegrityError, DatabaseError) as e:
    
                request.method = 'GET'
                messages.error(request, e.message)
                return super(ShowValidationAdmin, self).add_view(request, form_url, extra_context)
    
        def change_view(self, request, object_id, form_url='', extra_context=None):
            try:
                return super(ShowValidationAdmin, self).change_view(request, object_id, form_url, extra_context)
            except (IntegrityError, DatabaseError) as e:
    
                request.method = 'GET'
                messages.error(request, e.message)
                return super(ShowValidationAdmin, self).change_view(request, object_id, form_url, extra_context)
    

    注意:这个版本似乎也适用于跨版本(django 1.3 - 1.6)。让我知道是否有人有更好的方法。我会等待奖励赏金。

    【讨论】:

    • 您的解决方案更短,但对用户不太友好,因为您正在丢失request.method = 'GET' 的更改。用户必须重新填写所有必要的字段,而不仅仅是修复错误的字段。
    【解决方案3】:

    试试这个:

     from django.core.exceptions import ValidationError
        def save_model(self, request, obj, form, change):
            try:
                obj.save()
            except DatabaseError as e:
                raise ValidationError(e)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-05-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多