Front Python

用Django和Backbone.js生成表单

Django带了Forms框架,但如果不用Model Form,就要把在Model中定义的字段再写一遍。而且现在项目中都会用到前端工具包/框架,比如Bootstrap,于是as_table/as_ul/as_p就不能直接用了,每个字段都要手动写到HTML里。我之前是用django-crispy-forms这个模块来简化。

Backbone.js是一个基础框架,没有生成表单的功能,但有一个依赖于Backbone.js,很好用的Forms框架—— backbone-forms

django-crispy-forms和backbone-forms做的事非常相近(至少render出的结果是一样的),没必要同时使用。理想的方案是Django搭配backbone-forms,后者从Django定义的Model/Object中获得Schema,生成表单。目前代码是跑通了,但比较折腾,是用Python的JS解析器,引入需要的JS库,执行从Django Model生成的JS代码,最后返回Form的HTML代码到前端。有些步骤还需要改进。这篇文章先分享:从Django的Model生成JS代码,传到前台,生成表单的方法。

Install

在html模板中引入需要的JS库(引用的都是在线的库,可能会出现无法访问的情况),bootstrap.js会自动给表单添加上Bootstrap的样式,要在backbone-forms.min.js后引入。在DOM之后加入script标签,之后得到一个Customer的object,在script标签内调用render_form方法,生成JS代码。


<!DOCTYPE HTML>
<html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js"></script>
<script src="http://underscorejs.org/underscore-min.js"></script>
<script src="http://backbonejs.org/backbone-min.js"></script>
<script src="https://rawgithub.com/powmedia/backbone-forms/master/distribution/backbone-forms.min.js"></script>
<script src="https://rawgithub.com/powmedia/backbone-forms/master/distribution/templates/bootstrap.js"></script>
</head>
<body>
<div id="id_form"></div>
<script>
{{customer.render_form}}
</script>
</body>
</html>

Model & View & Template

例如有一个名为Customer的Model,给它添加render_form的方法:


from django.db import models
from django.utils.safestring import mark_safe
import json

GENDER_CHOICES = (
    ('female', 'Female'),
    ('male', 'Male'),
)

class Customer(models.Model):
    name = models.CharField(max_length=32)
    gender = models.CharField(max_length=8, choices=GENDER_CHOICES)
    is_active = models.BooleanField(default=True)
    age = models.IntegerField()
    birthday = models.DateField()
    description = models.TextField(blank=True)

    def render_form(self):
        field_map = {
            'CharField': 'Text',
            'BooleanField': 'Checkbox',
            'IntegerField': 'Number',
            'DateField': 'Date',
            'TextField': 'TextArea',
        }
        schema = {}
        for field in self._meta.fields:
            if field.name != 'id':
                schema[field.name] = {'type': field_map[field.__class__.__name__]}
                if len(field.choices) != 0:
                    schema[field.name] = {'type': 'Select', 'options': [v[0] for v in field.choices]}
        js = 'var form = new Backbone.Form({schema : %s}).render(); $("#id_form").append(form.el)' % json.dumps(schema)
        return mark_safe(js)

  • line 19: 定义Django Model Field与backbone-form Schema Type的对应关系,例如CharField/TextField对应Text和TextArea,Big/Small*IntegerField/PositiveIntegerField都是对应Number类型;
  • line 27~31: 对字段进行for操作,id字段不做处理。如果字段中设置了choices,就用Select来处理;
  • line 32: 生成JS代码。这里是把实例化Backbone.Form的工作也放在后端完成了。

views.py部分的代码看具体应用场景,得到object后,在模板中调用render_form方法,实际生成的表单如下图:

Validation & Fill

以上是完成了Django Form生成的工作,接下来用backbone-form来实现表单的验证,以及初始化数据的功能。

首先是验证,Django Model的字段,默认是必须填写,backbone-form中的字段默认可以为空,所以要把render_form方法做如下改动:


    def render_form(self):
        field_map = {
            ....
        }
        schema = {}
        for field in self._meta.fields:
            if field.name != 'id':
                schema[field.name] = {'type': field_map[field.__class__.__name__]}
                if len(field.choices) != 0:
                    schema[field.name] = {'type': 'Select', 'options': [v[0] for v in field.choices]}
                if field.blank is not True:
                    schema[field.name]['validators'] = ['required']
        ....

line 11~12:进行判断,如果field.blank不为True(model没有设置blank=True),就在JS代码中加上required的验证。当更新Backbone.js Model时(form.commit),如果required的字段没有填写会报错:

然后是初始化数据,这要用到Backbone.js的Model功能。代码中保持了Django Model的命名和Backbone.js Model的命名一致,把object的数据在Backbone.js Model实例化时传进去:


...
from django.forms.models import model_to_dict
from django.core.serializers.json import DjangoJSONEncoder # model_to_dict时处理时间戳使用

...
    def render_fill_form(self):
        model_name = self.__class__.__name__
        field_map = {
            'CharField': 'Text',
            'BooleanField': 'Checkbox',
            'IntegerField': 'Number',
            'DateField': 'Date',
            'TextField': 'TextArea',
        }
        schema = {}
        for field in self._meta.fields:
            if field.name != 'id':
                schema[field.name] = {'type': field_map[field.__class__.__name__]}
                if len(field.choices) != 0:
                    schema[field.name] = {'type': 'Select', 'options': [v[0] for v in field.choices]}
                if field.blank is not True:
                    schema[field.name]['validators'] = ['required']
        js = 'var %s = Backbone.Model.extend({schema: %s});' % (model_name, json.dumps(schema))
        js += 'var %s = new %s(%s);' % (model_name.lower(), model_name, json.dumps(model_to_dict(self), cls=DjangoJSONEncoder))
        js += 'var form = new Backbone.Form({model : %s}).render(); $("#id_form").append(form.el)' % model_name.lower()
        return mark_safe(js)

方法名是render_fill_form,以便和不初始化数据的render_form区分。Django模板不支持直接传递函数,没办法写在一个函数里,除非换其他模板引擎。也可以做成templatetags,只是代码量会增多,自由度也小了。

Afterword

以上是用backbone-form代替Django Forms的方法,节约了不少开发时间,render_(fill_)form函数比较灵活,对于大多数Django Model只要复制上去就能用。这样改动也更合理一些,毕竟作为一个后端框架,表单相关的操作本来就不适合它。类似字段是否为空、数据格式是否合法的校验,就应该是前端来完成的。

0 0 投票数
文章评分
订阅评论
提醒
guest

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据

2 评论
最新
最旧 最多投票
内联反馈
查看所有评论
jimmyyye
jimmyyye
10 年 前

推荐下面两个分别是对应bootstrap3 和 23的html由python代码生成,2的根据template生成,所以可以自定义成任何非bootstrap结构https://github.com/dyve/django-bootstrap3https://github.com/dyve/django-bootstrap-toolkit

种瓜得豆
10 年 前

在兄台的博客里看了不少django的资料,很是受益,冒个泡表示下感谢~