【问题标题】:django - How to cross check ModelAdmin and its inlines?django - 如何交叉检查 ModelAdmin 及其内联?
【发布时间】:2011-02-12 19:19:21
【问题描述】:

我有两个模型(ModelParent 和 ModelChild),在 Subject 模型上具有相同的 m2m 字段。 ModelChild 在 ModelParent 上有一个外键,并且 ModelChild 在管理页面上被定义为 ModelParent 的内联。

### models.py ###
  class Subject(Models.Model):
    pass

  class ModelParent(models.Model):
    subjects_parent = ManyToManyField(Subject)

  class ModelChild(models.Model):
    parent = ForeignKey(ModelParent)
    subjects_child = ManyToManyField(Subject)

### admin.py ###
  class ModelChildInline(admin.TabularInline):
      model = ModelChild

  class ModelParentAdmin(admin.ModelAdmin):
    inlines = [ModelChildInline]

  admin.site.register(ModelParent, ModelParentAdmin)

不过,我有一个重要的限制,ModelChild 的 subject_child 字段不得引用 subject_parent 对其subject_parent 所做的任何主题。

所以,如果我在两个模型的管理页面上选择相同的主题(在 subject_parent 和 subject_child 中),我该如何验证呢? 如果只有一个字段更改,您将根据数据库对其进行验证,但如果两者都更改(subject_parent 和 subject_child)怎么办? 如何在保存前同时验证两个表单?

【问题讨论】:

  • 为什么不在表单的 clean() 中验证它,而不是 field_specific clean?这样,您需要检查的所有字段都将被预先清理。我唯一不确定的是您是否可以访问两组数据,或者仅访问 clean() 中的 ModelParent 数据...
  • 感谢您的建议。我已经这样做了,但是对于表单,我只能分别验证主表单(ModelParentAdmin 的表单)和 inilines 表单集(仅针对 db)。我可以一起访问它们的唯一地方是 ModelParentAdmin 类。但是该类没有 clean() 方法。我可以考虑在使用此类的 save_formsets(...) 方法之前验证它们,但是在 raise ValidationError("error") 上什么都没有。
  • 我也遇到了同样的问题,但还不知道最好的解决方案是什么。我可以看到这个问题和答案现在已经很老了,而且我确信必须有一个简单的解决方案可以与较新的 django 版本一起使用。有人可以帮忙吗?

标签: python django django-admin django-forms


【解决方案1】:

管理类没有 clean() 方法。他们的表格可以。每个管理类都有一个名为 form 的参数。您只需扩展默认表单(它是普通的 ModelAdmin 表单),实现 clean() 方法并将表单添加到管理类。 示例:

class SomeForm(ModelForm):
  #some code
  def clean(self):
   #some code
class SomeAdminClass(ModelAdmin):
 #some code
 form = SomeForm
 #more code

【讨论】:

  • 我已经这样做了,但是这样我只能单独针对 DB 验证表单。如果您在一个管理页面上将相同的主题添加到 ModelParent.subjects_parent 和 ModelChild.sujects_child 会怎样(这当然发生在内联中)。一种形式不知道另一种选择了相同的主题,因此它们都被保存了。这是因为两种形式的验证都是在保存到数据库之前发生的,所以它们都看不到对方所做的更改。
  • 我现在明白了。这使事情变得有点复杂。这种保护级别必须在两种形式都使用的层中实现。我不知道您的问题的直接解决方案,但是这种保护必须在模型层或直接在 DBMS 中实现。查看 django 文档,或者在最坏的情况下查看 DBMS 文档。
【解决方案2】:

我从 admin.ModelAdmin 继承了一个名为 ModelAdminWithInline 的新类,并修改了方法 add_view(...) 和 change_view(...) 以调用函数 is_cross_valid(self, form, formsets),您可以在其中验证所有表单一起。 这两个函数都有:

#...
if all_valid(formsets) and form_validated:
#...

改为:

#...
formsets_validated = all_valid(formsets)
cross_validated = self.is_cross_valid(form, formsets)
if formsets_validated and form_validated and cross_validated:
#...

新函数 is_cross_valid(...) 定义如下:

def is_cross_valid(self, form, formsets):
  return True

因此,如果您不更改 is_cross_valid(...) 函数,新类应该与 ModelAdmin 完全相同。

现在我的 admin.py 看起来像这样:

###admin.py###
class ModelAdminWithInline(admin.ModelAdmin):
  def is_cross_valid(self, form, formsets):
    return True

  def add_view(self, request, form_url='', extra_context=None):
    #modified code

  def change_view(self, request, object_id, extra_context=None):
    #modified code

class ModelChildInline(admin.TabularInline):
  model = ModelChild

class ModelParentAdmin(ModelAdminWithInline):
  inlines = [ModelChildInline]

  def is_cross_valid(self, form, formsets):
    #Do some cross validation on forms
    #For example, here is my particular validation:
    valid = True

    if hasattr(form, 'cleaned_data'):   

      subjects_parent = form.cleaned_data.get("subjects_parent")

      #You can access forms from formsets like this:
      for formset in formsets:
        for formset_form in formset.forms:
          if hasattr(formset_form, 'cleaned_data'):

            subjects_child = formset_form.cleaned_data.get("subjects_child")
            delete_form = formset_form.cleaned_data.get("DELETE")

            if subjects_child and (delete_form == False):
              for subject in subjects_child:
                if subject in subjects_parent:
                  valid = False
                  #From here you can still report errors like in regular forms:
                  if "subjects_child" in formset_form.cleaned_data.keys():
                    formset_form._errors["subjects_child"] = ErrorList([u"Subject %s is already selected in parent ModelParent" % subject])
                    del formset_form.cleaned_data["subjects_child"]
                  else:
                    formset_form._errors["subjects_child"] += ErrorList(u"Subject %s is already selected in parent ModelParent" % subject])

      #return True on success or False otherwise.
      return valid

admin.site.register(ModelParent, ModelParentAdmin)

该解决方案有点骇人听闻,但它确实有效:)。错误显示与常规 ModelForm 和 ModelAdmin 类相同。 django 1.2(应该会很快发布)应该有模型验证,所以希望以后这个问题能更好的解决。

【讨论】:

    猜你喜欢
    • 2011-04-11
    • 1970-01-01
    • 1970-01-01
    • 2017-05-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-18
    • 1970-01-01
    相关资源
    最近更新 更多