Django问题在ModelForms中继承formfield_callback

时间:2011-09-08 03:51:00

标签: django django-forms

我现在只使用Django几周了,所以我可能会遇到各种错误,但是:

我有一个基本的ModelForm,我把一些样板文件放在尽可能保持干净的状态,而我所有的实际ModelForms只是将这个基本形式子类化。这对error_css_class = 'error'required_css_class = 'required'非常有用,但formfield_callback = add_css_classes的效果不如我预期的那样。

forms.py

# snippet I found
def add_css_classes(f, **kwargs):
    field = f.formfield(**kwargs)
    if field and 'class' not in field.widget.attrs:
        field.widget.attrs['class'] = '%s' % field.__class__.__name__.lower()
    return field

class BaseForm(forms.ModelForm):
    formfield_callback = add_css_classes  # not working

    error_css_class = 'error'
    required_css_class = 'required'
    class Meta:
        pass

class TimeLogForm(BaseForm):
    # I want the next line to be in the parent class
    # formfield_callback = add_css_classes
    class Meta(BaseForm.Meta):
        model = TimeLog

最终目标是使用一类datefield / timefield / datetimefield在表单上打一些jquery日期时间选择器。我希望应用程序中的所有日期时间字段都使用相同的小部件,因此我选择这样做,而不是明确地为每个模型中的每个字段执行此操作。为每个表单类添加一个额外的行并不是什么大不了的事,但它只是告诉我我无法理解它。在Django源头挖掘表明这可能正在做一些我不理解的事情:

django.forms.models

class ModelFormMetaclass(type):
    def __new__(cls, name, bases, attrs):
        formfield_callback = attrs.pop('formfield_callback', None)

但我不知道__init____new__是如何进行交互的。在BaseForm中,我尝试重写__init__并在调用super之前和之后设置formfield_callback,但我猜它需要在args或kwargs中的某个地方。

4 个答案:

答案 0 :(得分:1)

在对象构造之前调用

__ new__。实际上这是一个 factory 方法,它返回一个新构造的对象的实例。

所以 ModelFormMetaclass 中有3个关键行:

formfield_callback = attrs.pop('formfield_callback', None) #1
fields = fields_for_model(opts.model, opts.fields, 
                                      opts.exclude, opts.widgets, formfield_callback) #2
new_class.base_fields = fields #3

在课程中,我们将base_fields附加到表单

现在让我们来看看 ModelForm类

class ModelForm(BaseModelForm):
    __metaclass__ = ModelFormMetaclass

这意味着当我们创建ModelForm实例来更改未来实例的结构时,将调用ModelFormMetaclass .__ new __(...)。 ModelFormMetaclass 中的__new __( def __new __(cls,name,bases,attrs))是 的所有属性的词典ModelForm类

因此决定为我们的案例创建新的InheritedFormMetaclass(从 ModelFormMetaclass 继承它)。不要忘记在 InheritedFormMetaclass 中调用父级的 new 。然后创建我们的 BaseForm类并说:

__metaclass__ = InheritedFormMetaclass

InheritedFormMetaclass 的__new __(...)实现中,我们可以做我们想做的一切。

如果我的答案不够详细,请在评论的帮助下告诉我。

答案 1 :(得分:1)

您可以像这样设置小部件类:

class TimeLogForm(BaseForm):
# I want the next line to be in the parent class
# formfield_callback = add_css_classes
class Meta(BaseForm.Meta):
    model = TimeLog
    widgets = {
            'some_fields' : SomeWidgets(attrs={'class' : 'myclass'})
    }

答案 2 :(得分:1)

对于您正在尝试完成的任务,我认为您最好只是在表单init上遍历字段。例如,

class BaseForm(forms.ModelForm):
  def __init__(self, *args, **kwargs):
    super(BaseForm, self).__init__(*args, **kwargs)
    for name, field in self.fields.items():
      field.widget.attrs['class'] = 'error'

显然,您需要为您的具体案例提供更多逻辑。如果你想使用sergzach建议的方法(我认为你的特定问题有些过分),这里有一些代码,如果子类没有定义它,它将在基类上调用formfield_callback。

baseform_formfield_callback(field):
    # do some stuff
    return field.formfield()

class BaseModelFormMetaclass(forms.models.ModelFormMetaclass):
    def __new__(cls, name, bases, attrs):
        if not attrs.has_key('formfield_callback'):
            attrs['formfield_callback'] = baseform_formfield_callback
        new_class = super(BaseModelFormMetaclass, cls).__new__(
            cls, name, bases, attrs)
        return new_class

class BaseModelForm(forms.ModelForm):
    __metaclass__ = OrganizationModelFormMetaclass
    # other form stuff

最后,你可能想看看脆弱的形式:https://github.com/maraujop/django-crispy-forms

答案 3 :(得分:0)

sergzach是正确的,你必须使用元类;覆盖__init__是不够的。原因是ModelForm的元类(除非在子类中指定另一个元类,否则将为所有ModelForm子类调用)获取类定义,并使用类定义中的值创建一个类类属性。例如,META.fields和我们的formfield_callback都用于创建具有各种选项的表单字段(如哪个小部件)。

这意味着AFAIU formfield_callback是创建自定义模型表单类时使用的元类的参数,而不是在创建实际表单实例时在运行时使用的某些值。这使得formfield_callback放在__init__无用。

我解决了类似

等自定义元类的类似问题
from django.forms.models import ModelFormMetaclass

class MyModelFormMetaclass(ModelFormMetaclass):
    def __new__(cls,name,bases,attrs):
    attrs['formfield_callback']=my_callback_function
    return super(MyModelFormMetaclass,cls).__new__(cls,name,bases,attrs)

并在我的所有模型表单的基类中设置元类

class MyBaseModelForm(ModelForm):
    __metaclass__=MyModelFormMetaclass
    ...

可以像(至少在Django 1.6中)一样使用

class MyConcreteModelForm(MyBaseModelForm):
    # no need setting formfield_callback here
    ...