基于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

原文地址:https://www.cnblogs.com/yunwangjun-python-520/p/11137781.html