问卷的表结构设计和具体实现

######################################

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 = "问卷单选题的选项表"

##############################################

表结构分析
1,答案表,谁答题的,什么问题,单选的选项,打分的结果,平路你的结果
2,单选表,属于哪一个问题,选项的名字,选项的值,一个选项一行,方便扩展
3,问卷表,比较简单,就是一个头,问卷的名称,谁创建的,
4,问卷问题表,属于哪一个问卷,问题描述,问题的类型,

##########################################

from django.conf.urls import url
from django.contrib import admin
from app01 import views
urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^index/$', views.index),
    url(r'^questionedit/(d+)/$', views.questionedit),
    url(r'^questionedit2/(d+)/$', views.questionedit2),
    url(r'^questionsave/(d+)/$', views.questionsave),
    url(r'^student_login/$', views.student_login),
    url(r'^score/(d+)/(d+)/$', views.score),
]

###########################################

from django.core.validators import RegexValidator
from django.db.models.aggregates import Count
from django.forms.forms import Form
from django.http.response import JsonResponse
from django.shortcuts import render, HttpResponse,redirect
from app01 import models
from django.forms import ModelForm,fields,widgets
import json
from django.core.exceptions import ValidationError
from django.core.validators import RegexValidator
# Create your views here.
def index(request):
    Questionnaire_obj = models.Questionnaire.objects.all()
    #查询问卷所属的班级的学生个数
    for naire in Questionnaire_obj:
        naire.part_num = models.Answer.objects.filter(queston__in=naire.questions_set.all()).values_list('student_id').distinct().count()
        print(naire.part_num)
    return render(request,"index.html",{"Questionnaire_obj":Questionnaire_obj})

class QuestionForm(ModelForm):
    class Meta:
        model = models.Questions
        fields = ["caption","question_type"]

        error_messages = {
            "caption":{"required":"不能为空"}
        }
        widgets ={
            "caption":widgets.Textarea(attrs={"class": "question","rows":0,"cols":0})
        }

class OptionModelForm(ModelForm):
    class Meta:
        model = models.Option
        fields = ["name","score"]

def questionedit(request,nid):
    # 方式一:
    # #查询当前问卷的所有的问题
    # que_list = models.Questions.objects.filter(questionnaire_id=nid).all()
    # question_list = []
    # if not que_list:
    #     '''新建的问题,还没有创建问题'''
    #     form = QuestionForm()
    #     question_list.append(form)
    #     return render(request,"questionedit.html",{"question_list":question_list})
    # else:
    #     '''已经创建了问题的'''
    #     for que in que_list:
    #         print(que,"que===")
    #         form = QuestionForm(instance=que)
    #         question_list.append(form)
    # return render(request,"questionedit.html",{"question_list":question_list})

    # 方式二:
    #查询当前问卷的所有的问题
    # def inner():
    #     que_list = models.Questions.objects.filter(questionnaire_id=nid).all()
    #     if not que_list:
    #         '''新建的问题,还没有创建问题'''
    #         form = QuestionForm()
    #         yield form
    #     else:
    #         '''已经创建了问题的'''
    #         for que in que_list:
    #             form = QuestionForm(instance=que)
    #             yield form
    # return render(request,"questionedit.html",{"form":inner()})


    # 方式三,yield返回的时候吧form作为一个字典的key返回
    # def inner():
    #     que_list = models.Questions.objects.filter(questionnaire_id=nid).all()
    #     if not que_list:
    #         '''新建的问题,还没有创建问题'''
    #         form = QuestionForm()
    #         yield {"form":form,"obj":None}
    #     else:
    #         '''已经创建了问题的'''
    #         for que in que_list:
    #             print(que)
    #             form = QuestionForm(instance=que)
    #             temp = {"form":form,"obj":que,"option_class":"hide","options":None}
    #             if que.question_type == 2:
    #                 '''如果选项类型是单选的时候'''
    #                 temp["option_class"] = ""
    #                 #如果是单选的时候让显示所有的选项
    #                 question_option_list =[]
    #                 option_list = models.Option.objects.filter(question=que)
    #                 for obj in option_list:
    #                     vm = OptionModelForm(instance=obj)
    #                     question_option_list.append(vm)
    #                 print(question_option_list,"pppppppppppppp")
    #                 temp["options"] = question_option_list
    #             yield temp
    # return render(request, "questionedit.html", {"form": inner()})

    # 方式四
    def inner():
        que_list = models.Questions.objects.filter(questionnaire_id=nid).all()
        if not que_list:
            '''新建的问题,还没有创建问题'''
            form = QuestionForm()
            yield {"form":form,"obj":None,'option_class':"hide","options":None}
        else:
            '''已经创建了问题的'''
            for que in que_list:
                print(que)
                form = QuestionForm(instance=que)
                temp = {"form":form,"obj":que,"option_class":"hide","options":None}
                if que.question_type == 2:
                    '''如果选项类型是单选的时候'''
                    temp["option_class"] = ""
                    #如果是单选的时候让显示所有的选项
                    def inner_loop(quee):
                        option_list = models.Option.objects.filter(question=quee)
                        for v in option_list:
                            yield {"form":OptionModelForm(instance=v),"obj":v}
                    temp["options"] = inner_loop(que)
                yield temp
    return render(request, "questionedit.html", {"form": inner(),"nid":nid})

def questionedit2(request,nid):
        def inner():
            que_list = models.Questions.objects.filter(questionnaire_id=nid).all()
            if not que_list:
                '''新建的问题,还没有创建问题'''
                form = QuestionForm()
                yield {"form": form, "obj": None, 'option_class': "hide", "options": None}
            else:
                '''已经创建了问题的'''
                for que in que_list:
                    print(que)
                    form = QuestionForm(instance=que)
                    temp = {"form": form, "obj": que, "option_class": "hide", "options": None}
                    if que.question_type == 2:
                        '''如果选项类型是单选的时候'''
                        temp["option_class"] = ""

                        # 如果是单选的时候让显示所有的选项
                        def inner_loop(quee):
                            option_list = models.Option.objects.filter(question=quee)
                            for v in option_list:
                                yield {"form": OptionModelForm(instance=v), "obj": v}

                        temp["options"] = inner_loop(que)
                    yield temp
        return render(request,"questionedit.html",{"form":inner()})


def questionsave(request,nid):
    ret = {"status":True,"msg":None,"data":None}
    try:
        if request.is_ajax():
            #得到新提交的数据
            data=request.body.decode("utf8")
            post_data_list = json.loads(data)
            #找到所有的问题列表
            question_list = models.Questions.objects.filter(questionnaire_id=nid)
            #找到用户提交的所有的问题id
            post_id_list = [i.get("id") for i in post_data_list if i.get("id")]
            # print(post_id_list,"post_id_list")  #['1', '2', '1', '2', '1', '2', '1', '2'] post_id_list
            #找到数据库中的所有的问题id
            question_id_list = [i.id for i in question_list]
            # print(question_id_list,"question_id_list")  #[1, 2] question_id_list
            #数据库中的那些id需要删除(数据库里有前端没有的数据删除)
            del_id_list = set(question_id_list).difference(post_id_list)

            #循环ajax发过来的那些问题列表,
            for item in post_data_list:
                #item就是用户传进来的每个问题
                caption = item.get("caption")
                type_id = item.get("type_id")
                qid = item.get("id")
                options = item.get("options")
                if not qid in question_id_list:
                    #如果前端传进来的id不在数据库里面,就说明要新增
                    new_question_obj = models.Questions.objects.create(caption=caption,question_type=type_id,questionnaire_id=nid)
                    if type_id==2:
                        for op in options:
                            name = op.get("name")
                            score = op.get("score")
                            models.Option.objects.create(name=name,score=score,question=new_question_obj)
                else:
                    #否则说明是要更新
                    models.Questions.objects.filter(id=qid).update(caption=caption,question_type=type_id,questionnaire_id=qid)
                    if not options:
                        #如果没有options就把数据库的options记录给删除了
                        models.Option.objects.filter(id=nid).delete()
                    else:
                        #如果有先删除原来的后创建新传进来的
                        models.Option.objects.filter(id=nid).delete()
                        for op in options:
                            name = op.get("name")
                            score = op.get("score")
                            models.Option.objects.create(name=name,score=score,question_id=qid)
                models.Questions.objects.filter(id__in=del_id_list).delete()
    except Exception as e:
        ret['msg'] = str(e)
        ret["status"] = False
    return JsonResponse(ret)


class StudentForm(ModelForm):
    # password = fields.CharField(max_length=8, validators=[RegexValidator("d+", "密码只能是数字")],
    #                             error_messages={"max_length":"8"}
    #                             )
    # 这里如果写上password,下面也有了,就会把下面的给覆盖了
    class Meta:
        model=models.Student
        fields=["name","password"]

        error_messages ={
            "name":{"required":"用户名不能为空"},
            "password":{"required":"密码不能为空","max_length":"密码长度不能大于8位"},
        },
        widgets = {
            "password": widgets.PasswordInput(attrs={'placeholder': 'password', 'class': 'form-control'}),
            "name": widgets.TextInput(attrs={'placeholder': 'username', 'class': 'form-control'})
        }


def student_login(request):
    # obj = models.Student.objects.all().first()
    # print(obj.id,obj.name)
    if request.method=="GET":
        form = StudentForm()
    else:
        print("============")
        form = StudentForm(data=request.POST)
        if form.is_valid():
            print("======",form.cleaned_data)
            user = models.Student.objects.filter(**form.cleaned_data).first()
            if user:
                request.session["id"] =user.id
                request.session["user"] =user.name
                class_id = request.session.get("class_id")
                qn_id = request.session.get("qn_id")
                # if class_id==None or qn_id==None:
                # return redirect("/index/")
                return redirect('/score/%s/%s'%(class_id,qn_id))
            else:
                return render(request,"student_login.html",{"form":form})
    return render(request, "student_login.html", {"form": form})


def func(val):
    #参数要有,Form用正则匹配的时候不用加括号,自己就会执行这个函数,去验证
    if len(val)<15:
        raise ValidationError("字数不能小于15字")

def score(request,class_id,qn_id):
    # print(class_id,qn_id)
    student_id = request.session.get("id")
    print(student_id,"student_id")
    request.session["class_id"] = class_id
    request.session["qn_id"] = qn_id
    if not student_id:
        return redirect("/student_login/")
    #查看当前用户是否是要评论的班级的学生

    stu1 = models.Student.objects.filter(cls=class_id,id=student_id).count()
    print("stu1",stu1)
    if not stu1:
        return HttpResponse("你还不是这个班的学生呢,你无权访问我们这次问卷")

    #当前学生是否已经评论过当前问卷
    stu2 = models.Answer.objects.filter(student_id=student_id,queston__questionnaire_id=qn_id).count()
    # print(stu2)
    if stu2:
        return HttpResponse("你已经答过了,感谢你的参与。无法再进行第二次答卷")

    #验证通过以后就开始展示答卷的页面了
    # 展开当前问卷下的所有的问题
    question_list = models.Questions.objects.filter(questionnaire_id=qn_id)
    question_dict = {}
    for que in question_list:
        print(que.id)
        print("asssdsfsfs",models.Option.objects.filter(question_id=que.id).values_list('id', 'name'))
        # que是每一个问题
        if que.question_type==1:   #打分
            question_dict["val_%s"%que.id] = fields.ChoiceField(
                label=que.caption,
                error_messages={"required":"不能为空"},
                widget = widgets.RadioSelect,
                choices = [(i,i) for i in range(1,11)]
            )
        elif que.question_type==2:  #单选
            question_dict["option_id_%s"%que.id] = fields.ChoiceField(
                label=que.caption,
                error_messages={"required":"不能为空"},
                widget = widgets.RadioSelect,
                choices=models.Option.objects.filter(question_id=que.id).values_list('id', 'name')  #拿自己的选项
            )

        else:  #评价
            question_dict["content_%s"%que.id] = fields.CharField(
                label=que.caption,
                error_messages={"required": "不能为空"},
                widget=widgets.Textarea,
                validators=[func,]   #这里的func不用加参数
            )

    MyTestForm = type("MyTestForm",(Form,),question_dict)  #三个参数分别是:类名,继承的父类,后面是一个字典
    if request.method =="GET":
        form = MyTestForm()
        return render(request,"score.html",{"question_list":question_list,"form":form})
    else:
        form = MyTestForm(request.POST)
        if form.is_valid():
            #如果验证成功
            print(form.cleaned_data,"2222222")
            objs = []
            for key,v in form.cleaned_data.items():
                print(key,v,"1111111")
                k,qid = key.rsplit('_',1)
                print(k,qid,"2223333")
                answer_dict = {'student_id':student_id,'queston_id':qid,k:v}

                objs.append(models.Answer(**answer_dict))
            models.Answer.objects.bulk_create(objs)
            return HttpResponse("感谢你的参与!!")
        return render(request, "score.html", {"question_list": question_list, "form": form})

views.py

###########################################

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width">
    <title>Title</title>
    <link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.min.css">
    <link rel="stylesheet" href="/static/css/index.css">
    <link rel="stylesheet" href="/static/css/questionedit.css">
    <script src="/static/jquery-3.2.1.min.js"></script>
    <script src="https://cdn.bootcss.com/jquery-cookie/1.4.1/jquery.cookie.js"></script>
    <script src="/static/bootstrap-3.3.7-dist/js/bootstrap.min.js"></script>
</head>
<body>
{#导航条#}
<nav class="navbar label-primary">
    <div class="container-fluid">
        <!-- Brand and toggle get grouped for better mobile display -->
        <div class="navbar-header">
            <button type="button" class="navbar-toggle collapsed" data-toggle="collapse"
                    data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
                <span class="sr-only">Toggle navigation</span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
            </button>
            <a class="navbar-brand textstyle" href="#">CRM系统</a>
        </div>

        <!-- Collect the nav links, forms, and other content for toggling -->
        <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
            <ul class="nav navbar-nav">
                <li class="active"><a href="#" class="textstyle">平台首页 <span class="sr-only">(current)</span></a></li>
                <li><a href="#" class="textstyle">资产首页</a></li>
            </ul>
            <form class="navbar-form navbar-left">
                <div class="form-group">
                    <input type="text" class="form-control" placeholder="Search">
                </div>
                <button type="submit" class="btn btn-default">Submit</button>
            </form>
        </div><!-- /.navbar-collapse -->
    </div><!-- /.container-fluid -->
</nav>
<div class="container-fluid">
    <div class="row">
        <div class="left">
            <div class="col-md-3"></div>
        </div>
        <div class="right">
            <div class="col-md-9">
                <div class="panel panel-default">
                    <div class="panel-heading"><a href="">首页</a>/数据列表</div>
                    <div class="panel-body">
                    {% block content %}
                       
                    {% endblock %}
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>
</body>
</html>

base.html

###########################################

{% extends "base.html" %}

{% block content %}
    <button class="btn btn-success addBtn">添加</button>
    <table class="table table-bordered active">
        <thead>
            <th><input type="checkbox"></th>
            <th>问卷调查名称</th>
            <th>问卷调查班级</th>
            <th>参与人数</th>
            <th>问卷选项</th>
            <th>调查地址</th>
            <th>查看评分</th>
            <th>操作</th>
        </thead>
        <tbody>
        {% for Questionnaire in Questionnaire_obj %}
            <tr>
                <td><input type="checkbox"></td>
                <td>{{ Questionnaire.title }}</td>
                <td>{{ Questionnaire.cls.title }}</td>
                <td>{{ Questionnaire.part_num }}/{{ Questionnaire.cls.student_set.all.count }}</td>
                <td><a href="/questionedit/{{ Questionnaire.id }}/">编辑问卷</a></td>
                <td><a href="/score/{{ Questionnaire.cls.id }}/{{ Questionnaire.id }}/">/score/{{ Questionnaire.cls.id }}/{{ Questionnaire.id }}/</a></td>
                <td><a href="">查看评分</a></td>
                <td><a href=""><button class="btn btn-danger">删除</button></a></td>
            </tr>
        {% endfor %}
    </table>
{% endblock %}

index.html

###########################################

{% extends "base.html" %}
{% block content %}
    <div class="pull-right">
        <button class="btn btn-success addquestion">添加</button>
        <button class="btn btn-info savebtn">保存</button>
    </div>
    <div class="ccc">
        <ol>
            {% for item in form %}
                <li>
                    <div class="glyphicon glyphicon-remove pull-right delquestion"></div>
                    <div pk="{{ item.obj.id }}">
                        {#                <p>{{ item.form }}</p>#}
                        <p>问题名称:{{ item.form.caption }}</p>
                        <p class="name">类型名称:{{ item.form.question_type }}
                            <a class="{{ item.option_class }} addoption">添加选项</a>
                        </p>
                        <ul>
                            {% for v in item.options %}
                                <li class="{{ v.obj.id }}">{{ v.form }}
                                    <span class="glyphicon glyphicon-remove deloption"></span>
                                </li>
                            {% endfor %}
                        </ul>
                    </div>
                </li>
            {% endfor %}
        </ol>
    </div>
    <script>
        //添加选项
        $(".ccc").on("click", ".addoption", function () {
            var ele_ul = $(this).parent().parent().children("ul");
            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>';
            ele_ul.append(s)

        });

        //删除选项(绑定事件委派)
        $('.ccc').on('click', '.deloption', function () {
            //找到当前的那一行删除
            $(this).parent().remove()
        });

        //删除问题(添加事件委派)
        $("ol").on('click', ".delquestion", function () {
            console.log($("body"));
            $(this).parent().remove()
        });

        //改变下拉框的触发不同的事件
        $(".ccc").on("click", "#id_question_type", function () {
            if ($(this).val() == 2) {
                //如果是单选的时候,如果有选项就把下面的内容隐藏了
                $(this).next().removeClass("hide");
            }
            else {
                //否则就隐藏添加选项,吧西面的内容清空
                $(".addoption").addClass("hide");
                $(this).parent().next().empty()
            }
        });

        //添加问题
        $(".addquestion").click(function () {
            //克隆一个整个的
            var s = $("ol").children("li:last").clone();
            $("ol").append(s);
        });
        //保存修改的信息
        plist = [];
        $(".savebtn").click(function () {
            $("ol>li").each(function (i,v) {
{#            console.log(i,v);   //v打印的是每一个li#}
            var id =$(this).find("div").eq(1).attr("pk");
            var caption = $(v).find("textarea").val();
            var type_id = $(v).find("select").val();
            console.log($(v).find("select"),type_id);
            if (type_id==2){
                //如果类型id是2的时候,说明是有option的
                var options_list = [];
                var li = $(v).find("li");
                li.each(function (i,v) {
{#                    console.log(i,v);#}
                    var option_id = $(this).attr("class");
                    var option_name =$(this).find("input:first").val();
                    var option_score =$(this).find("input:last").val();
                    options_list.push({"option_id":option_id,"option_name":option_name,"option_score":option_score})
                });
                plist.push({"id":id,"caption":caption,"type_id":type_id,"options":options_list});
            }
            else {
                plist.push({"id":id,"caption":caption,"type_id":type_id});
            }
        });
            $.ajax({
{#                url: "/questionsave/"+s+"/",#}
                url:"/questionsave/{{ nid }}/",
                type: "post",
                data: JSON.stringify(plist),
                contentType: "json",
                headers: {"X-CSRFToken": $.cookie('csrftoken')},
                success: function (i, v) {
                    console.log(i, v);
                    location.href = "/index/"
                }
            })
        })

    </script>
{% endblock %}

questionedit.html

###########################################

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width">
    <title>Title</title>
    <style>
        li{
            list-style-type: none;
        }
        ul li{
            display: inline-block;
        }
    </style>
</head>
<body>
<form action="" method="post" novalidate>
    {% csrf_token %}
    {% for foo in form %}
    <p>{{ foo.label }}{{ foo }}{{ foo.errors.0 }}</p>
    {% endfor %}
    
    <input type="submit" value="提交">
</form>
</body>
</html>

score.html

###########################################

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width">
    <link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.min.css">
    <title>Title</title>
    <style>
        .container{
            margin-top: 50px;
        }
    </style>
</head>
<body>
<div class="container">
    <div class="row">
        <div class="col-md-4 col-md-offset-3">
            <form action="" method="post" novalidate>
                {% csrf_token %}
               {% for foo in form %}
               <p>{{ foo.label }}{{ foo }}{{ foo.errors.0 }}</p>
               {% endfor %}

                <button type="submit" class="btn btn-primary">登录</button>
            </form>
        </div>
    </div>
</div>
</body>
</html>

student_login

###########################################

###########################################

原文地址:https://www.cnblogs.com/andy0816/p/12511145.html