会议室预订系统
一、目标及业务流程
期望效果:
业务流程:
-
用户注册
-
用户登录
-
预订会议室
-
退订会议室
-
选择日期;今日以及以后日期
二、表结构设计和生成
1、models.py(用户继承AbstractUser)
from django.db import models from django.contrib.auth.models import AbstractUser # Create your models here. class UserInfo(AbstractUser): tel = models.CharField(max_length=32,verbose_name="电话") avatar = models.FileField(upload_to="avatars/", default="avatars/timg.jpg", verbose_name="头像") class Room(models.Model): """会议室表""" caption = models.CharField(max_length=32,verbose_name="会议室名称") num = models.IntegerField(verbose_name="容纳人数") # 容纳人数 def __str__(self): return self.caption class Meta: verbose_name = "会议室信息" verbose_name_plural = verbose_name class Book(models.Model): """会议室预订""" user = models.ForeignKey(to="UserInfo",on_delete=models.CASCADE) room = models.ForeignKey(to="Room",on_delete=models.CASCADE) date = models.DateField() time_choice = ( (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"), (14, "21:00"), (15, "22:00"), (16, "23:00"), ) time_id = models.IntegerField(choices=time_choice) def __str__(self): return str(self.user)+"预定了"+str(self.room) class Meta: verbose_name = "预定信息" verbose_name_plural = verbose_name unique_together = ( ("room","date","time_id"), # 这三个字段联合唯一,防止重复预订 )
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
|
三、系统登录login
urls.py:
from django.conf.urls import url from django.contrib import admin from django.views.static import serve from django.conf import settings from app01 import views urlpatterns = [ url(r'^admin/', admin.site.urls), # 用户登录 url(r'^login/',views.acc_login), # 展示预订信息 url(r'^index/',views.index), # 极验滑动验证码 获取验证码的url url(r'^pc-geetest/register', views.get_geetest), # media相关的路由设置 url(r'^media/(?P.*)$ ', serve, {"document_root": settings.MEDIA_ROOT}), # 处理预订请求 url(r'^book/',views.book), # 首页 url(r'^home/',views.home), # 注销 url(r'^logout/',views.acc_logout), # 用户注册 url(r'^reg/',views.reg), # 临时测试 url(r'^test/',views.test), # 修改密码 url(r'^change_password/',views.change_password), ]
login.html(使用了滑动验证)
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>用户登录title>
<link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css">
<script src="http://static.geetest.com/static/tools/gt.js">script>
head>
<body>
<h3 class="text-center" style="color: orangered">欢迎登录会议室预订系统h3>
<br>
<div class="container">
<div class="row">
<form class="form-horizontal col-md-6 col-md-offset-4" autocomplete="off">
{% csrf_token %}
<div class="form-group">
<label for="username" class="col-lg-2 control-label">用户名label>
<div class="col-sm-6">
<input type="text" class="form-control" id="username" name="username" placeholder="用户名">
div>
div>
<div class="form-group">
<label for="pwd" class="col-lg-2 control-label">密码label>
<div class="col-sm-6">
<input type="password" class="form-control" id="pwd" name="pwd" placeholder="密码">
div>
div>
<div class="form-group">
<div id="popup-captcha">div>
div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="button" id="login_btn" class="btn btn-info">登录button>
<span class="login-error has-error text-danger">span>
div>
div>
form>
div>
div>
<script src="/static/js/jquery-3.3.1.min.js">script>
<script src="/static/bootstrap/js/bootstrap.min.js">script>
<script>
var handlerPopup = function (captchaObj) {
// 成功的回调
captchaObj.onSuccess(function () {
var validate = captchaObj.getValidate();
// 1. 取到用户填写的用户名和密码 -> 取input框的值
var username = $("#username").val();
var password = $("#pwd").val();
$.ajax({
url: "/login/", // 进行二次验证
type: "post",
dataType: "json",
data: {
username: username,
pwd: password,
csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val(),
geetest_challenge: validate.geetest_challenge,
geetest_validate: validate.geetest_validate,
geetest_seccode: validate.geetest_seccode
},
success: function (data) {
console.log(data);
if (data.status) {
// 有错误,在页面上提示
$(".login-error").text(data.msg);
} else {
// 登陆成功
location.href = data.msg;
}
}
});
});
$("#login_btn").click(function () {
captchaObj.show();
});
// 将验证码加到id为captcha的元素里
captchaObj.appendTo("#popup-captcha");
// 更多接口参考:http://www.geetest.com/install/sections/idx-client-sdk.html
};
//当再次点击input输入框时,错误提示要消失
$("#username,#pwd").focus(function () {
$(".login-error").text("");
})
// 验证开始需要向网站主后台获取id,challenge,success(是否启用failback)
$.ajax({
url: "/pc-geetest/register?t=" + (new Date()).getTime(), // 加随机数防止缓存
type: "get",
dataType: "json",
success: function (data) {
// 使用initGeetest接口
// 参数1:配置参数
// 参数2:回调,回调的第一个参数验证码对象,之后可以使用它做appendTo之类的事件
initGeetest({
gt: data.gt,
challenge: data.challenge,
product: "popup", // 产品形式,包括:float,embed,popup。注意只对PC版验证码有效
offline: !data.success // 表示用户后台检测极验服务器是否宕机,一般不需要关注
// 更多配置参数请参见:http://www.geetest.com/install/sections/idx-client-sdk.html#config
}, handlerPopup);
}
})
script>
body>
html>
login视图函数
from django.shortcuts import render,redirect, HttpResponse from django.contrib.auth import authenticate, login, logout from django.http import JsonResponse from geetest import GeetestLib from django.contrib.auth.decorators import login_required from app01 import models from app01 import forms import json import datetime # 登录视图 def acc_login(request): if request.method == "POST": print(request.POST) res = {"status": 0, "msg": ""} username = request.POST.get("username") password = request.POST.get("pwd") # 获取极验 滑动验证码相关的参数 gt = GeetestLib(pc_geetest_id, pc_geetest_key) challenge = request.POST.get(gt.FN_CHALLENGE, '') validate = request.POST.get(gt.FN_VALIDATE, '') seccode = request.POST.get(gt.FN_SECCODE, '') status = request.session[gt.GT_STATUS_SESSION_KEY] user_id = request.session["user_id"] if status: result = gt.success_validate(challenge, validate, seccode, user_id) else: result = gt.failback_validate(challenge, validate, seccode) print("####################", result) if result: user = authenticate(username=username, password=password) if user: login(request, user) res["msg"] = "/index/" else: res["status"] =1 res["msg"] = "认证失败,请检查用户名及密码是否正确" else: res["status"] = 1 res["msg"] = "验证码错误" print("**************", res) return JsonResponse(res) return render(request, 'login.html') # 请在官网申请ID使用,示例ID不可使用 pc_geetest_id = "b46d1900d0a894591916ea94ea91bd2c" pc_geetest_key = "36fc3fe98530eea08dfc6ce76e3d24c4" # 处理极验 获取验证码的视图 def get_geetest(request): user_id = 'test' gt = GeetestLib(pc_geetest_id, pc_geetest_key) status = gt.pre_process(user_id) request.session[gt.GT_STATUS_SESSION_KEY] = status request.session["user_id"] = user_id response_str = gt.get_response_str() return HttpResponse(response_str)
注意:auth模块的authenticate()方法,提供了用户认证,如果认证信息有效,会返回一个 User 对象;如果认证失败,则返回None。
四、index部分
1、引入admin组件(后台数据管理组件)并完成admin注册
admin.py
from django.contrib import admin from app01 import models from django.contrib.auth.admin import UserAdmin from django.utils.translation import gettext_lazy # Register your models here. # 配置会议室信息表 class RoomConfig(admin.ModelAdmin): list_display = ('caption','num') list_filter=('num',) search_fields = ('caption','num') # 配置预订信息表 class BookConfig(admin.ModelAdmin): list_display = ('user','room','date','time_id') list_filter = ('user','room','date','time_id') search_fields = ('user','room','date','time_id') # 配置用户管理表 class UserProfileAdmin(UserAdmin): list_display = ('username','last_login','is_superuser','is_staff','is_active','date_joined') list_filter = ('last_login', 'is_staff', 'date_joined', 'is_active') search_fields = ('username',) fieldsets = ( (None,{'fields':('username','password','first_name','last_name','email')}), (gettext_lazy('用户信息'),{'fields':('username','email','tel','avatar')}), (gettext_lazy('用户权限'), {'fields': ('is_superuser','is_staff','is_active', 'groups', 'user_permissions')}), (gettext_lazy('Important dates'), {'fields': ('last_login', 'date_joined')}), ) admin.site.register(models.Room,RoomConfig) admin.site.register(models.UserInfo,UserProfileAdmin) admin.site.register(models.Book,BookConfig)
注意:配置用户管理表至关重要,如果不配置,你会发现在登录admin后添加用户时密码是明文,并没有被加密。
如果你的用户扩展表没有扩展新的字段,可以直接admin.site.register(models.UserInfo,UserAdmin)。
2、登录admin添加数据
3、index视图函数数据处理和index.html模板渲染
index视图
@login_required(login_url="/login/") def index(request): date = datetime.datetime.now().date() # 如果没有指定日期,默认使用当天日期 book_date = request.GET.get("book_date",date) print('日期:', request.GET.get("book_date")) print("book_date",book_date) # 获取会议室时间段列表 time_choice = models.Book.time_choice print(time_choice) # 获取会议室列表 room_list = models.Room.objects.all() # 获取会议室预订信息 book_list = models.Book.objects.filter(date=book_date) htmls='' for room in room_list: htmls += '" return render(request,'index.html',{"time_choice":time_choice,"htmls":htmls,}) {}({}) '.format(room.caption,room.num) for time in time_choice: # 判断该单元格是否被预订 flag = False for book in book_list: if book.room.pk == room.pk and book.time_id == time[0]: # 单元格被预定 flag = True break if flag: # 判断当前登录人与预订会议室的人是否一致,一致使用info样式 if request.user.username == book.user.username: htmls += '{} '.format(room.pk, time[0],book.user.username) else: htmls += '{} '.format(room.pk, time[0], book.user.username) else: htmls += ''.format(room.pk,time[0]) htmls += "
index前端
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>indextitle>
<link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="/static/datetimepicker/bootstrap-datetimepicker.min.css">
<style>
.td_active{
background-color: purple;
}
#my_div{
top: 215px!important;
}
style>
head>
<body>
<div class="page-header">
<h1 class="text-center">欢迎来到会议室预订系统 <small class="text-info">{{ request.user.username }}small>h1>
div>
<div class="text-center">
<span>当前用户:<img src="/static/img/info.png" alt="">span>
<span>其他用户:<img src="/static/img/success.png" alt="">span>
div>
<br>
<br>
<p class="text-center">
<span><a href="/home/">返回首页a>span>
<span><a href="/logout/">注销a>span>
p>
<div class="calender pull-right">
<div class='input-group' style="width: 230px;">
<span class="text-warning">注意:当前日期高亮显示span>
<input type='text' autocomplete="off" class="form-control" id='datetimepicker11' placeholder="请选择日期"/>
<span class="input-group-addon">
<span class="glyphicon glyphicon-calendar">
span>
span>
div>
div>
<br>
<br>
<table class="table table-bordered">
<thead>
<tr>
<th>会议室/时间th>
{% for row in time_choice %}
<th>{{ row.1 }}th>
{% endfor %}
tr>
thead>
<tbody>
{{ htmls|safe }}
tbody>
table>
<div >{% csrf_token %}div>
<div class="col-lg-offset-6" >
<button class="btn btn-info book_btn">预订button>
div>
<script src="/static/js/jquery-3.3.1.min.js">script>
<script src="/static/bootstrap/js/bootstrap.min.js">script>
<script src="/static/datetimepicker/bootstrap-datetimepicker.min.js">script>
<script src="/static/datetimepicker/bootstrap-datetimepicker.zh-CN.js">script>
<script>
// 日期格式化方法
Date.prototype.yun = function (fmt) { //author:yun
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;
};
TODAY_DATE=new Date().yun("yyyy-MM-dd");//获取当前日期
var POST_DATA={
"ADD":{},
"DEL":{},
};
function TdClick() {
$(".item").click(function () {
var room_id = $(this).attr("room_id");
var time_id = $(this).attr("time_id");
//取消预订
if($(this).hasClass("info")){
$(this).removeClass("info").empty();
if (POST_DATA.DEL[room_id]){
POST_DATA.DEL[room_id].push(time_id);
}
else
{POST_DATA.DEL[room_id]=[time_id,];}
}
//取消临时预订
else if($(this).hasClass("td_active")){
$(this).removeClass("td_active");
//console.log(room_id,time_id)
var index=$.inArray(time_id,POST_DATA.ADD[room_id]);
POST_DATA.ADD[room_id].splice(index,1);
//console.log(POST_DATA.ADD[room_id]);
console.log(POST_DATA);
}
// 增加预订
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,];}
console.log(POST_DATA);
}
});
};
TdClick();
// 日期
if (location.search.slice(11)){
CHOOSE_DATE = location.search.slice(11)
}
else {
CHOOSE_DATE = new Date().yun('yyyy-MM-dd');
console.log(CHOOSE_DATE);
}
// 通过ajax发送数据到后端
$(".book_btn").click(function () {
$.ajax({
url:"/book/",
type:"post",
data:{
choose_date:CHOOSE_DATE,
csrfmiddlewaretoken:$("[name='csrfmiddlewaretoken']").val(),
post_data:JSON.stringify(POST_DATA),
},
dataType:"json",
success:function (data) {
console.log(data);
if(data.status==1){
alert("预订成功");
location.href="";
}else if (data.status==2){
alert("未修改信息");
location.href="";
}
else {
alert("已经被预定")
location.href=""
}
},
});
});
// 日历插件
function book_query(e) {
CHOOSE_DATE=e.date.yun("yyyy-MM-dd");
location.href="/index/?book_date="+CHOOSE_DATE;
};
/**
判断输入框中输入的日期格式为yyyy-mm-dd和正确的日期
*/
function isDate(data){
var filter = /((^((1[8-9]\d{2})|([2-9]\d{3}))([-\/\._])(10|12|0?[13578])([-\/\._])(3[01]|[12][0-9]|0?[1-9])$)|(^((1[8-9]\d{2})|([2-9]\d{3}))([-\/\._])(11|0?[469])([-\/\._])(30|[12][0-9]|0?[1-9])$)|(^((1[8-9]\d{2})|([2-9]\d{3}))([-\/\._])(0?2)([-\/\._])(2[0-8]|1[0-9]|0?[1-9])$)|(^([2468][048]00)([-\/\._])(0?2)([-\/\._])(29)$)|(^([3579][26]00)([-\/\._])(0?2)([-\/\._])(29)$)|(^([1][89][0][48])([-\/\._])(0?2)([-\/\._])(29)$)|(^([2-9][0-9][0][48])([-\/\._])(0?2)([-\/\._])(29)$)|(^([1][89][2468][048])([-\/\._])(0?2)([-\/\._])(29)$)|(^([2-9][0-9][2468][048])([-\/\._])(0?2)([-\/\._])(29)$)|(^([1][89][13579][26])([-\/\._])(0?2)([-\/\._])(29)$)|(^([2-9][0-9][13579][26])([-\/\._])(0?2)([-\/\._])(29)$))/;
if (filter.test(data)){
return true;
}else {
return false;
}
}
$("#datetimepicker11").change(function () {
var test = $(this).val();
if(isDate(test)){
if(test<TODAY_DATE){
alert("注意:日期不能小于当前日期!")
}
CHOOSE_DATE=test;
location.href="/index/?book_date="+CHOOSE_DATE;
}else {
alert("日期格式错误!");
location.href='';
}
});
$('#datetimepicker11').datetimepicker({
minView : 2,
startView:2,
language: "zh-CN",
sideBySide: true,
format: 'yyyy-mm-dd',
startDate: TODAY_DATE,
todayBtn:true,
todayHighlight: 1,//当天日期高亮
enterLikeTab: false,
bootcssVer:3,
autoclose:true,
}).on('changeDate',book_query).val(CHOOSE_DATE).css('font-weight','bold');
$(".datetimepicker.datetimepicker-dropdown-bottom-right.dropdown-menu").attr("id" ,"my_div");
script>
body>
html>
注意:
(1)数据处理还是在后台更加方便,前台渲染后台传递来的标签字符串
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
由于模板语法功能不够强大,因此数据处理还是放在后台,在这里渲染后台传递来的标签字符串。 (2)视图函数字符串处理,运用format格式化函数
显示效果:
(3)循环会议室生成行,循环时段生成列,标签字符串拼接处理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属性for time_choice in time_choices: # 有多少时段就生成多少列 # 一次循环就是一个td标签 htmls += "<td room_id={} time_id={}>td>".format(room.pk, time_choice[0])
这样点击单元格可确定点击的是哪个会议室哪一个时段的单元格,效果如下所示:
(5)获取预约日期信息import datetime
def index(request):
# 取当前日期
date = datetime.datetime.now().date()
print(date)
# 取预约日期,没有指定取当前日期
book_date = request.GET.get("book_date", date)
print(book_date)
index页面访问中,如果没有指定日期,默认显示的就是当前日的预定信息。 因此在循环生成表格时,可以循环确定单元格是否被预定,已经被预定的添加class=‘success’属性。 # 构建标签 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='success' htmls += "<td class='success' 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)在预定单元格添加预定人姓名,并根据登录人判断显示单元格
if flag: # 已经被预定,添加class='active' if request.user.pk == book.user.pk: # 当前登录人查看自己的预约信息 htmls += "<td class='info item' room_id={} time_id={}>{}td>".format(room.pk, time_choice[0], book.user.username) else: # 非当前登录人自己的预约信息 htmls += "<td class='success item' 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.html)index.html DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>indextitle>
<link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="/static/datetimepicker/bootstrap-datetimepicker.min.css">
<style>
.td_active{
background-color: purple;
}
#my_div{
top: 215px!important;
}
style>
head>
<body>
<div class="page-header">
<h1 class="text-center">欢迎来到会议室预订系统 <small class="text-info">{{ request.user.username }}small>h1>
div>
<div class="text-center">
<span>当前用户:<img src="/static/img/info.png" alt="">span>
<span>其他用户:<img src="/static/img/success.png" alt="">span>
div>
<br>
<br>
<p class="text-center">
<span><a href="/home/">返回首页a>span>
<span><a href="/logout/">注销a>span>
p>
<div class="calender pull-right">
<div class='input-group' style="width: 230px;">
<span class="text-warning">注意:当前日期高亮显示span>
<input type='text' autocomplete="off" class="form-control" id='datetimepicker11' placeholder="请选择日期"/>
<span class="input-group-addon">
<span class="glyphicon glyphicon-calendar">
span>
span>
div>
div>
<br>
<br>
<table class="table table-bordered">
<thead>
<tr>
<th>会议室/时间th>
{% for row in time_choice %}
<th>{{ row.1 }}th>
{% endfor %}
tr>
thead>
<tbody>
{{ htmls|safe }}
tbody>
table>
<div >{% csrf_token %}div>
<div class="col-lg-offset-6" >
<button class="btn btn-info book_btn">预订button>
div>
<script src="/static/js/jquery-3.3.1.min.js">script>
<script src="/static/bootstrap/js/bootstrap.min.js">script>
<script src="/static/datetimepicker/bootstrap-datetimepicker.min.js">script>
<script src="/static/datetimepicker/bootstrap-datetimepicker.zh-CN.js">script>
<script>
// 日期格式化方法
Date.prototype.yun = function (fmt) { //author:yun
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;
};
TODAY_DATE=new Date().yun("yyyy-MM-dd");//获取当前日期
var POST_DATA={
"ADD":{},
"DEL":{},
};
function TdClick() {
$(".item").click(function () {
var room_id = $(this).attr("room_id");
var time_id = $(this).attr("time_id");
//取消预订
if($(this).hasClass("info")){
// 如果点击的标签具有info类,直接删除info类并清空内容
$(this).removeClass("info").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");
//点击删除临时预订的数据
var index=$.inArray(time_id,POST_DATA.ADD[room_id]);
POST_DATA.ADD[room_id].splice(index,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,];}
}
});
};
TdClick();
// 日期
if (location.search.slice(11)){
CHOOSE_DATE = location.search.slice(11)
}
else {
CHOOSE_DATE = new Date().yun('yyyy-MM-dd');
}
// 通过ajax发送数据到后端
$(".book_btn").click(function () {
$.ajax({
url:"/book/",
type:"post",
data:{
choose_date:CHOOSE_DATE,
csrfmiddlewaretoken:$("[name='csrfmiddlewaretoken']").val(),
post_data:JSON.stringify(POST_DATA),
},
dataType:"json",
success:function (data) {
console.log(data);
if(data.status==1){
alert("预订成功");
location.href="";
}else if (data.status==2){
alert("未修改信息");
location.href="";
}
else {
alert("已经被预定")
location.href=""
}
},
});
});
// 日历插件
function book_query(e) {
CHOOSE_DATE=e.date.yun("yyyy-MM-dd");
location.href="/index/?book_date="+CHOOSE_DATE;
};
/**
判断输入框中输入的日期格式为yyyy-mm-dd和正确的日期
*/
function isDate(data){
var filter = /((^((1[8-9]\d{2})|([2-9]\d{3}))([-\/\._])(10|12|0?[13578])([-\/\._])(3[01]|[12][0-9]|0?[1-9])$)|(^((1[8-9]\d{2})|([2-9]\d{3}))([-\/\._])(11|0?[469])([-\/\._])(30|[12][0-9]|0?[1-9])$)|(^((1[8-9]\d{2})|([2-9]\d{3}))([-\/\._])(0?2)([-\/\._])(2[0-8]|1[0-9]|0?[1-9])$)|(^([2468][048]00)([-\/\._])(0?2)([-\/\._])(29)$)|(^([3579][26]00)([-\/\._])(0?2)([-\/\._])(29)$)|(^([1][89][0][48])([-\/\._])(0?2)([-\/\._])(29)$)|(^([2-9][0-9][0][48])([-\/\._])(0?2)([-\/\._])(29)$)|(^([1][89][2468][048])([-\/\._])(0?2)([-\/\._])(29)$)|(^([2-9][0-9][2468][048])([-\/\._])(0?2)([-\/\._])(29)$)|(^([1][89][13579][26])([-\/\._])(0?2)([-\/\._])(29)$)|(^([2-9][0-9][13579][26])([-\/\._])(0?2)([-\/\._])(29)$))/;
if (filter.test(data)){
return true;
}else {
return false;
}
}
$("#datetimepicker11").change(function () {
var test = $(this).val();
if(isDate(test)){
if(test<TODAY_DATE){
alert("注意:日期不能小于当前日期!")
}
CHOOSE_DATE=test;
location.href="/index/?book_date="+CHOOSE_DATE;
}else {
alert("日期格式错误!");
location.href='';
}
});
$('#datetimepicker11').datetimepicker({
minView : 2,
startView:2,
language: "zh-CN",
sideBySide: true,
format: 'yyyy-mm-dd',
startDate: TODAY_DATE,
todayBtn:true,
todayHighlight: 1,//当天日期高亮
enterLikeTab: false,
bootcssVer:3,
autoclose:true,
}).on('changeDate',book_query).val(CHOOSE_DATE).css('font-weight','bold');
$(".datetimepicker.datetimepicker-dropdown-bottom-right.dropdown-menu").attr("id" ,"my_div");
script>
body>
html>
1、点击事件预定和取消——组织数据var POST_DATA={
"ADD":{},
"DEL":{},
};
function TdClick() {
$(".item").click(function () {
var room_id = $(this).attr("room_id");
var time_id = $(this).attr("time_id");
//取消预订
if($(this).hasClass("info")){
// 如果点击的标签具有info类,直接删除info类并清空内容
$(this).removeClass("info").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");
//点击删除临时预订的数据
var index=$.inArray(time_id,POST_DATA.ADD[room_id]);
POST_DATA.ADD[room_id].splice(index,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,];}
}
});
};
TdClick();
注意: (1)取消预定事件<script> // 为td绑定单击事件 function BindTd() { $('.item').click(function () { // alert($(this).attr("room_id")); // 点击显示会议室id // 取消预定 if ($(this).hasClass("info")){ // 如果点击的标签具有active类,直接删除active类并清空内容 $(this).removeClass("info").empty(); } else if ($(this).hasClass("td_active")) { $(this).removeClass("td_active"); } else { // 空白局域点击 $(this).addClass("td_active"); } }) } BindTd(); script>
在这次只处理了具有info类和td_active类的情况,但没有处理success类的情况,因为这种需要判断的情况,一定要交给后端,否则就是前端一套后端一套,点击保存按钮发送的js,客户可以伪装一个data发送给服务器,如果不做联合唯一,完全交给前端会造成很严重的安全问题。
(2)数据组织和添加预定 创建如下所示用js <script> // room_id 为键,time_id 为值 {1:[4,5],2:[4,] } {3:[9,10]} var POST_DATA = { "ADD":{}, "DEL":{} }; script> 在空白单元格点击,获取添加数据到POST_DATA中,以完成预定工作: function TdClick() {
$(".item").click(function () {
var room_id = $(this).attr("room_id");
var time_id = $(this).attr("time_id");
//取消预订
if($(this).hasClass("info")){
// 如果点击的标签具有info类,直接删除info类并清空内容
$(this).removeClass("info").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");
//点击删除临时预订的数据
var index=$.inArray(time_id,POST_DATA.ADD[room_id]);
POST_DATA.ADD[room_id].splice(index,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,];}
console.log(POST_DATA.ADD);
}
});
};
TdClick();
(3)临时预定取消数据处理//取消临时预订
else if($(this).hasClass("td_active")){
$(this).removeClass("td_active");
//点击删除临时预订的数据
var index=$.inArray(time_id,POST_DATA.ADD[room_id]);
POST_DATA.ADD[room_id].splice(index,1);
}
(4)js数组操作常用方法 1 // shift:删除原数组第一项,并返回删除元素的值;如果数组为空则返回undefined 2 var a = [1,2,3,4,5]; 3 var b = a.shift(); //a:[2,3,4,5] b:1 4 5 // pop:删除原数组最后一项,并返回删除元素的值;如果数组为空则返回undefined 6 var a = [1,2,3,4,5]; 7 var b = a.pop(); //a:[1,2,3,4] b:5 8 9 // push:将参数添加到原数组末尾,并返回数组的长度 10 var a = [1,2,3,4,5]; 11 var b = a.push(6,7); //a:[1,2,3,4,5,6,7] b:7 12 13 // concat:返回一个新数组,是将参数添加到原数组中构成的 14 var a = [1,2,3,4,5]; 15 var b = a.concat(6,7); //a:[1,2,3,4,5] b:[1,2,3,4,5,6,7] 16 17 // splice(start,deleteCount,val1,val2,...):从start位置开始删除deleteCount项,并从该位置起插入val1,val2,... 18 var a = [1,2,3,4,5]; 19 var b = a.splice(2,2,7,8,9); //a:[1,2,7,8,9,5] b:[3,4] 20 var b = a.splice(0,1); //同shift 21 a.splice(0,0,-2,-1); var b = a.length; //同unshift 22 var b = a.splice(a.length-1,1); //同pop 23 a.splice(a.length,0,6,7); var b = a.length; //同push 24 25 // reverse:将数组反序 26 // sort(orderfunction):按指定的参数对数组进行排序 27 28 // slice(start,end):返回从原数组中指定开始下标到结束下标之间的项组成的新数组 29 var a = [1,2,3,4,5]; 30 var b = a.slice(2,5); //a:[1,2,3,4,5] b:[3,4,5] 31 32 // join(separator):将数组的元素组起一个字符串,以separator为分隔符,省略的话则用默认用逗号为分隔符 33 var a = [1,2,3,4,5]; 34 var b = a.join("|"); //a:[1,2,3,4,5] b:"1|2|3|4|5"
网络编程本质是浏览器和服务器之间发送字符串,以POST请求为例 POST: 浏览器-------------------->server "请求首行\r\nContent-Type:url_encode\r\n\r\na=1&b=2" "请求首行\r\nContent-Type: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
2、日历插件(datetimepicker)官方文档:http://eonasdan.github.io/bootstrap-datetimepicker/日历html <div class="calender pull-right"> <div class='input-group' style="width: 230px;"> <span class="text-warning">注意:当前日期高亮显示span> <input type='text' autocomplete="off" class="form-control" id='datetimepicker11' placeholder="请选择日期"/> <span class="input-group-addon"> <span class="glyphicon glyphicon-calendar"> span> span> div> div>
日历js代码// 日期格式化方法 Date.prototype.yun = function (fmt) { //author:yun 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; }; TODAY_DATE=new Date().yun("yyyy-MM-dd");//获取当前日期 // 日期 if (location.search.slice(11)){ CHOOSE_DATE = location.search.slice(11) } else { CHOOSE_DATE = new Date().yun('yyyy-MM-dd'); } // 日历插件 function book_query(e) { CHOOSE_DATE=e.date.yun("yyyy-MM-dd"); location.href="/index/?book_date="+CHOOSE_DATE; }; /** 判断输入框中输入的日期格式为yyyy-mm-dd和正确的日期 */ function isDate(data){ var filter = /((^((1[8-9]\d{2})|([2-9]\d{3}))([-\/\._])(10|12|0?[13578])([-\/\._])(3[01]|[12][0-9]|0?[1-9])$)|(^((1[8-9]\d{2})|([2-9]\d{3}))([-\/\._])(11|0?[469])([-\/\._])(30|[12][0-9]|0?[1-9])$)|(^((1[8-9]\d{2})|([2-9]\d{3}))([-\/\._])(0?2)([-\/\._])(2[0-8]|1[0-9]|0?[1-9])$)|(^([2468][048]00)([-\/\._])(0?2)([-\/\._])(29)$)|(^([3579][26]00)([-\/\._])(0?2)([-\/\._])(29)$)|(^([1][89][0][48])([-\/\._])(0?2)([-\/\._])(29)$)|(^([2-9][0-9][0][48])([-\/\._])(0?2)([-\/\._])(29)$)|(^([1][89][2468][048])([-\/\._])(0?2)([-\/\._])(29)$)|(^([2-9][0-9][2468][048])([-\/\._])(0?2)([-\/\._])(29)$)|(^([1][89][13579][26])([-\/\._])(0?2)([-\/\._])(29)$)|(^([2-9][0-9][13579][26])([-\/\._])(0?2)([-\/\._])(29)$))/; if (filter.test(data)){ return true; }else { return false; } } $("#datetimepicker11").change(function () { var test = $(this).val(); if(isDate(test)){ if(test<TODAY_DATE){ alert("注意:日期不能小于当前日期!") } CHOOSE_DATE=test; location.href="/index/?book_date="+CHOOSE_DATE; }else { alert("日期格式错误!"); location.href=''; } }); //初始化日历 $('#datetimepicker11').datetimepicker({ minView : 2, startView:2, language: "zh-CN", sideBySide: true, format: 'yyyy-mm-dd', startDate: TODAY_DATE, todayBtn:true, todayHighlight: 1,//当天日期高亮 enterLikeTab: false, bootcssVer:3, autoclose:true, }).on('changeDate',book_query).val(CHOOSE_DATE).css('font-weight','bold'); $(".datetimepicker.datetimepicker-dropdown-bottom-right.dropdown-menu").attr("id" ,"my_div");
3、发送AJAX// 通过ajax发送数据到后端
$(".book_btn").click(function () {
$.ajax({
url:"/book/",
type:"post",
data:{
choose_date:CHOOSE_DATE,
csrfmiddlewaretoken:$("[name='csrfmiddlewaretoken']").val(),
post_data:JSON.stringify(POST_DATA),
},
dataType:"json",
success:function (data) {
console.log(data);
if(data.status==1){
alert("预订成功");
location.href="";
}else if (data.status==2){
alert("未修改信息");
location.href="";
}
else {
alert("已经被预定")
location.href=""
}
},
});
});
六、视图处理图书预定和取消def book(request):
if request.method == "POST":
choose_date = request.POST.get("choose_date")
print("choose_date:", choose_date)
# 获取会议室时间段列表
time_choice = models.Book.time_choice
try:
# 向数据库修改会议室预订记录
post_data = json.loads(request.POST.get("post_data"))
if not post_data["ADD"] and not post_data["DEL"]:
res = {"status":2, "msg":""}
return HttpResponse(json.dumps(res))
user = request.user
print(type(post_data), post_data)
# 添加新的预订信息
book_list = []
for room_id, time_id_list in post_data["ADD"].items():
for time_id in time_id_list:
book_obj = models.Book(user=user, room_id=room_id, time_id=time_id, date=choose_date)
book_list.append(book_obj)
models.Book.objects.bulk_create(book_list)
# 删除旧的预订信息
from django.db.models import Q
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:
models.Book.objects.filter(remove_book).delete()
for time in post_data["DEL"][room_id]:
models.Book.objects.filter(user=user, room_id=room_id, time_id=time, date=choose_date).delete()
res = {"status": 1, "msg": ''}
except Exception as e:
res = {"status": 0, "msg": str(e)}
return HttpResponse(json.dumps(res))
七、用户注册注册前端页面
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>欢迎注册title>
<link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css">
<style>
html, body {
width: 100%;
height: 100%;
}
#avatar-img {
width: 80px;
height: 80px;
}
.mui-content {
background: url("/static/img/reg_bak.jpg") bottom center no-repeat #efeff4;
background-size: 100% 100%;
width: 100%;
height: 100%;
}
style>
head>
<body>
<div class="container mui-content">
<h3 class="text-center" style="color: orangered">欢迎注册会议室预订系统h3>
<div class="row">
<div class="col-md-6 col-md-offset-3">
<form novalidate action="/reg/" method="post" autocomplete="off" class="form-horizontal reg-form" enctype="multipart/form-data">
{% csrf_token %}
<div class="form-group text-center" style="margin-top: 80px">
<label class="col-sm-2 control-label">头像label>
<div class="col-sm-8">
<label for="id_avatar"><img id="avatar-img" src="/static/img/timg.jpg" alt="">label>
<input accept="image/*" type="file" name="avatar" id="id_avatar" style="display: none">
<span class="help-block">span>
div>
div>
<div class="form-group">
<label for="{{ form_obj.username.id_for_label }}"
class="col-sm-2 control-label">{{ form_obj.username.label }}label>
<div class="col-sm-8">
{{ form_obj.username }}
<span class="help-block">{{ form_obj.username.errors.0 }}span>
div>
div>
<div class="form-group">
<label for="{{ form_obj.email.id_for_label }}"
class="col-sm-2 control-label">{{ form_obj.email.label }}label>
<div class="col-sm-8">
{{ form_obj.email }}
<span class="help-block">{{ form_obj.email.errors.0 }}span>
div>
div>
<div class="form-group">
<label for="{{ form_obj.password.id_for_label }}"
class="col-sm-2 control-label">{{ form_obj.password.label }}label>
<div class="col-sm-8">
{{ form_obj.password }}
<span class="help-block">{{ form_obj.password.errors.0 }}span>
div>
div>
<div class="form-group">
<label for="{{ form_obj.re_password.id_for_label }}"
class="col-sm-2 control-label">{{ form_obj.re_password.label }}label>
<div class="col-sm-8">
{{ form_obj.re_password }}
<span class="help-block">{{ form_obj.re_password.errors.0 }}span>
div>
div>
<div class="form-group">
<div class="col-sm-offset-4 col-sm-12">
<button type="button" class="btn btn-success" id="reg-submit">注册button>
<input type="reset" class="btn btn-danger" value="重置">
<a class="panel-warning" href="/home/">返回首页a>
div>
div>
form>
div>
div>
div>
<script src="/static/js/jquery-3.3.1.min.js">script>
<script src="/static/bootstrap/js/bootstrap.min.js">script>
<script>
// 找到头像的input标签绑定change事件
$("#id_avatar").change(function () {
// 1. 创建一个读取文件的对象
var fileReader = new FileReader();
// 取到当前选中的头像文件
// console.log(this.files[0]);
// 读取你选中的那个文件
fileReader.readAsDataURL(this.files[0]); // 读取文件是需要时间的
fileReader.onload = function () {
// 2. 等上一步读完文件之后才 把图片加载到img标签中
$("#avatar-img").attr("src", fileReader.result);
};
});
// AJAX提交注册的数据
$("#reg-submit").click(function () {
// 取到用户填写的注册数据,向后端发送AJAX请求
var formData = new FormData();
formData.append("username", $("#id_username").val());
formData.append("password", $("#id_password").val());
formData.append("re_password", $("#id_re_password").val());
formData.append("email", $("#id_email").val());
formData.append("avatar", $("#id_avatar")[0].files[0]);
formData.append("csrfmiddlewaretoken", $("[name='csrfmiddlewaretoken']").val());
$.ajax({
url: "/reg/",
type: "post",
processData: false, //告诉Jquery不要处理我的数据
contentType: false, //告诉jQuery不要设置content类型
data: formData,
success:function (data) {
if (data.status){
// 有错误就展示错误
// console.log(data.msg);
// 将报错信息填写到页面上
$.each(data.msg, function (k,v) {
// console.log("id_"+k, v[0]);
// console.log($("#id_"+k));
$("#id_"+k).next("span").text(v[0]).parent().parent().addClass("has-error");
})
}else {
// 没有错误就跳转到指定页面
location.href = data.msg;
}
}
})
});
// 将所有的input框绑定获取焦点的事件,将所有的错误信息清空
$("form input").focus(function () {
$(this).next().text("").parent().parent().removeClass("has-error");
});
//给username的input输入框,绑定失去焦点事件,失去焦点后检测用户名是否存在
$("#id_username").blur(function () {
// 取到用户填写的值
var username = $(this).val();
//发ajax请求
$.ajax({
url:"/check_username_exist/",
type:"get",
data:{username:username,},
success:function (data) {
if(data.status){
//有错误,用户名已被注册
$("#id_username").next().text(data.msg).parent().parent().addClass("has-error");
}
}
})
})
script>
body>
html>
注册后端页面 def reg(request):
if request.method == "POST":
ret = {"status": 0, "msg": ""}
form_obj = forms.RegForm(request.POST)
print('request.POST'.center(80, '#'))
print(request.POST)
print('request.POST'.center(80, '#'))
avatar_img = request.FILES.get("avatar")
print(avatar_img)
# 帮我做校验
if form_obj.is_valid():
# 校验通过,去数据库创建一个新的用户
form_obj.cleaned_data.pop("re_password")
print(form_obj.cleaned_data)
try:
models.UserInfo.objects.create_user(**form_obj.cleaned_data,avatar=avatar_img)
except Exception as e:
print(e)
ret["msg"] = "/login/"
return JsonResponse(ret)
else:
print(form_obj.errors)
ret["status"] = 1
ret["msg"] = form_obj.errors
print(ret)
print("=" * 120)
return JsonResponse(ret)
# 生成一个form对象
form_obj = forms.RegForm()
print(form_obj.fields)
return render(request,'reg.html',{"form_obj": form_obj})
八、项目源码https://github.com/Yun-Wangjun/BookSystem
转载于:https://www.cnblogs.com/yunwangjun-python-520/p/11137781.html |