目录
创建项目app
路由子表
数据库
创建数据库
什么是ORM
定义数据库表
Django Admin 管理数据
过滤条件
代码直接生成HTML
使用模板
前后端分离架构
对资源的增删改查处理
列出客户
添加客户
临时取消 CSRF 校验
修改客户信息
删除客户
Django中ORM的处理
数据模型的配置
字段设置
字段参数
数据更新的实现
创建项目app
Django中的一个app就是项目里面的一个应用的意思,一个项目包含多个app,一个app通常就是一个相对独立的模块,实现相对独立的功能。比如我们可以把系统中的管理员管理的功能放在一个名为mgr的app里面,把销售人员的操作实现在另一个名为sales的app里面。
Django中创建app可以通过执行命令,创建一个app目录,并在里面自动创建app常用的文件。
进入项目根目录,执行以下命令:
python manage.py startapp sales
这样就会创建一个目录名为 sales, 对应 一个名为 sales 的app,里面包含了如下自动生成的文件:
migrations
__init__.py
admin.py
apps.py
models.py
tests.py
views.py
路由子表
一个请求对应一个处理函数,项目较大时,请求的url会特别多,我们可以将不同的路由记录按照功能分拆到不同的url路由子表中。
数据库
后端开发基本需要操作数据,包括数据的存储、查询、修改、删除。通常,这些都是通过数据来完成,目前业界最广泛使用的数据库还是:关系型数据库。
关系型数据库系统,常用的开源数据库有mysql和postgresql.
创建数据库
项目中数据库的配置在settings.py中。
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql', # 数据库类型
'NAME': 'core', # 所使⽤的数据库的名字
'USER': 'root', # 数据库服务器的⽤户
'PASSWORD': 'xxxxxx', # 密码
'HOST': '127.0.0.1', # 主机
'PORT': '3306', # 端⼝
}
}
执行以下命令,创建数据库:
python manage.py migrate
项目的根目录下面生成一个配置文件中指定的数据库文件:db.sqlite3。
并且会在其中创建一些表。
什么是ORM
Django里面,数据库的操作,包括数据的增删改查,基本都是通过Model类型的对象进行的。
通常,在Django中:
- 定义一张数据库表就是定义一个继承自django.db.models.Model的类
- 定义该表中的字段(列),就是定义该类里面的一些属性
- 类的方法就是对该表中数据的处理方法,包括数据的增删改查
这样,开发者对数据库的访问,从原来的使用底层的sql语句,变成面向对象的开发,通过一系列对象的类定义和方法调用就可以操作数据库。
这样做:
首先极大地简化了我们应用中的数据库开发,因为无需使用sql语句操作数据库了,提高了开发的效率。
其次屏蔽了不同的数据库访问的底层细节,开发好代码后,如果要换数据库,几乎不需要改代码,修改几个配置项即可。
这种通过对象操作数据库的方法被称为ORM(object relational mapping)。
Django的ORM(对象关系映射)是一个强大的工具,用于在Django应用程序中管理数据库,借助它我们可以使用Python代码而不是编写SQL语句来操作数据库,这是因为Django的ORM提供了一个高级的抽象层,通过定义Python类来表示数据库表,并使用这些类来执行常见的数据库操作,如创建、读取、更新和删除数据。
定义数据库表
我们再创建一个名为common的应用目录,里面存放我们项目需要的一些公共的表的定义。
执行以下命令:
python manage.py startapp common
创建了一个名为common的app.
打开common/models.py,发现里面是空的,因为我们还没有定义我们的业务所需要的表。
加入以下内容:
from django.db import models
class Customer(models.Model):
name = models.CharField(max_length=100)
phonenumber = models.CharField(max_length=100)
address = models.CharField(max_length=100)
这个Customer类继承自django.db.models.Model,就是用来定义数据库表的,里面的name,phonenumber,address是该表的3个字段。
CharField对象对应的是varchar类型的数据库字段。定义表中的字段就是定义一些静态属性,这些属性是django.db.models里面的各种Field对象,对应不同类型的字段。
在settings.py中加入一句代码:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'sales',
# 加入以下代码:
'common.apps.CommonConfig'
]
'common.apps.CommonConfig' 告诉 Django,CommonConfig 是 common/apps.py 文件中定义的一个应用配置的类。
class CommonConfig(AppConfig):
name = 'common'
CommonConfig 是 AppConfig的 子类, 就表示这个是应用的配置类。
这里 name = 'common' , name 是用来定义 应用的python模块路径的。也就是说 应用 模块路径为 common 。
Django知道了我们的common应用,可以在项目根目录下执行命令
python manage.py makemigrations common
得到以下结果:
Migrations for 'common':
common\migrations\0001_initial.py
- Create model Customer
会发现在 common\migrations 目录下面出现了0001_initial.py, 这个脚本就是相应要进行的数据库操作代码。
随即,执行如下命令:
python manage.py migrate
查看数据库,发现创建了一张名为 common_customer的表。
多出来的 id 字段是该表的主键, 是Django自动帮我们添加的。
这个不需要我们在类中显式的定义。
注意:
如果以后我们修改了Models.py 里面的库表的定义,都需要再次运行 python manage.py makemigrations common 和 python manage.py migrate 命令,使数据库同步该修改结果
Django Admin 管理数据
Django提供了一个管理员操作界面,可以方便地添加、修改、删除定义的model表数据。
首先,我们需要创建一个超级管理员账号,进入到项目的根目录,执行以下命令,依次输入要创建的管理员的登录名、email、密码。
python manage.py createsuperuser
Username (leave blank to use '21544'): present
Error: That username is already taken.
Username (leave blank to use '21544'): present-01
Email address: 0123@qq.com
Password:
Password (again):
Superuser created successfully.
然后需要修改应用里面的管理员配置文件common/admin.py,注册我们定义的model类,这样Django才会知道
from django.contrib import admin
from .models import Customer
admin.site.register(Customer)
然后可以访问 http://127.0.0.1/admin/
,输入刚才注册的用户密码登录。
登录后可以看到如下界面。这里面是目前系统中可以修改的表。
点击ADD,添加用户之后点击SAVE,使用数据库查看工具,就发现数据库中确实有了添加的数据信息:
我们先实现一个函数,来处理sales/customers/访问请求,返回数据库中customer表所有记录。
Django中对数据库表的操作,建议都是通过其对应Model对象的方法来进行的,而不是SQL语句。
比如我们要获取customer表所有记录,该表是和我们前面定义的Customer类管理的,我们可以这样获取所有表的记录:
在文件sales/views.py中,定义一个listcustomers函数,内容如下:
# 导入 Customer 对象定义
from common.models import Customer
def listcustomers(request):
# 返回一个 QuerySet 对象 ,包含所有的表记录
# 每条表记录都是是一个dict对象,
# key 是字段名,value 是 字段值
qs = Customer.objects.values()
# 定义返回字符串
retStr = ''
for customer in qs:
for name,value in customer.items():
retStr += f'{name} : {value} | '
# <br> 表示换行
retStr += '<br>'
return HttpResponse(retStr)
Customer.objects.values() 会返回一个 QuerySet 对象,这个对象是Django 定义的,在这里它包含所有的Customer 表记录,QuerySet对象可以使用for循环遍历取出里面所有的元素,每个元素对应一条表记录。每条表记录元素都是一个dict对象,其中每个元素的key是表字段名,value是该记录的字段值。上面的代码可以将每条记录的信息存储到字符串中,返回给前端浏览器。
还需要修改路由表,加上对sales/customers/ url请求的路由。
在bysms/urls.py主路由文件中,已有以下记录:
# 凡是 url 以 sales/ 开头的,
# 都根据 sales.urls 里面的 子路由表进行路由
path('sales/', include('sales.urls')),
我们只需修改 sales/urls.py
即可,添加如下记录:
path('customers/', views.listcustomers),
然后可以在浏览器输入如下 网址: http://127.0.0.1/sales/customers/
回车后,浏览器显示结果类似如下:
和我们数据库中的记录信息一致:
过滤条件
有的我们需要根据过滤条件查询部分客户信息。
比如,当用户在浏览器输入 /sales/customers/?phonenumber=13000000001
,要求返回电话号码为 13000000001 客户记录。
我们可以通过 filter
方法加入过滤条件,修改view里面的代码,如下所示:
def listcustomers(request):
# 返回一个 QuerySet 对象 ,包含所有的表记录
qs = Customer.objects.values()
# 检查url中是否有参数phonenumber
ph = request.GET.get('phonenumber',None)
# 如果有,添加过滤条件
if ph:
qs = qs.filter(phonenumber=ph)
# 定义返回字符串
retStr = ''
for customer in qs:
for name,value in customer.items():
retStr += f'{name} : {value} | '
# <br> 表示换行
retStr += '<br>'
return HttpResponse(retStr)
Django框架在url路由匹配到函数后,调用函数时,会传入一个HttpRequest对象给参数变量request,该对象里面包含了请求的数据信息。
HTTP的GET请求url里面的参数(术语叫querystring里面的参数),可以通过HttpRequest对象的GET属性获取,这是一个类似dict的对象。
比如要获取querystring里面的phonenumber参数,就可以像这样:
ph = request.GET.get('phonenumber',None)
第二个参数传入None表示,如果没有phonenumber参数在querystring中,就会返回None
然后调用QuerySet对象的filter方法,就可以把查询过滤条件加上去
qs = qs.filter(phonenumber=ph)
有了这个过滤条件,Django 会在底层执行数据库查询的SQL语句 加上相应的 where 从句,进行过滤查询。注意,参数名 phonenumber 是和 定义的表 model 的属性名 phonenumber 一致的。
filter的过滤条件可以有多个,只要继续在后面的参数添加过滤条件即可。
代码直接生成HTML
HTML本身其实也是字符串,只是这个字符串里面的内容是符合HTML语言规范的。
既然它也是字符串,我们可以使用Python直接构建出 HTML 字符串内容。
修改:
# 先定义好HTML模板
html_template ='''
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
table {
border-collapse: collapse;
}
th, td {
padding: 8px;
text-align: left;
border-bottom: 1px solid #ddd;
}
</style>
</head>
<body>
<table>
<tr>
<th>id</th>
<th>姓名</th>
<th>电话号码</th>
<th>地址</th>
</tr>
%s
</table>
</body>
</html>
'''
def listcustomers(request):
# 返回一个 QuerySet 对象 ,包含所有的表记录
qs = Customer.objects.values()
# 检查url中是否有参数phonenumber
ph = request.GET.get('phonenumber',None)
# 如果有,添加过滤条件
if ph:
qs = qs.filter(phonenumber=ph)
# 生成html模板中要插入的html片段内容
tableContent = ''
for customer in qs:
tableContent += '<tr>'
for name,value in customer.items():
tableContent += f'<td>{value}</td>'
tableContent += '</tr>'
return HttpResponse(html_template%tableContent)
我们用一个变量 html_template 存储html模板,代码中生成html 里面需要插入的表格记录的内容,这个内容是html片段,也就是 html 表格的每行 。
最后填入到 html_template 模板里面,就产生了完整的HTML 字符串。
最后返回该 html 文档 字符串 即可。
修改后,再次访问 http://127.0.0.1/sales/customers/
得到如下内容:
使用模板
上面我们是用Python代码直接拼接出html内容。
但是这种方式处理代码比较麻烦。特别是当html里面有多处内容需要填入时,使用Python代码直接拼接就显得很繁杂,不好维护。
很多后端框架都提供了一种 模板技术, 可以在html 中嵌入编程语言代码片段, 用模板引擎(就是一个专门处理HTML模板的库)来动态的生成HTML代码。
我们修改一下代码,使用Django的模板引擎:
# 先定义好HTML模板
html_template ='''
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
table {
border-collapse: collapse;
}
th, td {
padding: 8px;
text-align: left;
border-bottom: 1px solid #ddd;
}
</style>
</head>
<body>
<table>
<tr>
<th>id</th>
<th>姓名</th>
<th>电话号码</th>
<th>地址</th>
</tr>
{% for customer in customers %}
<tr>
{% for name, value in customer.items %}
<td>{{ value }}</td>
{% endfor %}
</tr>
{% endfor %}
</table>
</body>
</html>
'''
from django.template import engines
django_engine = engines['django']
template = django_engine.from_string(html_template)
def listcustomers(request):
# 返回一个 QuerySet 对象 ,包含所有的表记录
qs = Customer.objects.values()
# 检查url中是否有参数phonenumber
ph = request.GET.get('phonenumber',None)
# 如果有,添加过滤条件
if ph:
qs = qs.filter(phonenumber=ph)
# 传入渲染模板需要的参数
rendered = template.render({'customers':qs})
return HttpResponse(rendered)
然后,访问浏览器,可以得到一样的结果。
对比 Python直接产生 HTML,可以发现使用模板引擎的好处,就是产生HTML的代码更简单方便了。
因为我们可以直接把要生成的 HTML片段 写在 HTML模板 里面。
然后,只需要传入渲染模板所需要的参数就可以了,模板引擎会自动化帮我们生成HTML
前后端分离架构
界面完全交给前端开发人员去做, 后端开发只需要提供前端界面所需要的数据即可。
前端和后端之间的交互就完全是业务处理了,需要定义好前端和后端交互数据的接口
目前通常这样的接口设计最普遍的就是使用REST风格的API接口。
前端通过API接口从后端获取数据展示在界面上,也通过API接口告诉后端需要更新的数据是什么。
通常前后端的API接口是由架构师设计的,有时也可以由经验丰富的前端开发者或者后端开发者设计。
对资源的增删改查处理
前面我们已经为 销售员用户 专门创建了一个应用 sales 来处理相关的 请求,我们可以 再为 管理员用户 专门创建一个应用 mgr 来处理相关的 请求。
执行以下命令:
python manage.py startapp mgr
我们可以发现对资源的增删改查 操作, 都是同一个URL,都是 /api/mgr/customers
。
而且我们发现,不同的操作请求,使用不同的 HTTP 请求方法 ,比如 添加是POST, 查询是 GET, 修改是 PUT, 删除是 DELETE。
而且请求的参数中都有 action 参数表明这次请求的操作具体是什么。
注意:Django 的 url路由功能 不支持 根据 HTTP 请求的方法 和请求体里面的参数 进行路由。
就是不能像下面这样,来根据请求 是 post 还是 get 来 路由:
path('customers/', 'app.views.list_customer', method='get'),
path('customers/', 'app.views.add_customer', method='post'),
有一种方式是:自己编写一个函数, 来 根据 http请求的类型 和请求体里面的参数 分发(或者说路由)给 不同的函数进行处理。
我们可以 在 customer.py 中定义如下 dispatcher 函数:
def dispatcher(request):
# 将请求参数统一放入request 的 params 属性中,方便后续处理
# GET请求 参数在url中,同过request 对象的 GET属性获取
if request.method == 'GET':
request.params = request.GET
# POST/PUT/DELETE 请求 参数 从 request 对象的 body 属性中获取
elif request.method in ['POST','PUT','DELETE']:
# 根据接口,POST/PUT/DELETE 请求的消息体都是 json格式
request.params = json.loads(request.body)
# 根据不同的action分派给不同的函数进行处理
action = request.params['action']
if action == 'list_customer':
return listcustomers(request)
elif action == 'add_customer':
return addcustomer(request)
elif action == 'modify_customer':
return modifycustomer(request)
elif action == 'del_customer':
return deletecustomer(request)
else:
return JsonResponse({'ret': 1, 'msg': '不支持该类型http请求'})
该函数 把 请求消息中的参数统一放入到 request请求对象的params 属性中。
params 属性 被 做成一个 dict 类型 , 方便后面的处理函数来获取消息中的参数。
然后 dispatch函数再根据 请求的 类型 和 action 参数的值 决定由那个函数具体处理该请求消息。
比如 action 参数 为 'add_customer' 的 请求 就由 addcustomer 函数 进行处理。
当然在文件的开头,我们需要 先导入 JsonResponse 和 json 的定义,像下面这样:
from django.http import JsonResponse
import json
接下来,根据 API 接口 ,我们发现 凡是 API 请求url为 /api/mgr/customers
的,都属于 客户 相关的API, 都应该交由 我们上面定义的dispatch函数进行分派处理。
那么我们需要在Django的url路由文件中加入对应的路由
第一步:我们应该在 总路由文件 bysms/urls.py
中定义了如下部分:
# 凡是 url 以 api/mgr 开头的,
# 都根据 mgr.urls 里面的 子路由表进行路由
path('api/mgr/', include('mgr.urls')),
第二步: 在 mgr 目录下面添加 urls.py 路由文件, 并 加入如下声明即可, 如下所示:
from django.urls import path
from mgr import customer
urlpatterns = [
path('customers', customer.dispatcher),
]
这样,就表示 凡是 API 请求url为 /api/mgr/customers
的,都交由 我们上面定义的dispatch函数进行分派处理 。
列出客户
根据接口文档,列出客户数据接口,后端返回的数据格式如下:
{
"ret": 0,
"retlist": [
{
"address": "江苏省常州武进市白云街44号",
"id": 1,
"name": "武进市 袁腾飞",
"phonenumber": "13886666666"
},
{
"address": "北京海淀区",
"id": 4,
"name": "北京海淀区代理 蔡国庆",
"phonenumber": "13990123456"
}
]
}
这里我们无需 将数据库中获取的数据 转化为 供浏览器展示的HTML。
在前后端分离的开发架构中,如何展示数据是前端的事情。后端只需要根据接口文档, 返回原始数据即可。
我们可以使用如下的函数来返回数据库的所有的 客户数据信息
def listcustomers(request):
# 返回一个 QuerySet 对象 ,包含所有的表记录
qs = Customer.objects.values()
# 将 QuerySet 对象 转化为 list 类型
# 否则不能 被 转化为 JSON 字符串
retlist = list(qs)
return JsonResponse({'ret': 0, 'retlist': retlist})
当然在文件的开头,我们需要 先导入 Customer 定义,像下面这样:
# 导入 Customer
from common.models import Customer
添加客户
根据接口文档,添加客户数据接口,前端提供的客户数据格式如:
{
"action":"add_customer",
"data":{
"name":"武汉市桥西医院",
"phonenumber":"13345679934",
"address":"武汉市桥西医院北路"
}
}
我们可以使用如下的函数来处理:
def addcustomer(request):
info = request.params['data']
# 从请求消息中 获取要添加客户的信息
# 并且插入到数据库中
# 返回值 就是对应插入记录的对象
record = Customer.objects.create(name=info['name'] ,
phonenumber=info['phonenumber'] ,
address=info['address'])
return JsonResponse({'ret': 0, 'id':record.id})
Customer.objects.create
方法就可以添加一条Customer表里面的记录。
临时取消 CSRF 校验
根据接口文档,添加客户 请求是个Post请求
POST /网站名/api/mgr/signin HTTP/1.1
Content-Type: application/x-www-form-urlencoded
注意,新创建的项目, Django 缺省会启用一个 CSRF (跨站请求伪造) 安全防护机制。
在这种情况下, 所有的Post、PUT 类型的 请求都必须在HTTP请求头中携带用于校验的数据。
为了简单起见,我们先临时取消掉CSRF的 校验机制,等以后有需要再打开。
要临时取消掉CSRF的 校验机制,非常简单,只需要在 项目的配置文件 bysms/settings.py
中 MIDDLEWARE
配置项 里 注释掉 'django.middleware.csrf.CsrfViewMiddleware' 即可。
如下所示:
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
# 'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
修改客户信息
根据接口文档,修改客户数据接口,前端提供的数据格式如下:
{
"action":"modify_customer",
"id": 6,
"newdata":{
"name":"武汉市桥北医院",
"phonenumber":"13345678888",
"address":"武汉市桥北医院北路"
}
}
我们可以使用如下的函数来处理:
def modifycustomer(request):
# 从请求消息中 获取修改客户的信息
# 找到该客户,并且进行修改操作
customerid = request.params['id']
newdata = request.params['newdata']
try:
# 根据 id 从数据库中找到相应的客户记录
customer = Customer.objects.get(id=customerid)
except Customer.DoesNotExist:
return {
'ret': 1,
'msg': f'id 为`{customerid}`的客户不存在'
}
if 'name' in newdata:
customer.name = newdata['name']
if 'phonenumber' in newdata:
customer.phonenumber = newdata['phonenumber']
if 'address' in newdata:
customer.address = newdata['address']
# 注意,一定要执行save才能将修改信息保存到数据库
customer.save()
return JsonResponse({'ret': 0})
删除客户
根据接口文档,删除客户数据接口,前端只需要提供要删除的客户的ID。
数据格式如下:
{
"action":"del_customer",
"id": 6
}
我们可以使用如下的函数来处理:
def deletecustomer(request):
customerid = request.params['id']
try:
# 根据 id 从数据库中找到相应的客户记录
customer = Customer.objects.get(id=customerid)
except Customer.DoesNotExist:
return {
'ret': 1,
'msg': f'id 为`{customerid}`的客户不存在'
}
# delete 方法就将该记录从数据库中删除了
customer.delete()
return JsonResponse({'ret': 0})
Django中ORM的处理
模型(Models):核心组件,一个模型类代表了一个数据库表,而类的属性则对应着表的字段
字段(Fields):模型类的属性通常被称为字段,各种字段类型在定义模型类中的属性和数据库表中的字段
查询集(QuerySets):数据库查询的方法,允许以链式方式构建复杂的查询,过滤、排序和限制结果集等操作
关联(Relations):定义模型之间的关联关系,关系使得进行数据查询和操作变得更加容易
迁移(Migrations):用于管理数据库模式的变化,更改模型定义时,可以使用迁移工具自动更新数据库表结构,而无需手动编写SQL语句
数据模型的配置
字段设置
AutoField:自增
IntegerField:整数字段,用于存储整数值
FloatField:浮点字段,存储小叔数值
CharField:字符字段,用于存储字符串
TextField:文本字段,用于存储长文本
字段参数
max_length:针对字符串或者长文本指定字段的最大长度
default:指定字段的默认值
null:设置字段是否允许为null
db_column:设置列的别名
on_delete:在外键设置中指定在关联的对象被删除时的处理方式
models.py中的代码:
from django.db import models
# Create your models here.
# 学校信息
class School(models.Model):
id = models.AutoField('记录编号', primary_key=True) # 主键
name = models.CharField('学校名称', max_length=32, null=False)
address = models.CharField('学校地址', max_length=200)
class Meta:
db_table = 'father_school' # 表名
# 校长信息
class Principal(models.Model):
id = models.AutoField('记录编号', primary_key=True) # 主键
name = models.CharField('校长名称', max_length=20, null=False)
school = models.OneToOneField(School, on_delete=models.CASCADE, db_column="school_id")
class Meta:
db_table = 'father_principal' # 表名
# 院系信息
class Department(models.Model):
id = models.AutoField('记录编号', primary_key=True) # 主键
name = models.CharField('院系名称', max_length=32, null=False)
school = models.ForeignKey(School, on_delete=models.CASCADE, db_column="school_id")
class Meta:
db_table = 'father_department' # 表名
# 授课教师
class Teacher(models.Model):
id = models.AutoField('记录编号', primary_key=True) # 主键
name = models.CharField('教师名称', max_length=20, null=False)
Department = models.ForeignKey(Department, on_delete=models.CASCADE, db_column="department_id")
class Meta:
db_table = 'father_teacher' # 表名
# 学生信息
class Student(models.Model):
id = models.AutoField('记录编号', primary_key=True) # 主键
name = models.CharField('学生名称', max_length=20, null=False)
gender = models.CharField('学生性别', default='男', max_length=2, null=False)
age = models.IntegerField('学生年龄', default=18, null=False)
school = models.ForeignKey(School, on_delete=models.CASCADE, db_column="school_id")
Teacher = models.ManyToManyField(Department, db_column="student_id")
class Meta:
db_table = 'father_student' # 表名
数据更新的实现
添加信息:
insert into 表名(列名列表) values(值列表)
修改信息:
update 表名 set 列名=值... where 修改条件
删除信息:
delete from 表名 where 修改条件