崩溃验证使用内联管理员

时间:2017-11-10 23:09:30

标签: django django-admin

我正在创建一对一模型来扩展现有模型类型的功能,但我希望它只允许在某些情况下创建扩展模型。我通过在新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版

3 个答案:

答案 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))