使用Django搭建项目室预定功能

达成效果

每到大促的时候,集团都会特批一个员工会议室做项目室,主要用于开会讨论和复盘使用。但是这个项目室一直没有一个系统来展示预定的细节,所以很多时候都是“谁抢上谁就用”,比较不方便。于是我使用Django2.1写了一个项目室预定的功能。

整个系统的前台效果如下:

整个页面就是一个table表格,然后横坐标是时间段,纵坐标是未来七天的日期,每个单元格里是一个modal模态框,“可预订”的模态框点击后的效果如下:

如果预定的人是当前登录人,那么就是一个黄色的模态框,点击之后效果如下:

代码部分

整个过程最难的地方其实是前端JavaScript,主要就是获取未来七天的日期和星期,这部分内容可以看 https://brucewayne2099.github.io/2021/07/11/点击表格里的单元格获取对应行首列首的信息 这一篇,这样获取到了前端的纵坐标。其他部分比如模态框传入值到views.py这部分就用ajax就好了。

先看models.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#  项目室预定记录表
class Booking(models.Model):
booking_date = models.DateField()
user = models.CharField(max_length=50, null=True, blank=True) # 会议室占用者
theme = models.CharField(max_length=50, null=True, blank=True) # 会议主题
is_deleted = models.CharField(max_length=50, null=True, blank=True) # 逻辑删除,0是删除,1是未删除
time_id = models.CharField(max_length=50, null=True, blank=True) # 会议时间

def __str__(self):
return str(self.user) + "预定了,主题是" + str(self.theme)

class Meta:
verbose_name = "预定信息"
verbose_name_plural = verbose_name
unique_together = (
("user", "booking_date", "time_id", "is_deleted"), # 这4个字段联合唯一,防止重复预订
)

创建对应的表,然后就是views.py如下:

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
# 大促项目室预定页面
def meeting(request):
today = datetime.now().date()
today2 = today + timedelta(days=1)
today3 = today + timedelta(days=2)
today4 = today + timedelta(days=3)
today5 = today + timedelta(days=4)
today6 = today + timedelta(days=5)
today7 = today + timedelta(days=6) # 获取未来7天的日期

date_dict = {str(today): "today", str(today2): "today2", str(today3): "today3", str(today4): "today4", str(today5): "today5", str(today6): "today6", str(today7): "today7"}
book_list = Booking.objects.filter(booking_date__gte=today).filter(is_deleted='0') # 展示所有未删除,而且预定时间大于今天的case
htmls = ""

for day in date_dict:
htmls += '<tr><td><a id="{}"></a></td>'.format(date_dict[day]) # 这里字典对应的values主要是给前端输入id用的,将字典的value传入进去
for time_choice in ["10:00~12:00", "12:00~14:00", "14:00~16:00", "16:00~18:00", "18:00~20:00", "20:00~22:00"]:
book = None
flag = False
for i,book in enumerate(book_list): # 这里的i就是时间轴列表的索引下标,后面是事件
if book.time_id == time_choice and book.booking_date == day: # 意味这个单元格已被预定
flag = True
break
if flag:
if book.user == request.user.username: # 如果登录人就是预约人
htmls += '<td><button type="button" class="btn btn-warning" data-toggle="modal" data-target="#modal-warning{}" onclick="RunAlg(this)">你已预订</button></td><div class="modal fade" id="modal-warning{}"><div class="modal-dialog"><div class="modal-content bg-warning"><div class="modal-header"><h4 class="modal-title">会议室已经被你占用</h4><button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button></div><div class="modal-body"><p>占用人:{}</p><p>会议主题:{}</p></div><div class="modal-footer justify-content-between"><button type="button" class="btn btn-outline-dark" data-dismiss="modal">Close</button><input type="button" class="btn btn-default" id="cancel" value="释放?"></div></div></div></div>'.format(i, i, book.user, book.theme) # 如果这里不加上i,那么所有的button target都是触发同一个模态框
else:
htmls += "<td bgcolor='#00bbff' class='info item'>预订人:{}<br>会议主题:{}</td>".format(book.user, book.theme)
else:
htmls += '<td><button type="button" class="btn btn-success" data-toggle="modal" data-target="#booking-success" onclick="RunAlg(this)">可预订</button></td>'
htmls += "</tr>"

return render_to_response('meeting.html', {'htmls': htmls})

# 增加预定项目室事件
def booking(request):
today = str(datetime.now().date())
if request.method == "POST":
book_user = request.POST.get("booking_user", None) # 获取订阅者名称
book_theme = request.POST.get("theme", None) # 获取会议主题
book_date = request.POST.get("date", None) # 获取会议日期
book_time = request.POST.get("time", None) # 获取会议时间段
now_time = time.strftime("%H:%M", time.localtime()) # 获取当前的时间,如果预定时间早于当前时间,那么无法预定!
if book_date[0:10] + " " + book_time[6:] > today + " " + now_time: # 预定期望时间晚于当前时间才能预定成功,这里的比较不用加上秒
book_data = Booking.objects.create( # 这里加上年份,区别不同年份
booking_date=book_date[0:10], # 这里book_date去掉后面的星期
user=book_user,
theme=book_theme,
time_id=book_time,
is_deleted=0 # 新生成的会议都是0,被释放后就可以变成1,这里是逻辑删除
)
book_data.save() # 保存到数据库里
return JsonResponse({'status': 'success'}) # 这个信息返回给AJAX
else:
return JsonResponse({'status': 'failed'}) # 这个信息返回给AJAX


# 取消预订项目室时间
def cancel_booking(request):
if request.method == "POST": # 这里是修改添加操作,所以要有CSRFtoken的控制
book_date = request.POST.get("date", None)[0:10] # 获取会议日期
book_time = request.POST.get("time", None) # 获取会议时间段
Booking.objects.filter(booking_date=book_date).filter(time_id=book_time).update(is_deleted=1) # 逻辑删除改成已删除就不透出了

return JsonResponse({'status': 'success'}) # 这个信息返回给AJAX

路由相关的urls.py部分如下:

1
2
3
4
5
6
urlpatterns = [
......
path('meeting', views.meeting, name='meeting'), #大促项目室预订页面
path('booking', views.booking, name='booking'), # ajax添加大促项目室预定数据
path('cancel_booking', views.cancel_booking, name='cancel_booking'), # ajax取消大促项目室预定数据
]

对应的meeting.html内容比较长,我就写表格和JavaScript部分:

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
<!-- Main content -->
<section class="content">
<div class="container-fluid">
<div class="row">
<table class="table table-bordered">
<thead>
<tr>
<th>日期/时间段</th>
<th>10:00~12:00</th>
<th>12:00~14:00</th>
<th>14:00~16:00</th>
<th>16:00~18:00</th>
<th>18:00~20:00</th>
<th>20:00~22:00</th>
</tr>
</thead>
<tbody>
{{ htmls|safe }}
</tbody>
</table>
<form class="modal fade" id="booking-success" action="#" method="post" role="form">
<div class="modal-dialog">
<div class="modal-content bg-success">
<div class="modal-header">
<h4 class="modal-title">该时段可以占用项目室</h4>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span></button>
</div>
<div class="modal-body">
<p>预定人:<input type="text" name="booking_user" value="预订人花名" id="booking_user" required="required"></p>
<p>会议主题:<input type="text" name="theme" id="theme" required="required"></p>
</div>
<div class="modal-footer justify-content-between">
<button type="button" class="btn btn-outline-light" data-dismiss="modal">Close</button>
<input type="button" class="btn btn-default" id="booking" value="提交">
</div>
</div>
</div>
</form>
</div>
</div>
</section>
<!-- 用来获取点击单元格后,对应的行首和列首的内容 -->
<script type="text/javascript">
function RunAlg(ths) {
  $("#myModal").modal('show');
var $td = $(ths).parent().parent().children();
date = $td.eq(0).text(); // 去掉var那么就是全局变量了,在下面的click里也能获取到
var $th = $(ths).parent().parent().parent().parent().children().children().find("th");
var lie = $(ths.parentNode).prevAll().length;
time = $th.eq(lie).text();
}

function padStartConvert(n){
n = n.toString()
return n[1] ? n : '0' + n
}
function formatDate(num){
var now = new Date();
var nowTime = now.getTime();
var oneDayTime = 24 * 60 * 60 * 1000;
var ShowTime = nowTime+num*oneDayTime;
var myDate = new Date(ShowTime);
var y = myDate.getFullYear();//年
var m = myDate.getMonth() + 1;//月
var d = myDate.getDate();//日
var weekday = myDate.getDay();
return [y, m, d].map(padStartConvert).join('-') + " 星期"+weekday
}

document.getElementById("today").innerHTML = formatDate(0);
document.getElementById("today2").innerHTML = formatDate(1);
document.getElementById("today3").innerHTML = formatDate(2);
document.getElementById("today4").innerHTML = formatDate(3);
document.getElementById("today5").innerHTML = formatDate(4);
document.getElementById("today6").innerHTML = formatDate(5);
document.getElementById("today7").innerHTML = formatDate(6);

$(document).ready(function(){
$('#booking').click(function(){
var booking_user = $("#booking_user").val();
var theme = $("#theme").val();

$.ajax({
cache: false,
type: "POST",//方法类型
dataType: "json",//预期服务器返回的数据类型
url: '{% url "booking" %}' ,//url
data: {
booking_user: booking_user,
theme: theme,
date: date,
time: time //这里就是传给后端views.py的值
},
async: false, //必须要为false,必须必须

success: function (data) {
if(data.status == "success"){
// 关闭模态框并清除框内数据,否则下次打开还是上次的数据
document.getElementById("booking-success").value = "";
// 判断确实正确入库之后提示
alert("数据库存储成功!")
location.reload(); // 直接刷新当前页面
}
else{
alert("预定失败,请先检查预定时间段是否早于当前时间?")
}
},
error : function() {
alert("预定失败!")
}
});
});
})
$(document).ready(function(){
$('#cancel').click(function(){ // 取消会议室

$.ajax({
cache: false,
type: "POST", // 方法类型
dataType: "json", // 预期服务器返回的数据类型
url: '{% url "cancel_booking" %}' ,//url
data: {
date: date,
time: time // 这里就是传给后端views.py的值
},
async: false, // 必须要为false,必须必须

success: function (data) {
if(data.status == "success"){
// 判断确实正确入库之后提示
alert("项目室取消成功!")
location.reload(); // 直接刷新当前页面
}
},
error : function() {
alert("项目室取消失败!")
}
});
});
})
</script>

这里有一个地方比较蛋疼,就是在views.py里的时候使用了for循环搭配了html +=。Django在views.py里把数据加工好,然后通过render_to_response是把整个内容传进去。这样是把整个内容传入到template里,因为前端加工数据能力有限,所以把内容再按条件细分就很痛苦。后来想过在for循环的时候给对应的button标签添加属性,但是JavaScript获取不同的属性又很麻烦,而且拿到属性之后还要展示到对应的前端,逻辑很费劲。所以就干脆在for循环里多加了好几个模态框,这样前端HTML代码就会比较臃肿,但是想了半天也不知道怎么办,想来想去只能这样了,如果看客有更好的方案请留言告诉我哈。

参考资料

https://www.cnblogs.com/yunwangjun-python-520/p/11137781.html#_label0_3
https://blog.csdn.net/Strive_0902/article/details/99705332

感谢您请我喝咖啡~O(∩_∩)O,如果要联系请直接发我邮箱chenx1242@163.com,我会回复你的
-------------本文结束感谢您的阅读-------------