zoukankan      html  css  js  c++  java
  • 基于django的会议室预订系统

    会议室预订系统

    一、目标及业务流程

    期望效果:

    业务流程:

    1. 用户注册

    2. 用户登录

    3. 预订会议室

    4. 退订会议室

    5. 选择日期;今日以及以后日期

     二、表结构设计和生成

     1、models.py(用户继承AbstractUser)

    from django.db import models
    from django.contrib.auth.models import AbstractUser
    # Create your models here.
    
    
    class UserInfo(AbstractUser):
        tel = models.CharField(max_length=32,verbose_name="电话")
        avatar = models.FileField(upload_to="avatars/", default="avatars/timg.jpg", verbose_name="头像")
    
    
    class Room(models.Model):
        """会议室表"""
        caption = models.CharField(max_length=32,verbose_name="会议室名称")
        num = models.IntegerField(verbose_name="容纳人数")  # 容纳人数
    
        def __str__(self):
            return self.caption
    
        class Meta:
            verbose_name = "会议室信息"
            verbose_name_plural = verbose_name
    
    
    class Book(models.Model):
        """会议室预订"""
        user = models.ForeignKey(to="UserInfo",on_delete=models.CASCADE)
        room = models.ForeignKey(to="Room",on_delete=models.CASCADE)
        date = models.DateField()
        time_choice = (
            (1, "8:00"),
            (2, "9:00"),
            (3, "10:00"),
            (4, "11:00"),
            (5, "12:00"),
            (6, "13:00"),
            (7, "14:00"),
            (8, "15:00"),
            (9, "16:00"),
            (10, "17:00"),
            (11, "18:00"),
            (12, "19:00"),
            (13, "20:00"),
            (14, "21:00"),
            (15, "22:00"),
            (16, "23:00"),
        )
    
        time_id = models.IntegerField(choices=time_choice)
    
        def __str__(self):
            return str(self.user)+"预定了"+str(self.room)
    
        class Meta:
            verbose_name = "预定信息"
            verbose_name_plural = verbose_name
            unique_together = (
                ("room","date","time_id"),  # 这三个字段联合唯一,防止重复预订
            )

    2、修改配置文件settings.py,覆盖默认的User模型

      Django允许你通过修改setting.py文件中的 AUTH_USER_MODEL 设置覆盖默认的User模型,其值引用一个自定义的模型。

    1
    AUTH_USER_MODEL = "app01.UserInfo"

      上面的值表示Django应用的名称(必须位于INSTALLLED_APPS中)和你想使用的User模型的名称。

    注意:在创建任何迁移或者第一次运行 manager.py migrate 前设置 AUTH_USER_MODEL

      设置AUTH_USER_MODEL数据库结构有很大的影响。改变了一些会使用到的表格,并且会影响到一些外键和多对多关系的构造。在你有表格被创建后更改此设置是不被 makemigrations 支持的,并且会导致你需要手动修改数据库结构,从旧用户表中导出数据,可能重新应用一些迁移。

    3、数据迁移及创建超级用户

    1
    2
    $ python3 manage.py makemigrations
    $ python3 manage.py migrate

     三、系统登录login

     urls.py:

    from django.conf.urls import url
    from django.contrib import admin
    from django.views.static import serve
    from django.conf import settings
    from app01 import views
    urlpatterns = [
        url(r'^admin/', admin.site.urls),
        # 用户登录
        url(r'^login/',views.acc_login),
        # 展示预订信息
        url(r'^index/',views.index),
        # 极验滑动验证码 获取验证码的url
        url(r'^pc-geetest/register', views.get_geetest),
        # media相关的路由设置
        url(r'^media/(?P<path>.*)$', serve, {"document_root": settings.MEDIA_ROOT}),
        # 处理预订请求
        url(r'^book/',views.book),
        # 首页
        url(r'^home/',views.home),
        # 注销
        url(r'^logout/',views.acc_logout),
        # 用户注册
        url(r'^reg/',views.reg),
        # 临时测试
        url(r'^test/',views.test),
        # 修改密码
        url(r'^change_password/',views.change_password),
    
    
    ]

     login.html(使用了滑动验证)

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>用户登录</title>
        <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css">
        <!-- 引入封装了failback的接口--initGeetest -->
        <script src="http://static.geetest.com/static/tools/gt.js"></script>
    </head>
    <body>
    <h3 class="text-center" style="color: orangered">欢迎登录会议室预订系统</h3>
    <br>
    <div class="container">
        <div class="row">
            <form class="form-horizontal col-md-6 col-md-offset-4" autocomplete="off">
                {% csrf_token %}
                <div class="form-group">
                    <label for="username" class="col-lg-2  control-label">用户名</label>
                    <div class="col-sm-6">
                        <input type="text" class="form-control" id="username" name="username" placeholder="用户名">
                    </div>
                </div>
                <div class="form-group">
                    <label for="pwd" class="col-lg-2 control-label">密码</label>
                    <div class="col-sm-6">
                        <input type="password" class="form-control" id="pwd" name="pwd" placeholder="密码">
                    </div>
                </div>
                <div class="form-group">
                    <!-- 放置极验的滑动验证码 -->
                    <div id="popup-captcha"></div>
                </div>
                <div class="form-group">
                    <div class="col-sm-offset-2 col-sm-10">
                        <button type="button" id="login_btn" class="btn btn-info">登录</button>
                        <span class="login-error has-error text-danger"></span>
                    </div>
                </div>
            </form>
        </div>
    </div>
    
    <script src="/static/js/jquery-3.3.1.min.js"></script>
    <script src="/static/bootstrap/js/bootstrap.min.js"></script>
    <script>
    
    
        var handlerPopup = function (captchaObj) {
            // 成功的回调
            captchaObj.onSuccess(function () {
                var validate = captchaObj.getValidate();
                // 1. 取到用户填写的用户名和密码 -> 取input框的值
                var username = $("#username").val();
                var password = $("#pwd").val();
                $.ajax({
                    url: "/login/", // 进行二次验证
                    type: "post",
                    dataType: "json",
                    data: {
                        username: username,
                        pwd: password,
                        csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val(),
                        geetest_challenge: validate.geetest_challenge,
                        geetest_validate: validate.geetest_validate,
                        geetest_seccode: validate.geetest_seccode
                    },
                    success: function (data) {
                        console.log(data);
                        if (data.status) {
                            // 有错误,在页面上提示
                            $(".login-error").text(data.msg);
                        } else {
                            // 登陆成功
                            location.href = data.msg;
                        }
                    }
                });
            });
    
             $("#login_btn").click(function () {
                captchaObj.show();
            });
            // 将验证码加到id为captcha的元素里
            captchaObj.appendTo("#popup-captcha");
            // 更多接口参考:http://www.geetest.com/install/sections/idx-client-sdk.html
        };
        //当再次点击input输入框时,错误提示要消失
        $("#username,#pwd").focus(function () {
            $(".login-error").text("");
        })
    
        // 验证开始需要向网站主后台获取id,challenge,success(是否启用failback)
        $.ajax({
            url: "/pc-geetest/register?t=" + (new Date()).getTime(), // 加随机数防止缓存
            type: "get",
            dataType: "json",
            success: function (data) {
                // 使用initGeetest接口
                // 参数1:配置参数
                // 参数2:回调,回调的第一个参数验证码对象,之后可以使用它做appendTo之类的事件
                initGeetest({
                    gt: data.gt,
                    challenge: data.challenge,
                    product: "popup", // 产品形式,包括:float,embed,popup。注意只对PC版验证码有效
                    offline: !data.success // 表示用户后台检测极验服务器是否宕机,一般不需要关注
                    // 更多配置参数请参见:http://www.geetest.com/install/sections/idx-client-sdk.html#config
                }, handlerPopup);
            }
        })
    
    
    </script>
    </body>
    </html>
    View Code

     login视图函数

    from django.shortcuts import render,redirect, HttpResponse
    from django.contrib.auth import authenticate, login, logout
    from django.http import JsonResponse
    from geetest import GeetestLib
    from django.contrib.auth.decorators import login_required
    from app01 import models
    from app01 import forms
    import json
    import datetime
    # 登录视图
    def acc_login(request):
        if request.method == "POST":
            print(request.POST)
            res = {"status": 0, "msg": ""}
            username = request.POST.get("username")
            password = request.POST.get("pwd")
            # 获取极验 滑动验证码相关的参数
            gt = GeetestLib(pc_geetest_id, pc_geetest_key)
            challenge = request.POST.get(gt.FN_CHALLENGE, '')
            validate = request.POST.get(gt.FN_VALIDATE, '')
            seccode = request.POST.get(gt.FN_SECCODE, '')
            status = request.session[gt.GT_STATUS_SESSION_KEY]
            user_id = request.session["user_id"]
            if status:
                result = gt.success_validate(challenge, validate, seccode, user_id)
            else:
                result = gt.failback_validate(challenge, validate, seccode)
            print("####################", result)
            if result:
                user = authenticate(username=username, password=password)
                if user:
                    login(request, user)
                    res["msg"] = "/index/"
                else:
                    res["status"] =1
                    res["msg"] = "认证失败,请检查用户名及密码是否正确"
            else:
                res["status"] = 1
                res["msg"] = "验证码错误"
            print("**************", res)
            return JsonResponse(res)
        return render(request, 'login.html')
    
    
    
    # 请在官网申请ID使用,示例ID不可使用
    pc_geetest_id = "b46d1900d0a894591916ea94ea91bd2c"
    pc_geetest_key = "36fc3fe98530eea08dfc6ce76e3d24c4"
    
    
    # 处理极验 获取验证码的视图
    def get_geetest(request):
        user_id = 'test'
        gt = GeetestLib(pc_geetest_id, pc_geetest_key)
        status = gt.pre_process(user_id)
        request.session[gt.GT_STATUS_SESSION_KEY] = status
        request.session["user_id"] = user_id
        response_str = gt.get_response_str()
        return HttpResponse(response_str)
    View Code

     注意:auth模块的authenticate()方法,提供了用户认证,如果认证信息有效,会返回一个  User  对象;如果认证失败,则返回None。

     四、index部分

     1、引入admin组件(后台数据管理组件)并完成admin注册

     admin.py

    from django.contrib import admin
    from app01 import models
    from django.contrib.auth.admin import UserAdmin
    from django.utils.translation import gettext_lazy
    # Register your models here.
    
    
    #  配置会议室信息表
    class RoomConfig(admin.ModelAdmin):
        list_display = ('caption','num')
        list_filter=('num',)
        search_fields = ('caption','num')
    
    
    # 配置预订信息表
    class BookConfig(admin.ModelAdmin):
        list_display = ('user','room','date','time_id')
        list_filter = ('user','room','date','time_id')
        search_fields = ('user','room','date','time_id')
    
    
    # 配置用户管理表
    class UserProfileAdmin(UserAdmin):
        list_display = ('username','last_login','is_superuser','is_staff','is_active','date_joined')
        list_filter = ('last_login', 'is_staff', 'date_joined', 'is_active')
        search_fields = ('username',)
        fieldsets = (
            (None,{'fields':('username','password','first_name','last_name','email')}),
    
            (gettext_lazy('用户信息'),{'fields':('username','email','tel','avatar')}),
    
            (gettext_lazy('用户权限'), {'fields': ('is_superuser','is_staff','is_active',
                                                      'groups', 'user_permissions')}),
    
            (gettext_lazy('Important dates'), {'fields': ('last_login', 'date_joined')}),
        )
    
    admin.site.register(models.Room,RoomConfig)
    admin.site.register(models.UserInfo,UserProfileAdmin)
    admin.site.register(models.Book,BookConfig)

     注意:配置用户管理表至关重要,如果不配置,你会发现在登录admin后添加用户时密码是明文,并没有被加密。

    如果你的用户扩展表没有扩展新的字段,可以直接admin.site.register(models.UserInfo,UserAdmin)。

     2、登录admin添加数据

     

     3、index视图函数数据处理和index.html模板渲染

     index视图

    @login_required(login_url="/login/")
    def index(request):
    
        date = datetime.datetime.now().date()
        # 如果没有指定日期,默认使用当天日期
        book_date = request.GET.get("book_date",date)
        print('日期:', request.GET.get("book_date"))
        print("book_date",book_date)
        # 获取会议室时间段列表
        time_choice = models.Book.time_choice
        print(time_choice)
        # 获取会议室列表
        room_list = models.Room.objects.all()
        # 获取会议室预订信息
        book_list = models.Book.objects.filter(date=book_date)
        htmls=''
        for room in room_list:
            htmls += '<tr><td>{}({})</td>'.format(room.caption,room.num)
            for time in time_choice:
                # 判断该单元格是否被预订
                flag = False
                for book in book_list:
                    if book.room.pk == room.pk and book.time_id == time[0]:
                        # 单元格被预定
                        flag = True
                        break
                if flag:
                    # 判断当前登录人与预订会议室的人是否一致,一致使用info样式
                    if request.user.username == book.user.username:
                        htmls += '<td class="info item"  room_id={} time_id={}>{}</td>'.format(room.pk, time[0],book.user.username)
                    else:
                        htmls += '<td class="success item"  room_id={} time_id={}>{}</td>'.format(room.pk, time[0],
                                                                                             book.user.username)
    
                else:
                    htmls += '<td class="item"  room_id={} time_id={}></td>'.format(room.pk,time[0])
            htmls += "</tr>"
        return render(request,'index.html',{"time_choice":time_choice,"htmls":htmls,})
    View Code

     index前端

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>index</title>
        <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css">
        <link rel="stylesheet" href="/static/datetimepicker/bootstrap-datetimepicker.min.css">
        <style>
            .td_active{
                background-color: purple;
            }
    
            #my_div{
                top: 215px!important;
            }
        </style>
    
    
    </head>
    <body>
    <div class="page-header">
      <h1 class="text-center">欢迎来到会议室预订系统 <small class="text-info">{{ request.user.username }}</small></h1>
    </div>
    
    <div class="text-center">
        <span>当前用户:<img src="/static/img/info.png" alt=""></span>
        <span>其他用户:<img src="/static/img/success.png" alt=""></span>
    </div>
    <br>
    <br>
    <p class="text-center">
        <span><a href="/home/">返回首页</a></span>
        <span><a href="/logout/">注销</a></span>
    </p>
    <div class="calender pull-right">
          <div class='input-group' style=" 230px;">
                <span class="text-warning">注意:当前日期高亮显示</span>
                <input type='text' autocomplete="off"  class="form-control" id='datetimepicker11' placeholder="请选择日期"/>
                <span class="input-group-addon">
                    <span class="glyphicon glyphicon-calendar">
                    </span>
                </span>
    
          </div>
    </div>
    <br>
    <br>
    <table class="table table-bordered">
        <thead>
            <tr>
                <th>会议室/时间</th>
                {% for row in time_choice %}
                    <th>{{ row.1 }}</th>
                {% endfor %}
            </tr>
        </thead>
        <tbody>
            {{ htmls|safe }}
        </tbody>
    </table>
    <div >{% csrf_token %}</div>
    <div class="col-lg-offset-6" >
        <button class="btn btn-info book_btn">预订</button>
    </div>
    
    
    <script src="/static/js/jquery-3.3.1.min.js"></script>
    <script src="/static/bootstrap/js/bootstrap.min.js"></script>
    <script src="/static/datetimepicker/bootstrap-datetimepicker.min.js"></script>
    <script src="/static/datetimepicker/bootstrap-datetimepicker.zh-CN.js"></script>
    
    <script>
    
         // 日期格式化方法
        Date.prototype.yun = function (fmt) { //author:yun
                var o = {
                    "M+": this.getMonth() + 1, //月份
                    "d+": this.getDate(), //
                    "h+": this.getHours(), //小时
                    "m+": this.getMinutes(), //
                    "s+": this.getSeconds(), //
                    "q+": Math.floor((this.getMonth() + 3) / 3), //季度
                    "S": this.getMilliseconds() //毫秒
                };
                if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
                for (var k in o)
                    if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
                return fmt;
            };
        TODAY_DATE=new Date().yun("yyyy-MM-dd");//获取当前日期
        var POST_DATA={
            "ADD":{},
            "DEL":{},
        };
        function TdClick() {
          $(".item").click(function () {
              var room_id = $(this).attr("room_id");
              var time_id = $(this).attr("time_id");
    
              //取消预订
              if($(this).hasClass("info")){
                  $(this).removeClass("info").empty();
                  if (POST_DATA.DEL[room_id]){
                      POST_DATA.DEL[room_id].push(time_id);
                  }
                  else
                      {POST_DATA.DEL[room_id]=[time_id,];}
              }
              //取消临时预订
              else if($(this).hasClass("td_active")){
                  $(this).removeClass("td_active");
    
                  //console.log(room_id,time_id)
                  var index=$.inArray(time_id,POST_DATA.ADD[room_id]);
                  POST_DATA.ADD[room_id].splice(index,1);
                  //console.log(POST_DATA.ADD[room_id]);
                  console.log(POST_DATA);
    
    
    
              }
    
              // 增加预订
              else {
                  $(this).addClass("td_active");
                  if (POST_DATA.ADD[room_id]){
                      POST_DATA.ADD[room_id].push(time_id);
                  }
                  else
                      {POST_DATA.ADD[room_id]=[time_id,];}
    
                  console.log(POST_DATA);
              }
          });
        };
        TdClick();
    
         // 日期
    
         if (location.search.slice(11)){
               CHOOSE_DATE = location.search.slice(11)
              }
          else {
             CHOOSE_DATE = new Date().yun('yyyy-MM-dd');
             console.log(CHOOSE_DATE);
         }
        // 通过ajax发送数据到后端
        $(".book_btn").click(function () {
            $.ajax({
                url:"/book/",
                type:"post",
                data:{
                    choose_date:CHOOSE_DATE,
                    csrfmiddlewaretoken:$("[name='csrfmiddlewaretoken']").val(),
                    post_data:JSON.stringify(POST_DATA),
                },
                dataType:"json",
                success:function (data) {
                    console.log(data);
                    if(data.status==1){
    
                        alert("预订成功");
                        location.href="";
                    }else if (data.status==2){
                        alert("未修改信息");
                        location.href="";
                    }
                    else {
                        alert("已经被预定")
                        location.href=""
                    }
                },
            });
        });
    
    
    
        // 日历插件
        function book_query(e) {
             CHOOSE_DATE=e.date.yun("yyyy-MM-dd");
             location.href="/index/?book_date="+CHOOSE_DATE;
         };
    
    
             /**
        判断输入框中输入的日期格式为yyyy-mm-dd和正确的日期
        */
         function isDate(data){
             var filter  = /((^((1[8-9]d{2})|([2-9]d{3}))([-/._])(10|12|0?[13578])([-/._])(3[01]|[12][0-9]|0?[1-9])$)|(^((1[8-9]d{2})|([2-9]d{3}))([-/._])(11|0?[469])([-/._])(30|[12][0-9]|0?[1-9])$)|(^((1[8-9]d{2})|([2-9]d{3}))([-/._])(0?2)([-/._])(2[0-8]|1[0-9]|0?[1-9])$)|(^([2468][048]00)([-/._])(0?2)([-/._])(29)$)|(^([3579][26]00)([-/._])(0?2)([-/._])(29)$)|(^([1][89][0][48])([-/._])(0?2)([-/._])(29)$)|(^([2-9][0-9][0][48])([-/._])(0?2)([-/._])(29)$)|(^([1][89][2468][048])([-/._])(0?2)([-/._])(29)$)|(^([2-9][0-9][2468][048])([-/._])(0?2)([-/._])(29)$)|(^([1][89][13579][26])([-/._])(0?2)([-/._])(29)$)|(^([2-9][0-9][13579][26])([-/._])(0?2)([-/._])(29)$))/;
             if (filter.test(data)){
                return true;
             }else {
                return false;
             }
        }
        $("#datetimepicker11").change(function () {
             var  test = $(this).val();
            if(isDate(test)){
                if(test<TODAY_DATE){
                    alert("注意:日期不能小于当前日期!")
                }
              CHOOSE_DATE=test;
              location.href="/index/?book_date="+CHOOSE_DATE;
          }else {
              alert("日期格式错误!");
              location.href='';
          }
        });
    
        $('#datetimepicker11').datetimepicker({
                    minView : 2,
                    startView:2,
                    language: "zh-CN",
                    sideBySide: true,
                    format: 'yyyy-mm-dd',
                    startDate: TODAY_DATE,
                    todayBtn:true,
                    todayHighlight: 1,//当天日期高亮
                    enterLikeTab: false,
                    bootcssVer:3,
                    autoclose:true,
                }).on('changeDate',book_query).val(CHOOSE_DATE).css('font-weight','bold');
        $(".datetimepicker.datetimepicker-dropdown-bottom-right.dropdown-menu").attr("id" ,"my_div");
    
    </script>
    </body>
    </html>
    View Code

    注意:

    (1)数据处理还是在后台更加方便,前台渲染后台传递来的标签字符串

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <table class="table table-bordered table-striped">
        <thead>
            <tr>
                <th>会议室时间</th>
                {% for time_choice in time_choices %}
                    {# 在元组中取第二个值 #}
                    <th>{{ time_choice.1 }}</th>
                {% endfor %}
            </tr>
        </thead>
        <tbody>
            {# 由于模板语法功能不够强大,因此数据处理还是放在后台,在这里渲染后台传递来的标签字符串 #}
            {{ htmls|safe }}
        </tbody>
    </table>

      由于模板语法功能不够强大,因此数据处理还是放在后台,在这里渲染后台传递来的标签字符串。

    (2)视图函数字符串处理,运用format格式化函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    def index(request):
        # 拿到预定表中的时间段
        time_choices = Book.time_choices
        # 拿到所有的会议室
        room_list = Room.objects.all()
        # 构建标签
        htmls = ""
        for room in room_list:
            # 第一列td完成后,还有其他td标签需要添加,因此此处没有闭合tr
            htmls += "<tr><td>{}({})</td>".format(room.caption, room.num)
     
        return render(request, "index.html", locals())

    显示效果:

     (3)循环会议室生成行,循环时段生成列,标签字符串拼接处理

    def index(request):
        # 拿到预定表中的时间段
        time_choices = Book.time_choices
        # 拿到所有的会议室
        room_list = Room.objects.all()
     
        # 构建标签
        htmls = ""
        for room in room_list:   # 有多少会议室生成多少行,
            # 每行仅生成了第一列。还有其他td标签需要添加,因此此处没有闭合tr
            htmls += "<tr><td>{}({})</td>".format(room.caption, room.num)
     
            for time_choice in time_choices:   # 有多少时段就生成多少列
                # 一次循环就是一个td标签
                htmls += "<td></td>"
     
            # 循环完成后闭合tr标签
            htmls += "</tr>"
        return render(request, "index.html", locals())

     比如会议室有3个,循环会议室生成三行,且拿到会议室名称和人数限制生成首列;再循环时段,这里有13个时段,因此生成13列,13个td标签依次添加进一个tr中,显示效果如下:

      

    (4)给td标签添加room_id和time_id属性

    for time_choice in time_choices:   # 有多少时段就生成多少列
        # 一次循环就是一个td标签
        htmls += "<td room_id={} time_id={}></td>".format(room.pk, time_choice[0])

     这样点击单元格可确定点击的是哪个会议室哪一个时段的单元格,效果如下所示:

      

    (5)获取预约日期信息

    import datetime
     
    def index(request):
        # 取当前日期
        date = datetime.datetime.now().date()
        print(date)  
        # 取预约日期,没有指定取当前日期
        book_date = request.GET.get("book_date", date)
        print(book_date)  

     index页面访问中,如果没有指定日期,默认显示的就是当前日的预定信息。

    因此在循环生成表格时,可以循环确定单元格是否被预定,已经被预定的添加class=‘success’属性。

    # 构建标签
    htmls = ""
    for room in room_list:   # 有多少会议室生成多少行,
        # 每行仅生成了第一列。还有其他td标签需要添加,因此此处没有闭合tr
        htmls += "<tr><td>{}({})</td>".format(room.caption, room.num)
     
        for time_choice in time_choices:   # 有多少时段就生成多少列
     
            flag = False   # False代表没有预定,True代表已经预定
            for book in book_list:    # 循环确定单元格是否被预定
                if book.room.pk == room.pk and book.time_id == time_choice[0]:
                    # 符合条件说明当前时段会议室已经被预定
                    flag = True
                    break
            if flag:
                # 已经被预定,添加class='success'
                htmls += "<td class='success' room_id={} time_id={}></td>".format(room.pk, time_choice[0])
            else:
                htmls += "<td room_id={} time_id={}></td>".format(room.pk, time_choice[0])
     
        # 循环完成后闭合tr标签
        htmls += "</tr>"

     (6)在预定单元格添加预定人姓名,并根据登录人判断显示单元格

    if flag:
        # 已经被预定,添加class='active'
        if request.user.pk == book.user.pk:
            # 当前登录人查看自己的预约信息
            htmls += "<td class='info item' room_id={} time_id={}>{}</td>".format(room.pk, time_choice[0],
                                                                               book.user.username)
        else:
            # 非当前登录人自己的预约信息
            htmls += "<td class='success item' room_id={} time_id={}>{}</td>".format(room.pk, time_choice[0],
                                                                               book.user.username)
    else:
        htmls += "<td room_id={} time_id={}></td>".format(room.pk, time_choice[0])

     显示效果如下:

    五、前端部分数据处理(index.html)

     index.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>index</title>
        <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css">
        <link rel="stylesheet" href="/static/datetimepicker/bootstrap-datetimepicker.min.css">
        <style>
            .td_active{
                background-color: purple;
            }
    
            #my_div{
                top: 215px!important;
            }
        </style>
    
    
    </head>
    <body>
    <div class="page-header">
      <h1 class="text-center">欢迎来到会议室预订系统 <small class="text-info">{{ request.user.username }}</small></h1>
    </div>
    
    <div class="text-center">
        <span>当前用户:<img src="/static/img/info.png" alt=""></span>
        <span>其他用户:<img src="/static/img/success.png" alt=""></span>
    </div>
    <br>
    <br>
    <p class="text-center">
        <span><a href="/home/">返回首页</a></span>
        <span><a href="/logout/">注销</a></span>
    </p>
    <div class="calender pull-right">
          <div class='input-group' style=" 230px;">
                <span class="text-warning">注意:当前日期高亮显示</span>
                <input type='text' autocomplete="off"  class="form-control" id='datetimepicker11' placeholder="请选择日期"/>
                <span class="input-group-addon">
                    <span class="glyphicon glyphicon-calendar">
                    </span>
                </span>
    
          </div>
    </div>
    <br>
    <br>
    <table class="table table-bordered">
        <thead>
            <tr>
                <th>会议室/时间</th>
                {% for row in time_choice %}
                    <th>{{ row.1 }}</th>
                {% endfor %}
            </tr>
        </thead>
        <tbody>
            <!--由于模板语法功能不够强大,因此数据处理还是放在后台,在这里渲染后台传递来的标签字符串-->
            {{ htmls|safe }}
        </tbody>
    </table>
    <div >{% csrf_token %}</div>
    <div class="col-lg-offset-6" >
        <button class="btn btn-info book_btn">预订</button>
    </div>
    
    
    <script src="/static/js/jquery-3.3.1.min.js"></script>
    <script src="/static/bootstrap/js/bootstrap.min.js"></script>
    <script src="/static/datetimepicker/bootstrap-datetimepicker.min.js"></script>
    <script src="/static/datetimepicker/bootstrap-datetimepicker.zh-CN.js"></script>
    
    <script>
    
         // 日期格式化方法
        Date.prototype.yun = function (fmt) { //author:yun
                var o = {
                    "M+": this.getMonth() + 1, //月份
                    "d+": this.getDate(), //
                    "h+": this.getHours(), //小时
                    "m+": this.getMinutes(), //
                    "s+": this.getSeconds(), //
                    "q+": Math.floor((this.getMonth() + 3) / 3), //季度
                    "S": this.getMilliseconds() //毫秒
                };
                if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
                for (var k in o)
                    if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
                return fmt;
            };
        TODAY_DATE=new Date().yun("yyyy-MM-dd");//获取当前日期
        var POST_DATA={
            "ADD":{},
            "DEL":{},
        };
        function TdClick() {
          $(".item").click(function () {
              var room_id = $(this).attr("room_id");
              var time_id = $(this).attr("time_id");
    
              //取消预订
              if($(this).hasClass("info")){
                  // 如果点击的标签具有info类,直接删除info类并清空内容
                  $(this).removeClass("info").empty();
                  if (POST_DATA.DEL[room_id]){
                      // 在数据中已经存有会议室信息,将新单元格time_id添加进数组
                      POST_DATA.DEL[room_id].push(time_id);
                  }
                  else
                      // 在数据中没有存过对应会议室记录,直接将time_id对其赋值创建一个字典
                      {POST_DATA.DEL[room_id]=[time_id,];}
              }
              //取消临时预订
              else if($(this).hasClass("td_active")){
                  $(this).removeClass("td_active");
                  //点击删除临时预订的数据
                  var index=$.inArray(time_id,POST_DATA.ADD[room_id]);
                  POST_DATA.ADD[room_id].splice(index,1);
    
              }
    
              // 增加预订
              else {
                  $(this).addClass("td_active");
                  if (POST_DATA.ADD[room_id]){
                      // 在数据中已经存有会议室信息,将新单元格time_id添加进数组
                      POST_DATA.ADD[room_id].push(time_id);
                  }
                  else
                      // 在数据中没有存过对应会议室记录,直接将time_id对其赋值创建一个字典
                      {POST_DATA.ADD[room_id]=[time_id,];}
              }
          });
        };
        TdClick();
    
         // 日期
    
         if (location.search.slice(11)){
               CHOOSE_DATE = location.search.slice(11)
              }
          else {
             CHOOSE_DATE = new Date().yun('yyyy-MM-dd');
    
         }
        // 通过ajax发送数据到后端
        $(".book_btn").click(function () {
            $.ajax({
                url:"/book/",
                type:"post",
                data:{
                    choose_date:CHOOSE_DATE,
                    csrfmiddlewaretoken:$("[name='csrfmiddlewaretoken']").val(),
                    post_data:JSON.stringify(POST_DATA),
                },
                dataType:"json",
                success:function (data) {
                    console.log(data);
                    if(data.status==1){
    
                        alert("预订成功");
                        location.href="";
                    }else if (data.status==2){
                        alert("未修改信息");
                        location.href="";
                    }
                    else {
                        alert("已经被预定")
                        location.href=""
                    }
                },
            });
        });
    
    
    
        // 日历插件
        function book_query(e) {
             CHOOSE_DATE=e.date.yun("yyyy-MM-dd");
             location.href="/index/?book_date="+CHOOSE_DATE;
         };
    
    
             /**
        判断输入框中输入的日期格式为yyyy-mm-dd和正确的日期
        */
         function isDate(data){
             var filter  = /((^((1[8-9]d{2})|([2-9]d{3}))([-/._])(10|12|0?[13578])([-/._])(3[01]|[12][0-9]|0?[1-9])$)|(^((1[8-9]d{2})|([2-9]d{3}))([-/._])(11|0?[469])([-/._])(30|[12][0-9]|0?[1-9])$)|(^((1[8-9]d{2})|([2-9]d{3}))([-/._])(0?2)([-/._])(2[0-8]|1[0-9]|0?[1-9])$)|(^([2468][048]00)([-/._])(0?2)([-/._])(29)$)|(^([3579][26]00)([-/._])(0?2)([-/._])(29)$)|(^([1][89][0][48])([-/._])(0?2)([-/._])(29)$)|(^([2-9][0-9][0][48])([-/._])(0?2)([-/._])(29)$)|(^([1][89][2468][048])([-/._])(0?2)([-/._])(29)$)|(^([2-9][0-9][2468][048])([-/._])(0?2)([-/._])(29)$)|(^([1][89][13579][26])([-/._])(0?2)([-/._])(29)$)|(^([2-9][0-9][13579][26])([-/._])(0?2)([-/._])(29)$))/;
             if (filter.test(data)){
                return true;
             }else {
                return false;
             }
        }
        $("#datetimepicker11").change(function () {
             var  test = $(this).val();
            if(isDate(test)){
                if(test<TODAY_DATE){
                    alert("注意:日期不能小于当前日期!")
                }
              CHOOSE_DATE=test;
              location.href="/index/?book_date="+CHOOSE_DATE;
          }else {
              alert("日期格式错误!");
              location.href='';
          }
        });
    
        $('#datetimepicker11').datetimepicker({
                    minView : 2,
                    startView:2,
                    language: "zh-CN",
                    sideBySide: true,
                    format: 'yyyy-mm-dd',
                    startDate: TODAY_DATE,
                    todayBtn:true,
                    todayHighlight: 1,//当天日期高亮
                    enterLikeTab: false,
                    bootcssVer:3,
                    autoclose:true,
                }).on('changeDate',book_query).val(CHOOSE_DATE).css('font-weight','bold');
        $(".datetimepicker.datetimepicker-dropdown-bottom-right.dropdown-menu").attr("id" ,"my_div");
    
    </script>
    </body>
    </html>
    index.html

    1、点击事件预定和取消——组织数据 

    var POST_DATA={
            "ADD":{},
            "DEL":{},
        };
        function TdClick() {
          $(".item").click(function () {
              var room_id = $(this).attr("room_id");
              var time_id = $(this).attr("time_id");
    
              //取消预订
              if($(this).hasClass("info")){
                  // 如果点击的标签具有info类,直接删除info类并清空内容
                  $(this).removeClass("info").empty();
                  if (POST_DATA.DEL[room_id]){
                      // 在数据中已经存有会议室信息,将新单元格time_id添加进数组
                      POST_DATA.DEL[room_id].push(time_id);
                  }
                  else
                      // 在数据中没有存过对应会议室记录,直接将time_id对其赋值创建一个字典
                      {POST_DATA.DEL[room_id]=[time_id,];}
              }
              //取消临时预订
              else if($(this).hasClass("td_active")){
                  $(this).removeClass("td_active");
                  //点击删除临时预订的数据
                  var index=$.inArray(time_id,POST_DATA.ADD[room_id]);
                  POST_DATA.ADD[room_id].splice(index,1);
    
              }
    
              // 增加预订
              else {
                  $(this).addClass("td_active");
                  if (POST_DATA.ADD[room_id]){
                      // 在数据中已经存有会议室信息,将新单元格time_id添加进数组
                      POST_DATA.ADD[room_id].push(time_id);
                  }
                  else
                      // 在数据中没有存过对应会议室记录,直接将time_id对其赋值创建一个字典
                      {POST_DATA.ADD[room_id]=[time_id,];}
              }
          });
        };
        TdClick();

    注意:

    (1)取消预定事件

    <script>
        // 为td绑定单击事件
        function BindTd() {
            $('.item').click(function () {
                // alert($(this).attr("room_id"));   // 点击显示会议室id
     
                // 取消预定
                if ($(this).hasClass("info")){
                    // 如果点击的标签具有active类,直接删除active类并清空内容
                    $(this).removeClass("info").empty();
                }
                else if ($(this).hasClass("td_active")) {
                    $(this).removeClass("td_active");
                }
                else {
                    // 空白局域点击
                    $(this).addClass("td_active");
                }
            })
        }
        BindTd();
    </script>

    在这次只处理了具有info类和td_active类的情况,但没有处理success类的情况,因为这种需要判断的情况,一定要交给后端,否则就是前端一套后端一套,点击保存按钮发送的js,客户可以伪装一个data发送给服务器,如果不做联合唯一,完全交给前端会造成很严重的安全问题。

    (2)数据组织和添加预定

      创建如下所示用js字面量方式创建对象POST_DATA,有两个属性(对象)ADD和DEL,这两个对象的值以room_id为键,以time_id为值:

    <script>
        // room_id 为键,time_id 为值  {1:[4,5],2:[4,] }   {3:[9,10]}
        var POST_DATA = {
            "ADD":{},
            "DEL":{}
        };
    </script>

     在空白单元格点击,获取添加数据到POST_DATA中,以完成预定工作:

    function TdClick() {
          $(".item").click(function () {
              var room_id = $(this).attr("room_id");
              var time_id = $(this).attr("time_id");
    
              //取消预订
              if($(this).hasClass("info")){
                  // 如果点击的标签具有info类,直接删除info类并清空内容
                  $(this).removeClass("info").empty();
                  if (POST_DATA.DEL[room_id]){
                      // 在数据中已经存有会议室信息,将新单元格time_id添加进数组
                      POST_DATA.DEL[room_id].push(time_id);
                  }
                  else
                      // 在数据中没有存过对应会议室记录,直接将time_id对其赋值创建一个字典
                      {POST_DATA.DEL[room_id]=[time_id,];}
              }
              //取消临时预订
              else if($(this).hasClass("td_active")){
                  $(this).removeClass("td_active");
                  //点击删除临时预订的数据
                  var index=$.inArray(time_id,POST_DATA.ADD[room_id]);
                  POST_DATA.ADD[room_id].splice(index,1);
    
              }
    
              // 增加预订
              else {
                  $(this).addClass("td_active");
                  if (POST_DATA.ADD[room_id]){
                      // 在数据中已经存有会议室信息,将新单元格time_id添加进数组
                      POST_DATA.ADD[room_id].push(time_id);
                  }
                  else
                      // 在数据中没有存过对应会议室记录,直接将time_id对其赋值创建一个字典
                      {POST_DATA.ADD[room_id]=[time_id,];}
                      console.log(POST_DATA.ADD);
              }
          });
        };
        TdClick();

    (3)临时预定取消数据处理

    //取消临时预订
              else if($(this).hasClass("td_active")){
                  $(this).removeClass("td_active");
                  //点击删除临时预订的数据
                  var index=$.inArray(time_id,POST_DATA.ADD[room_id]);
                  POST_DATA.ADD[room_id].splice(index,1);
    
              }

     (4)js数组操作常用方法

     1 // shift:删除原数组第一项,并返回删除元素的值;如果数组为空则返回undefined
     2 var a = [1,2,3,4,5];  
     3 var b = a.shift(); //a:[2,3,4,5] b:1
     4  
     5 // pop:删除原数组最后一项,并返回删除元素的值;如果数组为空则返回undefined
     6 var a = [1,2,3,4,5];  
     7 var b = a.pop(); //a:[1,2,3,4] b:5
     8  
     9 // push:将参数添加到原数组末尾,并返回数组的长度
    10 var a = [1,2,3,4,5];  
    11 var b = a.push(6,7); //a:[1,2,3,4,5,6,7] b:7 
    12  
    13 // concat:返回一个新数组,是将参数添加到原数组中构成的
    14 var a = [1,2,3,4,5];  
    15 var b = a.concat(6,7); //a:[1,2,3,4,5] b:[1,2,3,4,5,6,7]
    16  
    17 // splice(start,deleteCount,val1,val2,...):从start位置开始删除deleteCount项,并从该位置起插入val1,val2,...
    18 var a = [1,2,3,4,5];  
    19 var b = a.splice(2,2,7,8,9); //a:[1,2,7,8,9,5] b:[3,4]  
    20 var b = a.splice(0,1); //同shift  
    21 a.splice(0,0,-2,-1); var b = a.length; //同unshift  
    22 var b = a.splice(a.length-1,1); //同pop  
    23 a.splice(a.length,0,6,7); var b = a.length; //同push
    24  
    25 // reverse:将数组反序
    26 // sort(orderfunction):按指定的参数对数组进行排序
    27  
    28 // slice(start,end):返回从原数组中指定开始下标到结束下标之间的项组成的新数组
    29 var a = [1,2,3,4,5];  
    30 var b = a.slice(2,5); //a:[1,2,3,4,5] b:[3,4,5]
    31  
    32 // join(separator):将数组的元素组起一个字符串,以separator为分隔符,省略的话则用默认用逗号为分隔符
    33 var a = [1,2,3,4,5];  
    34 var b = a.join("|"); //a:[1,2,3,4,5] b:"1|2|3|4|5" 

     网络编程本质是浏览器和服务器之间发送字符串,以POST请求为例

    POST:
    
    浏览器-------------------->server 
        "请求首行
    Content-Type:url_encode
    
    a=1&b=2"
        "请求首行
    Content-Type:application/json
    
    '{"a":1,"b":2}'"
        
    在django的wsgi的request中:
        request.body:元数据'{"a":1,"b":2}'
        
        if 请求头中的Content-Type==url_encode:
            request.POST=解码a=1&b=2 
            

     

    2、日历插件(datetimepicker)官方文档:http://eonasdan.github.io/bootstrap-datetimepicker/

    日历html

    <div class="calender pull-right">
          <div class='input-group' style=" 230px;">
                <span class="text-warning">注意:当前日期高亮显示</span>
                <input type='text' autocomplete="off"  class="form-control" id='datetimepicker11' placeholder="请选择日期"/>
                <span class="input-group-addon">
                    <span class="glyphicon glyphicon-calendar">
                    </span>
                </span>
    
          </div>
    </div>

    日历js代码

     // 日期格式化方法
        Date.prototype.yun = function (fmt) { //author:yun
                var o = {
                    "M+": this.getMonth() + 1, //月份
                    "d+": this.getDate(), //日
                    "h+": this.getHours(), //小时
                    "m+": this.getMinutes(), //分
                    "s+": this.getSeconds(), //秒
                    "q+": Math.floor((this.getMonth() + 3) / 3), //季度
                    "S": this.getMilliseconds() //毫秒
                };
                if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
                for (var k in o)
                    if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
                return fmt;
            };
        TODAY_DATE=new Date().yun("yyyy-MM-dd");//获取当前日期
    
    
    
         // 日期
    
         if (location.search.slice(11)){
               CHOOSE_DATE = location.search.slice(11)
              }
          else {
             CHOOSE_DATE = new Date().yun('yyyy-MM-dd');
    
         }
    
    
      // 日历插件
        function book_query(e) {
             CHOOSE_DATE=e.date.yun("yyyy-MM-dd");
             location.href="/index/?book_date="+CHOOSE_DATE;
         };
    
    
        /**
        判断输入框中输入的日期格式为yyyy-mm-dd和正确的日期
        */
         function isDate(data){
             var filter  = /((^((1[8-9]d{2})|([2-9]d{3}))([-/._])(10|12|0?[13578])([-/._])(3[01]|[12][0-9]|0?[1-9])$)|(^((1[8-9]d{2})|([2-9]d{3}))([-/._])(11|0?[469])([-/._])(30|[12][0-9]|0?[1-9])$)|(^((1[8-9]d{2})|([2-9]d{3}))([-/._])(0?2)([-/._])(2[0-8]|1[0-9]|0?[1-9])$)|(^([2468][048]00)([-/._])(0?2)([-/._])(29)$)|(^([3579][26]00)([-/._])(0?2)([-/._])(29)$)|(^([1][89][0][48])([-/._])(0?2)([-/._])(29)$)|(^([2-9][0-9][0][48])([-/._])(0?2)([-/._])(29)$)|(^([1][89][2468][048])([-/._])(0?2)([-/._])(29)$)|(^([2-9][0-9][2468][048])([-/._])(0?2)([-/._])(29)$)|(^([1][89][13579][26])([-/._])(0?2)([-/._])(29)$)|(^([2-9][0-9][13579][26])([-/._])(0?2)([-/._])(29)$))/;
             if (filter.test(data)){
                return true;
             }else {
                return false;
             }
        }
        $("#datetimepicker11").change(function () {
             var  test = $(this).val();
            if(isDate(test)){
                if(test<TODAY_DATE){
                    alert("注意:日期不能小于当前日期!")
                }
              CHOOSE_DATE=test;
              location.href="/index/?book_date="+CHOOSE_DATE;
          }else {
              alert("日期格式错误!");
              location.href='';
          }
        });
    
    //初始化日历
        $('#datetimepicker11').datetimepicker({
                    minView : 2,
                    startView:2,
                    language: "zh-CN",
                    sideBySide: true,
                    format: 'yyyy-mm-dd',
                    startDate: TODAY_DATE,
                    todayBtn:true,
                    todayHighlight: 1,//当天日期高亮
                    enterLikeTab: false,
                    bootcssVer:3,
                    autoclose:true,
                }).on('changeDate',book_query).val(CHOOSE_DATE).css('font-weight','bold');
        $(".datetimepicker.datetimepicker-dropdown-bottom-right.dropdown-menu").attr("id" ,"my_div");

    3、发送AJAX

    // 通过ajax发送数据到后端
        $(".book_btn").click(function () {
            $.ajax({
                url:"/book/",
                type:"post",
                data:{
                    choose_date:CHOOSE_DATE,
                    csrfmiddlewaretoken:$("[name='csrfmiddlewaretoken']").val(),
                    post_data:JSON.stringify(POST_DATA),
                },
                dataType:"json",
                success:function (data) {
                    console.log(data);
                    if(data.status==1){
    
                        alert("预订成功");
                        location.href="";
                    }else if (data.status==2){
                        alert("未修改信息");
                        location.href="";
                    }
                    else {
                        alert("已经被预定")
                        location.href=""
                    }
                },
            });
        });

    六、视图处理图书预定和取消

    def book(request):
        if request.method == "POST":
            choose_date = request.POST.get("choose_date")
            print("choose_date:", choose_date)
            # 获取会议室时间段列表
            time_choice = models.Book.time_choice
            try:
                # 向数据库修改会议室预订记录
                post_data = json.loads(request.POST.get("post_data"))
                if not post_data["ADD"] and not post_data["DEL"]:
                    res = {"status":2, "msg":""}
                    return HttpResponse(json.dumps(res))
                user = request.user
                print(type(post_data), post_data)
                # 添加新的预订信息
                book_list = []
                for room_id, time_id_list in post_data["ADD"].items():
                    for time_id in time_id_list:
                        book_obj = models.Book(user=user, room_id=room_id, time_id=time_id, date=choose_date)
                        book_list.append(book_obj)
                models.Book.objects.bulk_create(book_list)
    
                # 删除旧的预订信息
                from django.db.models import Q
                remove_book = Q()
                for room_id,time_id_list in post_data["DEL"].items():
                    temp = Q()
                    for time_id in time_id_list:
                        temp.children.append(("room_id", room_id))
                        temp.children.append(("time_id", time_id))
                        temp.children.append(("user_id", request.user.pk))
                        temp.children.append(("date", choose_date))
                        remove_book.add(temp, "OR")
                if remove_book:
                    models.Book.objects.filter(remove_book).delete()
                    for time in post_data["DEL"][room_id]:
                        models.Book.objects.filter(user=user, room_id=room_id, time_id=time, date=choose_date).delete()
                res = {"status": 1, "msg": ''}
            except Exception as e:
                res = {"status": 0, "msg": str(e)}
            return HttpResponse(json.dumps(res))

    七、用户注册

    注册前端页面

     <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>欢迎注册</title>
        <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css">
        <style>
            html, body {
    
                width: 100%;
    
                height: 100%;
    
            }
    
            #avatar-img {
    
                width: 80px;
                height: 80px;
            }
    
            .mui-content {
    
                background: url("/static/img/reg_bak.jpg") bottom center no-repeat #efeff4;
    
                background-size: 100% 100%;
    
                width: 100%;
    
                height: 100%;
    
            }
        </style>
    </head>
    <body>
    
    <div class="container mui-content">
        <h3 class="text-center" style="color: orangered">欢迎注册会议室预订系统</h3>
        <div class="row">
            <div class="col-md-6 col-md-offset-3">
                <form novalidate action="/reg/" method="post" autocomplete="off" class="form-horizontal reg-form" enctype="multipart/form-data">
                    {% csrf_token %}
                    <!--头像-->
                    <div class="form-group text-center" style="margin-top: 80px">
                        <label class="col-sm-2 control-label">头像</label>
                        <div class="col-sm-8">
                            <label  for="id_avatar"><img  id="avatar-img" src="/static/img/timg.jpg" alt=""></label>
                            <input  accept="image/*" type="file" name="avatar" id="id_avatar" style="display: none">
                            <span class="help-block"></span>
                        </div>
                    </div>
                    <!--用户名-->
                    <div class="form-group">
                        <label for="{{ form_obj.username.id_for_label }}"
                               class="col-sm-2 control-label">{{ form_obj.username.label }}</label>
                        <div class="col-sm-8">
                            {{ form_obj.username }}
                            <span class="help-block">{{ form_obj.username.errors.0 }}</span>
                        </div>
                    </div>
                    <!--邮箱-->
                    <div class="form-group">
                        <label for="{{ form_obj.email.id_for_label }}"
                               class="col-sm-2 control-label">{{ form_obj.email.label }}</label>
                        <div class="col-sm-8">
                            {{ form_obj.email }}
                            <span class="help-block">{{ form_obj.email.errors.0 }}</span>
                        </div>
                    </div>
                    <!--密码-->
                    <div class="form-group">
                        <label for="{{ form_obj.password.id_for_label }}"
                               class="col-sm-2 control-label">{{ form_obj.password.label }}</label>
                        <div class="col-sm-8">
                            {{ form_obj.password }}
                            <span class="help-block">{{ form_obj.password.errors.0 }}</span>
                        </div>
                    </div>
                    <!--确认密码-->
                    <div class="form-group">
                        <label for="{{ form_obj.re_password.id_for_label }}"
                               class="col-sm-2 control-label">{{ form_obj.re_password.label }}</label>
                        <div class="col-sm-8">
                            {{ form_obj.re_password }}
                            <span class="help-block">{{ form_obj.re_password.errors.0 }}</span>
                        </div>
                    </div>
                    <!--注册-->
                    <div class="form-group">
                        <div class="col-sm-offset-4 col-sm-12">
                            <button type="button" class="btn btn-success" id="reg-submit">注册</button>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
                            <input type="reset" class="btn btn-danger" value="重置">&nbsp;&nbsp;&nbsp;&nbsp;
                            <a class="panel-warning" href="/home/">返回首页</a>
                        </div>
    
                    </div>
                </form>
            </div>
        </div>
    </div>
    
    
    <script src="/static/js/jquery-3.3.1.min.js"></script>
    <script src="/static/bootstrap/js/bootstrap.min.js"></script>
    <script>
        // 找到头像的input标签绑定change事件
        $("#id_avatar").change(function () {
            // 1. 创建一个读取文件的对象
            var fileReader = new FileReader();
            // 取到当前选中的头像文件
            // console.log(this.files[0]);
            // 读取你选中的那个文件
            fileReader.readAsDataURL(this.files[0]);  // 读取文件是需要时间的
            fileReader.onload = function () {
                // 2. 等上一步读完文件之后才 把图片加载到img标签中
                $("#avatar-img").attr("src", fileReader.result);
            };
        });
        // AJAX提交注册的数据
        $("#reg-submit").click(function () {
            // 取到用户填写的注册数据,向后端发送AJAX请求
            var formData = new FormData();
            formData.append("username", $("#id_username").val());
            formData.append("password", $("#id_password").val());
            formData.append("re_password", $("#id_re_password").val());
            formData.append("email", $("#id_email").val());
            formData.append("avatar", $("#id_avatar")[0].files[0]);
            formData.append("csrfmiddlewaretoken", $("[name='csrfmiddlewaretoken']").val());
    
            $.ajax({
                url: "/reg/",
                type: "post",
                processData: false,  //告诉Jquery不要处理我的数据
                contentType: false,  //告诉jQuery不要设置content类型
                data: formData,
                success:function (data) {
                    if (data.status){
                        // 有错误就展示错误
                        // console.log(data.msg);
                        // 将报错信息填写到页面上
                        $.each(data.msg, function (k,v) {
                            // console.log("id_"+k, v[0]);
                            // console.log($("#id_"+k));
                            $("#id_"+k).next("span").text(v[0]).parent().parent().addClass("has-error");
                        })
    
                    }else {
                        // 没有错误就跳转到指定页面
                        location.href = data.msg;
                    }
                }
            })
        });
    
        // 将所有的input框绑定获取焦点的事件,将所有的错误信息清空
        $("form input").focus(function () {
            $(this).next().text("").parent().parent().removeClass("has-error");
        });
        //给username的input输入框,绑定失去焦点事件,失去焦点后检测用户名是否存在
        $("#id_username").blur(function () {
            // 取到用户填写的值
            var username = $(this).val();
            //发ajax请求
            $.ajax({
                url:"/check_username_exist/",
                type:"get",
                data:{username:username,},
                success:function (data) {
                    if(data.status){
                        //有错误,用户名已被注册
                        $("#id_username").next().text(data.msg).parent().parent().addClass("has-error");
                    }
    
                }
            })
        })
    </script>
    </body>
    </html>
    reg.html

    注册后端页面

    def reg(request):
        if request.method == "POST":
            ret = {"status": 0, "msg": ""}
            form_obj = forms.RegForm(request.POST)
            print('request.POST'.center(80, '#'))
            print(request.POST)
            print('request.POST'.center(80, '#'))
            avatar_img = request.FILES.get("avatar")
            print(avatar_img)
            # 帮我做校验
            if form_obj.is_valid():
                # 校验通过,去数据库创建一个新的用户
                form_obj.cleaned_data.pop("re_password")
                print(form_obj.cleaned_data)
                try:
                    models.UserInfo.objects.create_user(**form_obj.cleaned_data,avatar=avatar_img)
                except Exception as e:
                    print(e)
                ret["msg"] = "/login/"
                return JsonResponse(ret)
            else:
                print(form_obj.errors)
                ret["status"] = 1
                ret["msg"] = form_obj.errors
                print(ret)
                print("=" * 120)
                return JsonResponse(ret)
                # 生成一个form对象
        form_obj = forms.RegForm()
        print(form_obj.fields)
        return render(request,'reg.html',{"form_obj": form_obj})
    View Code

    八、项目源码

    https://github.com/Yun-Wangjun/BookSystem

  • 相关阅读:
    Java代码输出到txt文件(申请专利贴源码的必备利器)
    Vmware10组建局域网
    Ubuntu14.04更换阿里云源
    Ubuntu16.04如何彻底删除Apache2
    HustOJ平台搭建
    Centos 7 联想Y430P无线网卡驱动安装 过程参考
    Windows远程CentOS桌面
    centos 7 查看系统/硬件信息及运维常用命令+联想Y430P无线网卡驱动安装
    zookeeper工作原理、安装配置、工具命令简介
    centos 7 安装五笔输入法
  • 原文地址:https://www.cnblogs.com/yunwangjun-python-520/p/11137781.html
Copyright © 2011-2022 走看看