会议室预定项目
1、建表:用户表、会议室表和会议室预定信息表
from django.db import models # Create your models here. from django.db import models from django.contrib.auth.models import AbstractUser # 用户表 class UserInfo(AbstractUser): # 扩展默认的auth_user表 tel = models.CharField(max_length=32) # 会议室表 class Room(models.Model): caption = models.CharField(max_length=32) # 会议室名称 num = models.IntegerField() # 容纳人数 def __str__(self): return self.caption # 会议室预定信息 class Book(models.Model): user = models.ForeignKey('UserInfo') room = models.ForeignKey('Room') date = models.DateField() time_choices = ( (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'), ) time_id = models.IntegerField(choices=time_choices) # choices=time_choices限定只能存上面13(1-13)个数字 # choices接收一个元组套元组数据,ModelForm会把这个字段渲染成下拉菜单,用户选的是文本值,数据库存的是value(1-13)值 class Meta: unique_together = ( ('room', 'date', 'time_id'), ) def __str__(self): return str(self.user) + "预定了" + str(self.room)
2、搭建登录界面
url(r'^login/', views.login),
def login(request): if request.method=="POST": user=request.POST.get("user") pwd=request.POST.get("pwd") # 使用auth模块进行登录认证 user=auth.authenticate(username=user,password=pwd) if user: auth.login(request,user) # 注册session,可以使用request.user拿到登录对象所有信息 return redirect("/index/") return render(request,"login.html")
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>Title</title> <meta name="viewport" content="width=device-width, initial-scale=1"> </head> <body> <form action="" method="post"> {% csrf_token %} 用户名:<input type="text" name="user"> 密码: <input type="password" name="pwd"> <input type="submit"> </form> </body> </html>
3、搭建index界面
登录后跳转到这个界面,用户预定/删除都要在这个界面上进行
from .models import * import datetime def index(request): date=datetime.datetime.now().date() #获取当前时间 book_date=request.GET.get("book_date",date) #获取会议室预定信息,如果获取不到展示当前日期 time_choices=Book.time_choices print(time_choices) #((1, '8:00'), (2, '9:00')…… (13, '20:00')) room_list=Room.objects.all() print(room_list) #<QuerySet [<Room: 301会议室>, <Room: 302会议室>, <Room: 402会议室>]> book_list=Book.objects.filter(date=book_date) print(book_list) #<QuerySet [<Book: zh预定了301会议室>, <Book: zh预定了301会议室>, <Book: hui预定了302会议室>]> htmls="" for room in room_list: #循环会议室列表 htmls+="<tr><td>{}({})</td>".format(room.caption,room.num) #(会议室名称,最大容纳数) #循环预定时间下面的单元格 for time_choice in time_choices: book=None flag=False for book in book_list: #如果存在会议室名称以及预定时间,意味这个单元格已被预定 if book.room.pk==room.pk and book.time_id==time_choice[0]: flag=True break if flag: #如果会议室被预定,在此单元格显示预定用户的姓名,不同的用户名称用不同的颜色标记 if request.user.pk==book.user.pk: htmls += "<td class='active item' room_id={} time_id={}>{}</td>".format(room.pk, time_choice[0],book.user.username) else: htmls += "<td class='another_active item' room_id={} time_id={}>{}</td>".format(room.pk, time_choice[0],book.user.username) else: #会议室没有被预定,单元格显示为空 htmls+="<td room_id={} time_id={} class='item'></td>".format(room.pk,time_choice[0]) htmls+="</tr>" #添加闭合的tr标签 return render(request,"index.html",locals())
index.html
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>Title</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.css"> <script src="/static/js/jquery-1.12.4.min.js"></script> <script src="/static/datetimepicker/bootstrap-datetimepicker.min.js"></script> <script src="/static/datetimepicker//bootstrap-datetimepicker.zh-CN.js"></script> <style> .active { background-color: green !important; color: white; } .another_active { background-color: #2b669a; color: white; } .td_active { background-color: lightblue; color: white; } </style> </head> <body> <h3>会议室预定</h3> <div class="calender pull-right"> <div class='input-group' style="width: 230px;"> <input type='text' class="form-control" id='datetimepicker11' placeholder="请选择日期"/> <span class="input-group-addon"> <span class="glyphicon glyphicon-calendar"> </span> </span> </div> </div> <table class="table table-bordered table-striped"> <thead> <tr> <th>会议室/时间</th> {% for time_choice in time_choices %} <!-- (1, '8:00'),--> <th>{{ time_choice.1 }}</th> <!-- '8:00' --> {% endfor %} </tr> </thead> <tbody> {{ htmls|safe }} <!-- 模板提供语法太少,把逻辑移到视图中处理后拿过来渲染--> </tbody> </table> <button class="btn btn-success pull-right keep">保存</button> <script> //构建数据结构,便于打包发送增加/删除预定信息,以room_id为键,time_id为值 var POST_DATA = { "ADD": {}, "DEL": {}, }; // 为td绑定单击事件 function BindTd() { $(".item").click(function () { var room_id = $(this).attr("room_id"); var time_id = $(this).attr("time_id"); // 取消预定 if ($(this).hasClass("active")) { $(this).removeClass("active").empty(); //移除样式并清空内容 //如果之前DEL里面有值,把新添加的数据push进去 if (POST_DATA.DEL[room_id]) { POST_DATA.DEL[room_id].push(time_id) } else { //DEL里面没有值,直接把数据存到数组中,方便后面进行push操作 POST_DATA.DEL[room_id] = [time_id,] } } // 临时取消预定 else if ($(this).hasClass("td_active")) { $(this).removeClass("td_active"); POST_DATA.ADD[room_id].pop() } 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,] } } }) } BindTd(); </script> </body> </html>
4、为td绑定单击事件
用来记录用户对会议室进行预定/删除
// 为td绑定单击事件 function BindTd() { $(".item").click(function () { var room_id = $(this).attr("room_id"); var time_id = $(this).attr("time_id"); // 取消预定 if ($(this).hasClass("active")) { $(this).removeClass("active").empty(); //移除样式并清空内容 //如果之前DEL里面有值,把新添加的数据push进去 if (POST_DATA.DEL[room_id]) { POST_DATA.DEL[room_id].push(time_id) } else { //DEL里面没有值,直接把数据存到数组中,方便后面进行push操作 POST_DATA.DEL[room_id] = [time_id,] } } // 临时取消预定 else if ($(this).hasClass("td_active")) { $(this).removeClass("td_active"); POST_DATA.ADD[room_id].pop() } 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,] } } }) } BindTd();
5、关于日历插件的使用
预定涉及到日期,所以我们选择一个日期插件来处理。首先在里面 HTML 代码中引入
<div class="calender pull-right"> <div class='input-group' style="width: 230px;"> <input type='text' class="form-control" id='datetimepicker11' placeholder="请选择日期"/> <span class="input-group-addon"> <span class="glyphicon glyphicon-calendar"> </span> </span> </div> </div>
然后,在 script 中加入使用代码:
// 日历插件 $('#datetimepicker11').datetimepicker({ minView: "month", language: "zh-CN", sideBySide: true, format: 'yyyy-mm-dd', startDate: new Date(), bootcssVer: 3, autoclose: true, }).on('changeDate', book_query); function book_query(e) { CHOOSE_DATE = e.date.yuan("yyyy-MM-dd"); location.href = "/index/?book_date=" + CHOOSE_DATE; }
最后,我们格式化日期,使之变成我们想要的格式,能够为我们所用。
// 日期格式化方法 Date.prototype.yuan = function (fmt) { //author: meizz 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; };
6、发送AJAX请求
当用户预定/删除会议室之后点击保存按钮时发送ajax请求,然后拿到返回结果进行处理
// 日期 if (location.search.slice(11)) { CHOOSE_DATE = location.search.slice(11) } else { CHOOSE_DATE = new Date().yuan('yyyy-MM-dd'); } // 发送ajax $(".keep").click(function () { $.ajax({ url: "/book/", type: "POST", data: { choose_date: CHOOSE_DATE, //POST_DATA是一个对象(object)类型,序列化成字符串之后才能发送 post_data: JSON.stringify(POST_DATA), }, dataType: "json", success: function (data) { console.log(data) if (data.state) { // 预定成功 location.href = "" } else { alert("预定的房间已经被预定") location.href = "" } } }) });
7、数据发送到视图中进行处理
import json def book(request): print(request.POST) #<QueryDict: {'choose_date': ['2020-05-17'], 'post_data': ['{"ADD":{"1":["10"],"2":["11","12"]},"DEL":{"2":["10"]}}']}> post_data=json.loads(request.POST.get("post_data")) # {"ADD":{"1":["10"],"2":["11","12"]},"DEL":{"2":["10"]}} choose_date=request.POST.get("choose_date") #2020-05-17 res={"state":True,"msg":None} try: # 添加预定 #post_data["ADD"] : {"1":["10"],"2":["11","12"]} book_list=[] for room_id,time_id_list in post_data["ADD"].items(): for time_id in time_id_list: book_obj=Book(user=request.user,room_id=room_id,time_id=time_id,date=choose_date) book_list.append(book_obj) Book.objects.bulk_create(book_list) #批量往数据库中创建数据 # 删除预定 from django.db.models import Q # post_data["DEL"]: {"2":["10"]}} 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: Book.objects.filter(remove_book).delete() except Exception as e: res["state"]=False res["msg"]=str(e) return HttpResponse(json.dumps(res))
以上所用完整代码:
from django.db import models # Create your models here. from django.db import models from django.contrib.auth.models import AbstractUser # 用户表 class UserInfo(AbstractUser): # 扩展默认的auth_user表 tel = models.CharField(max_length=32) # 会议室表 class Room(models.Model): caption = models.CharField(max_length=32) # 会议室名称 num = models.IntegerField() # 容纳人数 def __str__(self): return self.caption # 会议室预定信息 class Book(models.Model): user = models.ForeignKey('UserInfo') room = models.ForeignKey('Room') date = models.DateField() time_choices = ( (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'), ) time_id = models.IntegerField(choices=time_choices) # choices=time_choices限定只能存上面13(1-13)个数字 # choices接收一个元组套元组数据,ModelForm会把这个字段渲染成下拉菜单,用户选的是文本值,数据库存的是value(1-13)值 class Meta: unique_together = ( ('room', 'date', 'time_id'), ) def __str__(self): return str(self.user) + "预定了" + str(self.room)
from app01 import views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^login/', views.login), url(r'^index/', views.index), url(r'^book/', views.book), ]
from django.shortcuts import render,redirect,HttpResponse from django.contrib import auth def login(request): if request.method=="POST": user=request.POST.get("user") pwd=request.POST.get("pwd") # 使用auth模块进行登录认证 user=auth.authenticate(username=user,password=pwd) if user: auth.login(request,user) # 注册session,可以使用request.user拿到登录对象所有信息 return redirect("/index/") return render(request,"login.html") from .models import * import datetime def index(request): date=datetime.datetime.now().date() #获取当前时间 book_date=request.GET.get("book_date",date) #获取会议室预定信息,如果获取不到展示当前日期 time_choices=Book.time_choices print(time_choices) #((1, '8:00'), (2, '9:00')…… (13, '20:00')) room_list=Room.objects.all() print(room_list) #<QuerySet [<Room: 301会议室>, <Room: 302会议室>, <Room: 402会议室>]> book_list=Book.objects.filter(date=book_date) print(book_list) #<QuerySet [<Book: zh预定了301会议室>, <Book: zh预定了301会议室>, <Book: hui预定了302会议室>]> htmls="" for room in room_list: #循环会议室列表 htmls+="<tr><td>{}({})</td>".format(room.caption,room.num) #(会议室名称,最大容纳数) #循环预定时间下面的单元格 for time_choice in time_choices: book=None flag=False for book in book_list: #如果存在会议室名称以及预定时间,意味这个单元格已被预定 if book.room.pk==room.pk and book.time_id==time_choice[0]: flag=True break if flag: #如果会议室被预定,在此单元格显示预定用户的姓名,不同的用户名称用不同的颜色标记 if request.user.pk==book.user.pk: htmls += "<td class='active item' room_id={} time_id={}>{}</td>".format(room.pk, time_choice[0],book.user.username) else: htmls += "<td class='another_active item' room_id={} time_id={}>{}</td>".format(room.pk, time_choice[0],book.user.username) else: #会议室没有被预定,单元格显示为空 htmls+="<td room_id={} time_id={} class='item'></td>".format(room.pk,time_choice[0]) htmls+="</tr>" #添加闭合的tr标签 return render(request,"index.html",locals()) import json def book(request): print(request.POST) #<QueryDict: {'choose_date': ['2020-05-17'], 'post_data': ['{"ADD":{"1":["10"],"2":["11","12"]},"DEL":{"2":["10"]}}']}> post_data=json.loads(request.POST.get("post_data")) # {"ADD":{"1":["10"],"2":["11","12"]},"DEL":{"2":["10"]}} choose_date=request.POST.get("choose_date") #2020-05-17 res={"state":True,"msg":None} try: # 添加预定 #post_data["ADD"] : {"1":["10"],"2":["11","12"]} book_list=[] for room_id,time_id_list in post_data["ADD"].items(): for time_id in time_id_list: book_obj=Book(user=request.user,room_id=room_id,time_id=time_id,date=choose_date) book_list.append(book_obj) Book.objects.bulk_create(book_list) #批量往数据库中创建数据 # 删除预定 from django.db.models import Q # post_data["DEL"]: {"2":["10"]}} 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: Book.objects.filter(remove_book).delete() except Exception as e: res["state"]=False res["msg"]=str(e) return HttpResponse(json.dumps(res))
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>Title</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.css"> <script src="/static/js/jquery-1.12.4.min.js"></script> <script src="/static/datetimepicker/bootstrap-datetimepicker.min.js"></script> <script src="/static/datetimepicker//bootstrap-datetimepicker.zh-CN.js"></script> <style> .active { background-color: green !important; color: white; } .another_active { background-color: #2b669a; color: white; } .td_active { background-color: lightblue; color: white; } </style> </head> <body> <h3>会议室预定</h3> <div class="calender pull-right"> <div class='input-group' style="width: 230px;"> <input type='text' class="form-control" id='datetimepicker11' placeholder="请选择日期"/> <span class="input-group-addon"> <span class="glyphicon glyphicon-calendar"> </span> </span> </div> </div> <table class="table table-bordered table-striped"> <thead> <tr> <th>会议室/时间</th> {% for time_choice in time_choices %} <!-- (1, '8:00'),--> <th>{{ time_choice.1 }}</th> <!-- '8:00' --> {% endfor %} </tr> </thead> <tbody> {{ htmls|safe }} <!-- 模板提供语法太少,把逻辑移到视图中处理后拿过来渲染--> </tbody> </table> <button class="btn btn-success pull-right keep">保存</button> <script> // 日期格式化方法 Date.prototype.yuan = function (fmt) { //author: meizz 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; }; //构建数据结构,便于打包发送增加/删除预定信息,以room_id为键,time_id为值 var POST_DATA = { "ADD": {}, "DEL": {}, }; // 为td绑定单击事件 function BindTd() { $(".item").click(function () { var room_id = $(this).attr("room_id"); var time_id = $(this).attr("time_id"); // 取消预定 if ($(this).hasClass("active")) { $(this).removeClass("active").empty(); //移除样式并清空内容 //如果之前DEL里面有值,把新添加的数据push进去 if (POST_DATA.DEL[room_id]) { POST_DATA.DEL[room_id].push(time_id) } else { //DEL里面没有值,直接把数据存到数组中,方便后面进行push操作 POST_DATA.DEL[room_id] = [time_id,] } } // 临时取消预定 else if ($(this).hasClass("td_active")) { $(this).removeClass("td_active"); POST_DATA.ADD[room_id].pop() } 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,] } } }) } BindTd(); // 日期 if (location.search.slice(11)) { CHOOSE_DATE = location.search.slice(11) } else { CHOOSE_DATE = new Date().yuan('yyyy-MM-dd'); } // 发送ajax $(".keep").click(function () { $.ajax({ url: "/book/", type: "POST", data: { choose_date: CHOOSE_DATE, //POST_DATA是一个对象(object)类型,序列化成字符串之后才能发送 post_data: JSON.stringify(POST_DATA), }, dataType: "json", success: function (data) { console.log(data) if (data.state) { // 预定成功 location.href = "" } else { alert("预定的房间已经被预定") location.href = "" } } }) }); // 日历插件 $('#datetimepicker11').datetimepicker({ minView: "month", language: "zh-CN", sideBySide: true, format: 'yyyy-mm-dd', startDate: new Date(), bootcssVer: 3, autoclose: true, }).on('changeDate', book_query); function book_query(e) { CHOOSE_DATE = e.date.yuan("yyyy-MM-dd"); location.href = "/index/?book_date=" + CHOOSE_DATE; } </script> </body> </html>
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>Title</title> <meta name="viewport" content="width=device-width, initial-scale=1"> </head> <body> <form action="" method="post"> {% csrf_token %} 用户名:<input type="text" name="user"> 密码: <input type="password" name="pwd"> <input type="submit"> </form> </body> </html>