使用Form生成html标签的时候,虽然提供了widget的方法可以自定义标签的要是,但是只能给生成的input标签添加样式,对于生成的label标签无法添加样式。而很多场景下需要为label和input都添加class以实现自定义样式。

测试环境

创建一个Form,通过Form帮我们生成HTML:

# urls.py 文件,对应关系
    path('email/', views.email),

# forms.py 文件
from django.forms import Form
from django.forms import fields
from django.forms import widgets

class UserEmail(Form):
    username = fields.CharField()
    password = fields.CharField(
        widget=widgets.PasswordInput(attrs={'class': 'c1'})
    )
    email = fields.EmailField(
        widget=widgets.EmailInput(attrs={'class': 'c1'})
    )

# views.py 文件
def email(request):
    obj = forms.UserEmail()
    print(obj['email'].label_tag(attrs={'class': 'c1'}))  # 其实生成标签的方法是提供attrs参数的
    return render(request, 'demo/email.html', {'obj': obj})

在html中,直接使用Form帮我生成的表单:

<body>
{{ obj.as_p }}
{{ obj.email.label_tag }}
{{ obj.email }}
</body>

这里可以看到,input标签里都是有class属性的,但是lable标签里没有,并且Form组件里貌似也没有提供为label标签增加自定义属性的方式。

通过模板语言的自定义函数实现

上面的views里的 print(obj['email'].label_tag(attrs={'class': 'c1'})) ,从输出看,django提供的生成label标签的方法是支持attrs参数实现自定义属性的,问题是在前端使用模板语言的时候只能这样 {{ obj.email.label_tag }} 无法传入参数。这里就自定义个模板语言的函数来解决这个问题。

自定义函数

要自定义函数,按照下面的步骤操作:

  1. 在APP下,创建templatetags目录,目录名字很重要不能错。
  2. 创建任意 .py 文件,这里文件名随意,比如:myfun.py。
  3. 文件里创建一个template.Library()对象,名字是register。这里的对象名字必须是register。
  4. 然后写自己的函数,但是都用@register.simple_tag这个装饰器装饰好:

自定义的函数如下:

# app名/templatetags/myfun.py 文件
from django import template

register = template.Library()

@register.filter(is_safe=True)
def label_with_classes(value, arg):
    return value.label_tag(attrs={'class': arg})

然后在页面中使用自定义的函数:

<body>
{{ obj.as_p }}
{{ obj.email.label_tag }}
{{ obj.email }}
{% load myfun %}
{{ obj.email|label_with_classes:'c1 c2' }}
</body>

注意,上面的自定义函数引用的时候参数和参数之间一定不能有空格。
这里还有一个好处,把添加前端样式的代码放到了前端的html里实现了。

为input标签也写一个自定义函数

django默认的方法是在Form里,通过widgets小部件添加attrs参数来实现标签的自定义样式。这是在放在后端实现的。上面已经实现了前端的自定义样式,这里找了到生成input标签的方法,就是as_widget()。照着样子再写一个子定义函数:

# app名/templatetags/myfun.py 文件

from django import template

register = template.Library()

@register.filter()
def label_with_classes(value, arg):
    return value.label_tag(attrs={'class': arg})

@register.filter()
def widget_with_classes(value, arg):
    return value.as_widget(attrs={'class': arg})

最后,上面搞得难么麻烦,主要是为了可以前端一个for循环,就能把表单按自定义的样式显示出来:

<body>
<link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css"
      integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
{% load myfun %}
<form class="form-horizontal">
{% for item in obj %}
    <div class="form-group">
    {{ item|label_with_classes:'col-sm-2 control-label' }}
    <div class="col-sm-10">
        {{ item|widget_with_classes:'form-control' }}
    </div>
    </div>
{% endfor %}
</form>
</body>