css - 如何为 Django 中的所有内置表单小部件设置默认 css 类

标签 css django forms

简短版本:是否可以定义一组 Django 在呈现表单时应使用的默认 css 类?

长版: 上下文如下:我想将 w3.css 框架中定义的 css 类用于我的所有表单 ( http://www.w3schools.com/w3css/default.asp )。我已经看到可以在 Django 中在表单类定义或表单呈现时执行此操作,但在这两种情况下都需要对所有表单字段进行显式声明。这意味着我失去了为 ModelForms 自动生成表单的所有好处。我想要如下内容:

  1. 在某处(例如在设置文件中)定义表单字段/小部件和 css 类之间的默认映射,例如'textinput': 'my_default_css_class_for_text_inputs'
  2. 默认情况下,对于所有表单的自动生成和呈现,使用(1)中定义的默认 css 类,对现有表单类不做修改或修改很少
  3. 对于特定的表单,我可以用其他值重载默认值

据我所知,这种行为在 django 中是不可能的。 crispy-forms 包似乎朝着那个方向发展,但它似乎做的远不止于此,而且我不确定我是否想要所有额外的复杂性(我在这里仍然是新手)。另一种方法是使用 javascript 在客户端添加类。对我来说,这看起来像是一个丑陋的坏习惯。

谁能证实我对这个问题的理解,并指出优雅的解决方案(如果有的话)?

谢谢!

乔纳森

最佳答案

我已经设法找到我的问题的答案,我将其张贴在这里以供后代使用。对于标签类,我从 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 个 keys 之外,keys 必须与 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>用你的元素名称

然后,您只需继承 DefaultCssModelFormDefaultCssForm而不是 ModelFormForm在定义表单时。对于 formsets , 使用这些类作为基类。举例说明:

class MyForm(DefaultCssModelForm):
    class Meta:
        model = MyModel
        fields = '__all__'

MyFormSet = inlineformset_factory( ..., ..., form=DefaultCssModelForm, ... )            

关于css - 如何为 Django 中的所有内置表单小部件设置默认 css 类,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41340890/

相关文章:

html - 我如何使用 CSS 来选择和设置所有具有 UL 子元素的 LI 元素的样式?

javascript - Highchart 容器确实包含内部 div

html - 将链接 div 移到标语 p 上方的 h1 旁边

具有条件的相关对象的 Django 计数

html - 帮助 css 布局

python - 在指定 update_fields 时使用 pre_save

Django 1.7 与 Django1.6 与 Django 1.5

python - Django 形式的懒惰选择

html - 是否有 HTML 表单字段的命名约定,以便在所有现代浏览器中自动完成?

javascript - Ext-JS 在 Controller 中使用 View 表单数据作为 Ajax 请求参数