Background: my Django application sits onto top of a pre-existing Postgresql database. This database has a very complex network of triggers and constraints.
背景:我的Django应用程序位于预先存在的Postgresql数据库之上。该数据库具有非常复杂的触发器和约束网络。
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中,如果用户在保存时导致DatabaseError,我想以用户友好的格式向他们显示错误,类似于builtin 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
."
在管理员中显示给用户,“数据库错误:ID 58574 - 价格超出客户要求的范围。无法添加或更新子行:外键约束失败。”
3 个解决方案
#1
5
You need to slightly change your logic if possible. What you need is custom AdminModel.form
. All validation should be done there. See the note for save_model()
:
如果可能,您需要稍微改变逻辑。您需要的是自定义AdminModel.form。所有验证都应该在那里进行。请参阅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://*.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()里面挂钩,所以你必须复制粘贴部分表格准备代码。
#2
4
@twil -- Thanks for your help. 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. Here's want I ended up working with.
@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)
Note: That this version also seems to work cross version (django 1.3 - 1.6). Let me know if anyone has a better approach. I'll wait to award bounty.
注意:这个版本似乎也适用于跨版本(django 1.3 - 1.6)。如果有人有更好的方法,请告诉我。我等着奖励赏金。
#3
0
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)
#1
5
You need to slightly change your logic if possible. What you need is custom AdminModel.form
. All validation should be done there. See the note for save_model()
:
如果可能,您需要稍微改变逻辑。您需要的是自定义AdminModel.form。所有验证都应该在那里进行。请参阅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://*.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()里面挂钩,所以你必须复制粘贴部分表格准备代码。
#2
4
@twil -- Thanks for your help. 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. Here's want I ended up working with.
@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)
Note: That this version also seems to work cross version (django 1.3 - 1.6). Let me know if anyone has a better approach. I'll wait to award bounty.
注意:这个版本似乎也适用于跨版本(django 1.3 - 1.6)。如果有人有更好的方法,请告诉我。我等着奖励赏金。
#3
0
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)