调查问卷表结构设计及具体实现
一、表结构设计
from django.db import models # Create your models here. class UserInfo(models.Model): '''员工表''' username = models.CharField(max_length=64,verbose_name="用户名") password = models.CharField(max_length=32,verbose_name="用户密码") def __str__(self): return self.username class Meta: verbose_name_plural="员工表" class ClassList(models.Model): '''班级表''' title = models.CharField(max_length=32,verbose_name="班级名") def __str__(self): return self.title class Meta: verbose_name_plural = "班级表" class Student(models.Model): '''学生表''' name = models.CharField(max_length=32,verbose_name="学生姓名") password = models.CharField(max_length=32,verbose_name="学生密码") cls = models.ForeignKey(to="ClassList",verbose_name="所属班级") def __str__(self): return self.name class Meta: verbose_name_plural = "学生表" class Questionnaire(models.Model): '''问卷表''' title = models.CharField(max_length=32,verbose_name="问卷名") cls = models.ForeignKey(to="ClassList",verbose_name="问卷班级") create_user = models.ForeignKey(to="UserInfo",verbose_name="创建问卷的用户") def __str__(self): return self.title class Meta: verbose_name_plural = "问卷表" class Questions(models.Model): '''问卷问题表''' caption = models.CharField(max_length=32,verbose_name="问题题目") type_choices = ( (1,"打分"), (2,"单选"), (3,"评价") ) question_type = models.IntegerField(choices=type_choices,verbose_name="问题类型") questionnaire = models.ForeignKey(to="Questionnaire",verbose_name="所属问卷",default=1) def __str__(self): return self.caption class Meta: verbose_name_plural = "问卷问题表" class Answer(models.Model): '''问卷回答表''' #谁什么时候对那个问题作答了 student = models.ForeignKey(to="Student",verbose_name="所属学生") queston = models.ForeignKey(to="Questions",verbose_name="所属问题") option = models.ForeignKey(to="Option",null=True,blank=True) val = models.IntegerField(null=True,blank=True,verbose_name="数字答案") content = models.CharField(max_length=255,null=True,blank=True,verbose_name="文本答案") def __str__(self): return self.content class Meta: verbose_name_plural = "问卷回答表" class Option(models.Model): '''问卷单选题的选项表''' name = models.CharField(max_length=32,verbose_name="选项名") score = models.IntegerField(verbose_name="选项对应的分值") question = models.ForeignKey(to="Questions",verbose_name="所属问题") def __str__(self): return str(self.score) class Meta: verbose_name_plural = "问卷单选题的选项表"
二、具体实现
urls.py
1 from django.conf.urls import url 2 from django.contrib import admin 3 from app01 import views 4 urlpatterns = [ 5 url(r'^admin/', admin.site.urls), 6 url(r'^index/$', views.index), 7 url(r'^questionedit/(\d+)/$', views.questionedit), 8 url(r'^questionedit2/(\d+)/$', views.questionedit2), 9 url(r'^questionsave/(\d+)/$', views.questionsave), 10 url(r'^student_login/$', views.student_login), 11 url(r'^score/(\d+)/(\d+)/$', views.score), 12 ]
views.py
1 from django.core.validators import RegexValidator 2 from django.db.models.aggregates import Count 3 from django.forms.forms import Form 4 from django.http.response import JsonResponse 5 from django.shortcuts import render, HttpResponse,redirect 6 from app01 import models 7 from django.forms import ModelForm,fields,widgets 8 import json 9 from django.core.exceptions import ValidationError 10 from django.core.validators import RegexValidator 11 # Create your views here. 12 def index(request): 13 Questionnaire_obj = models.Questionnaire.objects.all() 14 #查询问卷所属的班级的学生个数 15 for naire in Questionnaire_obj: 16 naire.part_num = models.Answer.objects.filter(queston__in=naire.questions_set.all()).values_list('student_id').distinct().count() 17 print(naire.part_num) 18 return render(request,"index.html",{"Questionnaire_obj":Questionnaire_obj}) 19 20 class QuestionForm(ModelForm): 21 class Meta: 22 model = models.Questions 23 fields = ["caption","question_type"] 24 25 error_messages = { 26 "caption":{"required":"不能为空"} 27 } 28 widgets ={ 29 "caption":widgets.Textarea(attrs={"class": "question","rows":0,"cols":0}) 30 } 31 32 class OptionModelForm(ModelForm): 33 class Meta: 34 model = models.Option 35 fields = ["name","score"] 36 37 def questionedit(request,nid): 38 # 方式一: 39 # #查询当前问卷的所有的问题 40 # que_list = models.Questions.objects.filter(questionnaire_id=nid).all() 41 # question_list = [] 42 # if not que_list: 43 # '''新建的问题,还没有创建问题''' 44 # form = QuestionForm() 45 # question_list.append(form) 46 # return render(request,"questionedit.html",{"question_list":question_list}) 47 # else: 48 # '''已经创建了问题的''' 49 # for que in que_list: 50 # print(que,"que===") 51 # form = QuestionForm(instance=que) 52 # question_list.append(form) 53 # return render(request,"questionedit.html",{"question_list":question_list}) 54 55 # 方式二: 56 #查询当前问卷的所有的问题 57 # def inner(): 58 # que_list = models.Questions.objects.filter(questionnaire_id=nid).all() 59 # if not que_list: 60 # '''新建的问题,还没有创建问题''' 61 # form = QuestionForm() 62 # yield form 63 # else: 64 # '''已经创建了问题的''' 65 # for que in que_list: 66 # form = QuestionForm(instance=que) 67 # yield form 68 # return render(request,"questionedit.html",{"form":inner()}) 69 70 71 # 方式三,yield返回的时候吧form作为一个字典的key返回 72 # def inner(): 73 # que_list = models.Questions.objects.filter(questionnaire_id=nid).all() 74 # if not que_list: 75 # '''新建的问题,还没有创建问题''' 76 # form = QuestionForm() 77 # yield {"form":form,"obj":None} 78 # else: 79 # '''已经创建了问题的''' 80 # for que in que_list: 81 # print(que) 82 # form = QuestionForm(instance=que) 83 # temp = {"form":form,"obj":que,"option_class":"hide","options":None} 84 # if que.question_type == 2: 85 # '''如果选项类型是单选的时候''' 86 # temp["option_class"] = "" 87 # #如果是单选的时候让显示所有的选项 88 # question_option_list =[] 89 # option_list = models.Option.objects.filter(question=que) 90 # for obj in option_list: 91 # vm = OptionModelForm(instance=obj) 92 # question_option_list.append(vm) 93 # print(question_option_list,"pppppppppppppp") 94 # temp["options"] = question_option_list 95 # yield temp 96 # return render(request, "questionedit.html", {"form": inner()}) 97 98 # 方式四 99 def inner(): 100 que_list = models.Questions.objects.filter(questionnaire_id=nid).all() 101 if not que_list: 102 '''新建的问题,还没有创建问题''' 103 form = QuestionForm() 104 yield {"form":form,"obj":None,'option_class':"hide","options":None} 105 else: 106 '''已经创建了问题的''' 107 for que in que_list: 108 print(que) 109 form = QuestionForm(instance=que) 110 temp = {"form":form,"obj":que,"option_class":"hide","options":None} 111 if que.question_type == 2: 112 '''如果选项类型是单选的时候''' 113 temp["option_class"] = "" 114 #如果是单选的时候让显示所有的选项 115 def inner_loop(quee): 116 option_list = models.Option.objects.filter(question=quee) 117 for v in option_list: 118 yield {"form":OptionModelForm(instance=v),"obj":v} 119 temp["options"] = inner_loop(que) 120 yield temp 121 return render(request, "questionedit.html", {"form": inner(),"nid":nid}) 122 123 def questionedit2(request,nid): 124 def inner(): 125 que_list = models.Questions.objects.filter(questionnaire_id=nid).all() 126 if not que_list: 127 '''新建的问题,还没有创建问题''' 128 form = QuestionForm() 129 yield {"form": form, "obj": None, 'option_class': "hide", "options": None} 130 else: 131 '''已经创建了问题的''' 132 for que in que_list: 133 print(que) 134 form = QuestionForm(instance=que) 135 temp = {"form": form, "obj": que, "option_class": "hide", "options": None} 136 if que.question_type == 2: 137 '''如果选项类型是单选的时候''' 138 temp["option_class"] = "" 139 140 # 如果是单选的时候让显示所有的选项 141 def inner_loop(quee): 142 option_list = models.Option.objects.filter(question=quee) 143 for v in option_list: 144 yield {"form": OptionModelForm(instance=v), "obj": v} 145 146 temp["options"] = inner_loop(que) 147 yield temp 148 return render(request,"questionedit.html",{"form":inner()}) 149 150 151 def questionsave(request,nid): 152 ret = {"status":True,"msg":None,"data":None} 153 try: 154 if request.is_ajax(): 155 #得到新提交的数据 156 data=request.body.decode("utf8") 157 post_data_list = json.loads(data) 158 #找到所有的问题列表 159 question_list = models.Questions.objects.filter(questionnaire_id=nid) 160 #找到用户提交的所有的问题id 161 post_id_list = [i.get("id") for i in post_data_list if i.get("id")] 162 # print(post_id_list,"post_id_list") #['1', '2', '1', '2', '1', '2', '1', '2'] post_id_list 163 #找到数据库中的所有的问题id 164 question_id_list = [i.id for i in question_list] 165 # print(question_id_list,"question_id_list") #[1, 2] question_id_list 166 #数据库中的那些id需要删除(数据库里有前端没有的数据删除) 167 del_id_list = set(question_id_list).difference(post_id_list) 168 169 #循环ajax发过来的那些问题列表, 170 for item in post_data_list: 171 #item就是用户传进来的每个问题 172 caption = item.get("caption") 173 type_id = item.get("type_id") 174 qid = item.get("id") 175 options = item.get("options") 176 if not qid in question_id_list: 177 #如果前端传进来的id不在数据库里面,就说明要新增 178 new_question_obj = models.Questions.objects.create(caption=caption,question_type=type_id,questionnaire_id=nid) 179 if type_id==2: 180 for op in options: 181 name = op.get("name") 182 score = op.get("score") 183 models.Option.objects.create(name=name,score=score,question=new_question_obj) 184 else: 185 #否则说明是要更新 186 models.Questions.objects.filter(id=qid).update(caption=caption,question_type=type_id,questionnaire_id=qid) 187 if not options: 188 #如果没有options就把数据库的options记录给删除了 189 models.Option.objects.filter(id=nid).delete() 190 else: 191 #如果有先删除原来的后创建新传进来的 192 models.Option.objects.filter(id=nid).delete() 193 for op in options: 194 name = op.get("name") 195 score = op.get("score") 196 models.Option.objects.create(name=name,score=score,question_id=qid) 197 models.Questions.objects.filter(id__in=del_id_list).delete() 198 except Exception as e: 199 ret['msg'] = str(e) 200 ret["status"] = False 201 return JsonResponse(ret) 202 203 204 class StudentForm(ModelForm): 205 # password = fields.CharField(max_length=8, validators=[RegexValidator("\d+", "密码只能是数字")], 206 # error_messages={"max_length":"8"} 207 # ) 208 # 这里如果写上password,下面也有了,就会把下面的给覆盖了 209 class Meta: 210 model=models.Student 211 fields=["name","password"] 212 213 error_messages ={ 214 "name":{"required":"用户名不能为空"}, 215 "password":{"required":"密码不能为空","max_length":"密码长度不能大于8位"}, 216 }, 217 widgets = { 218 "password": widgets.PasswordInput(attrs={'placeholder': 'password', 'class': 'form-control'}), 219 "name": widgets.TextInput(attrs={'placeholder': 'username', 'class': 'form-control'}) 220 } 221 222 223 def student_login(request): 224 # obj = models.Student.objects.all().first() 225 # print(obj.id,obj.name) 226 if request.method=="GET": 227 form = StudentForm() 228 else: 229 print("============") 230 form = StudentForm(data=request.POST) 231 if form.is_valid(): 232 print("======",form.cleaned_data) 233 user = models.Student.objects.filter(**form.cleaned_data).first() 234 if user: 235 request.session["id"] =user.id 236 request.session["user"] =user.name 237 class_id = request.session.get("class_id") 238 qn_id = request.session.get("qn_id") 239 # if class_id==None or qn_id==None: 240 # return redirect("/index/") 241 return redirect('/score/%s/%s'%(class_id,qn_id)) 242 else: 243 return render(request,"student_login.html",{"form":form}) 244 return render(request, "student_login.html", {"form": form}) 245 246 247 def func(val): 248 #参数要有,Form用正则匹配的时候不用加括号,自己就会执行这个函数,去验证 249 if len(val)<15: 250 raise ValidationError("字数不能小于15字") 251 252 def score(request,class_id,qn_id): 253 # print(class_id,qn_id) 254 student_id = request.session.get("id") 255 print(student_id,"student_id") 256 request.session["class_id"] = class_id 257 request.session["qn_id"] = qn_id 258 if not student_id: 259 return redirect("/student_login/") 260 #查看当前用户是否是要评论的班级的学生 261 262 stu1 = models.Student.objects.filter(cls=class_id,id=student_id).count() 263 print("stu1",stu1) 264 if not stu1: 265 return HttpResponse("你还不是这个班的学生呢,你无权访问我们这次问卷") 266 267 #当前学生是否已经评论过当前问卷 268 stu2 = models.Answer.objects.filter(student_id=student_id,queston__questionnaire_id=qn_id).count() 269 # print(stu2) 270 if stu2: 271 return HttpResponse("你已经答过了,感谢你的参与。无法再进行第二次答卷") 272 273 #验证通过以后就开始展示答卷的页面了 274 # 展开当前问卷下的所有的问题 275 question_list = models.Questions.objects.filter(questionnaire_id=qn_id) 276 question_dict = {} 277 for que in question_list: 278 print(que.id) 279 print("asssdsfsfs",models.Option.objects.filter(question_id=que.id).values_list('id', 'name')) 280 # que是每一个问题 281 if que.question_type==1: #打分 282 question_dict["val_%s"%que.id] = fields.ChoiceField( 283 label=que.caption, 284 error_messages={"required":"不能为空"}, 285 widget = widgets.RadioSelect, 286 choices = [(i,i) for i in range(1,11)] 287 ) 288 elif que.question_type==2: #单选 289 question_dict["option_id_%s"%que.id] = fields.ChoiceField( 290 label=que.caption, 291 error_messages={"required":"不能为空"}, 292 widget = widgets.RadioSelect, 293 choices=models.Option.objects.filter(question_id=que.id).values_list('id', 'name') #拿自己的选项 294 ) 295 296 else: #评价 297 question_dict["content_%s"%que.id] = fields.CharField( 298 label=que.caption, 299 error_messages={"required": "不能为空"}, 300 widget=widgets.Textarea, 301 validators=[func,] #这里的func不用加参数 302 ) 303 304 MyTestForm = type("MyTestForm",(Form,),question_dict) #三个参数分别是:类名,继承的父类,后面是一个字典 305 if request.method =="GET": 306 form = MyTestForm() 307 return render(request,"score.html",{"question_list":question_list,"form":form}) 308 else: 309 form = MyTestForm(request.POST) 310 if form.is_valid(): 311 #如果验证成功 312 print(form.cleaned_data,"2222222") 313 objs = [] 314 for key,v in form.cleaned_data.items(): 315 print(key,v,"1111111") 316 k,qid = key.rsplit('_',1) 317 print(k,qid,"2223333") 318 answer_dict = {'student_id':student_id,'queston_id':qid,k:v} 319 320 objs.append(models.Answer(**answer_dict)) 321 models.Answer.objects.bulk_create(objs) 322 return HttpResponse("感谢你的参与!!") 323 return render(request, "score.html", {"question_list": question_list, "form": form})
templates
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta http-equiv="X-UA-Compatible" content="IE=edge"> 6 <meta name="viewport" content="width=device-width"> 7 <title>Title</title> 8 <link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.min.css"> 9 <link rel="stylesheet" href="/static/css/index.css"> 10 <link rel="stylesheet" href="/static/css/questionedit.css"> 11 <script src="/static/jquery-3.2.1.min.js"></script> 12 <script src="https://cdn.bootcss.com/jquery-cookie/1.4.1/jquery.cookie.js"></script> 13 <script src="/static/bootstrap-3.3.7-dist/js/bootstrap.min.js"></script> 14 </head> 15 <body> 16 {#导航条#} 17 <nav class="navbar label-primary"> 18 <div class="container-fluid"> 19 <!-- Brand and toggle get grouped for better mobile display --> 20 <div class="navbar-header"> 21 <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" 22 data-target="#bs-example-navbar-collapse-1" aria-expanded="false"> 23 <span class="sr-only">Toggle navigation</span> 24 <span class="icon-bar"></span> 25 <span class="icon-bar"></span> 26 <span class="icon-bar"></span> 27 </button> 28 <a class="navbar-brand textstyle" href="#">CRM系统</a> 29 </div> 30 31 <!-- Collect the nav links, forms, and other content for toggling --> 32 <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"> 33 <ul class="nav navbar-nav"> 34 <li class="active"><a href="#" class="textstyle">平台首页 <span class="sr-only">(current)</span></a></li> 35 <li><a href="#" class="textstyle">资产首页</a></li> 36 </ul> 37 <form class="navbar-form navbar-left"> 38 <div class="form-group"> 39 <input type="text" class="form-control" placeholder="Search"> 40 </div> 41 <button type="submit" class="btn btn-default">Submit</button> 42 </form> 43 </div><!-- /.navbar-collapse --> 44 </div><!-- /.container-fluid --> 45 </nav> 46 <div class="container-fluid"> 47 <div class="row"> 48 <div class="left"> 49 <div class="col-md-3"></div> 50 </div> 51 <div class="right"> 52 <div class="col-md-9"> 53 <div class="panel panel-default"> 54 <div class="panel-heading"><a href="">首页</a>/数据列表</div> 55 <div class="panel-body"> 56 {% block content %} 57 58 {% endblock %} 59 </div> 60 </div> 61 </div> 62 </div> 63 </div> 64 </div> 65 </body> 66 </html>
1 {% extends "base.html" %} 2 3 {% block content %} 4 <button class="btn btn-success addBtn">添加</button> 5 <table class="table table-bordered active"> 6 <thead> 7 <th><input type="checkbox"></th> 8 <th>问卷调查名称</th> 9 <th>问卷调查班级</th> 10 <th>参与人数</th> 11 <th>问卷选项</th> 12 <th>调查地址</th> 13 <th>查看评分</th> 14 <th>操作</th> 15 </thead> 16 <tbody> 17 {% for Questionnaire in Questionnaire_obj %} 18 <tr> 19 <td><input type="checkbox"></td> 20 <td>{{ Questionnaire.title }}</td> 21 <td>{{ Questionnaire.cls.title }}</td> 22 <td>{{ Questionnaire.part_num }}/{{ Questionnaire.cls.student_set.all.count }}</td> 23 <td><a href="/questionedit/{{ Questionnaire.id }}/">编辑问卷</a></td> 24 <td><a href="/score/{{ Questionnaire.cls.id }}/{{ Questionnaire.id }}/">/score/{{ Questionnaire.cls.id }}/{{ Questionnaire.id }}/</a></td> 25 <td><a href="">查看评分</a></td> 26 <td><a href=""><button class="btn btn-danger">删除</button></a></td> 27 </tr> 28 {% endfor %} 29 </table> 30 {% endblock %}
1 {% extends "base.html" %} 2 {% block content %} 3 <div class="pull-right"> 4 <button class="btn btn-success addquestion">添加</button> 5 <button class="btn btn-info savebtn">保存</button> 6 </div> 7 <div class="ccc"> 8 <ol> 9 {% for item in form %} 10 <li> 11 <div class="glyphicon glyphicon-remove pull-right delquestion"></div> 12 <div pk="{{ item.obj.id }}"> 13 {# <p>{{ item.form }}</p>#} 14 <p>问题名称:{{ item.form.caption }}</p> 15 <p class="name">类型名称:{{ item.form.question_type }} 16 <a class="{{ item.option_class }} addoption">添加选项</a> 17 </p> 18 <ul> 19 {% for v in item.options %} 20 <li class="{{ v.obj.id }}">{{ v.form }} 21 <span class="glyphicon glyphicon-remove deloption"></span> 22 </li> 23 {% endfor %} 24 </ul> 25 </div> 26 </li> 27 {% endfor %} 28 </ol> 29 </div> 30 <script> 31 //添加选项 32 $(".ccc").on("click", ".addoption", function () { 33 var ele_ul = $(this).parent().parent().children("ul"); 34 var s = '<li"><label for="id_name">选项名:</label><input type="text" name="name" maxlength="32" required="" id="id_name"><label for="id_score">选项对应的分值:</label><input type="number" name="score" required="" id="id_score"><span class="glyphicon glyphicon-remove deloption"></span></li>'; 35 ele_ul.append(s) 36 37 }); 38 39 //删除选项(绑定事件委派) 40 $('.ccc').on('click', '.deloption', function () { 41 //找到当前的那一行删除 42 $(this).parent().remove() 43 }); 44 45 //删除问题(添加事件委派) 46 $("ol").on('click', ".delquestion", function () { 47 console.log($("body")); 48 $(this).parent().remove() 49 }); 50 51 //改变下拉框的触发不同的事件 52 $(".ccc").on("click", "#id_question_type", function () { 53 if ($(this).val() == 2) { 54 //如果是单选的时候,如果有选项就把下面的内容隐藏了 55 $(this).next().removeClass("hide"); 56 } 57 else { 58 //否则就隐藏添加选项,吧西面的内容清空 59 $(".addoption").addClass("hide"); 60 $(this).parent().next().empty() 61 } 62 }); 63 64 //添加问题 65 $(".addquestion").click(function () { 66 //克隆一个整个的 67 var s = $("ol").children("li:last").clone(); 68 $("ol").append(s); 69 }); 70 //保存修改的信息 71 plist = []; 72 $(".savebtn").click(function () { 73 $("ol>li").each(function (i,v) { 74 {# console.log(i,v); //v打印的是每一个li#} 75 var id =$(this).find("div").eq(1).attr("pk"); 76 var caption = $(v).find("textarea").val(); 77 var type_id = $(v).find("select").val(); 78 console.log($(v).find("select"),type_id); 79 if (type_id==2){ 80 //如果类型id是2的时候,说明是有option的 81 var options_list = []; 82 var li = $(v).find("li"); 83 li.each(function (i,v) { 84 {# console.log(i,v);#} 85 var option_id = $(this).attr("class"); 86 var option_name =$(this).find("input:first").val(); 87 var option_score =$(this).find("input:last").val(); 88 options_list.push({"option_id":option_id,"option_name":option_name,"option_score":option_score}) 89 }); 90 plist.push({"id":id,"caption":caption,"type_id":type_id,"options":options_list}); 91 } 92 else { 93 plist.push({"id":id,"caption":caption,"type_id":type_id}); 94 } 95 }); 96 $.ajax({ 97 {# url: "/questionsave/"+s+"/",#} 98 url:"/questionsave/{{ nid }}/", 99 type: "post", 100 data: JSON.stringify(plist), 101 contentType: "json", 102 headers: {"X-CSRFToken": $.cookie('csrftoken')}, 103 success: function (i, v) { 104 console.log(i, v); 105 location.href = "/index/" 106 } 107 }) 108 }) 109 110 </script> 111 {% endblock %}
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta http-equiv="X-UA-Compatible" content="IE=edge"> 6 <meta name="viewport" content="width=device-width"> 7 <title>Title</title> 8 <style> 9 li{ 10 list-style-type: none; 11 } 12 ul li{ 13 display: inline-block; 14 } 15 </style> 16 </head> 17 <body> 18 <form action="" method="post" novalidate> 19 {% csrf_token %} 20 {% for foo in form %} 21 <p>{{ foo.label }}{{ foo }}{{ foo.errors.0 }}</p> 22 {% endfor %} 23 24 <input type="submit" value="提交"> 25 </form> 26 </body> 27 </html>
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta http-equiv="X-UA-Compatible" content="IE=edge"> 6 <meta name="viewport" content="width=device-width"> 7 <link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.min.css"> 8 <title>Title</title> 9 <style> 10 .container{ 11 margin-top: 50px; 12 } 13 </style> 14 </head> 15 <body> 16 <div class="container"> 17 <div class="row"> 18 <div class="col-md-4 col-md-offset-3"> 19 <form action="" method="post" novalidate> 20 {% csrf_token %} 21 {% for foo in form %} 22 <p>{{ foo.label }}{{ foo }}{{ foo.errors.0 }}</p> 23 {% endfor %} 24 25 <button type="submit" class="btn btn-primary">登录</button> 26 </form> 27 </div> 28 </div> 29 </div> 30 </body> 31 </html>