DB课程项目-校园食堂点餐系统-开发文档

本文最后更新于:2022年5月19日 中午

logo

Date:2021-11

Description:本文档作为 2021 年秋季学期「数据库系统」课程 Project 校园食堂点餐系统的开发文档,基于 MySQL+Django 实现。

Reference:参考了一系列 Django 入门教程,在文中相应部分有链接。

Code Repohttps://github.com/hewei2001/campus-canteen-ordering

Acknowledgement:感谢 Yiwen-Ding 小盆友 😭 不然不可能肝完这么多内容。

Copyright:© 2021 Hwcoder. All rights reserved.

项目说明

本实验要求设计并实现一个小型数据库应用系统,实现前后台数据交互,并将数据保存在之前实验所设计的数据库中。

由于不限语言和实现方式,同学们可能采用的方案大致有:Cpp+Qt,Python+Flask,Python+Django,Python+PyQt 等,还有其他 Java、PHP 一些我不太熟悉的方案。

本项目采用的是 Python+Django,是因为 Django 具有功能强大的脚手架和诸多开箱即用的组件,用 Django 搭建 Web 应用快速而又省力。相比于其他同学手敲 Qt 窗口,Django 自带的 Admin 已经基本把这些功能都实现了,甚至装了插件后还更美观,因此可以聚焦于前台交互的快速开发。

项目背景

本项目是建立在之前的实验结果的基础(已经用 PowerDesigner 设计并导出了数据库到 MySQL Workbench)上,再用 Django 进行应用系统的开发的过程。

这里简单描述一下之前的实验:

  • 设计校园食堂点餐系统,要求具备功能:食堂管理、商户管理、菜品管理、订单管理、用户管理;
  • E-R 图至少包括 8 个实体和 7 个联系;
  • 需要考虑关系完整性约束:主键约束、外键约束、空值约束;
  • 设计至少 1 个视图、1 个索引、1 个触发器(在应用中不需要体现)。

以及设计好的 PDM (物理数据模型):

PDM

最终实现的功能:

user
admin

Django 基础

如果你之前从未接触过 Web 框架,可以先看这一篇文章:一杯茶的时间,上手 Django 框架开发,再往下阅读。

通过这篇文章,你可以了解到:

  1. Django 环境的安装、项目初始化
  2. 项目的骨架以及各个文件的作用
  3. Django App 的创建、各个子文件的作用
  4. App 中 views.py 的编写、路由 urls.py 的使用
  5. Django Template 的实现——模板生成的 HTML 文件将随数据变化
  6. Django ORM 与数据库中的内容交互、迁移数据模型 models.py
  7. Django Admin 实现应用与后台管理接口、使用超级用户登录后台

设计文档

运行环境

操作系统:

  • Windows 10

开发工具:

  • IDE:PyCharm Community Edition 2020.2

  • 设计:PowerDesigner

  • 数据库:MySQL Workbench 8.0 CE

主要技术:

  • 环境:Django 3.8

  • 前端样式:Bootstrap

  • 后台样式:Django-SimpleUI

Django 后台开发

本部分参考资料有:

新建项目

1
$ django-admin startproject django_CCOS

调整基础配置 settings.py

1
2
LANGUAGE_CODE = 'zh-Hans'
TIME_ZONE = 'Asia/Shanghai'

其他字段的意义可以参考:http://c.biancheng.net/view/7475.html

为了存放图片等资源,需要在 settings.py 中加上以下内容,:

1
2
3
4
5
6
7
8
9
import os

# 这个是设置静态文件夹目录的路径
STATICFILES_DIRS = (
os.path.join(BASE_DIR, 'static'),
)
# 设置文件上传路径,图片上传、文件上传都会存放在此目录里
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

同时,在 TEMPLATES 中加上如下字段:

1
'django.template.context_processors.media',

Python3 的 Django 默认的数据库类型是 sqlite,由于之前的实验做的数据库是 MySQL 的,这里进行修改 settings.py

1
2
3
4
5
6
7
8
9
10
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'django_CCOS', # 连接的数据库
'HOST': '127.0.0.1', # mysql的ip地址
'PORT': 3306, # mysql的端口
'USER': 'root', # mysql的用户名
'PASSWORD': '123456' # mysql的密码
}
}

同时还要安装第三方库,并在 __init__.py 中加上:

1
2
3
import pymysql

pymysql.install_as_MySQLdb()

连接 MySQL 数据库

打开 MySQL Workbench,创建新数据库,create database CCOS。将 Powerdesigner 生成的 crebas.sql 打开并执行,注意要注释掉 drop 等语句。此时我们的数据库就建好了。

打开项目,创建三个 App:

1
2
3
$ python manage.py startapp canteen
$ python manage.py startapp customer
$ python manage.py startapp dish

然后在 INSTALLED_APPS 中加上对应字段,注意要按顺序,否则后面的指令都会报错。

接下来要反向导入我们的数据库,在 Django 中生成 Model,参考:https://blog.csdn.net/diao1057/article/details/98472327

1
2
3
$ python manage.py inspectdb --database default canteen shop shop_manager > canteen/models.py
$ python manage.py inspectdb --database default customer address > customer/models.py
$ python manage.py inspectdb --database default dish orders comments > dish/models.py

导入后如果出现如下报错:

1
Python Django ValueError: source code string cannot contain null bytes

很可能是因为生成的 models.py 的编码方式默认被改为 UTF-16 了,需要修改回 UTF-8。

模型重建

生成的 Model 不完整,还不能直接运行,据说是因为 Powerdesigner 比较拉跨。因此需要在刚刚生成的 models.py 中修改 class 的内容,并导出到数据库里,覆盖掉原来 Powerdesigner 生成的数据库。class 各个字段的意义参考:https://www.liujiangblog.com/course/django/95

可以看到所有的属性在 class 中都有自己的 Field,并传入了详细的定义,下面给出一些常用字段:

  • verbose_name:显示名
  • choices:多选框
  • upload_to:图片上传路径
  • max_length:最大长度
  • blank=True, null=True,:可以为空

特别要注意外键属性(ForeignKey),如果外键在当前 App 内还好(跟定义顺序有关),要是在其他 App 里,则要 from [其他app].models import xxx。此外,外键还有一个 on_delete 属性,代表删除外键对本表记录的影响,参考:https://www.liujiangblog.com/course/django/96

还有 class Meta 部分,可以设置对整体的操作,比如 ordering 显示顺序等。注意要把之前反向导入产生的 managed = False 字段删除。

在每一个 class 下加上 Python 的一个魔法方法,用于返回实例对象的打印字符串。:

1
2
def __str__(self):
return self.title # title 换成想显示的属性

最后的成品举例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Dish(models.Model):
dish_id = models.AutoField(primary_key=True, verbose_name='菜品编号')
shop = models.ForeignKey('canteen.Shop', models.CASCADE, verbose_name='窗口')
dish_name = models.CharField(max_length=20, verbose_name='菜品名称')
dish_detail = models.CharField(max_length=200, blank=True, null=True, verbose_name='菜品描述')
dish_price = models.DecimalField(max_digits=5, decimal_places=2, verbose_name="菜品价格")
dish_photo = models.ImageField(upload_to='image/dish', null=True, blank=True, verbose_name='菜品照片')
dish_active = models.IntegerField(choices = [(1, '销售中'),(0, '售罄')], verbose_name='菜品状态')

class Meta:
ordering = ['dish_id']
db_table = 'dish'
verbose_name = "菜品信息"
verbose_name_plural = verbose_name

def __str__(self):
return self.dish_name

数据库迁移

运行以下命令创建迁移文件:

1
$ python manage.py makemigrations

由于导出时会覆盖原来的 MySQL 里的数据库,会创造很多乱七八糟的表,可能会有些许报错,建议在 MySQL 重新创一个空数据库 create database django_CCOS,并修改 settings.py 中的连接。

接着我们进行数据库迁移:

1
$ python manage.py migrate

后台接口管理

数据库迁移完成后,我们就可以创建用于登录后台管理的超级用户:

1
$ python manage.py createsuperuser

按照提示填写用户名和密码即可。

在每个 App 下的 admin.py 添加后台管理接口如下:

1
2
3
4
5
6
7
from django.contrib import admin

from .models import Canteen, Shop, ShopManager

admin.site.register(Canteen)
admin.site.register(Shop)
admin.site.register(ShopManager)

完成上述操作后,访问 localhost:8000/admin,进入后台系统的登录页面,填入刚才设置的用户名和密码,进入后台管理页面。此时已经可以看到所有的表单,且可以完成基础的管理操作。

BootStrap 前端开发

完成了后台,现在来实现前端,这就需要用到 Django 的 templates 模板。首先在主目录下新建 templates 用于存放前端页面 html,再创建 static 用来存放渲染静态页面需要的 css 资源。

参考博客:https://www.cnblogs.com/qican/p/14626811.html

静态模板安装

首先下载 Bootstrap:https://v3.bootcss.com/getting-started/#download,下载下来后放入 static 文件夹(也可以直接从别人的 Django 项目中复制进来,可能样式更高级)。

修改 setting.py,前面已经加了 static 内容,现在要在 TEMPLATE 字典中加入如下,用以表示模板路径:

1
'DIRS': [BASE_DIR / 'templates'],

view.py

在 canteen 的 view.py 中写入如下内容,用以 render 数据:

1
2
3
4
from django.shortcuts import render

def index(request):
return render(request, 'base.html')

HTML

templates 中放入我们的 base.html,作为展示的首页,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
{% load static %}
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title>校园食堂点餐系统</title>
<link rel="stylesheet" href="{% static 'css/master.css' %}">
</head>
<body>
<!-- 此处省略首页代码,如导航栏等 -->
</body>
</html>

其中静态资源引用了 static 下面的 css/master.css,来自 GitHub 的其他开源项目。

URL 路径配置

修改 urls.py

1
2
3
4
5
6
7
# 用于 include 其他模板所在的 url
from django.conf.urls import url, include

urlpatterns = [
path('admin/', admin.site.urls),
path('', include('canteen.urls')),
]

由于加了 canteen,相应的也要在 canteen/urls.py 中加入:

1
2
3
4
5
6
7
from django.urls import path
from .views import index

app_name = 'canteen'
urlpatterns = [
path('', index),
]

之后就可以看到首页啦,其他功能将在下一节介绍。

交互功能开发

要求实现的功能:食堂管理、商户管理、菜品管理、订单管理、用户管理。

用户登录注册

参考资料:https://www.liujiangblog.com/course/django/102

customerview.py 中加入用户登录的方法,其中 locals() 代表返回当前方法里的全部变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from django.shortcuts import render
from .forms import LoginForm, RegisterForm # 导入表单,下面将介绍
from django.contrib import messages
from .models import Customer


def register(request):
# 写注册方法
return render(request, 'customer/register.html', locals())

def login(request):
# 写登录方法
return render(request, 'customer/login.html', locals())

def logout(request):
# 写登出方法
return render(request, 'customer/index.html', locals())

具体访问策略如下:

  • 未登录人员,不论是访问 index 还是 login 和 logout,全部跳转到 login 界面
  • 已登录人员,访问 login 会自动跳转到 index 页面
  • 已登录人员,不允许直接访问 register 页面,需先 logout
  • 登出后,自动跳转到 login 界面

于是,在主目录 urls.py 中加入:

1
path('customer/', include('customer.urls')),

customer/urls.py 中加入:

1
2
3
4
5
6
7
8
9
10
from django.urls import path
from .views import index, login, register, logout

app_name = 'customer'
urlpatterns = [
path('', index),
path('login/', login),
path('register/', register),
path('logout/', logout),
]

实现登录时,需要用到表单 form,创建 cutomser/forms.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from django import forms

class LoginForm(forms.Form):
username = forms.CharField(label="用户名", max_length=128, widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': "Username",'autofocus': ''}),
error_messages={'required': '用户名不能为空','min_length': '用户名最少为3个字符',
'max_length': '用户名最不超过为20个字符'},)
password = forms.CharField(label="密码", max_length=256, widget=forms.PasswordInput(attrs={'class': 'form-control','placeholder': "Password"}))


class RegisterForm(forms.Form):
username = forms.CharField(label="用户名", max_length=128, widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': "Username",'autofocus': ''}))
password1 = forms.CharField(label="密码", max_length=256, widget=forms.PasswordInput(attrs={'class': 'form-control','placeholder': "Password"}))
password2 = forms.CharField(label="确认密码", max_length=256, widget=forms.PasswordInput(attrs={'class': 'form-control','placeholder': "Password"}))
tel = forms.CharField(label="电话", widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': "TEL"}))

其他诸如 session(会话) 的内容默认已经配置好了(session 是用于保存当前在线用户信息的,用于各种与用户关联的查询),现在可以打开页面查看登录。

食堂、窗口、菜品展示

在相应的 views.py 中写好 ORM 方法,将数据库内的表元组全部传到 Django,再到 templates 中写好 html,将传出的元组以循环遍历显示。

例如显示食堂和窗口,在 canteen/views.py 写入:

1
2
3
4
5
6
7
from django.shortcuts import render
from .models import Canteen, Shop

def show_canteen(request):
template_name = 'canteen/canteen_list.html'
context = {'canteen_list': Canteen.objects.all()}
return render(request, template_name, context)

再到 canteen_list.html 中写下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{% block store_content %}
<div>
<div class="container">
<div class="row">
{% for i in canteen_list %}
<div class="align-items-stretch">
<div class="card cardmodify cardbackground">
<div class="card-header">
<h5><b>{{ i.canteen_name }}</b></h5>
<img class="card-img-top" src="{{ i.canteen_photo.url }}">
<h5><b>{{ i.canteen_active }}</b></h5>
<h5><b>卫生等级:{{ i.sanitation_level }}</b></h5>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
{% endblock %}

再修改相应的路由,就能在页面中以卡片的形式展示食堂了!样式代码来自 GitHub 的其他开源项目。

然而,这样只能实现所有食堂、所有窗口、所有菜品在一个静态页面中的显示,无法根据用户选中某个食堂而显示其对应的窗口。于是,本项目中采用了一个取巧的方法,利用 HTML 的锚定位和 id 属性,选中某个食堂跳转到窗口页的相应 id 处,让用户「看似」进入了该食堂独立的页面。

用户下单

这里用到了 reverse 方法,也是全项目卡住最久的地方,需要在 Model 中定义如下内容:

1
2
def get_order_url(self):
return reverse("dish:get_order", kwargs={'dish_id': self.dish_id})

这意味着当用户点击按钮时,触发 get_order_url 方法,该方法将传入的 dish_id 转发到 dish/views.py 中的 get_order 方法,再进行处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def get_order(request, dish_id):
dish = get_object_or_404(Dish, dish_id=dish_id)
user_id = request.session['user_id']

try:
user = Customer.objects.filter(customer_id=user_id).first()
order = Orders.objects.create(dish=dish, customer=user)
order.order_price = order.dish.dish_price
order.order_status = 0
order.save()
messages.success(request, '下单成功!')
return redirect("dish:show_order")

except ObjectDoesNotExist:
messages.warning(request, "你还没有订单哦~")
return redirect("dish:show_order")

相应的按钮触发事件还需要在 HTML 标签中指出,这里不再赘述。

其他页面

诸如订单页面、个人中心页面等,实现方法都是用 ORM 筛选数据库中当前用户的数据,并用模板显示出来。需要注意的是,进入该界面前要先判断,如果未登录则重定向至登录界面:

1
2
3
if not request.session.get('is_login', None):
messages.warning(request, "请先登录顾客账户~")
return redirect('/customer/login/')

优化后台管理

admin.py 里修改显示内容,使后台展示更全面。例如:

1
2
3
4
class DishAdmin(admin.ModelAdmin):
list_per_page = 10
search_fields = ['dish_name']
list_display = ['dish_id', 'dish_name', 'shop', 'dish_price', 'dish_active']

就可以在后台显示详细信息,并加入搜索框,可以根据菜品名搜索。

此外,开源资源中还有许多对 Django Admin 的 UI 优化插件,例如 simpleui,只需在 app 中包含即可。

页面展示

菜品展示界面
用户登录界面
订单展示界面
后台管理界面
后台操作演示

DB课程项目-校园食堂点餐系统-开发文档
https://hwcoder.top/DB-Project
作者
Wei He
发布于
2021年11月21日
许可协议