少女祈祷中...

开场白

做一个简单的、基于Django框架和MySQLWeb数据库。

ο(=•ω<=)ρ⌒☆

好久没有手搓html了。

数据库通用建立WEB查询指南

写在前面

这是一个非常简单的、通过Django模板框架搭建可在线查询的数据库的建立指南,通过MySQL本地配置。由于是面向作业编程,所以讲解会尽量细致简洁,拓展较少。

需要用到的工具:

  • 本地MySQL
  • PyCharm专业版

环境准备

从官网下载MySQL8.0版本(其实这个版本应该没什么太大影响):

参看https://blog.csdn.net/JYT20031003/article/details/143926595

下载PyCharm专业版(学生入口):

参考入口https://www.jetbrains.com/community/education/#students

可能还需要配置一个本地的Conda环境,这个就交给互联网了。

正式开始

个人建议同时写前端和后端,但是为了保持清晰的思路,这里选择先从后端开始,然后是前端,两者交替编辑。

后端1

创建数据库

打开MySQL命令行,创建database,用于存放信息。

1
create database '你的数据库名称' character set utf8;

settings.py文件里加入如下设置:

1
2
3
4
5
6
7
8
9
10
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': '你的数据库名称',
'USER': '你的数据库用户',
'PASSWORD': '你的密码',
'HOST': 'localhost',
'PORT': '3306',
}
}

ORM

接下来就是有关表的操作,使用的是ORM,这玩意可以简单理解为函数封装。

参考链接:https://blog.csdn.net/weixin_47035302/article/details/131423759

创建表的模型、导入表

LuvSQL/models.py中定义模型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Book(models.Model):
book_id = models.CharField(max_length=8, primary_key=True)
book_name = models.CharField(max_length=50)
book_isbn = models.CharField(max_length=17)
book_author = models.CharField(max_length=10)
book_publisher = models.CharField(max_length=50)
interview_times = models.IntegerField()
book_price = models.FloatField()
class Reader(models.Model):
reader_id = models.CharField(max_length=8, primary_key=True)
reader_name = models.CharField(max_length=50)
reader_sex = models.CharField(max_length=2)
reader_department = models.CharField(max_length=60)
class Record(models.Model):
reader_id = models.ForeignKey(to="Book", on_delete=models.SET_NULL, null=True)
book_id = models.ForeignKey(to="Reader", on_delete=models.SET_NULL, null=True)
borrow_date = models.DateField()
return_date = models.DateField()
notes = models.CharField(max_length=50, null=True)

ORM默认设置不允许NULL

ORM中设置主键、外键如代码所示。

有关主键、外键的设置参看:https://blog.csdn.net/xiaoyu070321/article/details/133687812

添加完表的设置之后,先回settings.py确认是否INSTALLED_APPS中是否已经添加'LuvSQL.apps.LuvsqlConfig'的条目,没有则添加。

接下来我们先来尝试通过镜像将上面设计好的模型映射到实际的数据库表中。

在终端运行这两个命令:

1
2
python manage.py makemigrations			# 生成要迁移到数据库的文件
python manage.py migrate # 应用数据库迁移

在本地MySQL终端输入下面指令:

1
2
use LuvSQL;
show tables;

你应该能看到bookreaderrecord出现了:

前端1

主站

简单起见,我们直接写html文件,如果想要装饰网站就写CSS文件。

参看:HTML 基础 - 学习 Web 开发 | MDN

templates目录下添加一个html文件,命名为site.html,写入内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>LuvSQL主页</title>
</head>
<body>
<a href="./select"> <h2>查询功能</h2> </a>
<a href="./insert"> <h2>插入功能</h2> </a>
<a href="./delete"> <h2>删除功能</h2> </a>
<a href="./update"> <h2>更新功能</h2> </a>
</body>
</html>

无论如何,还是希望读者能理解这些步骤是在做什么。

可以直接打开这个html文件,查看一下,应该会有四个对应的超链接。

同样的创建各类功能的网页的html文件如下:

然后要让Django知道这个home.html文件是主页,在DjangoProject/urls.py中添加路径如下:

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

urlpatterns = [
# 主页
path('', show_home),
]

show_home是什么呢?它是我们即将在LuvSQL/views.py里添加的函数:

1
2
3
4
from django.shortcuts import render

def show_home(request): # 主页
return render(request, 'home.html')

render函数用来回馈HTTP请求。

运行一下Django的本地服务器,就会发现在主页里了。

表服务

那么对于其他的站点也是类似的操作,先在urls.py里添加路径:

1
2
3
4
5
6
7
8
9
10
11
12
from LuvSQL.views import show_select, show_insert, show_delete, show_update

urlpatterns = [
# 查询
path('select/', show_select),
# 插入
path('insert/', show_insert),
# 删除
path('delete/', show_delete),
# 更新
path('update/', show_update),
]

然后修改views.py文件:

1
2
3
4
5
6
7
8
9
10
11
def show_select(request):
return render(request, 'select.html')

def show_insert(request):
return render(request, 'insert.html')

def show_delete(request):
return render(request, 'delete.html')

def show_update(request):
return render(request, 'update.html')

可以尝试一下,这时从主站到各个功能页面的跳转功能也能使用了。

查询操作

前端2

先填写select.html的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>LuvSQL查询</title>
</head>
<body>
<h2>请输入您的查询内容</h2>
<form method="post" action="/select_result/">
{% csrf_token %}
<h4> From 项(只支持单表查询) </h4>
<label for="From">查询的表格:</label>
<input type="text" id="From" name="From"><br><br>
<h4> Where 项(至多一项) </h4>
<label for="Where">查询的条件:</label>
<input type="text" id="Where" name="Where"><br><br>
<h4> Select 项(允许*号) </h4>
<label for="Select">查询的属性:</label>
<input type="text" id="Select" name="Select"><br><br>
<input type="submit" value="查询">
</form>
</body>
</html>

留意这个action="/select_result/"这是发送请求的目的urls

运行一下,就能看到这个比较简陋的页面:

这里的查询逻辑写得很有限,但是无妨,功能都是可以扩展的。

尝试一下查询,可以看到运行栏有这样的提示:

这就说明前端已经发送消息返回了。

为了查看消息,接着我们在views.py文件里添加select的实现函数:

1
2
3
4
5
6
7
8
9
10
def select(request):                        # 查询
_from = request.POST.get('From', 'no_found')
_where = request.POST.get('Where', 'no_found')
_select = request.POST.get('Select', 'no_found')
context = {
'From': _from,
'Where': _where,
'Select': _select,
}
return render(request, 'select.html', context)

暂时先不进行表的查询,这里只是返回原本的查询语句。

当然前端要通过urls调用这个函数,就要通过之前的action="/select_result/",我们还是将其加入DjangoProject/urls.py的对应位置:

1
2
3
4
5
from LuvSQL.views import select
urlpatterns = [
# 进行查询
path('select_result/', select),
]

接着给select.html里加入显示返回结果的标签:

1
2
3
4
<p>查询语句:
Select ( {{ Select }} )
From ( {{ From }} )
Where ( {{ Where }} ) </p>

尝试发送查询请求,就能看见网页结果了。

其实这里的实现是有些别扭的,函数查询的主体不应该写在views.py中,不过小程序的话无妨。

后端2

SQL的查询语句已经能在后端接收到了,接着我们要处理它,返回结果。

models.py中,

先在各个models.Model的继承类中加入col_list列表,存放列名。

写出查询函数,这里给我个人的实现方案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
def mod_select(_from, _where, _select):         # 查询
# from
table = globals().get(_from) # globals()获取类
if table is None: # 没有这个表直接返回None
return None
# where
where_list = {} # 下面得到这个参数列表
if _where == '': # 无条件查询
pass
else: # 有条件则先分割条件
where_list_s = _where.split(',') # 按逗号分隔
for case in where_list_s:
temp = case.split('=') # 按等号分隔
for i in range(len(temp)):
temp[i] = temp[i].strip() # 去除多余空格
where_list[temp[0]] = temp[1] # 放入结果中
# select
select_list = [] # 下面得到这个参数列表
if _select == '*': # 全部查询
select_list = table.col_list
else: # 将要查找的列放入参数列表
select_list_s = _select.split(',') # 按逗号分隔
for i in range(len(select_list_s)):
select_list_s[i] = select_list_s[i].strip() # 去除多余空格
select_list.append(select_list_s[i]) # 放入结果中
# 开始查询
# 这里传两个参数列表
data_list = table.objects.filter(**where_list).values(*select_list)
# 这里得到的是一个dict的集合,每个dict是一行数据项,key为属性,val为内容
# 返回查询结果
return data_list

这是后端处理查询请求的核心,可以看到这里只支持单表查询,目前阶段能这样就够了。

views.py开始导入刚才定义在models.py中的函数:

1
from LuvSQL.models import mod_select

select中调用这个函数:

1
2
3
4
def select(request):
# 调用mod_select函数
result = mod_select(_from, _where, _select)
print(result)

这里使用了print函数,我们先给本地的MySQLBook表中随便添加一项数据,方便测试。

在网页中尝试查询刚才添加的项:

本地应该能输出如下结果:

把它放入content中:

1
2
3
4
5
6
7
8
9
# 处理成带有换行符的字符串
ret_s = ""
for i in range(len(result)):
ret_s += str(result[i])
ret_s += '\n'
# 返回内容
context = {
'Result': ret_s,
}

别忘了在html文件里加入显示结果的标签:

1
2
<h4> 查询结果: </h4>
<div style="white-space:pre">{{ Result }}</div>

使用<div style="white-space:pre">是为了能让它换行

这边显示结果比较潦草,感兴趣完全可以将结果装饰得好很多。

对它使用查询吧!

解决一些简单的BUG

由于上面的mod_select函数在table输入不合法时会返回None,所以会产生问题,加一个特判即可:

1
2
3
# 处理table填写错误或未填写的问题
if result is None: # 直接返回即可
return render(request, 'select.html')

当查询的Select项为空时,默认视为"*"即可,也方便了网页的查询填写:

1
2
3
4
5
6
# select
select_list = [] # 下面得到这个参数列表
if _select == '*' or _select == "": # 全部查询
select_list = table.col_list
else: # 将要查找的列放入参数列表
# # #

如此一来,可以在只输入表名而不输入其他查询信息时返回整个表:

那么一个简单实用的查询功能就做完了。

当然,实际上的SQL语句解析应该是靠语法树解析得到的,这里的实现很直接也很简单,像遇到不存在的列名等问题会缺乏查错机制,不过都是可以完善的。

增添操作

前端2

增、删、改这些操作是类似的,这里还是用同一个流程处理。

先填写insert.html文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>LuvSQL插入</title>
</head>
<body>
<h2>请输入您的插入内容</h2>
<form method="post" action="/insert_result/">
{% csrf_token %}
<h4> Into 项 </h4>
<label for="Into">插入的表格:</label>
<input type="text" id="Into" name="Into"><br><br>
<h4> Values 项 </h4>
<label for="Values">插入值:(要求必须完整格式)</label>
<input type="text" id="Values" name="Values"><br><br>
<input type="submit" value="插入">
<p> 插入语句:
Insert into ( {{ Into }} )
Values ( {{ Values }} )</p>
<h4> 插入结果: </h4>
<div style="white-space:pre">{{ Result }}</div>
</form>
</body>
</html>

借鉴select功能,对应填写urls.py文件,以及insert函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
def insert(request):                        # 插入
# 插入请求
_into = request.POST.get('Into', 'no_found')
_values = request.POST.get('Values', 'no_found')
# 调用mod_insert函数
result = mod_insert(_into, _values)
# 返回内容
context = {
'Into': _into,
'Values': _values,
'Result': result,
}
return render(request, 'insert.html', context)

后端2

设计insert函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def mod_insert(_into, _values):                 # 插入
# into
table = globals().get(_into) # globals()获取类
if table is None: # 没有这个表直接返回None
return None
# values
values_list = {} # 下面得到这个参数列表
values_list_s = _values.split(',') # 按逗号分隔
for case in values_list_s:
temp = case.split('=') # 按等号分隔
for i in range(len(temp)):
temp[i] = temp[i].strip() # 去除多余空格
values_list[temp[0]] = temp[1] # 放入结果中
# 创建实体对象
obj = table.objects.create(**values_list)
# 保存
obj.save()
# 返回SUCCESS
return "SUCCESS"

尝试一下,插入成功返回SUCCESS

再查询一下,可以看到多了对应项:

增添操作完成了。

删除操作

在清楚前两个操作的逻辑之后,其实希望之后的操作都能由读者自己完成。

前端2

先填写delete.html文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>LuvSQL删除</title>
</head>
<body>
<h2>请输入您的删除内容</h2>
<form method="post" action="/delete_result/">
{% csrf_token %}
<h4> From 项 </h4>
<label for="From">删除的表格:</label>
<input type="text" id="From" name="From"><br><br>
<h4> Where 项(只允许相等条件) </h4>
<label for="Where">删除的条件:</label>
<input type="text" id="Where" name="Where"><br><br>
<input type="submit" value="删除">
<p>删除语句:
Delete from ( {{ From }} )
Where ( {{ Where }} ) </p>
<h4> 删除结果: </h4>
<div style="white-space:pre">{{ Result }}</div>
</form>
</body>
</html>

然后设计前端delete函数并补充相应设置:

1
2
3
4
5
6
7
8
9
10
11
12
13
def delete(request):                        # 删除
# 删除请求
_from = request.POST.get('From', 'no_found')
_where = request.POST.get('Where', 'no_found')
# 调用mod_delete函数
result = mod_delete(_from, _where)
# 返回内容
context = {
'From': _from,
'Where': _where,
'Result': result,
}
return render(request, 'delete.html', context)

后端2

类似前面设计mod_delete函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def mod_delete(_from, _where):                  # 删除
# from
table = globals().get(_from) # globals()获取类
if table is None: # 没有这个表直接返回None
return None
# where
where_list = {} # 下面得到这个参数列表
if _where == '': # 无条件删除(即全删除)
pass
else: # 有条件则先分割条件
where_list_s = _where.split(',') # 按逗号分隔
for case in where_list_s:
temp = case.split('=') # 按等号分隔
for i in range(len(temp)):
temp[i] = temp[i].strip() # 去除多余空格
where_list[temp[0]] = temp[1] # 放入结果中
# 删除
# 这里传参数列表
obj = table.objects.filter(**where_list).delete()
# 返回SUCCESS
return "SUCCESS"

这里的实现中,如果没有输入条件,就是认为删除所有数据项

这里给Reader表先插入一个数据:

然后可以尝试一下删除操作:

查询Reader表,应该是一个空表:

删除操作完成了。

更新操作

前端2

更新的话,我希望能有一个更新前后的对比,所以先执行一次查询操作,再执行更新操作,都返回表项,并列展示。

先填写update.html文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>LuvSQL更新</title>
</head>
<body>
<h2>请输入您的更新内容</h2>
<form method="post" action="/update_result/">
{% csrf_token %}
<h4> Update 项 </h4>
<label for="Update">更新的表格:</label>
<input type="text" id="Update" name="Update"><br><br>
<h4> Where 项(只允许相等条件) </h4>
<label for="Where">更新的条件:</label>
<input type="text" id="Where" name="Where"><br><br>
<h4> Set 项 </h4>
<label for="Set">更新的属性:</label>
<input type="text" id="Set" name="Set"><br><br>
<input type="submit" value="更新">
<p> 更新语句:
Update ( {{ Update }} )
Set ( {{ Set }} )
Where ( {{ Where }} ) </p>
<h4> 更新结果(更新前): </h4>
<div style="white-space:pre">{{ Result1 }}</div>
<h4> 更新结果(更新后): </h4>
<div style="white-space:pre">{{ Result2 }}</div>
</form>
</body>
</html>

这时候views.pymodels.py分离的好处就体现出来了,可以简洁地先调用一次mod_select再调用一次mod_update

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def update(request):                        # 更新
# 更新请求
_update = request.POST.get('Update', 'no_found')
_set = request.POST.get('Set', 'no_found')
_where = request.POST.get('Where', 'no_found')
# 先调用mod_select函数
result1 = mod_select(_update, _where, '*')
# 处理table填写错误或未填写的问题
if result1 is None: # 直接返回即可
return render(request, 'update.html')
# 处理成带有换行符的字符串(略)
# 然后调用mod_update函数
result2 = mod_update(_update, _set, _where)
# 处理成带有换行符的字符串(略)
# 返回内容
context = {
'Update': _update,
'Set': _set,
'Where': _where,
'Result1': ret_s_1,
'Result2': ret_s_2,
}
return render(request, 'update.html', context)

后端2

models.py设计mod_update函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def mod_update(_update, _set, _where):          # 更新
# update
table = globals().get(_update) # globals()获取类
if table is None: # 没有这个表直接返回None
return None
# where
where_list = {} # 下面得到这个参数列表
if _where == '': # 无条件更新(即全更新)
pass
else: # 有条件则先分割条件(略)
# set
set_list = {} # 下面得到这个参数列表
if _set == '': # 这项没有则不更新了
pass
else: # 分割更新项(略)
# 开始更新
# 这里分别传两个参数列表
data_list = table.objects.filter(**where_list)
data_list.update(**set_list)
# 这里得到的是一个dict的集合,每个dict是一行数据项,key为属性,val为内容
# 向select一样返回更新结果
return data_list.values()

重新向Reader表插入项:

尝试修改该项,让项羽回归西楚霸王

更新操作完成了。

功能完善

子页面向主页的返回

在测试的过程中发现,每次从子页面回主页面要手动修改url,有点废手,在各个子页面里添加回主页的标签:

1
<a href="../"> <h2>回到主页</h2> </a>

这样就能方便返回主页了:

完结撒花

恭喜,到此为止你完成了一个简单的Web数据库。

其实本来还想加上CSS使得网页更好看的,结果由于跨域请求等问题就放弃了。没事的,这一次写这个Web还是让我自身也回顾了很多东西。

ο(=•ω<=)ρ⌒☆