指望效果:javascript
業務流程:css
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)
注意:html
from django.contrib.auth.models import AbstractUser class UserInfo(AbstractUser): """用戶信息表""" tel = models.CharField(max_length=32)
如上所示,便可在Django的基礎上添加咱們所須要的信息。前端
設置model的時候,設置三個字段聯合惟一
class Book(models.Model): """會議室預訂信息""" .... class Meta: unique_together = ( # 三個聯合惟一,防止有人重複預約 ('room', 'date', 'time_id'), )
存的是key 顯示的是value,且只能存key。java
Django容許你經過修改setting.py文件中的 AUTH_USER_MODEL 設置覆蓋默認的User模型,其值引用一個自定義的模型。python
AUTH_USER_MODEL = "app01.UserInfo"
上面的值表示Django應用的名稱(必須位於INSTALLLED_APPS中)和你想使用的User模型的名稱。jquery
注意:在建立任何遷移或者第一次運行 manager.py migrate 前設置 AUTH_USER_MODEL。ajax
設置AUTH_USER_MODEL數據庫結構有很大的影響。改變了一些會使用到的表格,而且會影響到一些外鍵和多對多關係的構造。在你有表格被建立後更改此設置是不被 makemigrations 支持的,而且會致使你須要手動修改數據庫結構,從舊用戶表中導出數據,可能從新應用一些遷移。數據庫
$ python3 manage.py makemigrations $ python3 manage.py migrate
這裏遇到了一個問題:建立項目時沒有建立應用,手動經過manage.py startapp user建立子項目,修改AUTH_USER_MODEL後執行makemigrations一直報錯,找不到app01。django
建立兩個超級用戶:
MacBook-Pro:MRBS hqs$ python3 manage.py createsuperuser Username: yuan Password:yuan1234 MacBook-Pro:MRBS hqs$ python3 manage.py createsuperuser Username: alex Password:alex1234
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:
<!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視圖函數:
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。
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)
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>
注意:
<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>
因爲模板語法功能不夠強大,所以數據處理仍是放在後臺,在這裏渲染後臺傳遞來的標籤字符串。
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 函數能夠接受不限個參數,位置能夠不按順序。
>>>"{} {}".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" 是必須的
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中,顯示效果以下:
for time_choice in time_choices: # 有多少時段就生成多少列 # 一次循環就是一個td標籤 htmls += "<td room_id={} time_id={}></td>".format(room.pk, time_choice[0])
這樣點擊單元格可肯定點擊的是哪一個會議室哪個時段的單元格,效果以下所示:
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 += "<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>"
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中添加樣式:
<style> .active { background-color: green!important; color: white; } .another_active { background-color: #0f5687; color: white; } </style>
顯示效果以下:
<!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>
<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>
注意:
<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發送給服務器,若是不作聯合惟一,徹底交給前端會形成很嚴重的安全問題。
建立以下所示用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中,以完成預約工做:
// 爲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顯示以下:
// 取消臨時預約 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方法在數組中從指定位置開始刪除,且指定僅刪除一項。
// 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"
// 發送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報錯。
這一塊沒有視頻須要研究一下。
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))