为什么要自定义小组件
在我们使用
django表单
的过程中,官方给出的Filed虽然已经很丰富了,但是还是很难满足我们的开发要求,例如隐藏
FileInput
的按钮及显示上传图片的缩略图而非文件名,定制的多选框
MultiChoice
等。
Django官方文档
虽然有简单提过一下自定义小组件的方法,并使用了
MultiWidget
进行演示,例子如下,可还是让人一头雾水...互联网上相应的文章也很少,所以写下一点点入门见解。
from datetime import date
from django.forms import widgets
class DateSelectorWidget(widgets.MultiWidget):
def __init__(self, attrs=None):
years = [(year, year) for year in (2011, 2012, 2013)]
_widgets = (
widgets.Select(attrs=attrs, choices=days),
widgets.Select(attrs=attrs, choices=months),
widgets.Select(attrs=attrs, choices=years),
super().__init__(_widgets, attrs)
def decompress(self, value):
if value:
return [value.day, value.month, value.year]
return [None, None, None]
def value_from_datadict(self, data, files, name):
datelist = [
widget.value_from_datadict(data, files, name + '_%s' % i)
for i, widget in enumerate(self.widgets)]
try:
D = date(
day=int(datelist[0]),
month=int(datelist[1]),
year=int(datelist[2]),
except ValueError:
return ''
else:
return str(D)
自定义表单小组件
我们以稍微复杂一点的例子,多对多字段来实现自定义表单小组件的过程
class Article(model.Model):
title = models.CharField(max_length=70, verbose_name='标题')
body = models.TextField(verbose_name='内容')
tags = models.ManyToManyField(ArticleTag, verbose_name='文章标签')
class ArticleTag(model.Model):
name = models.CharField(max_length=20, verbose_name='标签名')
def __str__(self):
return self.name
使用ModelForm构建表单
from yourapp.widgets import MySelect
class ArticleForm(ModelForm):
class Meta:
model = Article
fileds = '__all__'
widgets = {
'tags': MySelect,
复制代码
当不设置widgets时候,对应的model字段ManyToManyField自动转化为默认的表单字段MultiChoiceField,其中对应默认的django小组件为SelectMultiple。
我们设置widgets使默认的小组件更换为我们自定义的小组件。
初始化自定义小组件
from django import forms
from django.template import loader
from django.utils.safestring import mark_safe
class MySelect(forms.SelectMultiple):
template_name = 'articles/my_widget.html'
def get_context(self, name, value, attrs):
context = super(MySelect, self).get_context(name, value, attrs)
context['widget']['choices'] = self.choices.queryset
return context
def render(self, name, value, attrs=None, renderer=None):
context = self.get_context(name, value, attrs)
template = loader.get_template(self.template_name).render(context)
return mark_safe(template)
当我们只使用自己的template,而不定义自己的render()方法,django会给出模版找不到的错误。是由于django不会从项目中寻找模版文件。
自定义小组件的模版文件
<select style="border: 1px solid red;width:30%;" type="text"
id="id_{{ widget.name }}" class="form-control" name="{{ widget.name }}"
placeholder="标签" multiple>
{% for op in widget.choices %}
<option value="{{ op.pk }}">{{ op }}</option>
{% endfor %}
</select>
复制代码
我们简单定义几个样式,看看django自动渲染的表单的效果是不是我们刚刚自定义的。
样式已经改变,大功告成。
当然这只是简单的样式更改,我们可以使用更复杂的样式来修改它,比如使用多选框插件js等。那么如何为我们的模版加载资源文件呢?django也为我们做好了封装!详细见官方文档。这里给一个例子:
我们更改我们刚刚自定义的小组件
class MySelect(forms.SelectMultiple):
template_name = 'articles/my_widget.html'
def get_context(self, name, value, attrs):
context = super(MySelect, self).get_context(name, value, attrs)
context['widget']['choices'] = self.choices.queryset
return context
def render(self, name, value, attrs=None, renderer=None):
context = self.get_context(name, value, attrs)
template = loader.get_template(self.template_name).render(context)
return mark_safe(template)
class Media:
css = {
'all': ('css/pretty.css',)
js = ('js/actions.js',)
复制代码
然后我们在使用小组件生成的表单实例中,引用资源。
form = ArticleForm(request.POST)
context['form'] = form
return render(request,template_name,context)
复制代码
然后在你的模版文件中,使用{{form.media}}即可在form渲染的时候加载资源。