在 ModelForms 中继承 formfield_callback 的 Django 问题
Posted
技术标签:
【中文标题】在 ModelForms 中继承 formfield_callback 的 Django 问题【英文标题】:Django Problem inheriting formfield_callback in ModelForms 【发布时间】:2011-11-12 15:55:25 【问题描述】:我现在才使用 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
最终目标是在具有日期字段/时间字段/日期时间字段类的表单上添加一些 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 中的某个位置。
【问题讨论】:
它不起作用,因为 formfield_callback 不是对象的字段。这只是一个变量。 那么有没有办法在运行时设置呢?还是formfield_callback = attrs.pop('formfield_callback', None)
杀死了ModelFormMetaclass
中的任何机会。
【参考方案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 附加到我们的 form。
现在让我们看看 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__(...) 实现中,我们可以为所欲为。
如果我的回答不够详细,请在 cmets 的帮助下告诉我。
【讨论】:
+1 表示整洁的东西,-1 表示我认为矫枉过正的东西。假设我理解这个问题,这可以通过覆盖 BaseForm.__init__ 来解决。我的答案中的详细信息。【参考方案2】:您可以像这样设置小部件类:
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')
【讨论】:
我的想法是我不想在所有表单中重复编写相同的 Meta 类,而是只需正确一次,一切都会继承它。我们在使用 php 时受到了猛烈抨击,所以我还没有机会深入研究它,但我认为 s-zakharov 走在了正确的轨道上。【参考方案3】:对于您要完成的工作,我认为您最好只遍历表单 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
【讨论】:
【参考方案4】: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
...
【讨论】:
以上是关于在 ModelForms 中继承 formfield_callback 的 Django 问题的主要内容,如果未能解决你的问题,请参考以下文章