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只要复制上去就能用。这样改动也更合理一些,毕竟作为一个后端框架,表单相关的操作本来就不适合它。类似字段是否为空、数据格式是否合法的校验,就应该是前端来完成的。
转载请注明:爱开源 » 用Django和Backbone.js生成表单