一、目标及业务流程
期望效果:
业务流程:
- 用户登录
- 预定会议室
- 退订会议室
- 选择日期;今日以及以后日期
二、表结构设计和生成
1、models.py(用户继承AbstractUser)
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
from django.contrib.auth.models import AbstractUser
class UserInfo(AbstractUser):
"""用户信息表"""
tel = models.CharField(max_length=32)
如上所示,即可在Django的基础上添加我们所需要的信息。
(2)设置model的时候,设置三个字段联合唯一
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模型,其值引用一个自定义的模型。
AUTH_USER_MODEL = "app01.UserInfo"
上面的值表示Django应用的名称(必须位于INSTALLLED_APPS中)和你想使用的User模型的名称。
注意:在创建任何迁移或者第一次运行 manager.py migrate 前设置 AUTH_USER_MODEL。
设置AUTH_USER_MODEL数据库结构有很大的影响。改变了一些会使用到的表格,并且会影响到一些外键和多对多关系的构造。在你有表格被创建后更改此设置是不被 makemigrations 支持的,并且会导致你需要手动修改数据库结构,从旧用户表中导出数据,可能重新应用一些迁移。
3、数据迁移及创建超级用户
$ python3 manage.py makemigrations
$ python3 manage.py migrate
这里遇到了一个问题:创建项目时没有创建应用,手动通过manage.py startapp user创建子项目,修改AUTH_USER_MODEL后执行makemigrations一直报错,找不到app01。
创建两个超级用户:
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:
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:
Title
login视图函数:
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:
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 += "" return render(request, "index.html", locals()) {}({}) ".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 += "{} ".format(room.pk, time_choice[0], book.user.username) else: # 非当前登录人自己的预约信息 htmls += "{} ".format(room.pk, time_choice[0], book.user.username) else: htmls += "".format(room.pk, time_choice[0]) # 循环完成后闭合tr标签 htmls += "
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
<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)数据处理还是在后台更加方便,前台渲染后台传递来的标签字符串
会议室时间
{% for time_choice in time_choices %}
{# 在元组中取第二个值 #}
{{ time_choice.1 }}
{% endfor %}
{# 由于模板语法功能不够强大,因此数据处理还是放在后台,在这里渲染后台传递来的标签字符串 #}
{{ htmls|safe }}
由于模板语法功能不够强大,因此数据处理还是放在后台,在这里渲染后台传递来的标签字符串。
(2)视图函数字符串处理,运用format格式化函数
def index(request):
# 拿到预定表中的时间段
time_choices = Book.time_choices
# 拿到所有的会议室
room_list = Room.objects.all()
# 构建标签
htmls = ""
for room in room_list:
# 第一列td完成后,还有其他td标签需要添加,因此此处没有闭合tr
htmls += "{}({}) ".format(room.caption, room.num)
return render(request, "index.html", locals())
显示效果:
这是Python2.6后新增了一种格式化字符串的函数 str.format(),它增强了字符串格式化的功能。基本语法是通过 {} 和 : 来代替以前的 % 。format 函数可以接受不限个参数,位置可以不按顺序。
>>>"{} {}".format("hello", "world") # 不设置指定位置,按默认顺序
'hello world'
>>> "{0} {1}".format("hello", "world") # 设置指定位置
'hello world'
>>> "{1} {0} {1}".format("hello", "world") # 设置指定位置
'world hello world'
还可以设置参数:
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)循环会议室生成行,循环时段生成列,标签字符串拼接处理
def index(request):
# 拿到预定表中的时间段
time_choices = Book.time_choices
# 拿到所有的会议室
room_list = Room.objects.all()
# 构建标签
htmls = ""
for room in room_list: # 有多少会议室生成多少行,
# 每行仅生成了第一列。还有其他td标签需要添加,因此此处没有闭合tr
htmls += "{}({}) ".format(room.caption, room.num)
for time_choice in time_choices: # 有多少时段就生成多少列
# 一次循环就是一个td标签
htmls += " "
# 循环完成后闭合tr标签
htmls += " "
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 += " ".format(room.pk, time_choice[0])
这样点击单元格可确定点击的是哪个会议室哪一个时段的单元格,效果如下所示:
(5)获取预约日期信息
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’属性。
# 构建标签
htmls = ""
for room in room_list: # 有多少会议室生成多少行,
# 每行仅生成了第一列。还有其他td标签需要添加,因此此处没有闭合tr
htmls += "{}({}) ".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 += " ".format(room.pk, time_choice[0])
else:
htmls += " ".format(room.pk, time_choice[0])
# 循环完成后闭合tr标签
htmls += " "
(6)在预定单元格添加预定人姓名,并根据登录人判断显示单元格
if flag:
# 已经被预定,添加class='active'
if request.user.pk == book.user.pk:
# 当前登录人查看自己的预约信息
htmls += "{} ".format(room.pk, time_choice[0],
book.user.username)
else:
# 非当前登录人自己的预约信息
htmls += "{} ".format(room.pk, time_choice[0],
book.user.username)
else:
htmls += " ".format(room.pk, time_choice[0])
在index中添加样式:
显示效果如下:
五、前端部分数据处理(index.html)
Title
会议室预定
会议室时间
{% for time_choice in time_choices %}
{# 在元组中取第二个值 #}
{{ time_choice.1 }}
{% endfor %}
{# 由于模板语法功能不够强大,因此数据处理还是放在后台,在这里渲染后台传递来的标签字符串 #}
{{ htmls|safe }}
1、点击事件预定和取消——组织数据
注意:
(1)取消预定事件
在这次只处理了具有active类和td_active类的情况,但没有处理another_active类的情况,因为这种需要判断的情况,一定要交给后端,否则就是前端一套后端一套,点击保存按钮发送的js,客户可以伪装一个data发送给服务器,如果不做联合唯一,完全交给前端会造成很严重的安全问题。
(2)数据组织和添加预定
创建如下所示用js字面量方式创建对象
POST_DATA,有两个属性(对象)ADD和DEL,这两个对象的值以room_id为键,以time_id为值:
在空白单元格点击,获取添加数据到POST_DATA中,以完成预定工作:
// 为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)临时预定取消数据处理
// 取消临时预定
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数组操作常用方法
// 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
// 发送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=""
}
}
})
});
网络编程本质是浏览器和服务器之间发送字符串。
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、使用日历插件
这一块没有视频需要研究一下。
六、视图处理图书预定和取消
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、批量插入预订数据
3、Q查询
4、删除预订数据
转载于:https://www.cnblogs.com/xiugeng/p/9490830.html
你可能感兴趣的:(会议室预订系统(meeting room booking system))