Django在ModelForms中继承formfield_回调时出现问题

Django在ModelForms中继承formfield_回调时出现问题,django,django-forms,Django,Django Forms,我现在只使用Django几个星期了,所以我可能有各种各样的错误,但是: 我有一个基本的模型表单,我在其中放了一些样板文件以尽可能保持干燥,而我所有的实际模型表单都只是这个基本表单的子类。这对于error\u css\u class='error'和required\u css\u class='required'非常有效,但是formfield\u callback=add\u css\u class的工作方式与我预期的不一样 forms.py 最终目标是在具有datefield/timefie

我现在只使用Django几个星期了,所以我可能有各种各样的错误,但是:

我有一个基本的模型表单,我在其中放了一些样板文件以尽可能保持干燥,而我所有的实际模型表单都只是这个基本表单的子类。这对于
error\u css\u class='error'
required\u css\u class='required'
非常有效,但是
formfield\u callback=add\u css\u class
的工作方式与我预期的不一样

forms.py 最终目标是在具有datefield/timefield/datetimefield类的表单上使用一些jquery日期时间选择器。我希望应用程序中的所有日期时间字段都使用相同的小部件,因此我选择这样做,而不是对每个模型中的每个字段显式地这样做。在每个表单类中添加一行并没有什么大不了的,但它只是让我感到困惑,我无法理解它。在django的资料中挖掘发现,这可能是在做一些我不理解的事情:

django.forms.models
但是我不知道
\uuuuu init\uuuuu
\uuuu new\uuuuuu
是如何相互关联的。在BaseForm中,我尝试重写
\uuuu init\uuuu
并在调用super之前和之后设置formfield\u回调,但我猜它需要在args或kwargs中的某个地方

\uuuu new\uuuu在对象构造之前被调用。实际上,这是一个工厂方法,返回新构造对象的实例

因此,ModelFormMetaclass中有3条关键行:

在课堂上,我们将基本字段附加到表单上

现在让我们看看ModelForm类:

这意味着当我们创建一个ModelForm实例以更改未来实例的结构时,将调用ModelFormMetaclass.\uuuu new\uuuuu(…)。ModelFormMetaclass中的属性是ModelForm类的所有属性的dict

所以决定为我们的案例创建新的InheritedFormMetaclass(从ModelFormMetaclass继承它)。别忘了在InheritedFormMetaclass中调用父级的new。然后创建我们的BaseForm类并说:

在InheritedFormMetaclass的新实现中,我们可以随心所欲


如果我的回答不够详细,请通过评论让我知道。

您可以这样设置widgets类:

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'})
    }

对于您试图实现的目标,我认为您最好只通过表单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_回调

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

最后,您可能需要研究一些简单的表单:

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

这意味着AFAIU formfield_callback是创建自定义模型表单类时使用的元类的参数,而不是创建实际表单实例时在运行时使用的某些值。这使得将formfield_回调放在
\uuu init\uu
中毫无用处

我用一个自定义元类解决了一个类似的问题,比如

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中)


它不起作用,因为formfield\u回调不是对象的字段。它只是一个变量。有没有办法在运行时设置它?或者是
formfield\u callback=attrs.pop('formfield\u callback',None)
ModelFormMetaclass
中扼杀了这种可能性。我的想法是,我不想在所有表单中重复编写同一个元类,而是只需修改一次,所有的东西都会继承它。我们在使用php时受到了抨击,所以我还没有机会深入研究,但我认为s-zakharov走上了正确的道路。+1表示整洁的东西,-1表示我认为太过分了。假设我理解这个问题,只需重写BaseForm即可解决。我的回答中有详细内容。
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'})
    }
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'
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
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
    ...
class MyConcreteModelForm(MyBaseModelForm):
    # no need setting formfield_callback here
    ...