我正在创建一对一模型来扩展现有模型类型的功能,但我希望它只允许在某些情况下创建扩展模型。我通过在新ValidationError
模型的full_clean
中放置Extended
来强制执行此约束。当我使用Extended ModelAdmin
直接创建扩展模型时(如果它是错误的类型会突出显示a
字段),但是当我使用StackedInline
来内联Extended
创建时,这非常有用在A
的ModelAdmin中,A
类型错误,表单无法捕获ValidationError
,我收到消息A server error occurred. Please contact the administrator.
这就是我设置模型的方法:
# models.py
from django.db import models
class A(models.Model):
type = models.IntegerField(...)
class Extended(models.Model)
a = models.OneToOneField(A)
def clean_fields(self, **kwargs):
if self.a.type != 3:
raise ValidationError({'a': ["a must be of type 3"]})
super(Extended, self).clean_fields(**kwargs)
def save(self, *args, **kwargs):
self.full_clean()
super(Extended, self).save(*args, **kwargs)
# admin.py
from django.contrib import admin
class ExtendedInline(admin.StackedInline):
model = Extended
@admin.register(A)
class AAdmin(admin.ModelAdmin):
inlines = (ExtendedInline,)
完整的追溯:
Traceback (most recent call last):
File "/usr/local/lib/python2.7/wsgiref/handlers.py", line 85, in run
self.result = application(self.environ, self.start_response)
File "/usr/local/lib/python2.7/site-packages/django/contrib/staticfiles/handlers.py", line 63, in __call__
return self.application(environ, start_response)
File "/usr/local/lib/python2.7/site-packages/whitenoise/base.py", line 66, in __call__
return self.application(environ, start_response)
File "/usr/local/lib/python2.7/site-packages/django/core/handlers/wsgi.py", line 189, in __call__
response = self.get_response(request)
File "/usr/local/lib/python2.7/site-packages/django/core/handlers/base.py", line 218, in get_response
response = self.handle_uncaught_exception(request, resolver, sys.exc_info())
File "/usr/local/lib/python2.7/site-packages/django/core/handlers/base.py", line 132, in get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/usr/local/lib/python2.7/site-packages/django/contrib/admin/options.py", line 618, in wrapper
return self.admin_site.admin_view(view)(*args, **kwargs)
File "/usr/local/lib/python2.7/site-packages/django/utils/decorators.py", line 110, in _wrapped_view
response = view_func(request, *args, **kwargs)
File "/usr/local/lib/python2.7/site-packages/django/views/decorators/cache.py", line 57, in _wrapped_view_func
response = view_func(request, *args, **kwargs)
File "/usr/local/lib/python2.7/site-packages/django/contrib/admin/sites.py", line 233, in inner
return view(request, *args, **kwargs)
File "/usr/local/lib/python2.7/site-packages/django/contrib/admin/options.py", line 1521, in change_view
return self.changeform_view(request, object_id, form_url, extra_context)
File "/usr/local/lib/python2.7/site-packages/django/utils/decorators.py", line 34, in _wrapper
return bound_func(*args, **kwargs)
File "/usr/local/lib/python2.7/site-packages/django/utils/decorators.py", line 110, in _wrapped_view
response = view_func(request, *args, **kwargs)
File "/usr/local/lib/python2.7/site-packages/django/utils/decorators.py", line 30, in bound_func
return func.__get__(self, type(self))(*args2, **kwargs2)
File "/usr/local/lib/python2.7/site-packages/django/utils/decorators.py", line 145, in inner
return func(*args, **kwargs)
File "/usr/local/lib/python2.7/site-packages/django/contrib/admin/options.py", line 1470, in changeform_view
self.save_related(request, form, formsets, not add)
File "/usr/local/lib/python2.7/site-packages/django/contrib/admin/options.py", line 1104, in save_related
self.save_formset(request, form, formset, change=change)
File "/usr/local/lib/python2.7/site-packages/django/contrib/admin/options.py", line 1092, in save_formset
formset.save()
File "/usr/local/lib/python2.7/site-packages/django/forms/models.py", line 636, in save
return self.save_existing_objects(commit) + self.save_new_objects(commit)
File "/usr/local/lib/python2.7/site-packages/django/forms/models.py", line 767, in save_new_objects
self.new_objects.append(self.save_new(form, commit=commit))
File "/usr/local/lib/python2.7/site-packages/django/forms/models.py", line 900, in save_new
obj.save()
File "/code/app/models.py", line 162, in save
self.full_clean()
File "/usr/local/lib/python2.7/site-packages/django/db/models/base.py", line 1171, in full_clean
raise ValidationError(errors)
ValidationError: {'a': [u'a must be of type 3']}
我目前正在使用Django 1.8版
答案 0 :(得分:1)
在modelform验证器中发生的对full_clean()的调用中排除了内联外键,因此表单的is_valid()调用不会捕获ValidationError。
来自django / forms / models.py:
def _post_clean(self):
opts = self._meta
exclude = self._get_validation_exclusions()
try:
self.instance = construct_instance(self, self.instance, opts.fields, exclude)
except ValidationError as e:
self._update_errors(e)
# Foreign Keys being used to represent inline relationships
# are excluded from basic field value validation. This is for two
# reasons: firstly, the value may not be supplied (#12507; the
# case of providing new values to the admin); secondly the
# object being referred to may not yet fully exist (#12749).
# However, these fields *must* be included in uniqueness checks,
# so this can't be part of _get_validation_exclusions().
for name, field in self.fields.items():
if isinstance(field, InlineForeignKeyField):
exclude.append(name)
try:
self.instance.full_clean(exclude=exclude, validate_unique=False)
except ValidationError as e:
self._update_errors(e)
# Validate uniqueness if needed.
if self._validate_unique:
self.validate_unique()
他们会在save()中被捕获(你在没有排除的情况下调用full_clean),这太晚了。
将验证移至clean():
def clean(self):
if self.a.type != 3:
raise ValidationError({'a': ["a must be of type 3"]})
然后就不需要从save方法调用full_clean了。这种方法应该是as per the docs的任何验证。
答案 1 :(得分:0)
虽然不理想,但spinkus发布了an answer,这也解决了我的崩溃问题。它涉及覆盖changeform_view
中的AAdmin
方法:
@admin.register(A)
class AAdmin(admin.ModelAdmin):
inlines = (ExtendedInline,)
def changeform_view(self, request, object_id=None, form_url='', extra_context=None):
# Need to override to catch ValidationError in pre_save and save hooks.
try:
return super(AAdmin, self).changeform_view(request, object_id, form_url, extra_context)
except ValidationError as e:
self.message_user(request, '\n'.join(e.messages), level=messages.ERROR)
return HttpResponseRedirect(form_url)
这会导致AAdmin
在表单顶部显示错误消息而不是崩溃。不幸的是,它清除了用户的其他更改,并且没有进行字段级突出显示。
答案 2 :(得分:0)
我通过常规(非内联)ModelAdmin
获得此功能,但是该解决方案也应该有效。
我只是简单地将save_model()
包裹起来,然后通过messages.warning()
发送异常消息
from django.contrib import admin, messages
class ConstraintErrorBoundaryMixin:
def save_model(self, request, obj, form, change):
try:
super().save_model(request, obj, form, change)
except Exception as exc:
messages.error(request, str(exc))