简体   繁体   English

如何在 Django Admin 中向用户显示数据库错误

[英]How to show database errors to user in Django Admin

Background: my Django application sits onto top of a pre-existing Postgresql database.背景:我的 Django 应用程序位于预先存在的 Postgresql 数据库之上。 This database has a very complex.network of triggers and constraints.这个数据库有一个非常复杂的触发器和约束网络。

Question: In the Django Admin, if a user causes a DatabaseError on save, I would like to show the error back to them in a user friendly format, similar to the builtin forms.ValidationError.问题:在 Django Admin 中,如果用户在保存时导致数据库错误,我想以用户友好的格式向他们显示错误,类似于内置的 forms.ValidationError。

Example (this doesn't work, it causes a 500):示例(这不起作用,它会导致 500):

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

Expected Result:预期结果:

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

You need to slightly change your logic if possible. 如果可能,您需要略微更改逻辑。 What you need is custom AdminModel.form . 您需要的是自定义AdminModel.form All validation should be done there. 所有验证都应该在那里进行。 See the note for save_model() : 请参阅save_model()的注释:

ModelAdmin.save_model() and ModelAdmin.delete_model() must save/delete the object, they are not for veto purposes, rather they allow you to perform extra operations. ModelAdmin.save_model()和ModelAdmin.delete_model()必须保存/删除对象,它们不是用于否决目的,而是允许您执行额外的操作。

But if your circumstances are so that you can't do all validation inside the form I'd subclass ModelAdmin and override def add_view() , def change_view() and def changelist_view() like so: 但是如果你的情况不能在表格中进行所有验证,我将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)

My models.py : 我的models.py

from django.db import models

class TestModel(models.Model):

    text = models.TextField()

You see, there's no easy way of hooking inside save_model() so you'll have to copy-paste part of form preparation code. 你看,在save_model()里面没有简单的方法可以挂钩,所以你必须复制粘贴部分表格准备代码。

@twil -- Thanks for your help. @twil - 感谢您的帮助。 You put me on the right track. 你让我走上了正确的轨道。 Really appreciate your help. 非常感谢您的帮助。 However, the solution didn't work out of box. 但是,解决方案没有开箱即用。 Didn't actually show errors in my test case or work with change_view. 实际上没有在我的测试用例中显示错误或使用change_view。 Here's want I ended up working with. 这是我希望我最终合作。

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)

Note: That this version also seems to work cross version (django 1.3 - 1.6). 注意:这个版本似乎也适用于交叉版本(django 1.3 - 1.6)。 Let me know if anyone has a better approach. 如果有人有更好的方法,请告诉我。 I'll wait to award bounty. 我等着奖励赏金。

Try this: 尝试这个:

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

Old question, but nobody mentioned ModelAdmin.message_user() yet, which also uses the messages framework .老问题,但还没有人提到ModelAdmin.message_user() ,它也使用 消息框架

For example:例如:

from django.contrib import admin, messages

class MyModelAdmin(admin.ModelAdmin):
    ...
    
    def save_model(self, request, *args, **kwargs):
        try:
            super().save_model(request, *args, **kwargs)
        except DatabaseError as e:
            messages.set_level(request=request, level=messages.ERROR)            
            self.message_user(request=request, message=e, level=messages.ERROR)

This would show your message as follows (assuming that's the error string in e ):这将显示您的消息如下(假设这是e中的错误字符串):

错误信息

The set_level call suppresses the default success message. set_level调用会抑制默认的成功消息。 Also see eg here and here .另请参见此处此处

In save_model() , you can handle DatabaseError exception separately and differently for adding or changing an object as shown below:save_model()中,您可以单独和不同地处理DatabaseError异常以添加或更改 object ,如下所示:

@admin.register(Person)
class PersonAdmin(admin.ModelAdmin):
    
    def save_model(self, request, obj, form, change): # Here
        last_part_of_path = request.path.split('/')[-2]

        if last_part_of_path == "add":
            try: # ↓ I intentionally raise an exception
                raise DatabaseError("Add Error")
                obj.save()
            except DatabaseError as e:
                messages.set_level(request, messages.ERROR)
                messages.error(request, e)

        if last_part_of_path == "change":
            try: # ↓ I intentionally raise an exception
                raise DatabaseError("Change Error")
                obj.save()
            except DatabaseError as e:
                messages.set_level(request, messages.ERROR)
                messages.error(request, e)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM