为什么要自定义小组件

在我们使用 django表单 的过程中,官方给出的Filed虽然已经很丰富了,但是还是很难满足我们的开发要求,例如隐藏 FileInput 的按钮及显示上传图片的缩略图而非文件名,定制的多选框 MultiChoice 等。 Django官方文档 虽然有简单提过一下自定义小组件的方法,并使用了 MultiWidget 进行演示,例子如下,可还是让人一头雾水...互联网上相应的文章也很少,所以写下一点点入门见解。

from datetime import date
from django.forms import widgets
class DateSelectorWidget(widgets.MultiWidget):
    def __init__(self, attrs=None):
        # create choices for days, months, years
        # example below, the rest snipped for brevity.
        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)

自定义表单小组件

我们以稍微复杂一点的例子,多对多字段来实现自定义表单小组件的过程

# models.py
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构建表单

# forms.py
from yourapp.widgets import MySelect  # 在app目录下建立一个widgets.py文件,我们在里面定义我们的小组件
class ArticleForm(ModelForm):
    class Meta:
        model = Article
        fileds = '__all__'
        widgets = {
            'tags': MySelect, # 设置我们自己的多选小组件
复制代码

当不设置widgets时候,对应的model字段ManyToManyField自动转化为默认的表单字段MultiChoiceField,其中对应默认的django小组件为SelectMultiple

我们设置widgets使默认的小组件更换为我们自定义的小组件。

初始化自定义小组件

# yourapp/widgets.py
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'
    # 建立上下文变量使其模版文件可以使用
    # name是表单中的字段名,value是表单中的值,attrs是request的相关值
    def get_context(self, name, value, attrs):
        # 等到父类的上下文
        context = super(MySelect, self).get_context(name, value, attrs)
        # 添加自己的上下文
        # 获取多对多模型的查询集,此处为ArticleTag的查询集,choices应该只有一对多或多对多才有的属性
        context['widget']['choices'] = self.choices.queryset
        return context
    # 将上下文变量导入模版,生成对应的Html字符串
    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不会从项目中寻找模版文件。

自定义小组件的模版文件

<!-- templates/yourapp/my_widgets.html -->
<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)
    # add 添加的新内容,要加载的css,js文件
    class Media:
        css = {
            'all': ('css/pretty.css',)
        js = ('js/actions.js',)
复制代码

然后我们在使用小组件生成的表单实例中,引用资源。

# yourapp/view.py
# 表单实例
form = ArticleForm(request.POST)
#将表单添加到上下文
context['form'] = form
# 你的逻辑代码...
return render(request,template_name,context)
复制代码

然后在你的模版文件中,使用{{form.media}}即可在form渲染的时候加载资源。

分类:
后端
标签: