logo Date
:2021-11
Description
:本文档作为 2021 年秋季学期「数据库系统」课程 Project 校园食堂点餐系统的开发文档,基于 MySQL+Django 实现。
Reference
:参考了一系列 Django 入门教程,在文中相应部分有链接。
Code Repo
:https://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 (物理数据模型):
最终实现的功能:
user admin Django 基础 如果你之前从未接触过 Web 框架,可以先看这一篇文章:一杯茶的时间,上手 Django 框架开发 ,再往下阅读。
通过这篇文章,你可以了解到:
Django 环境的安装、项目初始化 项目的骨架以及各个文件的作用 Django App 的创建、各个子文件的作用 App 中 views.py
的编写、路由 urls.py
的使用 Django Template 的实现——模板生成的 HTML 文件将随数据变化 Django ORM 与数据库中的内容交互、迁移数据模型 models.py
Django Admin 实现应用与后台管理接口、使用超级用户登录后台 设计文档 运行环境 操作系统:
开发工具:
主要技术:
环境: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' , 'PORT' : 3306 , 'USER' : 'root' , 'PASSWORD' : '123456' } }
同时还要安装第三方库,并在 __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
最后的成品举例如下:
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 adminfrom .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 renderdef 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 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 pathfrom .views import index app_name = 'canteen' urlpatterns = [ path('' , index), ]
之后就可以看到首页啦,其他功能将在下一节介绍。
交互功能开发 要求实现的功能:食堂管理、商户管理、菜品管理、订单管理、用户管理。
用户登录注册 参考资料:https://www.liujiangblog.com/course/django/102
在 customer
下 view.py
中加入用户登录的方法,其中 locals()
代表返回当前方法里的全部变量:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 from django.shortcuts import renderfrom .forms import LoginForm, RegisterForm from django.contrib import messagesfrom .models import Customerdef 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 pathfrom .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 formsclass 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 renderfrom .models import Canteen, Shopdef 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 中包含即可。
页面展示 菜品展示界面 用户登录界面 订单展示界面 后台管理界面 后台操作演示