会议室预订系统(meeting room booking system)
正文
一、目标及业务流程
期望效果:
业务流程:
- 用户登录
- 预定会议室
- 退订会议室
- 选择日期;今日以及以后日期
二、表结构设计和生成
1、models.py(用户继承AbstractUser)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
from
django.db
import
models
# Create your models here.
from
django.db
import
models
from
django.contrib.auth.models
import
AbstractUser
class
UserInfo(AbstractUser):
"""用户信息表"""
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"
, on_delete
=
models.CASCADE)
# CASCADE级联删除
room
=
models.ForeignKey(
"Room"
, on_delete
=
models.CASCADE)
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参数
class
Meta:
unique_together
=
(
# 三个联合唯一,防止有人重复预定
(
'room'
,
'date'
,
'time_id'
),
)
def
__str__(
self
):
return
str
(
self
.user)
+
"预定了"
+
str
(
self
.room)
|
注意:
(1)Django中提供了AbstractUser类,可以用来自由定制需要的model
1
2
3
4
5
|
from
django.contrib.auth.models
import
AbstractUser
class
UserInfo(AbstractUser):
"""用户信息表"""
tel
=
models.CharField(max_length
=
32
)
|
如上所示,即可在Django的基础上添加我们所需要的信息。
(2)设置model的时候,设置三个字段联合唯一
1
2
3
4
5
6
7
|
class
Book(models.Model):
"""会议室预订信息"""
....
class
Meta:
unique_together
=
(
# 三个联合唯一,防止有人重复预定
(
'room'
,
'date'
,
'time_id'
),
)
|
存的是key 显示的是value,且只能存key。
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
|
这里遇到了一个问题:创建项目时没有创建应用,手动通过manage.py startapp user创建子项目,修改AUTH_USER_MODEL后执行makemigrations一直报错,找不到app01。
创建两个超级用户:
1
2
3
4
5
6
7
|
MacBook-Pro:MRBS hqs$ python3 manage.py createsuperuser
Username: yuan
Password:yuan1234
MacBook-Pro:MRBS hqs$ python3 manage.py createsuperuser
Username: alex
Password:alex1234
|
三、系统登录login
urls.py:
1
2
3
4
5
6
7
8
9
10
|
from
django.contrib
import
admin
from
django.urls
import
path
from
app01
import
views
urlpatterns
=
[
path(
'admin/'
, admin.site.urls),
path(
'login/'
, views.login),
path(
'index/'
, views.index),
path(
'book/'
, views.book),
]
|
简单login.html:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
<!DOCTYPE html>
<html lang
=
"en"
>
<head>
<meta charset
=
"UTF-8"
>
<title>Title<
/
title>
<
/
head>
<body>
<form action
=
"
" method="
post">
用户名:<
input
type
=
"text"
name
=
"user"
>
密码:<
input
type
=
"password"
name
=
"pwd"
>
<
input
type
=
"submit"
>
<
/
form>
<
/
body>
<
/
html>
|
login视图函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
from
django.shortcuts
import
render, redirect
# Create your views here.
from
django.contrib
import
auth
def
login(request):
if
request.method
=
=
"POST"
:
user
=
request.POST.get(
"user"
)
pwd
=
request.POST.get(
"pwd"
)
user
=
auth.authenticate(username
=
user, password
=
pwd)
if
user:
# 登录成功
auth.login(request, user)
# 注册request.user,可以拿到登录用户对象所有信息
redirect(
"/index/"
)
return
render(request,
"login.html"
)
|
注意:auth模块的authenticate()方法,提供了用户认证,如果认证信息有效,会返回一个 User 对象;如果认证失败,则返回None。
四、index部分
1、引入admin组件(后台数据管理组件)并完成admin注册
admin.py:
1
2
3
4
5
6
7
|
from
django.contrib
import
admin
# Register your models here.
from
app01.models
import
*
admin.site.register(UserInfo)
admin.site.register(Book)
admin.site.register(Room)
|
2、在数据库添加数据
3、index视图函数数据处理和index.html模板渲染


def index(request): # 取当前日期 date = datetime.datetime.now().date() print(date) # 2018-08-17 # 取预约日期,没有指定取当前日期 book_date = request.GET.get("book_date", date) print(book_date) # 2018-08-17 # 拿到预定表中的时段 time_choices = Book.time_choices # 拿到所有的会议室 room_list = Room.objects.all() # 拿到预定信息 book_list = Book.objects.filter(date=book_date) # 构建标签 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 print(book) # 这个book是预定信息 if flag: # 已经被预定,添加class='active' if request.user.pk == book.user.pk: # 当前登录人查看自己的预约信息 htmls += "<td class='active' room_id={} time_id={}>{}</td>".format(room.pk, time_choice[0], book.user.username) else: # 非当前登录人自己的预约信息 htmls += "<td class='another_active' 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]) # 循环完成后闭合tr标签 htmls += "</tr>" return render(request, "index.html", locals())


<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <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: #0f5687; color: white; } </style> </head> <body> <H3>会议室预定</H3> <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> </html>
注意:
(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
())
|
显示效果:
这是Python2.6后新增了一种格式化字符串的函数 str.format(),它增强了字符串格式化的功能。基本语法是通过 {} 和 : 来代替以前的 % 。format 函数可以接受不限个参数,位置可以不按顺序。
1
2
3
4
5
6
7
8
|
>>>
"{} {}"
.
format
(
"hello"
,
"world"
)
# 不设置指定位置,按默认顺序
'hello world'
>>>
"{0} {1}"
.
format
(
"hello"
,
"world"
)
# 设置指定位置
'hello world'
>>>
"{1} {0} {1}"
.
format
(
"hello"
,
"world"
)
# 设置指定位置
'world hello world'
|
还可以设置参数:
1
2
3
4
5
6
7
8
9
|
print
(
"网站名:{name}, 地址 {url}"
.
format
(name
=
"菜鸟教程"
, url
=
"www.runoob.com"
))
# 通过字典设置参数
site
=
{
"name"
:
"菜鸟教程"
,
"url"
:
"www.runoob.com"
}
print
(
"网站名:{name}, 地址 {url}"
.
format
(
*
*
site))
# 通过列表索引设置参数
my_list
=
[
'菜鸟教程'
,
'www.runoob.com'
]
print
(
"网站名:{0[0]}, 地址 {0[1]}"
.
format
(my_list))
# "0" 是必须的
|
(3)循环会议室生成行,循环时段生成列,标签字符串拼接处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
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属性
1
2
3
|
for
time_choice
in
time_choices:
# 有多少时段就生成多少列
# 一次循环就是一个td标签
htmls
+
=
"<td room_id={} time_id={}></td>"
.
format
(room.pk, time_choice[
0
])
|
这样点击单元格可确定点击的是哪个会议室哪一个时段的单元格,效果如下所示:
(5)获取预约日期信息
1
2
3
4
5
6
7
8
9
|
import
datetime
def
index(request):
# 取当前日期
date
=
datetime.datetime.now().date()
print
(date)
# 2018-08-17
# 取预约日期,没有指定取当前日期
book_date
=
request.GET.get(
"book_date"
, date)
print
(book_date)
# 2018-08-17
|
index页面访问中,如果没有指定日期,默认显示的就是当前日的预定信息。
因此在循环生成表格时,可以循环确定单元格是否被预定,已经被预定的添加class=‘active’属性。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
# 构建标签
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='active'
htmls
+
=
"<td class='active' 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)在预定单元格添加预定人姓名,并根据登录人判断显示单元格
1
2
3
4
5
6
7
8
9
10
11
12
|
if
flag:
# 已经被预定,添加class='active'
if
request.user.pk
=
=
book.user.pk:
# 当前登录人查看自己的预约信息
htmls
+
=
"<td class='active' room_id={} time_id={}>{}</td>"
.
format
(room.pk, time_choice[
0
],
book.user.username)
else
:
# 非当前登录人自己的预约信息
htmls
+
=
"<td class='another_active' 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中添加样式:
1
2
3
4
5
6
7
8
9
10
|
<style>
.active {
background-color
:
green
!important
;
color
:
white
;
}
.another_active {
background-color
:
#0f5687
;
color
:
white
;
}
</style>
|
显示效果如下:
五、前端部分数据处理(index.html)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
|
<!DOCTYPE html>
<html lang=
"en"
>
<head>
<meta charset=
"UTF-8"
>
<title>Title</title>
<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:
#0f5687;
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 %}
{
# 在元组中取第二个值 #}
<th>{{ time_choice.1 }}</th>
{% 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 为值 {1:[4,5],2:[4,] } {3:[9,10]}
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"
)){
// 如果点击的标签具有active类,直接删除active类并清空内容
$(
this
).removeClass(
"active"
).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"
);
// 点击删除临时预定时添加的数据
// POST_DATA.ADD[room_id].pop(); // 这个是删除最后一个元素不对
POST_DATA.ADD[room_id].splice(POST_DATA.ADD[room_id].indexOf(time_id),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, ];
}
}
})
}
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:{
csrfmiddlewaretoken:
'{{ csrf_token }}'
,
choose_date:CHOOSE_DATE,
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>
</html>
|
1、点击事件预定和取消——组织数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
<script>
// room_id 为键,time_id 为值 {1:[4,5],2:[4,] } {3:[9,10]}
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"
)){
// 如果点击的标签具有active类,直接删除active类并清空内容
$(
this
).removeClass(
"active"
).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"
);
// 点击删除临时预定时添加的数据
// POST_DATA.ADD[room_id].pop(); // 这个是删除最后一个元素不对
POST_DATA.ADD[room_id].splice(POST_DATA.ADD[room_id].indexOf(time_id),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, ];
}
}
})
}
BindTd();
</script>
|
注意:
(1)取消预定事件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
<script>
// 为td绑定单击事件
function
BindTd() {
$(
'.item'
).click(
function
() {
// alert($(this).attr("room_id")); // 点击显示会议室id
// 取消预定
if
($(
this
).hasClass(
"active"
)){
// 如果点击的标签具有active类,直接删除active类并清空内容
$(
this
).removeClass(
"active"
).empty();
}
else
if
($(
this
).hasClass(
"td_active"
)) {
$(
this
).removeClass(
"td_active"
);
}
else
{
// 空白局域点击
$(
this
).addClass(
"td_active"
);
}
})
}
BindTd();
</script>
|
在这次只处理了具有active类和td_active类的情况,但没有处理another_active类的情况,因为这种需要判断的情况,一定要交给后端,否则就是前端一套后端一套,点击保存按钮发送的js,客户可以伪装一个data发送给服务器,如果不做联合唯一,完全交给前端会造成很严重的安全问题。
(2)数据组织和添加预定
创建如下所示用js字面量方式创建对象
POST_DATA,有两个属性(对象)ADD和DEL,这两个对象的值以room_id为键,以time_id为值:
1
2
3
4
5
6
7
|
<script>
// room_id 为键,time_id 为值 {1:[4,5],2:[4,] } {3:[9,10]}
var
POST_DATA = {
"ADD"
:{},
"DEL"
:{}
};
</script>
|
在空白单元格点击,获取添加数据到POST_DATA中,以完成预定工作:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
// 为td绑定单击事件
function
BindTd() {
$(
'.item'
).click(
function
() {
var
room_id = $(
this
).attr(
"room_id"
);
var
time_id = $(
this
).attr(
"time_id"
);
// 取消预定
if
($(
this
).hasClass(
"active"
)){
// 如果点击的标签具有active类,直接删除active类并清空内容
$(
this
).removeClass(
"active"
).empty();
}
else
if
($(
this
).hasClass(
"td_active"
)) {
$(
this
).removeClass(
"td_active"
);
}
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, ]
}
}
})
}
|
点击两个按钮后,在页面控制台打印POST_DATA显示如下:
(3)临时预定取消数据处理
1
2
3
4
5
6
7
|
// 取消临时预定
else
if
($(
this
).hasClass(
"td_active"
)) {
$(
this
).removeClass(
"td_active"
);
// 点击删除临时预定时添加的数据
// POST_DATA.ADD[room_id].pop(); // 这个是删除最后一个元素不对
POST_DATA.ADD[room_id].splice(POST_DATA.ADD[room_id].indexOf(time_id),1)
}
|
利用splice方法在数组中从指定位置开始删除,且指定仅删除一项。
(4)js数组操作常用方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
// shift:删除原数组第一项,并返回删除元素的值;如果数组为空则返回undefined
var
a = [1,2,3,4,5];
var
b = a.shift();
//a:[2,3,4,5] b:1
// pop:删除原数组最后一项,并返回删除元素的值;如果数组为空则返回undefined
var
a = [1,2,3,4,5];
var
b = a.pop();
//a:[1,2,3,4] b:5
// push:将参数添加到原数组末尾,并返回数组的长度
var
a = [1,2,3,4,5];
var
b = a.push(6,7);
//a:[1,2,3,4,5,6,7] b:7
// concat:返回一个新数组,是将参数添加到原数组中构成的
var
a = [1,2,3,4,5];
var
b = a.concat(6,7);
//a:[1,2,3,4,5] b:[1,2,3,4,5,6,7]
// splice(start,deleteCount,val1,val2,...):从start位置开始删除deleteCount项,并从该位置起插入val1,val2,...
var
a = [1,2,3,4,5];
var
b = a.splice(2,2,7,8,9);
//a:[1,2,7,8,9,5] b:[3,4]
var
b = a.splice(0,1);
//同shift
a.splice(0,0,-2,-1);
var
b = a.length;
//同unshift
var
b = a.splice(a.length-1,1);
//同pop
a.splice(a.length,0,6,7);
var
b = a.length;
//同push
// reverse:将数组反序
// sort(orderfunction):按指定的参数对数组进行排序
// slice(start,end):返回从原数组中指定开始下标到结束下标之间的项组成的新数组
var
a = [1,2,3,4,5];
var
b = a.slice(2,5);
//a:[1,2,3,4,5] b:[3,4,5]
// join(separator):将数组的元素组起一个字符串,以separator为分隔符,省略的话则用默认用逗号为分隔符
var
a = [1,2,3,4,5];
var
b = a.join(
"|"
);
//a:[1,2,3,4,5] b:"1|2|3|4|5"
|
2、发送AJAX
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
// 发送ajax
$(
".keep"
).click(
function
() {
$.ajax({
url:
"/book/"
,
type:
"POST"
,
data:{
csrfmiddlewaretoken:
'{{ csrf_token }}'
,
choose_date:CHOOSE_DATE,
post_data:JSON.stringify(POST_DATA)
},
dataType:
"json"
,
success:
function
(data) {
console.log(data);
if
(data.state){
// 预定成功
location.href=
""
}
else
{
alert(
"预定的房间已经被预定"
);
location.href=
""
}
}
})
});
|
网络编程本质是浏览器和服务器之间发送字符串。
1
2
3
4
5
6
7
8
9
10
|
POST请求
浏览器——————》server
"请求首行\r\nContent-Type:url_encode\r\n\r\na=1&b=2"
"请求首行\r\nContent-Typr:application/json\r\n\r\n("
a
":1, "
b
":2)"
在django的wsgi的request中:
request.body:元数据
'{"a":1, "b":2}'
if
请求头中的Content
-
Type
=
=
url_encode:
request.POST
=
解码a
=
1
&b
=
2
|
注意这里是选择在data中添加csrfmiddlewaretoken: '{{ csrf_token }}',来解决forbiden报错。
3、使用日历插件
// 日历插件 $('#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; }
六、视图处理图书预定和取消
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
import
datetime
import
json
def
book(request):
print
(request.POST)
post_data
=
json.loads(request.POST.get(
"post_data"
))
# {"ADD":{"1":["5"],"2":["5","6"]},"DEL":{"3":["9","10"]}}
choose_date
=
request.POST.get(
"choose_date"
)
res
=
{
"state"
:
True
,
"msg"
:
None
}
try
:
# 添加预定
# post_data["ADD"] : {"1":["5"],"2":["5","6"]}
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":["2","3"]}
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))
|
1、json.loads()
2、批量插入预订数据、删除预订数据
import json
def book(request): # print(request.POST) post_data=json.loads(request.POST.get("post_data")) # {"ADD":{"1":["5"],"2":["5","6"]},"DEL":{"3":["9","10"]}} choose_date=request.POST.get("choose_date") res={"state":True,"msg":None} try: # 添加预定 #post_data["ADD"] : {"1":["5"],"2":["5","6"]} 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":["2","3"]} 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))