简短版本:是否可以定义Django在呈现表单时应该使用的一组默认css类?
长版: 上下文如下:我想使用w3.css框架中定义的css类来表示我的所有表单(http://www.w3schools.com/w3css/default.asp)。我已经看到可以在表单类定义或表单呈现中在Django中执行此操作,但在两种情况下都需要显式声明所有表单字段。这意味着我放弃了ModelForms自动表单生成的所有好处。我想改为:
'textinput': 'my_default_css_class_for_text_inputs'
据我所知,这种行为在django中是不可能的。 crispy-form包似乎朝这个方向发展,但它似乎做的不仅仅是那个,而且我不确定我是否想要所有额外的复杂性(我仍然是这里的新手)。另一种方法是使用javascript在客户端添加类。对我来说,这看起来像是一个丑陋的坏习惯。
任何人都可以确认我对这个问题的理解并指出优雅的解决方案吗?
谢谢!
乔纳森
答案 0 :(得分:0)
我已经设法找到了我的问题的答案,我在这里张贴给后人。对于标签类,我从here和here获得了一些灵感(来自user2732686)。第一个链接建议在运行时重新定义label_tag
类的BoundField
方法。这是一个比第二个链接建议的解决方案更简洁的解决方案,但是以整个项目的黑客为代价,我不建议这样做。在这里,我按照Django的子类化狂热,如标签的第二个链接所示。
在项目 settings.py 中,添加:
# Default css classes for widgets and labels
DEFAULT_CSS = {
'error': 'w3-panel w3-red', # displayed in the label
'errorlist': 'w3-padding-8 w3-red', # encloses the error list
'required': 'w3-text-indigo', # used in the label and label + input enclosing box. NB: w3-validate only works if the input precedes the label!
'label': 'w3-label',
'Textarea': 'w3-input w3-border',
'TextInput': 'w3-input w3-border',
'Select': 'w3-select w3-border',
}
注意:除了4个第一个键之外,键必须与Django的小部件名称匹配。
在 forms.py (或其他地方)中,添加:
from django.forms import ModelForm, inlineformset_factory, Form, BoundField
from django.forms.utils import ErrorList
from django.utils.html import format_html, force_text
from django.conf import settings
class CustErrorList(ErrorList):
# custom error list format to use defcss
def __str__(self):
return self.as_div()
def as_div(self):
if not self:
return ''
return format_html('<div class="{}">{}</div>',
settings.DEFAULT_CSS['errorlist'],
' '.join( [ force_text(e) for e in self ] )
)
class CustBoundField(BoundField):
# overload label_tag to include default css classes for labels
def label_tag(self, contents=None, attrs=None, label_suffix=None):
newcssclass = settings.DEFAULT_CSS['label']
if attrs is None:
attrs = {}
elif 'class' in attrs:
newcssclass = ' '.join( [ attrs['class'], newcssclass ] ) # NB: order has no impact here (but it does in the style sheet)
attrs.update( { 'class': newcssclass } )
# return the output of the original method with the modified attrs
return super( CustBoundField, self ).label_tag( contents, attrs, label_suffix )
def custinit(self, subclass, *args, **kwargs):
# overload Form or ModelForm inits, to use default CSS classes for widgets
super( subclass, self ).__init__(*args, **kwargs)
self.error_class = CustErrorList # change the default error class
# Loop on fields and add css classes
# Warning: must loop on fields, not on boundfields, otherwise inline_formsets break
for field in self.fields.values():
thiswidget = field.widget
if thiswidget .is_hidden:
continue
newcssclass = settings.DEFAULT_CSS[ thiswidget.__class__.__name__ ]
thisattrs = thiswidget.attrs
if 'class' in thisattrs:
newcssclass = ' '.join( [ thisattrs['class'], newcssclass ] ) # NB: order has no impact here (but it does in the style sheet)
thisattrs.update( { 'class': newcssclass } )
def custgetitem(self, name):
# Overload of Form getitem to use the custom BoundField with
# css-classed labels. Everything here is just a copy of django's version,
# apart from the call to CustBoundField
try:
field = self.fields[name]
except KeyError:
raise KeyError(
"Key '%s' not found in '%s'. Choices are: %s." % (
name,
self.__class__.__name__,
', '.join(sorted(f for f in self.fields)),
)
)
if name not in self._bound_fields_cache:
self._bound_fields_cache[name] = CustBoundField( self, field, name )
# In the original version, field.get_bound_field is called, but
# this method only calls BoundField. It is much easier to
# subclass BoundField and call it directly here
return self._bound_fields_cache[name]
class DefaultCssModelForm(ModelForm):
# Defines the new reference ModelForm, with default css classes
error_css_class = settings.DEFAULT_CSS['error']
required_css_class = settings.DEFAULT_CSS['required']
def __init__(self, *args, **kwargs):
custinit(self, DefaultCssModelForm, *args, **kwargs)
def __getitem__(self, name):
return custgetitem(self, name)
class DefaultCssForm(Form):
# Defines the new reference Form, with default css classes
error_css_class = settings.DEFAULT_CSS['error']
required_css_class = settings.DEFAULT_CSS['required']
def __init__(self, *args, **kwargs):
custinit(self, DefaultCssForm, *args, **kwargs)
def __getitem__(self, name):
return custgetitem(self, name)
注意:将<MY_PROJECT>
替换为您的项目名称
然后,在定义表单时,您只需将DefaultCssModelForm
和DefaultCssForm
子类化为ModelForm
和Form
。对于formsets
,请将这些类用作基类。举例说明:
class MyForm(DefaultCssModelForm):
class Meta:
model = MyModel
fields = '__all__'
MyFormSet = inlineformset_factory( ..., ..., form=DefaultCssModelForm, ... )