機(jī)票預(yù)定系統(tǒng) - Shuai-Xie - Github
一哼丈、機(jī)票預(yù)定系統(tǒng)
1.1 題目要求
要求具備如下基本功能
- 班機(jī)基本信息的管理;
- 航班信息的管理乔宿;
- 旅客預(yù)定機(jī)票茄袖、取消預(yù)約、付款取票雏胃、退票的管理请毛;
- 查詢航班信息、航班預(yù)定情況瞭亮、旅客信息方仿,計(jì)算航班滿座率。
- 統(tǒng)計(jì)每周统翩、每月仙蚜,每年?duì)I業(yè)收入情況。
1.2 開發(fā)環(huán)境
- 語言:Python 3.5
- 框架:Django 1.10.6
- 前端設(shè)計(jì):HTML, CSS, JavaScript
- 開發(fā)環(huán)境:PyCharm
二厂汗、需求分析
2.1 具體需求
為方便旅客出行委粉,某航空公司(CSU Airlines)擬開發(fā)一個(gè)機(jī)票預(yù)定系統(tǒng)。旅客可通過網(wǎng)上訂票平臺(tái)查詢公司航班情況娶桦,通過輸入起飛地艳丛、目的地匣掸、起飛時(shí)間等信息系統(tǒng)為旅客安排航班趟紊,旅客可根據(jù)自身需要氮双,按照起飛時(shí)間和機(jī)票價(jià)位來選擇航班。訂票成功之后霎匈,系統(tǒng)為旅客生成訂單信息戴差,旅客可以再個(gè)人信息頁面查看自己的訂票信息,并且可以向系統(tǒng)提出退票要求铛嘱,系統(tǒng)針對(duì)具體情況計(jì)算手續(xù)費(fèi)后進(jìn)行相應(yīng)退票處理暖释。
2.2 功能分析
(1)用戶界面
- 查詢:用戶對(duì)航班信息進(jìn)行查詢操作;
- 排序:用戶根據(jù)自己的需求對(duì)查詢結(jié)果進(jìn)行排序篩選墨吓;
- 訂票:對(duì)用戶訂票需求進(jìn)去處理并記錄旅客預(yù)定信息和更新數(shù)據(jù)庫信息球匕;
- 退票:對(duì)用戶退票需求進(jìn)行處理并更新數(shù)據(jù)庫;
- 查看個(gè)人信息:用戶查看自己的個(gè)人票務(wù)信息
- 幫助:提供系統(tǒng)使用幫助文檔帖烘;
- 退出:關(guān)閉當(dāng)前頁面亮曹。
(2)管理員界面
- 航班信息管理:可對(duì)航班信息進(jìn)行增刪改查操作;
- 旅客信息管理:可對(duì)旅客信息進(jìn)行增刪改查操作秘症;
- 財(cái)務(wù)信息管理:可以統(tǒng)計(jì)航空公司每周照卦、每月,每年?duì)I業(yè)收入情況乡摹。
- 幫助:提供系統(tǒng)使用幫助文檔役耕;
- 退出:關(guān)閉當(dāng)前頁面。
2.3 系統(tǒng)主功能圖
2.4 系統(tǒng)數(shù)據(jù)流圖
三聪廉、邏輯設(shè)計(jì)
3.1 ER圖
3.2 數(shù)據(jù)庫表
(1)航班信息表
(2)旅客信息表
旅客信息表繼承了 Django 模板中默認(rèn)的 User 類瞬痘,所以有一些繼承來的別的字段,我們的 Passenger 對(duì)象只需要 id板熊,username框全,password 即可。
(3)實(shí)體間多對(duì)多關(guān)系
通過在兩表之間建立一張新表的方式邻邮,將 booksystem_flight 表和 auth_user 表的主鍵聯(lián)系在一起竣况,實(shí)現(xiàn)了多對(duì)多關(guān)系。
四筒严、功能設(shè)計(jì)
本系統(tǒng)采用的 Python + Django + Sqlite 的設(shè)計(jì)方法丹泉,后臺(tái)功能寫在 views.py 文件中。
4.1 用戶模塊
4.1.1 訂票模塊
在用戶訂票的過程中鸭蛙,首先判定用戶是否登錄摹恨,如果沒有登錄,就加載登錄頁面娶视;如果用戶已經(jīng)登陸了晒哄,通過前端用戶選擇的航班傳入flight.id睁宰,然后判斷如果用戶已經(jīng)訂過這次航班,就反饋沖突信息寝凌,如果沒有柒傻,訂票成功,數(shù)據(jù)庫更新较木,顯示訂票成功頁面红符。
# 免除csrf
@csrf_exempt
def book_ticket(request, flight_id):
if not request.user.is_authenticated(): # 如果沒登錄就render登錄頁面
return render(request, 'booksystem/login.html')
else:
flight = Flight.objects.get(pk=flight_id)
# 查看乘客已經(jīng)訂購的flights
booked_flights = Flight.objects.filter(user=request.user) # 返回 QuerySet
if flight in booked_flights:
return render(request, 'booksystem/book_conflict.html')
# book_flight.html 點(diǎn)確認(rèn)之后,request為 POST 方法伐债,雖然沒有傳遞什么值预侯,但是傳遞了 POST 信號(hào)
# 確認(rèn)訂票,flight數(shù)據(jù)庫改變
# 驗(yàn)證一下峰锁,同樣的機(jī)票只能訂一次
if request.method == 'POST':
if flight.capacity > 0:
flight.book_sum += 1
flight.capacity -= 1
flight.income += flight.price
flight.user.add(request.user)
flight.save() # 一定要記著save
# 傳遞更改之后的票務(wù)信息
context = {
'flight': flight,
'username': request.user.username
}
return render(request, 'booksystem/book_flight.html', context)
4.1.2 查詢模塊
前端表單接收用戶傳入的出發(fā)地萎馅、目的地和出發(fā)時(shí)間,然后在航班數(shù)據(jù)庫中尋找滿足條件的航班虹蒋,分兩步:
- 尋找出發(fā)地和目的地相同的航班糜芳;
- 尋找航班出發(fā)日期與旅客出發(fā)日期相同的航班。
為了給用戶良好的體驗(yàn)千诬,滿足條件的航班信息按照不同的 key 值(起飛時(shí)間耍目、降落時(shí)間、機(jī)票價(jià)格)進(jìn)行升序排列徐绑。
# 搜索結(jié)果頁面
def result(request):
if request.method == 'POST':
form = PassengerInfoForm(request.POST) # 綁定數(shù)據(jù)至表單
if form.is_valid():
passenger_lcity = form.cleaned_data.get('leave_city')
passenger_acity = form.cleaned_data.get('arrive_city')
passenger_ldate = form.cleaned_data.get('leave_date')
# print(type(passenger_ldate))
# 全設(shè)為naive比較
# china_tz = pytz.timezone('Asia/Shanghai')
# passenger_ltime = datetime.datetime(
# year=passenger_ldate.year,
# month=passenger_ldate.month,
# day=passenger_ldate.day,
# hour=0, minute=0, second=0,
# tzinfo=china_tz
# )
# 全設(shè)為aware比較
passenger_ltime = datetime.datetime.combine(passenger_ldate, datetime.time())
print(passenger_ltime)
# filter 可用航班
all_flights = Flight.objects.filter(leave_city=passenger_lcity, arrive_city=passenger_acity)
usable_flights = []
for flight in all_flights: # off-set aware
flight.leave_time = flight.leave_time.replace(tzinfo=None) # replace方法必須要賦值邪驮。。笑哭
if flight.leave_time.date() == passenger_ltime.date(): # 只查找當(dāng)天的航班
usable_flights.append(flight)
# 按不同的key排序
usable_flights_by_ltime = sorted(usable_flights, key=attrgetter('leave_time')) # 起飛時(shí)間從早到晚
usable_flights_by_atime = sorted(usable_flights, key=attrgetter('arrive_time'))
usable_flights_by_price = sorted(usable_flights, key=attrgetter('price')) # 價(jià)格從低到高
# 轉(zhuǎn)換時(shí)間格式
time_format = '%H:%M'
# for flight in usable_flights_by_ltime:
# flight.leave_time = flight.leave_time.strftime(time_format) # 轉(zhuǎn)成了str
# flight.arrive_time = flight.arrive_time.strftime(time_format)
#
# for flight in usable_flights_by_atime:
# flight.leave_time = flight.leave_time.strftime(time_format) # 轉(zhuǎn)成了str
# flight.arrive_time = flight.arrive_time.strftime(time_format)
# 雖然只轉(zhuǎn)換了一個(gè)list傲茄,其實(shí)所有的都轉(zhuǎn)換了
for flight in usable_flights_by_price:
flight.leave_time = flight.leave_time.strftime(time_format) # 轉(zhuǎn)成了str
flight.arrive_time = flight.arrive_time.strftime(time_format)
# 決定 search_head , search_failure 是否顯示
dis_search_head = 'block'
dis_search_failure = 'none'
if len(usable_flights_by_price) == 0:
dis_search_head = 'none'
dis_search_failure = 'block'
context = {
# 搜多框數(shù)據(jù)
'leave_city': passenger_lcity,
'arrive_city': passenger_acity,
'leave_date': str(passenger_ldate),
# 搜索結(jié)果
'usable_flights_by_ltime': usable_flights_by_ltime,
'usable_flights_by_atime': usable_flights_by_atime,
'usable_flights_by_price': usable_flights_by_price,
# 標(biāo)記
'dis_search_head': dis_search_head,
'dis_search_failure': dis_search_failure
}
if request.user.is_authenticated():
context['username'] = request.user.username
return render(request, 'booksystem/result.html', context) # 最前面如果加了/就變成根目錄了毅访,url錯(cuò)誤
else:
return render(request, 'booksystem/index.html') # 在index界面提交的表單無效,就保持在index界面
else:
context = {
'dis_search_head': 'none',
'dis_search_failure': 'none'
}
return render(request, 'booksystem/result.html', context)
4.1.3 退票模塊
退票時(shí)需要更新數(shù)據(jù)庫盘榨,更新航班的(capacity, book_sum, income)字段喻粹,并且在 booksystem_flight_user 表中刪除這個(gè)訂單。
# 退票
def refund_ticket(request, flight_id):
flight = Flight.objects.get(pk=flight_id)
flight.book_sum -= 1
flight.capacity += 1
flight.income -= flight.price
flight.user.remove(request.user)
flight.save()
return HttpResponseRedirect('/booksystem/user_info')
4.1.4 個(gè)人信息模塊
由于管理員和用戶共用一個(gè)登錄窗口草巡,所以在顯示用戶信息時(shí)守呜,需要對(duì)登錄的用戶身份進(jìn)行判定,如果登錄的用戶是管理員山憨,則加載管理頁面查乒,如果是普通用戶,則加載用戶個(gè)人的訂單頁面郁竟。
# 顯示用戶訂單信息
# 航班信息玛迄,退票管理
def user_info(request):
if request.user.is_authenticated():
# 如果用戶是管理員,render公司航班收入統(tǒng)計(jì)信息頁面 admin_finance
if request.user.id == ADMIN_ID:
context = admin_finance(request) # 獲取要傳入前端的數(shù)據(jù)
return render(request, 'booksystem/admin_finance.html', context)
# 如果用戶是普通用戶棚亩,render用戶的機(jī)票信息 user_info
else:
booked_flights = Flight.objects.filter(user=request.user) # 從 booksystem_flight_user 表過濾出該用戶訂的航班
context = {
'booked_flights': booked_flights,
'username': request.user.username, # 導(dǎo)航欄信息更新
}
return render(request, 'booksystem/user_info.html', context)
return render(request, 'booksystem/login.html') # 用戶如果沒登錄蓖议,render登錄頁面
4.2 管理員模塊
4.2.1 航班信息管理
航班對(duì)象繼承了 models.Model虏杰,航班信息管理在 Django 默認(rèn)的后臺(tái)管理界面中實(shí)現(xiàn)。
from django.contrib.auth.models import Permission, User
from django.db import models
# Create your models here.
# 添加primary_key會(huì)覆蓋掉默認(rèn)的主鍵
class Flight(models.Model):
user = models.ManyToManyField(User, default=1) # 有了這個(gè)字段之后勒虾,默認(rèn)的后臺(tái)添加失效纺阔,必須要自定義Form,除去這個(gè)字段
name = models.CharField(max_length=100) # 班次 南方航空CZ3969
leave_city = models.CharField(max_length=100, null=True) # 離開城市
arrive_city = models.CharField(max_length=100, null=True) # 到達(dá)城市
leave_airport = models.CharField(max_length=100, null=True) # 離開的機(jī)場(chǎng)
arrive_airport = models.CharField(max_length=100, null=True) # 到達(dá)的機(jī)場(chǎng)
leave_time = models.DateTimeField(null=True) # DateTimeField包括了DateField信息从撼,并且添加了時(shí)間
arrive_time = models.DateTimeField(null=True)
capacity = models.IntegerField(default=0, null=True) # 座位總數(shù)
price = models.FloatField(default=0, null=True) # 價(jià)格
book_sum = models.IntegerField(default=0, null=True) # 訂票總?cè)藬?shù)
income = models.FloatField(default=0, null=True) # 收入
def __str__(self):
return self.name
在使用 Django 默認(rèn)的后臺(tái)管理時(shí)州弟,由于 Flight 中多了字段 user,而 user 對(duì)象是不能從后臺(tái)輸入的低零,所以在 Django 默認(rèn)的表單管理中出去 user,因?yàn)?Flight 與 User 之間關(guān)系是多對(duì)多拯杠,所以 Django 建立的 book_system 表中是沒有 user 字段的掏婶,通過下面的方法解決。
# 自定義Flight對(duì)象的輸入信息
class FlightForm(forms.ModelForm):
class Meta:
model = Flight
exclude = ['user'] # user信息不能從后臺(tái)輸入
4.2.2 旅客信息管理
旅客繼承了 django.contrib.auth.User 類潭陪,我們只需要自定義用戶表單需要輸入的對(duì)象雄妥,其他默認(rèn)生成的字段不用考慮。
# 用戶需要輸入的字段
class UserForm(forms.ModelForm):
password = forms.CharField(widget=forms.PasswordInput)
class Meta:
model = User
fields = ['username', 'email', 'password']
4.2.3 航空公司財(cái)務(wù)統(tǒng)計(jì)
統(tǒng)計(jì)航空公司每周依溯、每月老厌、每年的收入,并且顯示所有的訂單信息黎炉。
# 管理員后臺(tái)財(cái)務(wù)管理
# 統(tǒng)計(jì)航空公司每周枝秤、每月,每年?duì)I業(yè)收入情況慷嗜。
def admin_finance(request):
all_flights = Flight.objects.all()
all_flights = sorted(all_flights, key=attrgetter('leave_time')) # 將所有航班按照起飛時(shí)間排序
# 將航班每天的輸入打上不同的時(shí)間標(biāo)簽 [周淀弹,月,日]
week_day_incomes = []
month_day_incomes = []
year_day_incomes = []
# 用set存儲(chǔ)所有的 周庆械,月薇溃,年
week_set = set()
month_set = set()
year_set = set()
for flight in all_flights:
if flight.income > 0: # 只統(tǒng)計(jì)有收入的航班
# 打上周標(biāo)簽
this_week = flight.leave_time.strftime('%W') # datetime獲取周
week_day_incomes.append((this_week, flight.income)) # 添加元組(week, income)
week_set.add(this_week)
# 打上月標(biāo)簽
this_month = flight.leave_time.strftime('%m') # datetime獲取月
month_day_incomes.append((this_month, flight.income)) # 添加元組(month, income)
month_set.add(this_month)
# 打上年標(biāo)簽
this_year = flight.leave_time.strftime('%Y') # datetime獲取年
year_day_incomes.append((this_year, flight.income)) # 添加元組(year, income)
year_set.add(this_year)
# 存儲(chǔ)每周收入
# 將每周的收入用 IncomeMetric 類型存儲(chǔ)在 week_incomes List中
week_incomes = []
for week in week_set:
income = sum(x[1] for x in week_day_incomes if x[0] == week) # 同周次的income求和
flight_sum = sum(1 for x in week_day_incomes if x[0] == week) # 同周次的航班總數(shù)目
week_income = IncomeMetric(week, flight_sum, income) # 將數(shù)據(jù)存儲(chǔ)到IncomeMetric類中,方便jinja語法
week_incomes.append(week_income)
week_incomes = sorted(week_incomes, key=attrgetter('metric')) # 將List類型的 week_incomes 按周次升序排列
# 存儲(chǔ)每月收入
# 將每月的收入用 IncomeMetric 類型存儲(chǔ)在 month_incomes List中
month_incomes = []
for month in month_set:
income = sum(x[1] for x in month_day_incomes if x[0] == month)
flight_sum = sum(1 for x in month_day_incomes if x[0] == month)
month_income = IncomeMetric(month, flight_sum, income)
month_incomes.append(month_income)
month_incomes = sorted(month_incomes, key=attrgetter('metric')) # 將List類型的 month_incomes 按月份升序排列
# 存儲(chǔ)每年收入
# 將每年的收入用 IncomeMetric 類型存儲(chǔ)在 year_incomes List中
year_incomes = []
for year in year_set:
income = sum(x[1] for x in year_day_incomes if x[0] == year)
flight_sum = sum(1 for x in year_day_incomes if x[0] == year)
year_income = IncomeMetric(year, flight_sum, income)
year_incomes.append(year_income)
year_incomes = sorted(year_incomes, key=attrgetter('metric')) # 將List類型的 year_incomes 按年份升序排列
# 存儲(chǔ)order信息
passengers = User.objects.exclude(pk=1) # 去掉管理員
order_set = set()
for p in passengers:
flights = Flight.objects.filter(user=p)
for f in flights:
route = f.leave_city + ' → ' + f.arrive_city
order = Order(p.username, f.name, route, f.leave_time, f.price)
order_set.add(order)
# 信息傳給前端
context = {
'week_incomes': week_incomes,
'month_incomes': month_incomes,
'year_incomes': year_incomes,
'order_set': order_set
}
return context
五缭乘、界面設(shè)計(jì)
5.1 歡迎界面
擬定一趟行程(長(zhǎng)沙→上海 2017/4/2)
5.2 查詢界面
用戶 Let’s Go 之后沐序,加載查詢結(jié)果頁面。
默認(rèn)的機(jī)票信息按照價(jià)格升序排列堕绩,用戶通過點(diǎn)擊機(jī)票信息上方的字段可以選擇按照起飛時(shí)間或者到達(dá)時(shí)間升序排列策幼,如下圖,注意后兩行的變化逛尚。
如果用戶需要的航班數(shù)據(jù)庫中不存在垄惧,就反饋錯(cuò)誤信息。
將用戶的目的地修改成中國(guó)(數(shù)據(jù)庫中沒有這趟航班)進(jìn)行測(cè)試绰寞。
5.3 訂票界面
由于用戶還沒有登錄到逊,會(huì)直接反饋到登錄界面铣口。
由于用戶尚未注冊(cè),用戶在該頁面點(diǎn)擊 Click here 進(jìn)入注冊(cè)賬號(hào)頁面觉壶,完成賬號(hào)注冊(cè)脑题。
用戶注冊(cè)完賬號(hào)直接加載到查詢頁面。
用戶再次點(diǎn)擊訂票铜靶,如果用戶尚未訂過該趟航班叔遂,加載訂票確認(rèn)頁面,如果用戶已經(jīng)訂過了争剿,加載訂票沖突頁面已艰。
在正常訂票頁面點(diǎn)擊確認(rèn),完成訂票蚕苇。
在個(gè)人中心用戶可以查看自己的訂票信息哩掺。
如果用戶選擇了自己已經(jīng)訂過的機(jī)票,加載訂票沖突頁面涩笤。
5.4 退票界面
在用戶的個(gè)人中心嚼吞,可以進(jìn)行退票。
選擇確認(rèn)蹬碧,完成退票舱禽,用戶訂票信息刷新。
5.5 管理員界面
在前面的 login_user 函數(shù)中已經(jīng)有過判定恩沽,如果登錄用戶是管理員誊稚,加載航空公司的財(cái)務(wù)頁面。
管理員登錄成功飒筑。
5.6 后臺(tái)管理界面
鏈接尾部輸入 admin 進(jìn)入后臺(tái)管理
管理員登錄賬號(hào)
后臺(tái)數(shù)據(jù)片吊,包括 Flight,User 和 Django 默認(rèn)生成的數(shù)據(jù)协屡。
航班信息管理俏脊,顯示所有航班信息,可以增刪改查肤晓。
旅客信息管理爷贫,操作同航班信息管理,注冊(cè)的用戶的信息都會(huì)保存在這里补憾。
六漫萄、結(jié)束語
其實(shí)從大三上的寒假開始,我就在為這次的數(shù)據(jù)庫課設(shè)做準(zhǔn)備盈匾,從學(xué)長(zhǎng)們那里了解到了數(shù)據(jù)庫課設(shè)最好的實(shí)現(xiàn)方法是寫網(wǎng)站腾务,于是寒假回去我就學(xué)習(xí)了基礎(chǔ)的 html, css, js,對(duì)網(wǎng)站前端有了基本認(rèn)識(shí)削饵,又結(jié)合我目前常用的 Python 語言岩瘦,學(xué)習(xí)了 Django 網(wǎng)頁開發(fā)框架未巫,對(duì)網(wǎng)站后端處理有了深層認(rèn)識(shí)。雖然對(duì)網(wǎng)站前后端的交互還有點(diǎn)模糊启昧,但是經(jīng)過這次數(shù)據(jù)庫課設(shè)之后叙凡,我很多的疑問得到了解答,對(duì) Python + Django + Sqlite 開發(fā)更加熟練了密末。
這次數(shù)據(jù)庫我用的是 Django 默認(rèn)的數(shù)據(jù)庫 Sqlite握爷,這是一個(gè)輕量級(jí)的數(shù)據(jù)庫,除了自帶的一些指令與其他數(shù)據(jù)庫有差異严里,大部分 SQL 語句與主流數(shù)據(jù)庫都相同新啼,但是 Sqlite 是一個(gè)本地型的數(shù)據(jù)庫,無需安裝和管理配置田炭,并且占用空間非常小师抄,用來做小型的網(wǎng)站開發(fā)完全夠用。
在建設(shè)機(jī)票預(yù)訂系統(tǒng)時(shí)教硫,主要的問題就是建立實(shí)體,并確定實(shí)體之間的關(guān)系辆布,一個(gè)旅客可以訂購多架飛機(jī)瞬矩,一個(gè)飛機(jī)可以承載多個(gè)用戶,飛機(jī)和旅客之間是多對(duì)多關(guān)系锋玲,清楚這一點(diǎn)的前提下景用,才能建設(shè)合理的數(shù)據(jù)庫完成事務(wù)需求。
另外惭蹂,有一點(diǎn)很深的收獲是伞插,在用 web 開發(fā)的時(shí)候,對(duì)數(shù)據(jù)庫的操作已經(jīng)不是 SQL 語句了盾碗,而是通過高級(jí)語言(如Python)的語法來完成對(duì)數(shù)據(jù)庫的增刪改查操作媚污。
舉個(gè)簡(jiǎn)單的例子,在查詢 booksystem_flight 表中的所有航班信息時(shí):
- SQL: select * from booksystem_flight;
- Django: Flight.objects.all( )
可見高級(jí)語言和數(shù)據(jù)庫的結(jié)合開發(fā)使得很多底層的數(shù)據(jù)操作也轉(zhuǎn)化成了開發(fā)人員熟悉的高級(jí)語言程序廷雅,但無論如何耗美,仍然是對(duì)數(shù)據(jù)庫進(jìn)行了操作,傳統(tǒng)的 SQL 語句依然有用航缀,方便我們驗(yàn)證代碼邏輯是否正確商架,總之,收獲很多芥玉。