我现在只使用Django几周了,所以我可能会遇到各种错误,但是:
我有一个基本的ModelForm,我把一些样板文件放在尽可能保持干净的状态,而我所有的实际ModelForms只是将这个基本形式子类化。这对error_css_class = 'error'
和required_css_class = 'required'
非常有用,但formfield_callback = add_css_classes
的效果不如我预期的那样。
# 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源头挖掘表明这可能正在做一些我不理解的事情:
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中的某个地方。
答案 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
...