Django:我是怎么做到使用django动态定义表单(form)的 .

来源:互联网 发布:淘宝开店现在挣钱吗 编辑:程序博客网 时间:2024/06/04 03:53

 

http://blog.csdn.net/huyoo/article/details/6627967

第一节 数据结构

最近的项目开发,用到了这样的数据结构:
主要是来管理设备的.

下面我列一下它简化的结构,不需要更多了,其余的只是多余或者说是锦上添花:
设备分类(相当于设备的大分类)的数据结构:

[python] view plaincopyprint?
  1. class Category(models.Model):  
  2.     name         = models.CharField(max_length=40)  


   
具体设备的数据结构:  

[python] view plaincopyprint?
  1. class Equipment(models.Model):  
  2.     name        = models.CharField(max_length=100)  
  3.     category    = models.ForeignKey(Category, related_name="cg_equip_list")  


设备参数的数据结构:

[python] view plaincopyprint?
  1. class Characteristic(models.Model):  
  2.     category  = models.ForeignKey(Category, related_name="eq_characteristics")  
  3.     name      = models.CharField(max_length=40)  


 

设备参数的值的数据结构:

[python] view plaincopyprint?
  1. class CharacteristicValue(models.Model):  
  2.     equipment       = models.ForeignKey(Equipment, related_name="eq_characteristic_values")  
  3.     characteristic  = models.ForeignKey(Characteristic, related_name="eq_key_values")  
  4.     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中表单向导使用起来很简单的.

[python] view plaincopyprint?
  1. from django.utils.translation import ugettext_lazy as _  
  2. from django import forms  
  3. from django.forms.formsets import BaseFormSet  
  4. from django.forms.fields import FileField  
  5. from django.forms.util import ValidationError  
  6.   
  7. from django.shortcuts import render_to_response  
  8. from django.contrib.formtools.wizard import FormWizard  
  9.   
  10. from ddtcms.office.equipment.models import Equipment,Characteristic,CharacteristicValue  
  11.   
  12. class EquipmentForm(forms.ModelForm):  
  13.   
  14.     class Meta:  
  15.         model = Equipment  
  16.   
  17. class CharacteristicValueForm(forms.Form):  
  18.     def clean(self):  
  19.         a=self.fields  
  20.         s=self.data  
  21.         self.cleaned_data = {}  
  22.         # 下面的这一段for 是从 django的forms.py中的 full_clean 中复制来的  
  23.         for name, field in self.fields.items():  
  24.             # value_from_datadict() gets the data from the data dictionaries.  
  25.             # Each widget type knows how to retrieve its own data, because some  
  26.             # widgets split data over several HTML fields.  
  27.             value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))  
  28.             try:  
  29.                 if isinstance(field, FileField):  
  30.                     initial = self.initial.get(name, field.initial)  
  31.                     value = field.clean(value, initial)  
  32.                 else:  
  33.                     value = field.clean(value)  
  34.                 self.cleaned_data[name] = value  
  35.                 if hasattr(self'clean_%s' % name):  
  36.                     value = getattr(self'clean_%s' % name)()  
  37.                     self.cleaned_data[name] = value  
  38.             except ValidationError, e:  
  39.                 self._errors[name] = self.error_class(e.messages)  
  40.                 if name in self.cleaned_data:  
  41.                     del self.cleaned_data[name]  
  42.         #cl=self.cleaned_data   
  43.         #debug()<<<调试用的,查看cl的值,主要是看self.cleaned_data的值,如果return了,就看不到了  
  44.         return self.cleaned_data  
  45.   
  46. class EquipmentCreateWizard(FormWizard):  
  47.     def done(self, request, form_list):  
  48.         return render_to_response('equipment/done.html',  
  49.         {  
  50.         'form_data': [form.cleaned_data for form in form_list],  
  51.         })  
  52.   
  53.   
  54.     def get_form(self, step, data=None):  
  55.         "Helper method that returns the Form instance for the given step."  
  56.         form     = self.form_list[step](data, prefix=self.prefix_for_step(step), initial=self.initial.get(step, None))  
  57.           
  58.         if step == 1:  
  59.             if data:  
  60.                 cg       = data.get('0-category'1)  
  61.                 cs       = Characteristic.objects.all().filter(category__id=cg)  
  62.                 for c in cs:  
  63.                     form.fields['Characteristic-'+str(c.id)] = forms.CharField(label = c.name)  
  64.                 g=form.fields  
  65.                 #debug()   
  66.         return form  
  67.           
  68.     # 从wizard.py中复制过来进行更改的.       
  69.     def render(self, form, request, step, context=None):  
  70.         "Renders the given Form object, returning an HttpResponse."  
  71.         old_data = request.POST  
  72.         prev_fields = []  
  73.         if old_data:  
  74.             hidden = forms.HiddenInput()  
  75.             # Collect all data from previous steps and render it as HTML hidden fields.  
  76.             for i in range(step):  
  77.                 old_form = self.get_form(i, old_data)  
  78.                 hash_name = 'hash_%s' % i  
  79.                 prev_fields.extend([bf.as_hidden() for bf in old_form])  
  80.                 prev_fields.append(hidden.render(hash_name, old_data.get(hash_name, self.security_hash(request, old_form))))  
  81.             if step == 1:  
  82.                 cg       = old_data.get('0-category'1)  
  83.                 cs       = Characteristic.objects.all().filter(category__id=cg)  
  84.                 for c in cs:  
  85.                     form.fields['Characteristic-'+str(c.id)] = forms.CharField(label = c.name)  
  86.                 g=form.fields  
  87.                 #debug()   
  88.             if step == 2:  
  89.                 debug()  
  90.         return super(EquipmentCreateWizard, self).render(form, request, step, context=None)  
  91.           
  92.   
  93.     def get_template(self, step):  
  94.         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:

[python] view plaincopyprint?
  1. if step == 1:  
  2.     cg       = old_data.get('0-category'1)  
  3.     cs       = Characteristic.objects.all().filter(category__id=cg)  
  4.     for c in cs:  
  5.         form.fields['Characteristic-'+str(c.id)] = forms.CharField(label = c.name)  
  6.     g=form.fields  
  7.     #debug()  


 

这段代码在get_form和 render中都有,都是判断是不是第2步,然后就根据第1步中选择的设备的分类来查询到具体的分类,再根据分类来获取该种分类的设备有哪些参数,然后根据参数个数修改form的参数field的个数.
'Characteristic-'+str(c.id)是用来以后保存数据的时候,split这个字符串,得到参数的id,并在参数值表中保存Characteristic-1,Characteristic-2...的value.

g=form.fields
#debug()

用来断点查看参数field有多少个,是否修改成功.
   

=========================

[python] view plaincopyprint?
  1. from django.conf.urls.defaults import *  
  2. from ddtcms.office.equipment.forms import EquipmentForm,CharacteristicValueForm,EquipmentCreateWizard  
  3.   
  4.   
  5. urlpatterns = patterns('ddtcms.office.equipment.views',  
  6.     url(r'^$''index', name="equipment_index"),   
  7.     url(r'^add/$''equipment_create', name="equipment_create"),  
  8.     url(r'^add-by-wizard/$',EquipmentCreateWizard([EquipmentForm, CharacteristicValueForm]), name="equipment_create_by_wizard"), )  
  9.     以上代码,csdnbolg 自动过滤了 $符号,我加了上去,可能有不对的地方.  


==========================
wizard_0.html

[html] view plaincopyprint?
  1. {% block content %}  
  2.   
  3.  <h2>添加/修改设备向导</h2>  
  4.  <p>第 {{ step }} 步, 共 {{ step_count }} 步.</p>  
  5.  <p>填写设备基本情况</p>  
  6.   
  7.     <form method="POST" action="">{% csrf_token %}  
  8.         <table>  
  9.             {{ form }}  
  10.         </table>  
  11.         <input type="hidden" name="{{ step_field }}" value="{{ step0 }}" />  
  12.         {{ previous_fields|safe }}  
  13.         <input type="submit" value="Submit" />  
  14.     </form>  
  15.   
  16. {% endblock %}  


 

===================
wizard_1.html

[html] view plaincopyprint?
  1. {% block content %}  
  2.   
  3.  <h2>添加/修改设备向导</h2>  
  4.  <p>第 {{ step }} 步, 共 {{ step_count }} 步.</p>  
  5.  <p>填写设备参数, 如果没有要填写的内容, 请直接点击确定.</p>  
  6.   
  7.     <form method="POST" action="">{% csrf_token %}  
  8.         <table>  
  9.             {{ form }}  
  10.         </table>  
  11.         <input type="hidden" name="{{ step_field }}" value="{{ step0 }}" />  
  12.         {{ previous_fields|safe }}  
  13.         <input type="submit" value="Submit" />  
  14.     </form>  
  15.   
  16. {% endblock %}  


 

====================
done.html

[html] view plaincopyprint?
  1. {% block content %}  
  2.   
  3.  <h2>添加/修改设备向导</h2>  
  4.  <p>您已经成功添加了一个设备.</p>  
  5.   
  6.     {{form_data}}  
  7.      
  8. {% endblock %}  


============

还可以用另外的form来实现formwizard,就是第一个form1,主要用来让用户选择设备的分类,form2就根据前面的来动态生成参数的表单.原理是一样的.

还有就是写2个view来模拟formwizard,第一个view增加一个设备,第二个view带设备id这个参数即可,可以很有效的增加设备的参数.

原创粉丝点击