05:ModelForm 数据验证 & 生成html & 数据库操作
目录:Django其他篇
05:ModelForm 数据验证 & 生成html & 数据库操作
目录:
- 1.1 ModelForm作用及基本使用
- 1.2 Meta中定义字段验证规则
- 1.3 ModelForm应用:编辑默认选中 及 提交自动保存
- 1.4 MordelForm中生成html & 数据验证 常用方法
- 1.5 ModelForm验证规则中的内置钩子
- 1.6 使用type动态生成ModelForm类进行数据验证、生成html
- 1.7 ModelForm实例:不使用ModelForm生成html、并用ajax提交显示错误信息
1.1 ModelForm作用及基本使用返回顶部
1、form 作用
1、功能1: 验证
2、功能2: 生成html标签(默认功能:保留上次提交的值)
3、功能3: 数据操作
4、功能4: HTML Form提交保留上次提交数据
5、功能5: 初始化页面显示内容
2、form使用原则
1、 新url方式操作(一定要用form方式生成html,避免提交刷新页面,丢失当前页面中填的值)
2、 发Ajax请求时可以不用form生成html标签,仅用form做验证,因为ajax请求本身不刷新页面,不必担心填
的值会丢失,当然使用form生成html也是可以的
1.2 Meta中定义字段验证规则返回顶部
注意: 导入模块名(fields、widgets)和字段名重复,所以导入时要起个别名。
- from django import forms
- from django.forms import fields as Ffields
- from django.forms import widgets as Fwidgets
- class UserInfoModelForm(forms.ModelForm):
- is_rmb = Ffields.CharField(widget=Fwidgets.CheckboxInput())
- class Meta:
- model = models.UserInfo
- fields = '__all__'
- # fields = ['username','email']
- # exclude = ['username']
- labels = {
- 'username': '用户名',
- 'email': '邮箱',
- }
- help_texts = {
- 'username': '...'
- }
- widgets = {
- 'username': Fwidgets.Textarea(attrs={'class': 'c1'})
- }
- error_messages = {
- '__all__':{ # 整体错误信息
- },
- 'email': {
- 'required': '邮箱不能为空',
- 'invalid': '邮箱格式错误..',
- }
- }
- field_classes = { # 定义字段的类是什么
- # 'email': Ffields.URLField # 这里只能填类,加上括号就是对象了。
- }
- # localized_fields=('ctime',) # 哪些字段做本地化
Meta中可以定义的字段类型
1.3 ModelForm应用:编辑默认选中 及 提交自动保存返回顶部
1、所有文件
- from app01 import views
- urlpatterns = [
- url(r'^admin/', admin.site.urls),
- url(r'^index/', views.index),
- url(r'^user_list/', views.user_list),
- url(r'^user_edit/(?P<nid>\d+)/', views.user_edit),
- ]
urls.py 路由系统
- from django.db import models
- class UserType(models.Model):
- caption = models.CharField(max_length=32)
- def __str__(self):
- return self.caption
- class UserInfo(models.Model):
- username = models.CharField(max_length=32)
- email = models.EmailField()
- user_type = models.ForeignKey(to='UserType', to_field='id')
models.py 定义表
- from django.shortcuts import render,HttpResponse
- from app01 import models
- from app01.forms import UserInfoModelForm
- #1、创建UserInfo表中数据
- def index(request):
- if request.method == 'GET':
- obj = UserInfoModelForm()
- return render(request,'index.html',{'obj':obj})
- elif request.method == 'POST':
- obj = UserInfoModelForm(request.POST)
- if obj.is_valid():
- obj.save()
- return render(request,'index.html',{'obj':obj})
- #2、展示UserInfo表中数据
- def user_list(request):
- li = models.UserInfo.objects.all().select_related('user_type')
- return render(request,'user_list.html',{'li':li})
- #3、编辑默认选中 & 提交自动保存
- def user_edit(request,nid):
- if request.method == 'GET':
- user_obj = models.UserInfo.objects.filter(id=nid).first()
- mf = UserInfoModelForm(instance=user_obj) #只用传入instance点击编辑时就会默认选中
- return render(request,'user_edit.html',{'mf':mf,'nid':nid})
- elif request.method == 'POST': #ModelForm修改
- user_obj = models.UserInfo.objects.filter(id=nid).first() # 如果这里没有传入 instance=user_obj就会变成ModelForm添加
- mf = UserInfoModelForm(request.POST,instance=user_obj) #修改后数据要传递进去
- if mf.is_valid():
- mf.save()
- else:
- print(mf.errors.as_json())
- return render(request,'user_edit.html',{'mf':mf,'nid':nid})
- #################### 自动生成UserType 和 UserInfo 表中的数据 ##############
- usertype_list = [
- {'caption':'python_group'},
- {'caption':'linux_group'},
- ]
- userinfo_list = [
- {'username':'zhangsan','email':'zhangsan@qq.com','user_type_id':1,},
- {'username':'lisi','email':'lisi@qq.com','user_type_id':1,},
- {'username':'wangwu','email':'wangwu@qq.com','user_type_id':1,},
- ]
- '''
- for u_type in usertype_list:
- models.UserType.objects.create(**u_type)
- for userinfo in userinfo_list:
- models.UserInfo.objects.create(**userinfo)
- '''
views.py 视图函数
- from django.shortcuts import render,HttpResponse
- from django import forms
- # 在ModelForm中有插件名widgets,所以这里引入插件时要取别名,否则报错
- from django.forms import fields as Ffields
- from django.forms import widgets as Fwidgets
- from app01 import models
- class UserInfoModelForm(forms.ModelForm):
- extraField = Ffields.CharField( widget=Fwidgets.CheckboxInput() ) #ModelForm中可以自定制额外字段
- class Meta:
- model = models.UserInfo #model指定关联那个类
- fields = '__all__'
- labels = {
- 'username':'用户名',
- 'email':'邮箱',
- }
- help_texts = {
- 'username':'username字段提示信息'
- }
- widgets = {
- 'username':Fwidgets.Textarea(attrs={'class':'c1',})
- }
- error_messages = {
- '__all__':{}, #定义整体的错误信息
- 'email':{
- 'required':'邮箱不能为空',
- 'invalid':'邮箱格式错误',
- }
- }
- field_classes = {
- # 'email':Ffields.URLField #改变字段格式为url
- }
- localized_fields=('birth_date',)
forms.py 数据验证规则
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <title>Title</title>
- </head>
- <body>
- <form action="/index/" method="POST">
- {% csrf_token %}
- <p>{{ obj.username.label }}: {{ obj.username }} {{ obj.errors.username.0 }}</p>
- <p>{{ obj.email.label }}: {{ obj.email }} {{ obj.errors.email.0 }}</p>
- <p>{{ obj.user_type.label }}: {{ obj.user_type }} {{ obj.errors.user_type.0 }}</p>
- <p>{{ obj.extraField.label }}: {{ obj.extraField }} {{ obj.errors.extraField.0 }}</p>
- <input type="submit" value="提交">
- </form>
- </body>
- </html>
index.html生成html 创建数据
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <title>Title</title>
- </head>
- <body>
- <ul>
- {% for row in li %}
- <li>{{ row.username }}-{{ row.user_type.caption }}
- <a href="/user_edit/{{ row.id }}/">编辑</a></li>
- {% endfor %}
- </ul>
- </body>
- </html>
user_list.html 展示数据
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <title>Title</title>
- </head>
- <body>
- <form method="POST" action="/user_edit/{{ nid }}/">
- {% csrf_token %}
- {{ mf.as_p }}
- <input type="submit" value="提交">
- </form>
- </body>
- </html>
user_edit.html 编辑数据
2、说明
1. http://127.0.0.1:8000/index/ 页面用来新建用户(在views.py中也有批量创建的for循环)
2. http://127.0.0.1:8000/user_list/ 页面用来展示已创建用户,当点击编辑是会携带对应用户的id,已gett请求提交给user_edit
3. http://127.0.0.1:8000/user_edit/1/ 这个路径携带有需要修改的用户id,实现编辑默认选中 及 提交自动保存
1.4 MordelForm中生成html & 数据验证 常用方法返回顶部
1、生成HTML常用语法
1、 obj.user.label 标签显示内容(如:用户名)
obj.user.label_tag
2、 obj.user 自动生成一个input标签,这种表自动保留上一次提交的数据功能
3、 obj.errors.user.0 获取字段错误信息(提取到user字段)
obj.errors 所有字段错误信息的html字符串
obj.user.errors 错误信息(返回html标签)<ul class="errorlist"><li>用户名不能为空</li></ul>
4、 obj.email.help_text 获取提示帮助信息(必须输入邮箱格式)
4、ModelForm对象数据验证
1. 用于验证
model_form_obj = XXOOModelForm(request.POST) # 将POST中提交的所有数据传给处理类(类中做校验)
model_form_obj.is_valid() # 类中对输入信息校验结果,符合返回True,否则返回False
model_form_obj.clean() # 用户POST中所有正确信息,格式就是字典
model_form_obj.cleaned_data
model_form_obj.errors.as_json() # 错误信息转换成json格式
model_form_obj.errors # 所有错误信息的html字符串(ul li格式)
2. 用于创建保存
# 默认保存多对多
obj = form.save(commit=True)
# 不做任何操作,内部定义 save_m2m(用于保存多对多)
obj = form.save(commit=False)
obj.save() # 保存单表信息
obj.save_m2m() # 保存关联多对多信息
4、新建数据 、修改数据、默认选中
UserInfoModelForm(request.POST,instance=user_obj) # 修改新旧数据都需要提交
UserInfoModelForm(request.POST) # 新建只需提交request.POST数据
UserInfoModelForm(instance=user_obj) # 只用传入instance点击编辑时就会默认选中
5. 验证执行过程
is_valid -> full_clean -> 钩子 -> 整体错误
- def clean_字段名(self):
- # 可以抛出异常
- # from django.core.exceptions import ValidationError
- return "新值"
定义字段钩子
1.5 ModelForm验证规则中的内置钩子返回顶部
1. from验证经历的顺序(搜索:Form and field validation)
验证执行过程: is_valid -> full_clean -> 钩子 -> 整体错误
1、拿到字段:用户发送一堆数据,根据form循环,拿到第一个字段
2、正则匹配:先进行fields默认正则表达式判断,然后进行自定的正则表达式判断(如果有)
3、字段钩子函数:然后执行字段的钩子函数,接着进行第二个字段,然后是第二个字段钩子函数...
4、clean钩子函数:字段钩子函数执行完了再执行clean钩子函数进行整体验证
5、_post_clean: 最后执行_post_clean钩子做其他验证
2、 form验证的错误信息存放位置
1、字段钩子错误信息放到对应的字段中 (obj.error中对应的字段字典)
2、整体错误信息会放到 {"__all__":[],}中等价于{'NON_FIELD_ERRORS':[],} (如:执行clean)
3、forms.py文件中使用这三种钩子
- from django import forms
- from django.forms import fields as Ffields
- from django.forms import widgets as Fwidgets
- from app01 import models
- from django.core.exceptions import ValidationError
- class UserInfoModelForm(forms.ModelForm):
- extraField = Ffields.CharField( widget=Fwidgets.CheckboxInput() ) #ModelForm中可以自定制额外字段
- class Meta:
- model = models.User #model指定关联那个类
- fields = '__all__'
- labels = {
- 'name':'用户名',
- 'pwd':'密码',
- }
- #1 clean_字段名 是字段钩子(每个字段都有对应的这个钩子):如判断:“用户名已存在”
- def clean_user(self):
- # self.cleand_data['user']是用户提交的数据'
- c = models.User.objects.filter(name=self.cleand_data['user']).count()
- if not c:
- return self.cleand_data['user'] #必须要有返回值
- else:
- raise ValidationError('用户名已存在',code='xxx')
- #2 clean钩子对整体验证:如判断“用户名或密码错误”
- def clean(self):
- c = models.User.objects.filter(
- name=self.cleand_data['user'],
- pwd=self.cleand_data['pwd']).count()
- if c:
- return self.cleand_data #正确的值必须return回去
- else:
- raise ValidationError('用户名或密码错误')
- #3 在这里可以做 其他验证
- def _post_clean(self):
- pass
forms.py文件中使用这三种钩子
1.6 使用type动态生成ModelForm类进行数据验证、生成html返回顶部
- from django.db import models
- class User(models.Model):
- username = models.CharField(max_length=32)
- def __str__(self):
- return self.username
- def default_form_validation(self):
- ''' 每个class_admin都可以重写这个方法来对整体验证'''
models.py创建表
- from django.shortcuts import render
- from app01 import models
- from app01.forms import create_model_form
- def login(request):
- model_form_class = create_model_form(request,models.User)
- form_obj = model_form_class()
- if request.method == 'POST':
- obj = models.User.objects.get(id=1)
- form_obj = model_form_class(instance=obj)
- form_obj = model_form_class(request.POST,instance=obj)
- if form_obj.is_valid():
- form_obj.save()
- else:
- print('errors',form_obj.errors)
- return render(request, 'loin.html',{'form_obj':form_obj})
views.py视图函数
- from django.forms import ModelForm,ValidationError
- from app01 import models
- def create_model_form(request,admin_class):
- def __new__(cls,*args,**kwargs): # 重写ModelForm的__new__方法
- '''
- 作用1--> 添加字段样式: class="form-control"
- 作用2--> 添加字段钩子: clean_字段名
- '''
- for field_name, field_obj in cls.base_fields.items():
- # field_name : 字段名称,比如 "username"
- # field_obj : 定义字段样式的类
- field_obj.widget.attrs['class'] = "form-control" # 给所有字段添加样式:class="form-control"
- if hasattr(admin_class, "clean_%s" % field_name): # clean_字段名 是字段钩子(每个字段都有对应的这个钩子)
- field_clean_func = getattr(admin_class, "clean_%s" % field_name)
- setattr(cls, "clean_%s" % field_name, field_clean_func)
- return ModelForm.__new__(cls) # 调用一下ModelForm的__new__方法否则不往下走
- def default_clean(self): # 重写ModelForm的 default_clean 方法
- '''添加默认钩子'''
- error_list = []
- # 在这个cleaned方法中定义一个允许用户自己定义的方法做验证
- response = admin_class.default_form_validation(self)
- if response:
- error_list.append(response)
- if error_list:
- raise ValidationError(error_list)
- class Meta: # ModelForm中使用Meta类进行条件过滤
- model = models.User # model指定关联那个类
- fields = "__all__" # 对那些字段过滤
- labels = {
- 'username': '用户名',
- }
- attrs = {'Meta':Meta}
- _model_form_class = type("DynamicModelForm",(ModelForm,),attrs) #创建类并设置Meta属性
- setattr(_model_form_class,"__new__",__new__) #动态将__new__函数添加到类中
- setattr(_model_form_class,'clean',default_clean) #动态将_default_clean__函数添加到类中
- return _model_form_class
froms.py动态生成ModelForm类
1.7 ModelForm实例:不使用ModelForm生成html、并用ajax提交显示错误信息返回顶部
相关知识点:
1、不使用ModelForm生成html
2、使用ajax提交数据,所以无法使用obj.errors.xxx 在前端显示错误信息
3、使用json序列化ModelForm验证的错误信息
4、使用ajax将错误信息放到对应位置
- from django.shortcuts import render,HttpResponse
- from django.utils import timezone
- from django.core.exceptions import ValidationError
- import hashlib
- import json
- from app01.models import User
- from app01.forms import RegisterFrm
- class JsonCustomEncoder(json.JSONEncoder):
- def default(self, field):
- if isinstance(field, ValidationError):
- return {'code':field.code,'messages':field.messages}
- else:
- return json.JSONEncoder.default(self, field)
- def register(request):
- if request.method == 'POST':
- if request.method == 'POST':
- ret = {'status': False, 'error': None, 'data': None}
- obj = RegisterFrm(request.POST)
- if obj.is_valid():
- cd = obj.cleaned_data
- new_user = obj.save(commit=False)
- # 将密码md5加密后再存到数据库中
- password2 = cd.get("password2")
- m = hashlib.md5()
- m.update(password2.encode())
- new_user.password = m.hexdigest()
- new_user.save()
- ret['status']=True
- return HttpResponse(json.dumps(ret))
- else:
- ret['error'] = obj.errors.as_data() # as_json() 返回的是字符串
- request = json.dumps(ret, cls=JsonCustomEncoder)
- return HttpResponse(request)
- obj = RegisterFrm()
- return render(request,'register.html',{'obj':obj})
- # 登陆 : 这里没有做登录界面只有验证密码的函数
- def login(request):
- if request.method == 'POST':
- login_name = request.POST.get("login_name")
- password = request.POST.get("password")
- if login_name and password:
- # 将密码转md5
- m = hashlib.md5()
- m.update(password.encode())
- password_md5 = m.hexdigest()
- # 获取用户对象
- user = User.objects.filter(login_name=login_name, password=password_md5).first()
- if user:
- print('用户密码正确')
views.py
- from django.db import models
- class User(models.Model):
- login_name = models.CharField(max_length=32,unique=True,verbose_name="用户名",error_messages={'unique':"用户名已占用"})
- password = models.CharField(max_length=32)
- email = models.EmailField(unique=True,error_messages={'unique':'邮箱已注册'})
- def __str__(self):
- return self.login_name
models.py
- from django import forms
- from django.forms import fields
- from app01.models import User
- class RegisterFrm(forms.ModelForm):
- login_name = forms.CharField(
- required=True,
- error_messages={
- 'required': '用户名不能为空',
- })
- email = forms.EmailField(
- error_messages={
- 'required': '邮箱不能为空',
- 'invalid':'邮箱格式错误'
- })
- password = forms.CharField(
- widget=forms.PasswordInput,
- label="密码",
- error_messages={
- 'required': '密码不能为空',
- })
- password2 = forms.CharField(
- widget=forms.PasswordInput,
- label="确认密码",
- error_messages={
- 'required': '确认密码不能为空',
- })
- class Meta:
- model = User
- fields = ("login_name", "password", "email")
- def clean_login_name(self):
- """login_name里不允许有空格"""
- cd = self.cleaned_data
- login_name = cd.get("login_name").strip()
- if " " in login_name:
- raise forms.ValidationError("用户名不能有空格")
- return login_name
- def clean_password2(self):
- """两次密码是否一致"""
- cd = self.cleaned_data
- if cd['password'] != cd['password2']:
- raise forms.ValidationError('确认密码不一致')
- return cd['password2']
forms.py
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <title>Title</title>
- </head>
- <body>
- <div class="reg_frm fl">
- <h1>注册{{ obj.errors }}</h1>
- <form id="register_frm">
- <table>
- <tr>
- <th>用户名</th>
- <td><input type="text" id="login_name" name="login_name"></td>
- <td class="tips">{{ obj.login_name.errors.0 }}</td>
- </tr>
- <tr>
- <th>邮箱</th>
- <td><input type="email" id="email" name="email"></td>
- <td class="tips">{{ obj.email.errors.0 }}</td>
- </tr>
- <tr>
- <th>密码</th>
- <td><input id="password" type="password" name="password" autocomplete="off" minlength="3"></td>
- <td class="tips">{{ obj.password.errors.0 }}</td>
- </tr>
- <tr>
- <th>确认密码</th>
- <td><input id="password2" type="password" name="password2" autocomplete="off" minlength="3"></td>
- <td class="tips"></td>
- </tr>
- </table>
- <p><input type="button" value="ajax提交" onclick="ajaxSubmit();"></p>
- </form>
- </div>
- <script src="/static/jquery-1.12.4.min.js"></script>
- <script>
- function ajaxSubmit() {
- $.ajax({
- url:'/register/',
- data:$('#register_frm').serialize(),
- type:'POST',
- dataType:'json',
- success:function(arg){ //服务端返回的是字符串格式
- if(arg.status==true){
- console.log('成功创建用户后的操作')
- }else {
- var check_list = {
- 'login_name':'用户名',
- 'email':'邮箱',
- 'password':'密码',
- 'password2':'确认密码'
- };
- for(var key in check_list){
- $('#'+key).parent().parent().find('.tips').text('');
- var err_val = "arg.error." + key +"[0].messages";
- try{
- var err = eval(err_val);
- $('#'+key).parent().parent().find('.tips').text(err)
- }catch (err){
- console.log(err.message);
- }
- }
- }
- }
- })
- }
- </script>
- </body>
- </html>
register.html
05:ModelForm 数据验证 & 生成html & 数据库操作的更多相关文章
- 手把手封装数据层之DataUtil数据库操作的封装
上一篇我们写完了数据库连接的封装 没有看的请移步上一篇关于数据库连接的内容 这次我们讲数据库操作的封装.数据库的操作就是增删改查:心再大一点就可以直接分为查询和其他. 因为查询是有返回对象的,而其他都 ...
- Android数据读取之Sqlite数据库操作
咱们书接上文,继续来说说Android数据读取,这回,我们要讲的是Sqlite数据库的相关操作.以一个实例开始吧: 首先,上图,看看做成后的效果: 大概描述:类似于浏览器的收藏夹,网站名称,网站地址, ...
- android菜鸟学习笔记20----Android数据存储(四))Android数据库操作
Android内置了一个名为SQLite的关系型数据库,这是一款轻量型的数据库,操作十分简便.SQLite与别的数据库不同的是,它没有数据类型.可以保存任何类型的数据到你所想要保存的任何表的任何列中. ...
- Effective C++ .05 一些不自动生成copy assigment操作的情况
主要讲了 1. 一般情况下编译器会为类创建默认的构造函数,拷贝构造函数和copy assignment函数 2. 执行默认的拷贝构造/copy assignment函数时,如果成员有自己的拷贝构造/c ...
- 03: Django Model数据库操作
目录:Django其他篇 01:Django基础篇 02:Django进阶篇 03:Django数据库操作--->Model 04: Form 验证用户数据 & 生成html 05:Mo ...
- python 学习笔记十六 django深入学习一 路由系统,模板,admin,数据库操作
django 请求流程图 django 路由系统 在django中我们可以通过定义urls,让不同的url路由到不同的处理函数 from . import views urlpatterns = [ ...
- 结合java的反射和泛型性质简化JDBC和相应的同步等服务器数据库操作代码
github地址:https://github.com/hzphzp/HeartTrace_Server 我们的服务器端数据库并没有用sqllite, 而是直接用mysql,并且用JDBC直接进行操作 ...
- PHP 生成 MySql 数据库字典
项目说明 通过配置 MySql 数据库信息,使用 PHP 生成数据表字典可以输出在当前页面,可以生成文件保存在指定位置,也可以下载格式支持网页HTML格式.CSV格式(Excel 读取).ZIP压缩格 ...
- JSP中的数据库操作,MySQL基础操作(一)
一.JDBC JDBC(java data base concectivity),是一种用于执行SQL语句的java API,可以为多种关系库提供统一访问. 通常使用JDBC完成以下操作: 1)同数据 ...
随机推荐
- SQL Fundamentals || DCL(Data Control Language) || 角色ROLES
SQL Fundamentals || Oracle SQL语言 语句 解释 Create user Creates a user(usually performed by a DBA) Grant ...
- Git:从github上克隆、修改和更新项目
一.在本地新建一个文件夹,作为本地仓库,如“BigProjet”.在该文件夹打开git bash,进入到该文件夹目录下 二.将本地仓库初始化 $ git init 三.将项目从github或者服务器上 ...
- 从url到请求 再到页面生成
百度面试 从url到请求 再到页面生成 - MartinDing - 博客园 https://www.cnblogs.com/martinding/p/7458723.html
- C#中DataSet、DataTable、DataReader的区别
简单说就是: DataSet:数据集.一般包含多个DataTable,用的时候,dataset["表名"]得到DataTable DataTable:数据表 ...
- Map集合遍历
Map<String,String> map = new HashMap<String, String>(); map.put("1","java ...
- 缓存服务,还未创建完缓存时, 需要更改图层名称、服务名称、数据源位置、mxd名称等
缓存服务,还未创建完缓存时, 需要更改图层名称.服务名称.数据源位置.mxd名称等.已经创建好的缓存还可以再用吗? 测试后可以, 注意:新服务相对旧服务,符号样式没有改变,切片方案没有变化. 测试步骤 ...
- centos7设置iptables
https://www.linuxidc.com/Linux/2017-10/147238.htm
- paas容器云
- python修饰器各种实用方法
This page is meant to be a central repository of decorator code pieces, whether useful or not <wi ...
- Django单元测试简单示例
对一个功能的验证往往是需要很多多测试用例,可以把测试用例集合在一起执行,这就产生了测试套件TestSuite 的概念,它是用来组装单个测试用例,规定用例的执行的顺序,而且TestSuite也可以嵌套T ...