可重复使用的管理表单生成器始终检查最后一个字段

时间:2018-03-07 21:25:18

标签: django django-admin

(这是所有伪代码,无法保证运行。)

我正在尝试制作一个" django管理表格生成器功能"输出django形式。当前的用例是编写可重用的代码,禁止管理员将字段留空,而不是marking these fields as non-nullable

假设存在一个模型Foo,其中有一些可以为空的字段:

class Foo(Model):
    field1 = FloatField(null=True, blank=True, default=0.0)
    field2 = FloatField(null=True, blank=True, default=0.0)
    field3 = FloatField(null=True, blank=True, default=0.0)

以及相应的FooAdminFooForm,以便管理员无法将这些字段设为None

class FooAdmin(ModelAdmin):

    class FooForm(ModelForm):

        class Meta(object):
            model = Foo
            fields = '__all__'

        def _ensure_no_blanks(self, field):
            value = self.cleaned_data.get(field)
            if value is None:
                raise forms.ValidationError(_('This field is required.'))
            return value

        # repeat methods for every field to check
        def clean_field1(self):
            return self._ensure_no_blanks('field1')

        def clean_field2(self):
            return self._ensure_no_blanks('field2')

        def clean_field3(self):
            return self._ensure_no_blanks('field3')

    form = FooForm

正如您所看到的,必须编写clean_field1clean_field2clean_field_n是重复且容易出错的,所以我编写了这个帮助函数来生成模型管理员:

import functools
from django import forms

def form_with_fields(model_class, required_fields):

    class CustomForm(forms.ModelForm):
        class Meta(object):
            model = model_class
            fields = '__all__'

        def ensure_no_blanks(self, field):
            print field
            value = self.cleaned_data.get(field)
            if value is None:
                raise forms.ValidationError('This field is required.')
            return value

    # make a clean_bar method for every field that I need to check for None
    for field_name in required_fields:
        handler = functools.partial(CustomForm.ensure_no_blanks, field=field_name)
        setattr(CustomForm, 'clean_' + field_name, lambda self: handler(self))

    return CustomForm

class CustomAdmin(ModelAdmin):
    form = form_with_fields(Foo, ['field1', 'field2', 'field3'])

但是,如果您运行此类管理员,并且您尝试通过管理员保存Foo模型,则会在终端中看到print field三次打印field3(即所有partial都保留最后运行的值。)

其他尝试包括覆盖CustomForm __getattr__(),并将CustomForm包裹在type('Form', (CustomForm,), ...中,这也会表现出相同的行为。

有没有干涸的方法来实现这个目标?

1 个答案:

答案 0 :(得分:2)

setattr(CustomForm, 'clean_' + field_name, lambda self: handler(self))

问题是lambda函数。 handler在lambda之外定义。调用lambda时访问它,而不是在定义时访问它。由于这是在for循环完成之后,所以总是得到使用最后一个字段名的函数。

请参阅Python文档中的this FAQ entry以获得更全面的解释。

在这种情况下,您根本不需要lambda。只需使用handler本身。

setattr(CustomForm, 'clean_' + field_name, handler)

然而,正如保罗在评论中所暗示的那样,它会很多

def clean(self):
    for field in required_fields(self):
        self.ensure_no_blanks(field)

您可能需要更改ensure_no_blanks以使用add_error,以便将验证错误添加到正确的字段中。

if value is None:
    self.add_error(field, 'This field is required.')

另一种选择是为required=True方法中的字段设置__init__,然后让表单进行验证。

class CustomForm(forms.ModelForm):
    class Meta(object):
        model = model_class
        fields = '__all__'

    def __init__(self, *args, **kwargs):
        super(CustomForm, self).__init__(*args, **kwargs)
        for field in required_fields:
            self.fields[field].required = True