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只要复制上去就能用。这样改动也更合理一些,毕竟作为一个后端框架,表单相关的操作本来就不适合它。类似字段是否为空、数据格式是否合法的校验,就应该是前端来完成的。
推荐下面两个分别是对应bootstrap3 和 23的html由python代码生成,2的根据template生成,所以可以自定义成任何非bootstrap结构https://github.com/dyve/django-bootstrap3https://github.com/dyve/django-bootstrap-toolkit
在兄台的博客里看了不少django的资料,很是受益,冒个泡表示下感谢~