Django:我是怎么做到使用django动态定义表单(form)的 .
http://blog.csdn.net/huyoo/article/details/6627967
第一节 数据结构
最近的项目开发,用到了这样的数据结构:
主要是来管理设备的.
下面我列一下它简化的结构,不需要更多了,其余的只是多余或者说是锦上添花:
设备分类(相当于设备的大分类)的数据结构:
- class Category(models.Model):
- name = models.CharField(max_length=40)
class Category(models.Model): name = models.CharField(max_length=40)
具体设备的数据结构:
- class Equipment(models.Model):
- name = models.CharField(max_length=100)
- category = models.ForeignKey(Category, related_name="cg_equip_list")
class Equipment(models.Model): name = models.CharField(max_length=100) category = models.ForeignKey(Category, related_name="cg_equip_list")
设备参数的数据结构:
- class Characteristic(models.Model):
- category = models.ForeignKey(Category, related_name="eq_characteristics")
- name = models.CharField(max_length=40)
class Characteristic(models.Model): category = models.ForeignKey(Category, related_name="eq_characteristics") name = models.CharField(max_length=40)
设备参数的值的数据结构:
- class CharacteristicValue(models.Model):
- equipment = models.ForeignKey(Equipment, related_name="eq_characteristic_values")
- characteristic = models.ForeignKey(Characteristic, related_name="eq_key_values")
- value = models.CharField(max_length=100)
class CharacteristicValue(models.Model): equipment = models.ForeignKey(Equipment, related_name="eq_characteristic_values") characteristic = models.ForeignKey(Characteristic, related_name="eq_key_values") value = models.CharField(max_length=100)
可能好多人会问:你怎么不把设备的参数值直接定义到设备的数据结构中去呢?
我的回答就是:这样可以自己定义更多的设备.
每个设备的参数是不一样的,比如说电脑的参数基本上就是:
CPU频率,内存大小,硬盘大小,显示器类型,显示器大小等等.
而打印机的参数可以这样:
最大打印纸张幅面,打印机类型等等.
这样两种有着不同参数的设备该怎么将它们的数据保存至数据库中呢?
按照刚才上面的提问,我们将需要建立2个表,电脑表和打印机表,分别包含相应的参数在列中.这样的话,如果再多一种设备,比如照相机,我们又得多建立一张数据表,而疲于维护数据库了.
但是按照参数与设备分离,以参数表的形式定义各类设备的参数,以外键链接到设备分类表.
再将具体设备的各项参数的值放入参数值表中,就可以保存各个设备的参数了.
参数值的数据表中,一个参数属于哪个设备,主要根据设备的id,和参数的id来确定.
这样的表结构就相对来说比较通用.
比较麻烦的就是,增加一个设备的时候,同时要增加它相应的参数值,它的参数不在设备表中,这样就要通过参数表来查询:
在参数表中,参数的设备分类id=具体设备的所属设备分类id的行就是该设备所拥有的参数.
比如说:
设备分类表中数据:
id name
1 台式计算机
2 打印机
参数在参数表中保存了以下几行数据:
id name category
1 CPU频率 1
2 MAC地址 1
3 内存大小 1
4 显示器大小 1
5 显示器类型 1
6 硬盘大小 1
7 最大打印幅面 2
可以一目了然的看出台式计算机有6个参数,打印机有1个参数.
第二节 解决方案的选用
数据结构搞清楚了,现在的问题是怎么录入这些参数的值呢?
困难在于,每种设备的参数的个数是不确定的,因此,在定义form的时候,我们不能固定的定死form的 field个数,只有动态的增减field的个数.
那么,怎么动态生成一个有不同数目field的form呢?
这里,我的想法就是,既然参数个数保存在数据库中了,那么肯定是要查询数据库来动态生成了.
那么,使用django,该用什么具体的方法呢?
我自己是试过不少的方法的,也是在调试的过程中慢慢找到了解决这个问题的方法.
过程是这样的:
我开始想的解决的方案有:
1.使用ajax来动态的生成form
2.使用admin中的方法,用inline的 方法,在增加一个设备的同时增加几个参数
3.使用formset来制作
4.使用formtools中的formwizard(表单向导)来制作.
问题的困境主要在于参数值的表中有2个外键,所以不好确定该怎么弄.
前面三种方法我仅仅试了一下,调试了一两天就放弃了,主要原因就是:
1.ajax我目前还是不太熟,要想用好它,又得转向去学习一些相关的javascript库,一时半会弄不好.
2.admin中的inline是针对一种数据model的,增加的参数值也是相同的,比如说可能增加成6个CPU参数.而且是用在admin中的,想抠出来自己使用还挺麻烦的.
3.formset中的form也是要求相同的数据模型,但是参数值表中2个外键高的你根本没法使用formset,formset是在简单的数据结构情况下录入数据用的.
formset有一个函数就是add_fields,这个函数比较有用,但是form没有这个函数.了解了原理就简单了,就是form['field_name']=forms.CharField()之类就可以为一个form 实例添加field了.
我觉得formset是可以解决问题的,但是前提是构造formset前,必须提前知道formset中有几个参数值的form在里面(就是要知道某种分类的设备有几个参数,好在formset中构建几个form,CharacteristicValue的form,每个form采用不同的参数来进行初始化initial).
在一个页面中,先增加设备的form,因为设备的参数个数不是固定的,需要动态的增加含有几个参数值表单的formset,这样就可以选择设备的参数了.formset比较适用于使用固定个数的form.所以这样就给使用formset增加了难度,如果要让这种方式能够使用,最终还是要用到ajax来根据用户选择设备类型的时候,动态的生成formset.
formset需要在view中初始化.
所以这种方式我没有采用.
我采用的是表单向导的方式.
django的contrib中提供了formtools,用过了,会用了才觉得真好用.我是看英文文档一点一滴的学会使用的,感觉真的很方便.
但是上面的4的解决方案的实现过程倒是很艰难的.我弄了3天才弄出点眉目来.
第三节 具体的实现
django中表单向导使用起来很简单的.
- from django.utils.translation import ugettext_lazy as _
- from django import forms
- from django.forms.formsets import BaseFormSet
- from django.forms.fields import FileField
- from django.forms.util import ValidationError
- from django.shortcuts import render_to_response
- from django.contrib.formtools.wizard import FormWizard
- from ddtcms.office.equipment.models import Equipment,Characteristic,CharacteristicValue
- class EquipmentForm(forms.ModelForm):
- class Meta:
- model = Equipment
- class CharacteristicValueForm(forms.Form):
- def clean(self):
- a=self.fields
- s=self.data
- self.cleaned_data = {}
- # 下面的这一段for 是从 django的forms.py中的 full_clean 中复制来的
- for name, field in self.fields.items():
- # value_from_datadict() gets the data from the data dictionaries.
- # Each widget type knows how to retrieve its own data, because some
- # widgets split data over several HTML fields.
- value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))
- try:
- if isinstance(field, FileField):
- initial = self.initial.get(name, field.initial)
- value = field.clean(value, initial)
- else:
- value = field.clean(value)
- self.cleaned_data[name] = value
- if hasattr(self, 'clean_%s' % name):
- value = getattr(self, 'clean_%s' % name)()
- self.cleaned_data[name] = value
- except ValidationError, e:
- self._errors[name] = self.error_class(e.messages)
- if name in self.cleaned_data:
- del self.cleaned_data[name]
- #cl=self.cleaned_data
- #debug()<<<调试用的,查看cl的值,主要是看self.cleaned_data的值,如果return了,就看不到了
- return self.cleaned_data
- class EquipmentCreateWizard(FormWizard):
- def done(self, request, form_list):
- return render_to_response('equipment/done.html',
- {
- 'form_data': [form.cleaned_data for form in form_list],
- })
- def get_form(self, step, data=None):
- "Helper method that returns the Form instance for the given step."
- form = self.form_list[step](data, prefix=self.prefix_for_step(step), initial=self.initial.get(step, None))
- if step == 1:
- if data:
- cg = data.get('0-category', 1)
- cs = Characteristic.objects.all().filter(category__id=cg)
- for c in cs:
- form.fields['Characteristic-'+str(c.id)] = forms.CharField(label = c.name)
- g=form.fields
- #debug()
- return form
- # 从wizard.py中复制过来进行更改的.
- def render(self, form, request, step, context=None):
- "Renders the given Form object, returning an HttpResponse."
- old_data = request.POST
- prev_fields = []
- if old_data:
- hidden = forms.HiddenInput()
- # Collect all data from previous steps and render it as HTML hidden fields.
- for i in range(step):
- old_form = self.get_form(i, old_data)
- hash_name = 'hash_%s' % i
- prev_fields.extend([bf.as_hidden() for bf in old_form])
- prev_fields.append(hidden.render(hash_name, old_data.get(hash_name, self.security_hash(request, old_form))))
- if step == 1:
- cg = old_data.get('0-category', 1)
- cs = Characteristic.objects.all().filter(category__id=cg)
- for c in cs:
- form.fields['Characteristic-'+str(c.id)] = forms.CharField(label = c.name)
- g=form.fields
- #debug()
- if step == 2:
- debug()
- return super(EquipmentCreateWizard, self).render(form, request, step, context=None)
- def get_template(self, step):
- return 'equipment/wizard_%s.html' % step
from django.utils.translation import ugettext_lazy as _ from django import forms from django.forms.formsets import BaseFormSet from django.forms.fields import FileField from django.forms.util import ValidationError from django.shortcuts import render_to_response from django.contrib.formtools.wizard import FormWizard from ddtcms.office.equipment.models import Equipment,Characteristic,CharacteristicValue class EquipmentForm(forms.ModelForm): class Meta: model = Equipment class CharacteristicValueForm(forms.Form): def clean(self): a=self.fields s=self.data self.cleaned_data = {} # 下面的这一段for 是从 django的forms.py中的 full_clean 中复制来的 for name, field in self.fields.items(): # value_from_datadict() gets the data from the data dictionaries. # Each widget type knows how to retrieve its own data, because some # widgets split data over several HTML fields. value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name)) try: if isinstance(field, FileField): initial = self.initial.get(name, field.initial) value = field.clean(value, initial) else: value = field.clean(value) self.cleaned_data[name] = value if hasattr(self, 'clean_%s' % name): value = getattr(self, 'clean_%s' % name)() self.cleaned_data[name] = value except ValidationError, e: self._errors[name] = self.error_class(e.messages) if name in self.cleaned_data: del self.cleaned_data[name] #cl=self.cleaned_data #debug()<<<调试用的,查看cl的值,主要是看self.cleaned_data的值,如果return了,就看不到了 return self.cleaned_data class EquipmentCreateWizard(FormWizard): def done(self, request, form_list): return render_to_response('equipment/done.html', { 'form_data': [form.cleaned_data for form in form_list], }) def get_form(self, step, data=None): "Helper method that returns the Form instance for the given step." form = self.form_list[step](data, prefix=self.prefix_for_step(step), initial=self.initial.get(step, None)) if step == 1: if data: cg = data.get('0-category', 1) cs = Characteristic.objects.all().filter(category__id=cg) for c in cs: form.fields['Characteristic-'+str(c.id)] = forms.CharField(label = c.name) g=form.fields #debug() return form # 从wizard.py中复制过来进行更改的. def render(self, form, request, step, context=None): "Renders the given Form object, returning an HttpResponse." old_data = request.POST prev_fields = [] if old_data: hidden = forms.HiddenInput() # Collect all data from previous steps and render it as HTML hidden fields. for i in range(step): old_form = self.get_form(i, old_data) hash_name = 'hash_%s' % i prev_fields.extend([bf.as_hidden() for bf in old_form]) prev_fields.append(hidden.render(hash_name, old_data.get(hash_name, self.security_hash(request, old_form)))) if step == 1: cg = old_data.get('0-category', 1) cs = Characteristic.objects.all().filter(category__id=cg) for c in cs: form.fields['Characteristic-'+str(c.id)] = forms.CharField(label = c.name) g=form.fields #debug() if step == 2: debug() return super(EquipmentCreateWizard, self).render(form, request, step, context=None) def get_template(self, step): return 'equipment/wizard_%s.html' % step
EquipmentCreateWizard其实也可以放在views.py中,而且我觉得更合理一点.
在EquipmentCreateWizard 中,我试着修改过process_step 函数,但是得不到正确的结果,后来修改了get_form,都是想从django的formtools的wizard.py中复制过来再进行修改的.
get_form的修改也没有得到正确的结果.后来就修改render函数,在第2步的时候,我将动态参数个数显示出来了.但是到最后结束done的环节,取得的formdata中,第二个form没有数据,就是一个空的{},
于是我又重新修改get_form函数,无非就是判断是不是第二步,然后给第二个form动态添加几个field:
- if step == 1:
- cg = old_data.get('0-category', 1)
- cs = Characteristic.objects.all().filter(category__id=cg)
- for c in cs:
- form.fields['Characteristic-'+str(c.id)] = forms.CharField(label = c.name)
- g=form.fields
- #debug()
if step == 1: cg = old_data.get('0-category', 1) cs = Characteristic.objects.all().filter(category__id=cg) for c in cs: form.fields['Characteristic-'+str(c.id)] = forms.CharField(label = c.name) g=form.fields #debug()
这段代码在get_form和 render中都有,都是判断是不是第2步,然后就根据第1步中选择的设备的分类来查询到具体的分类,再根据分类来获取该种分类的设备有哪些参数,然后根据参数个数修改form的参数field的个数.
'Characteristic-'+str(c.id)是用来以后保存数据的时候,split这个字符串,得到参数的id,并在参数值表中保存Characteristic-1,Characteristic-2...的value.
g=form.fields
#debug()
用来断点查看参数field有多少个,是否修改成功.
=========================
- from django.conf.urls.defaults import *
- from ddtcms.office.equipment.forms import EquipmentForm,CharacteristicValueForm,EquipmentCreateWizard
- urlpatterns = patterns('ddtcms.office.equipment.views',
- url(r'^$', 'index', name="equipment_index"),
- url(r'^add/$', 'equipment_create', name="equipment_create"),
- url(r'^add-by-wizard/$',EquipmentCreateWizard([EquipmentForm, CharacteristicValueForm]), name="equipment_create_by_wizard"), )
- 以上代码,csdnbolg 自动过滤了 $符号,我加了上去,可能有不对的地方.
from django.conf.urls.defaults import * from ddtcms.office.equipment.forms import EquipmentForm,CharacteristicValueForm,EquipmentCreateWizard urlpatterns = patterns('ddtcms.office.equipment.views', url(r'^$', 'index', name="equipment_index"), url(r'^add/$', 'equipment_create', name="equipment_create"), url(r'^add-by-wizard/$',EquipmentCreateWizard([EquipmentForm, CharacteristicValueForm]), name="equipment_create_by_wizard"), ) 以上代码,csdnbolg 自动过滤了 $符号,我加了上去,可能有不对的地方.
==========================
wizard_0.html
- {% block content %}
- <h2>添加/修改设备向导</h2>
- <p>第 {{ step }} 步, 共 {{ step_count }} 步.</p>
- <p>填写设备基本情况</p>
- <form method="POST" action="">{% csrf_token %}
- <table>
- {{ form }}
- </table>
- <input type="hidden" name="{{ step_field }}" value="{{ step0 }}" />
- {{ previous_fields|safe }}
- <input type="submit" value="Submit" />
- </form>
- {% endblock %}
{% block content %} <h2>添加/修改设备向导</h2> <p>第 {{ step }} 步, 共 {{ step_count }} 步.</p> <p>填写设备基本情况</p> <form method="POST" action="">{% csrf_token %} <table> {{ form }} </table> <input type="hidden" name="{{ step_field }}" value="{{ step0 }}" /> {{ previous_fields|safe }} <input type="submit" value="Submit" /> </form> {% endblock %}
===================
wizard_1.html
- {% block content %}
- <h2>添加/修改设备向导</h2>
- <p>第 {{ step }} 步, 共 {{ step_count }} 步.</p>
- <p>填写设备参数, 如果没有要填写的内容, 请直接点击确定.</p>
- <form method="POST" action="">{% csrf_token %}
- <table>
- {{ form }}
- </table>
- <input type="hidden" name="{{ step_field }}" value="{{ step0 }}" />
- {{ previous_fields|safe }}
- <input type="submit" value="Submit" />
- </form>
- {% endblock %}
{% block content %} <h2>添加/修改设备向导</h2> <p>第 {{ step }} 步, 共 {{ step_count }} 步.</p> <p>填写设备参数, 如果没有要填写的内容, 请直接点击确定.</p> <form method="POST" action="">{% csrf_token %} <table> {{ form }} </table> <input type="hidden" name="{{ step_field }}" value="{{ step0 }}" /> {{ previous_fields|safe }} <input type="submit" value="Submit" /> </form> {% endblock %}
====================
done.html
- {% block content %}
- <h2>添加/修改设备向导</h2>
- <p>您已经成功添加了一个设备.</p>
- {{form_data}}
- {% endblock %}
{% block content %} <h2>添加/修改设备向导</h2> <p>您已经成功添加了一个设备.</p> {{form_data}} {% endblock %}
============
还可以用另外的form来实现formwizard,就是第一个form1,主要用来让用户选择设备的分类,form2就根据前面的来动态生成参数的表单.原理是一样的.
还有就是写2个view来模拟formwizard,第一个view增加一个设备,第二个view带设备id这个参数即可,可以很有效的增加设备的参数.
Django:我是怎么做到使用django动态定义表单(form)的 .相关推荐
- django一个html先后两个form,Django教程(三)- Django表单Form
目录: 1.Form 基本使用 django中的Form组件有以下几个功能: 生成HTML标签 验证用户数据(显示错误信息) HTML Form提交保留上次提交数据 初始化页面显示内容 2.Form中 ...
- vue实现动态添加表单
前言 日常开发中,动态添加表单的场景还是蛮多的,可能第一次听到动态添加表单的同学会有一点懵,但其实实现起来还是很简单的,下面一起来看看吧. 实现思路 当我们点击 "新增车辆信息" ...
- html实现动态多表单输入,提交多个动态添加的html表单
我正在构建一个可以动态添加表单的功能.不是表单字段,而是一个完整的单独的HTML表单.提交多个动态添加的html表单 我使用JS添加它们. add_email_template $('#add_ema ...
- 带小鱼游动动画的动态登录表单html页面前端源码
大家好,今天给大家介绍一款,带小鱼游动动画的动态登录表单html页面前端源码(图1),送给大家哦,获取方式在本文末尾. 图1 有一条活灵活现的小鱼围着表单游动,给页面添加了许多生机(图2), 图2 源 ...
- 泛微动态获取表单字段ID
泛微8.0动态获取表单字段ID 说明 前端代码 前端调用代码 前端jslabel.js文件方法 后端代码 后端getjsLabel.jsp文件 查询字段基础数据SQL 说明 这个功能是基于泛微8.0平 ...
- Python+django网页设计入门(13):表单、修改密码
前导课程: Python+django网页设计入门(12):使用Bootstrap和jQuery Python+django网页设计入门(11):在线考试与自动评分 Python+django网页设计 ...
- 接口测试-接口定义功能-前端-实现动态增删表单
基于 springboot+vue 的测试平台开发继续更新. 目前已经进入到接口定义功能的开发阶段,首先我还是直接在前段画了个大概的页面,先预览下: 不过目前只是画了这个页面都主要功能,细节未尽事宜待 ...
- antd 动态添加表单_ZooTeam 拍了拍你,来看看如何设计动态化表单
? 这是第 58篇不掺水的原创,想要了解更多,请戳上方蓝色字体:政采云前端团队关注我们吧- 本文首发于政采云前端团队博客:ZooTeam 拍了拍你,来看看如何设计动态化表单 https://www.z ...
- vue动态生成表单元素基础篇
这里讲解一个vue生成表单的简单实例: (图一) (图二) (图三) 如上图所示: 图一: 空的输入框的情况 图二: 点击 "+" 添加生成表单的情况 图三: 表单中可以输入值,并 ...
最新文章
- 前端电脑和浏览器分辨率不同_不同岗位的程序员如何选电脑 :)
- 自定义msi安装包的执行过程
- 【ABAP】通过SE30来测试程序的执行效率
- linux_mint语言卡住,使用linux mint 16的容易死机怎么处理?
- 用python编写daemon监控进程并自动恢复(附Shell版)
- es6 Class简介
- linux系统管理常用命令--top
- linux学习笔记:yum命令的常见用法
- java 线程协作 wait(等待)与 notiy(通知)
- Julia :复合类型struct当索引时
- 影视后期制作(Pr)
- 自定义加密cookie加盐密码
- 单片机学习——看门狗
- 弃用个人博客站重返CSDN缘由
- 应用二 stm32使用esp8266进行串口wifi通信
- [ 代码审计篇 ] 代码审计思路 详解
- 【Git从入门到精通】Git入门
- 经典算法题:二分查找
- RabbitMq安装(Erlang前置安装)
- 如何获取客户端真实IP