Django开发学习笔记

Web 开发简介

Web 应用技术

web系统,通常可以分为 : 客户端 和 服务端 。

客户端的浏览器获取信息------使用http协议(Hypertext Transfer Protocol)。

客户端通过http协议发送请求信息给服务端,并从服务端接收响应信息。

Web 前端开发

Web 前端开发的重点是 : 提供用户界面给 用户进行观看和操作。

  • html文件

用来显示界面给用户看,各种美观的web网页就是通过解释html实现的。

  • CSS

CSS 文档控制界面的显示样式和效果,比如字体、大小、前景色、背景色、间距、一些动画效果等等,就是让你的界面更好看。

  • 资源文件

显示在界面上的图片、视频等。

  • javascript 脚本 ( 简称 js )

js 和 python 一样是一种编程语言。python脚本通过python解释器执行,js脚本通过浏览器内置的

js 引擎执行。

注意:html 和 css 文档 只是定义了一些静态的界面内容。前端的动态功能, 就是通过浏览器执

行 js脚本产生的。

这些文件开发出来后, 最终都是通过 浏览器来运行,展示出界面来给用户观看和操作的。

(html,css,js)没法直接放到浏览器端,都是部署在后端服务器上(有些在cdn上)。

浏览一个网站的时候,浏览器先通过http协议获取这些文档,然后读取解释它们的内容,生成对应

的界面呈现给我们操作。

Web 后端开发

后端开发服务进程,处理前端http请求,返回相应的数据。

通常包括数据的 查询、增加、删除、修改。

  • 静态文件服务方案

就是前端开发出来的HTML、css、js文件存储在什么地方,使用什么的服务提供给前端浏览器访

问。 通常一个比较大型的网站, 静态文件往往会使用单独的服务器专门提供服务,甚至一部分特

别消耗带宽的数据(比如视频、图片)会使用第三方的云服务厂商(比如阿里云的cdn和oss服

务)。

  • API 接口设计

定义前端和后端交互接口规范。

  • 数据库存储方案

选择什么样的数据库,包括关系型和非关系型的数据库。

  • 数据库表结构设计

要能合理、高效的存储业务数据,这样才能高效查询、修改各种关联性的数据。

  • 缓存服务和异步任务服务

为了提高性能。

  • 云存储技术

将有的数据,如图片,视频等合理存储在云端。

使用 Django 开发后端服务

Django是一个 基于Python语言的开源免费的 Web应用 开发框架。

Django的 官方网站 https://www.djangoproject.com

HTTP协议

HTTP协议简介

HTTP 协议全称是超文本传输协议, 英文是 Hypertext Transfer Protocol 。

HTTP 最初是用来在浏览器和 网站服务器(web服务之间传输超文本(网页、视频、图片等)信息的。

HTTP 有好几个版本,包括: 0.9、1.0、1.1、2,当前最广泛使用的是 HTTP/1.1 版本。

HTTP 协议最大的特点是通讯双方为客户端 和 服务端 。

HTTP是基于TCP协议的, 所以要进行通讯,客户端 必须先和服务端创建 TCP 连接。

HTTP 双方的信息交互方式:

  • 客户端先发送 http请求(request)给服务端

  • 然后服务端发送 http响应(response)给客户端

特别注意:HTTP协议中,服务端不能主动先发送信息给客户端。

1.1 以前的版本, 服务端返回响应给客户端后,连接就会断开 ,下一次双方要进行信息交流,必

须重复上面的过程,重新建立连接,客户端发送请求,服务返回响应。

1.1 版本, 建立连接后,这个连接可以保持一段时间(keep alive), 这段时间,双方可以多次进

行请求和响应, 无需重新建立连接。

HTTP请求消息

http请求消息的示例

GET /mgr/login.html HTTP/1.1
Host: www.baiyueheiyu.com
User-Agent: Mozilla/6.0 (compatible; MSIE5.01; Windows NT)
Accept-Language: zh-cn
Accept-Encoding: gzip, deflate
POST /api/medicine HTTP/1.1
Host: www.baiyueheiyu.com
User-Agent: Mozilla/6.0 (compatible; MSIE5.01; Windows NT)
Content-Type: application/x-www-form-urlencoded
Content-Length: 51
Accept-Language: zh-cn
Accept-Encoding: gzip, deflate

name=qingmeisu&sn=099877883837&desc=qingmeisuyaopin

请求行 request line

http请求的第一行的内容,表示要操作什么资源,使用的 http协议版本是什么。

包含了3部分信息: 请求的方法,操作资源的地址, 协议的版本号。

GET /mgr/login.html HTTP/1.1

表示要获取资源, 资源的地址 /mgr/login.html , 使用的协议是 HTTP/1.1

POST /api/medicine HTTP/1.1

表示添加资源信息, 添加资源到 地址 /api/medicine , 使用的协议是 HTTP/1.1

常见的HTTP 请求方法包括:

  • GET

从服务器获取资源信息,这是一种最常见的请求。

比如要从服务器获取 网页资源、获取图片资源、获取用户信息数据等等。

  • POST

添加资源信息到服务器进行处理(例如提交表单或者上传文件)。

比如要添加用户信息、上传图片数据 到服务器等等。

  • PUT

请求服务器更新资源信息 。

比如要更新用户姓名、地址 等等。

  • DELETE

请求服务器删除资源信息 。

比如要删除某个用户、某个药品 等等。

请求头 request headers

请求头是http请求行下面的的内容,里面存放一些信息。

比如,请求发送的服务端域名是什么, 希望接收的响应消息使用什么语言,请求消息体的长度等

等。

单个请求头的格式是: 名字: 值

消息体 message body

请求的url、请求头中可以存放一些数据信息, 但是有些数据信息,往往需要存放在消息体中。

特别是 POST、PUT等请求,添加、修改的数据信息通常都是存放在请求消息体中的。

如果 HTTP 请求有消息体, 协议规定需要在消息头和消息体之间插入一个空行, 隔开它们。

请求消息体中保存了要提交给服务端的数据信息。

WEB API 请求消息体通常是某种格式的文本,常见的有

  • Json
  • Xml
  • www-form-urlencoded

HTTP响应消息

http响应消息的示例

HTTP/1.1 200 OK
Date: Thu, 19 Sep 2019 08:08:27 GMT
Server: WSGIServer/0.2 CPython/3.7.3
Content-Type: application/json
Content-Length: 37
X-Frame-Options: SAMEORIGIN
Vary: Cookie

{"ret": 0, "retlist": [], "total": 0}

状态行 status line

状态行在第一行,包含3个部分:

  • 协议版本----示例中,就是 HTTP/1.1
  • 状态码----示例中,就是 200
  • 描述状态的短语----示例中,就是 OK

状态码表示了服务端对客户端请求的处理结果 。

状态码用3位的数字来表示,第一位的数字代表处理结果的大体类型,常见的有如下几种:

  • 2xx  通常表示请求消息没有问题,而且服务器也正确处理了, 最常见的就是 200。
  • 3xx  重定向响应,常见的值是 301,302, 表示客户端的这个请求的url地址已经改变了, 需要客户端重新发起一个请求到另外的一个url。
  • 4xx  表示客户端请求有错误, 常见的值有:
    • 400 Bad Request 表示客户端请求不符合接口要求,比如格式完全错误
    • 401 Unauthorized 表示客户端需要先认证才能发送次请求
    • 403 Forbidden 表示客户端没有权限要求服务器处理这样的请求, 比如普通用户请求删除别人账号等
    • 404 Not Found 表示客户端请求的url 不存在
  • 5xx  表示服务端在处理请求中,发生了未知的错误。通常是服务端的代码设计问题,或者是服务端子系统出了故障(比如数据库服务宕机了)。

响应头 response headers

响应头是响应状态行下面的的内容,里面存放一些信息。 作用和格式与请求头类似。

消息体 message body

如果 HTTP响应 有消息体, 协议规定需要在消息头和消息体之间插入一个空行, 隔开它们。

和请求消息体一样,WEB API 响应消息体 通常也是某种格式的文本,常见的有:

  • Json
  • Xml
  • www-form-urlencoded

安装与运行

安装Django

Django 框架是用Python语言开发的, 所以安装Django 就像安装其他的 Python库一样,执行如下

命令即可。

pip install django

检查Django是否安装好, 并且查看安装的Django版本

python -m django --version

创建项目

安装好以后, 需要创建项目目录,项目目录里面保存了开发系统的所有文件。

可以创建 C:\Users\yang\PycharmProjects作为项目所在的目录。

从命令行窗口中进入到C:\Users\yang\PycharmProjects目录,执行下面的命令创建项目目录。

django-admin startproject yfsms

创建后的目录结构:

yfsms/
    manage.py
    yfsms/
        __init__.py
        settings.py
        urls.py
        wsgi.py
  • 最外层 yfsms/就是项目根目录C:\Users\yang\PycharmProjects\fysms, 项目文件都放在里面。

  • manage.py 是一个工具脚本,用作项目管理的。

  • 里面的yfsms/目录是python包。 里面包含项目的重要配置文件。这个目录名字不能随便改,因为manage.py 要用到它。

  • yfsms/settings.py 是 Django 项目的配置文件. 包含了非常重要的配置项。
  • yfsms/urls.py 里面存放了 一张表, 声明了前端发过来的各种http请求,分别由哪些函数处理。
  • yfsms/wsgi.py

python 组织制定了web服务网关接口(Web Server Gateway Interface)规范 ,简称wsgi。

遵循wsgi规范的web后端系统, 我们可以理解为由两部分组成:wsgi web server 和 wsgi web

application。它们通常是运行在一个python进程中的两个模块,或者说两个子系统。

wsgi web server 接受到前端的http请求后,会调用 wsgi web application 的接口( 比如函数或者类方

法)方法,由wsgi web application 具体处理该请求。然后再把处理结果返回给 wsgi web server, 

wsgi web server再返回给前端。

wsgi web server 负责 提供高效的http请求处理环境,可以使用多线程、多进程或者协程的机制。

http 请求发送到 wsgi web server , wsgi web server 分配线程或者进程或者轻量级线程(协程),然后

在这些线程、进程、或者协程里面,去调用执行 wsgi web application 的入口代码。

wsgi web application 被调用后,负责处理业务逻辑。 业务逻辑的处理可能非常复杂, wsgi web

application 需要精心的设计来正确处理。

django是 wsgi web application 的框架,它只有一个简单的单线程 wsgi web server。 供调试时使用。

产品正式上线运行的时候,通常我们需要高效的 wsgi web server 产品,比如 gunicorn,uwsgi,

cherrypy等,结合Django ,组成一个高效的后端服务。

所以这个 wsgi.py 就是提供给wsgi web server调用的接口文件,里面的变量application对应对象实现

了 wsgi入口,供wsgi web server调用 。

运行 Django web服务

django虽然只是 wsgi web application 的框架,但是它也有一个简单的 wsgi web server。 供调试时使

用。

进入到项目根目录:

C:\Users\yang\PycharmProjects\fysms

执行如下命令:

python manage.py runserver 0.0.0.0:80

0.0.0.0:80 是指定 web服务绑定的 IP 地址和端口。

0.0.0.0 表示绑定本机所有的IP地址, 就是可以通过任何一个本机的IP (包括环回地址 127.0.0.1 )

都可以访问服务。

80 表示是服务启动在80端口上。

打开浏览器,地址栏输入 ‘127.0.0.1’ ,就可以看到如下的界面,表示Django服务搭建成功,启动

成功。

HTTP请求的url路由

创建项目app

Django 中的一个app 就是项目里面的一个应用的意思。

一个项目包含多个app。

一个app 通常就是一个相对独立的模块 ,实现相对独立的功能。

一个app 本质上就是一个 Python 包, 里面包含了一些应用相关的代码文件。

Django 中创建app 可以 通过执行命令,创建一个app目录,并在里面自动创建app常用的文件。

进入项目根目录,执行下面的命令。

python manage.py startapp sales 

就会创建一个目录名为 sales, 对应一个名为 sales 的app,里面包含了如下自动生成的文件。

sales/
    __init__.py
    admin.py
    apps.py
    migrations/
        __init__.py
    models.py
    tests.py
    views.py

这个目录其实就是一个python package。

返回页面内容给浏览器

当我们输入网址,比如百度网址,比较敲回车后, 浏览器会发出http请求给百度的服务器,百度服

务器返回 HTML 文档内容给浏览器, 浏览器解析后,呈现出我们最终看到的网页内容。

服务器返回的 HTML 文档内容其实就是 符合 HTML 语法的 一段字符串 而已。

使用Django 开发后端服务, 就可以响应浏览器的http请求, 返回一段HTML字符串, 浏览器就可

以呈现在界面上了。

刚才创建的 sales 应用里面 有个 views.py 文件。 这个文件里面通常是写处理http 请求的代码的。

设计浏览器访问的http 请求的 url 地址 是 /sales/orders/ , 就由 views.py 里面的函数 listorders 来处

理, 返回一段字符串给浏览器。

 打开 views.py , 在里面加入如下内容:

from django.http import HttpResponse

def listorders(request):
    return HttpResponse("下面是系统中所有的订单信息。。。")

注意

最终的返回结果是 HttpResponse 对象的参数字符串 ,也就是这句话

下面是系统中所有的订单信息。。。

listorders的参数 request 是Django中的 HttpRequest 对象,包含了HTTP请求中的信息。

后端程序处理请求,常常要根据请求中的数据进行相应的处理:

比如请求添加一个用户,那么HTTP请求消息中就会携带要添加用户的信息(姓名、登录账号

等)。

url路由

添加路由记录

在创建项目d时候,在项目的设置目录下,有如下的一个urls.py 文件

这个文件是 url路由设置的入口文件。

打开该文件,在 urlpatterns 列表变量中添加一条路由信息,结果如下

from django.contrib import admin
from django.urls import path

# 别忘了导入 listorders 函数
from sales.views import listorders

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

    # 添加如下的路由记录
    path('sales/orders/', listorders),
]

urlpatterns 列表 就是 Django 的 url 路由的入口。

里面是一条条的路由记录,我们添加的:

path('sales/orders/', listorders)

就是告诉当前端过来的请求 url地址 是 /sales/orders/ , 就由 views.py 里面的函数 listorders 来

处理。

所谓 路由 就是指 : 根据 HTTP请求的url路径, 设置由哪个函数来处理这个请求。

通常我们项目代码的修改, Django的测试服务可以自动检测到,并且重新加载,不需要我们重启

Django Web 服务。

这时,就可以登录浏览器输入网址 http://127.0.0.1/sales/orders/

这就是浏览器的请求经过 Django路由后, 选择执行我们定义的函数 listorders,该函数返回的字

符串, 被作为http响应的消息体中的内容返回给浏览器了。

所以浏览器最终显示的就是我们 listorders 函数返回的字符串。

路由子表

复杂的系统url条目多达几百甚至上千个, 放在一个表中,查看时,要找一条路由记录就非常麻

烦。

通常可以将不同的路由记录按照功能分拆到不同的 url路由子表 文件中。

比如,这里我们可以把 访问 的 url 凡是 以 sales 开头的全部都 由 sales app目录下面的子路由文

件 urls.py 处理。

首先需要在 sales 目录下面创建一个新的文件 sales\urls.py 。

然后在这个 sales\urls.py 文件中输入如下内容

from django.urls import path

from . import views

urlpatterns = [
    path('orders/', views.listorders),
]

然后,再修改主url路由文件 yfsms/urls.py , 如下

from django.contrib import admin

# 导入一个include函数
from django.urls import path, include

from sales.views import listorders
urlpatterns = [
    path('admin/', admin.site.urls),

    # 凡是 url 以 sales/  开头的,
    # 都根据 sales.urls 里面的 子路由表进行路由
    path('sales/', include('sales.urls')),

]

当一个http请求过来时, Django检查 url,比如这里是sales/orders/

先到主url路由文件 yfsms/urls.py中查看 是否有匹配的路由项。

如果有匹配 ( 这里匹配了 sales/ ), 并且匹配的对象不是函数, 而是一个子路由设置 , 比如这里

是 include('sales.urls')。

就会去子路由文件中查看, 这里就是 sales.urls 对应的文件 sales\urls.py 。

注意这时,会从请求url中去掉 前面主路由文件 已经匹配上的部分(这里是 sales/ ), 将剩余的部

分 (这里是 orders/ )去子路由文件中查看是否有匹配的路由项。

这里就匹配了 orders/ ,匹配的对象,这里是 views.listorders ,它是一个处理函数,就调用该

函数处理 这个http请求, 将该函数的返回对象 构建 HTTP响应消息,返回给客户端。

创建数据库和表

后端开发离不开数据库

后端开发基本都需要操作数据,包括数据的存储、查询、修改、删除。

目前业界最广泛使用的数据库还是:关系型数据库。

关系型数据库系统,常用的开源数据库有 mysql 和 postgresql。

sqlite 没有 独立的数据库服务进程,数据操作被做成库直接供应用程序调用。

Django中可以直接使用,无须先搭建数据服务。

创建数据库

项目中数据库的配置在 yfsms/settings.py 中

# Database
# https://docs.djangoproject.com/en/2.0/ref/settings/#databases

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}

创建数据库,执行如下命令

python manage.py migrate

就会在项目的根目录下面 生成一个配置文件中指定的数据库文件 db.sqlite3

并且会在其中创建一些表。

下载sqlite 数据库工具 sqlitestudio SQLiteStudio

下载后解压即可, 运行该工具, 然后打开上面的 文件 db.sqlite3

可以发现该库中有些表已经创建好了。

什么是 ORM

 表的格式定义 和 表里面一条条数据很像 类定义 和 类的实例 之间的关系。

既然数据库 表定义和表记录之间的关系 就像 类和实例 之间的关系,Django 就让开发者通过类

和实例的操作来对应数据库表和记录的操作。

Django 里面, 数据库表的操作,包括表的定义、表中数据的增删改查,都可以通过 Model 类型

的对象进行的。

通常,在Django中

  • 定义一张数据库的表 就是定义一个继承自 django.db.models.Model 的类

  • 定义该表中的字段(列), 就是定义该类里面的一些属性

  • 类的方法就是对该表中数据的处理方法,包括数据的增删改查

这样,开发者对数据库的访问,从原来的使用底层的 sql 语句,变成面向对象的开发,通过一系列

对象的类定义和方法调用就可以操作数据库。

这样做:首先极大的简化了我们应用中的数据库开发,因为无需使用sql语句操作数据库了, 提高

了开发的效率;其次屏蔽了不同的数据库访问的底层细节,基本做到了 开发好代码后,如果要换

数据库,几乎不需要改代码, 修改几个配置项就可以了。

这种通过对象操作数据库的方法被称之为 ORM (object relational mapping)。

定义数据库表

创建一个名为common的应用目录, 里面存放我们项目需要的一些公共的表的定义。

进入项目根目录,执行下面的命令。

python manage.py startapp common 

就会创建一个目录名为 common, 对应 一个名为 common 的app,里面包含了如下自动生成的文

件。

common/
    __init__.py
    admin.py
    apps.py
    migrations/
        __init__.py
    models.py
    tests.py
    views.py

数据库表的定义,一般是放在app目录中的 models.py里面的。

打开 common/models.py,加入如下内容

from django.db import models

class Customer(models.Model):
    # 客户名称
    name = models.CharField(max_length=200)

    # 联系电话
    phonenumber = models.CharField(max_length=200)

    # 地址
    address = models.CharField(max_length=200)

这个 Customer 类继承自 django.db.models.Model, 就是用来定义数据库表的。

里面的 name、phonenumber、address 是该表的3个字段。

定义表中的字段就是定义一些静态属性,这些属性是 django.db.models 里面的各种 Field 对象,

对应不同类型的字段。

比如这里的3个字段都是 CharField 对象,对应 varchar类型的数据库字段。

后面的参数 max_length 指明了该 varchar字段的 最大长度。

Djanog 有很多字段对象类型, 对应不同的类型的数据库字段。

创建数据库表

我们需要告诉Django: 我们的 common 应用中需要你关注, 因为其中包含了数据库Model的定

义。

在项目的配置文件 settings.py 中, INSTALLED_APPS 配置项 加入如下内容

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    # 加入下面这行
    'common.apps.CommonConfig',
]

common.apps.CommonConfig’ 告诉 Django , CommonConfig 是 common/apps.py 文件中定义

的一个应用配置的类。

class CommonConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'common'

CommonConfig 是 AppConfig的 子类, 就表示这是应用的配置类。

 name = ‘common’ , name 是用来定义应用的python模块路径的。 这里就是说应用模块路径为

common 。

在 INSTALLED_APPS中添加声明, 也可以直接写app的包名,比如

INSTALLED_APPS = [
    
    # 加入下面这行
    'common',
]

现在Django知道了我们的 common 应用, 我们可以在项目根目录下执行命令

python manage.py makemigrations common

得到如下结果

Migrations for 'common':
  common\migrations\0001_initial.py
    - Create model Customer

这个命令,告诉Django , 去看看common这个app里面的models.py ,我们已经修改了数据定义,

你现在去产生相应的更新脚本。

执行一下,会发现在 common\migrations 目录下面出现了0001_initial.py, 这个脚本就是相应要进

行的数据库操作代码。

随即,执行如下命令

python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, common, contenttypes, sessions
Running migrations:
  Applying common.0001_initial... OK

用 sqlitestudio 查看数据库,发现创建了一张名为 common_customer的表,如下

多出来的 id 字段是该表的主键, 是Django自动帮我们添加的。这个不需要我们在类中显式的定

义。

注意

如果以后我们修改了Models.py 里面的库表的定义,都需要再次运行 python manage.py

makemigrations common 和 python manage.py migrate 命令,使数据库同步该修改结果。

Django Admin 管理数据

Django提供了一个管理员操作界面可以方便的 添加、修改、删除你定义的 model 表数据。

首先,需要创建 一个超级管理员账号。

进入到项目的根目录,执行如下命令,依次输入你要创建的管理员的 登录名、email、密码。

C:\Users\yang\PycharmProjects\fysms>python manage.py createsuperuser
Username: feng
Email address: feng@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/``` ,输入刚才注册的用户密码登录。

登录后可以看到如下界面。这里面是目前系统中可以修改的表。

点击定义的Customers表

点击下面的 ADD CUSTOMER 按钮来添加一条客户记录

在跳出的界面中输入客户信息后,点击SAVE按钮

完成上面操作后,使用数据库查看工具,就发现数据库中确实有了添加的数据信息。

想使用中文的admin界面,应该在配置文件 settings.py 中 MIDDLEWARE 最后加入如下配置

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    # admin界面语言本地化
    'django.middleware.locale.LocaleMiddleware',
]

读取数据库数据

实现一个功能:浏览器访问 sales/customers/ ,服务端就返回系统中所有的客户记录给浏览器。

先实现一个函数,来处理浏览器发出的URL为 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')),

这条URL记录,指明凡是 url 以 sales/ 开头的,都根据 sales.urls 里面的子路由表进行路由。

只需修改 sales/urls.py 即可,添加如下记录

path('customers/', views.listcustomers),

然后可以在浏览器输入如下 网址: http://127.0.0.1/sales/customers/

和数据库中的记录信息一致

过滤条件

有的时候,需要根据过滤条件查询部分客户信息。

比如,当用户在浏览器输入 /sales/customers/?phonenumber=12432354,要求返回电话号码为

12432354客户记录。

可以通过 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的过滤条件可以有多个,只要继续在后面的参数添加过滤条件即可。

比如

qs = qs.filter(phonenumber=ph,address='大连')

浏览器输入如下 url

http://127.0.0.1/sales/customers/?phonenumber=12432354

访问结果如下

前后端分离的架构

代码直接生成HTML

HTML本身其实也是字符串,只是这个字符串里面的内容是符合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/

使用模板

很多后端框架都提供了一种 模板技术, 可以在html中嵌入编程语言代码片段, 用模板引擎(就是

一个专门处理HTML模板的库)来动态的生成HTML代码。

Python 中有很多这样的模板引擎 比如 jinja2 、Mako, 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)

使用模板引擎的好处,就是产生HTML的代码更简单方便了。

前后端分离架构

现在随着 浏览器中javascript 解释器性能的突飞猛进,以及一些前端模板库和框架的流行。很多架

构师将 页面的html 内容生成 的任务放到前端。

服务端就只负责提供数据, 界面的构成全部在前端(浏览器前端或者手机前端)进行,称之为前

端渲染。

这样需要 定义好 前端和后端 交互数据 的接口。

目前通常这样的接口设计最普遍的就是使用 REST 风格的 API 接口。

前端通过 API 接口 从后端获取数据展示在界面上。

前端通过 API 接口 告诉后端需要更新的数据是什么。

需要Django返回的信息,通常都是所谓的 动态 数据信息。 比如:用户信息,药品信息,订单信

息,等等。

这些信息通常都是存在数据库中,这些信息是会随着系统的使用发生变化的。

而 静态 信息,比如: 页面HTML文档、css文档、图片、视频等,是不应该由 Django 负责返回数

据的。

这些数据通常都是由其他的 静态资源服务软件,比如 Nginx、Varnish等等,返回给前端。这些软

件都会有效的对静态数据进行缓存,大大提高服务效率。在实际的项目中,往往还会直接使用静

态文件 云服务( OSS + CDN )提供静态数据的访问服务。

对资源的增查改删处理

创建 mgr应用目录

这是针对管理员用户的请求。

python manage.py startapp mgr 

添加处理请求模块和url路由

不同的操作请求,使用不同的 HTTP 请求方法 ,比如 添加是POST, 查询是 GET, 修改是

PUT, 删除是 DELETE。

注意:Django 的 url路由功能 不支持 根据 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

需要在Django的url路由文件中加入对应的路由。

第一步:我们应该在 总路由文件 fysms/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),
]

列出客户

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

添加客户

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})

临时取消 CSRF 校验

注意,缺省创建的项目, Django 会启用一个 CSRF (跨站请求伪造) 安全防护机制。

在这种情况下, 所有的Post、PUT 类型的 请求都必须在HTTP请求头中携带用于校验的数据。

先临时取消掉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',
]

修改客户信息

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})

删除客户

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})

和前端集成

把前端的代码资源文件放到项目根目录下面。形成一个目录 z_dist。

Django的开发环境也可以从浏览器访问这些前端的资源文件。

但是前端文件都是静态文件,需要我们配置一下Django的配置文件, 指定http请求如果访问静态文

件,Django在哪个目录下查找。

打开 fysms/urls.py 文件,在末尾添加一个

+  static("/", document_root="./z_dist")

在url 路由中加入前端静态文件的查找路径。

这样如果 http请求的url 不是以 admin/ sales/ api/mgr/ 开头, Django 就会认为是要访问 z_dist目

录下面的静态文件。

并添加如下声明

# 静态文件服务
from django.conf.urls.static import static
from django.contrib import admin

# 导入一个include函数
from django.urls import path, include

# 静态文件服务
from django.conf.urls.static import static

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

    # 凡是 url 以 sales/  开头的,
    # 都根据 sales.urls 里面的 子路由表进行路由
    path('sales/', include('sales.urls')),

    # 凡是 url 以 api/mgr  开头的,
    # 都根据 mgr.urls 里面的 子路由表进行路由
    path('api/mgr/', include('mgr.urls')),

]  +  static("/", document_root="./z_dist")

启动Django 开发服务器

python manage.py runserver 80

打开浏览器,输入如下网址:

http://localhost/mgr/index.html

实现登录

处理登录、登出请求

在mgr目录里面创建一个代码文件 sign_in_out.py。

这个代码文件就是用来处理 管理员登录和登出 的API请求的。

Django中有个内置app 名为 django.contrib.auth ,缺省包含在项目Installed App设置中。

这个app 的 models 定义中包含了一张 用户表,名为 auth_user 。

django.contrib.auth 这个app 已经 为我们做好了登录验证功能

sign_in_out.py 文件中,输入如下内容

from django.http import JsonResponse

from django.contrib.auth import authenticate, login, logout

# 登录处理
def signin( request):
    # 从 HTTP POST 请求中获取用户名、密码参数
    userName = request.POST.get('username')
    passWord = request.POST.get('password')

    # 使用 Django auth 库里面的 方法校验用户名、密码
    user = authenticate(username=userName, password=passWord)
    
    # 如果能找到用户,并且密码正确
    if user is not None:
        if user.is_active:
            if user.is_superuser:
                login(request, user)
                # 在session中存入用户类型
                request.session['usertype'] = 'mgr'

                return JsonResponse({'ret': 0})
            else:
                return JsonResponse({'ret': 1, 'msg': '请使用管理员账户登录'})
        else:
            return JsonResponse({'ret': 0, 'msg': '用户已经被禁用'})
        
    # 否则就是用户名、密码有误
    else:
        return JsonResponse({'ret': 1, 'msg': '用户名或者密码错误'})


# 登出处理
def signout( request):
    # 使用登出方法
    logout(request)
    return JsonResponse({'ret': 0})

创建 url路由

需要 在mgr 目录下面的子路由文件 urls.py 里添加如下内容

from django.urls import path
from mgr import sign_in_out

urlpatterns = [

    path('signin', sign_in_out.signin),
    path('signout', sign_in_out.signout),

]

测试代码

使用 requests库构建登录请求http消息, 并且检查响应,看看是否能登录成功。

import  requests,pprint

payload = {
    'username': 'yang',
    'password': '12345678'
}

response = requests.post('http://localhost/api/mgr/signin',
              data=payload)

pprint.pprint(response.json())

session和token

session 方案

session 就是会话的意思。

服务端在数据库中保存一张session表。 这张表记录了一次用户登录的相关信息。

Django中该表名字就叫 django_session

sessionid 通常就是 一串字符串 用来标记一个session的。 而session对应的数据在这里是加密的。

通过这张表,服务端可以根据 session号(通常叫session ID)查到 session 的信息数据。

在用户登录成功后, 服务端就在数据库session表中 中创建一条记录,记录这次会话。

也就是创建一个新的 sessionid 插入到 该表中。

同时也 放入一些 该session对应的数据到 记录的数据字段中,比如登录用户的信息。

然后在该登录请求的HTTP响应消息中, 的头字段 Set-Cookie 里填入 sessionid 数据。

Set-Cookie: sessionid=6qu1cuk8cxvtf4w9rjxeppexh2izy0hh

根据http协议, 这个Set-Cookie字段的意思就是 要求前端将其中的数据存入 cookie中。 

并且随后访问该服务端的时候, 在HTTP请求消息中必须带上 这些 cookie数据。

cookie 通常就是存储在客户端浏览器的一些数据。

服务端可以通过http响应消息 要求 浏览器存储 一些数据。

cookie数据由多个键值对组成, 比如:

sessionid=6qu1cuk8cxvtf4w9rjxeppexh2izy0hh
username=byhy
favorite=phone_laptop_watch

用户的后续操作,触发的HTTP请求, 都会在请求头的Cookie字段带上前面说的sessionid 。

Cookie: sessionid=6qu1cuk8cxvtf4w9rjxeppexh2izy0hh

服务端接受到该请求后,只需要到session表中查看是否有该 sessionid 对应的记录,

这样就可以判断这个请求是否是前面已经登录的用户发出的。

如果不是,就可以拒绝服务,重定向http请求到登录页面让用户登录。

token机制

使用session机制验证用户请求的合法性的主要缺点有两个

性能问题

验证请求是根据sessionid 到数据库中查找session表的,而数据库操作是服务端常见的性能瓶颈,

尤其是当用户量比较大的时候。

扩展性问题

当系统用户特别多的时候,后端处理请求的服务端通常由多个,部署在多个节点上。 但是多个节

点都要访问session表,这样就要求数据库服务能够被多个节点访问,不方便切分数据库以提高性

能。

token 简单来说,就是包含了数据信息和校验信息的数据包。

Session 机制是把 数据信息(比如session表)放到服务端,服务端数据是客户无法篡改的,从而

保证验证的可靠性。

token机制 数据信息直接传给客户端,客户每次请求再携带过来给服务端。服务端无需查找数据

库,直接根据token里面的数据信息进行校验。

token 机制的原理如下:

服务端配置一个密钥(secret key),该密钥是服务端私密保存的,不能外泄

在用户登录成功后, 服务端将 用户的信息数据 + 密钥 一起进行一个哈希计算, 得到一个哈希值。

然后将 用户数据信息 和 哈希值 一起 做成一个字节串 ,这个字节串就称之为 token 。

token 里面 包含了用户数据信息和用于校验完整性的哈希值

用户的后续操作,触发的HTTP API请求, 会在请求消息里面 带上 token 。

服务端接收到请求后,会根据 数据信息 和 密钥 使用哈希算法再次生成哈希值。

如果客户修改了数据信息, 因为他不知道密钥,没法得到正确的新的哈希值,那么服务端根据 

改后的数据+密钥 得到的新 哈希值一定和保存在token中的老哈希值 不同。就知道数据被修改

了。

如果客户没有修改数据,服务端 根据 原来的数据+密钥 得到的哈希值 和保存在token中原来的哈

希值一致,就校验通过。

校验通过后,就确信了数据没有被修改,可以放心的使用token里面的数据 进行后续的业务逻辑处

理了。

使用session验证客户端请求

这行代码的作用 就是在登录认证后,将 用户类型保存到session数据中

Django 框架会自动在HTTP响应消息头中 加入 类似下面的 sessionid cookie

 Set-Cookie: sessionid=????????

后续的HTTP请求就会携带这个sessionid,

处理 URL 以 /api/mgr 开头的 API 请求 代码里面, 需要 加上一个验证逻辑。

验证请求的cookie里面是否有sessionid,并且检查session表,看看是否存在session_key为该

sessionid 的一条记录,该记录的数据字典里面是否 包含了 usertype 为 mgr 的 数据。

修改 mgr/customer.py 的dispatcher 函数,在前面加上如下代码

  # 根据session判断用户是否是登录的管理员用户
    if 'usertype' not in request.session:
        return JsonResponse({
            'ret': 302,
            'msg': '未登录',
            'redirect': '/mgr/sign.html'}, 
            status=302)

    if request.session['usertype'] != 'mgr' :
        return JsonResponse({
            'ret': 302,
            'msg': '用户非mgr类型',
            'redirect': '/mgr/sign.html'} ,
            status=302)

注意request对象里面的session属性对应的就是 session记录里面的数据。

该数据对象类似字典,所以检查是否有usertype类型为mgr的信息,就是这样写

if request.session['usertype'] != 'mgr' 

数据库表的关联

目前使用的数据库系统主要还是 关系型数据库 。

关系型数据库,设计的一个难点就是 各种表之间的关联关系 。

常见的3种关联关系就是: 一对多 , 一对一 , 多对多

一对多

表之间 一对多 的关系,就是 外键 关联关系

客户(Customer) 这张表 。如下所示

class Customer(models.Model):
    # 客户名称
    name = models.CharField(max_length=200)

    # 联系电话
    phonenumber = models.CharField(max_length=200)

    # 地址
    address = models.CharField(max_length=200)

 药品(Medicine) 这张表,包括药品名称、编号和描述 这些信息。

class Medicine(models.Model):
    # 药品名
    name = models.CharField(max_length=200)
    # 药品编号
    sn = models.CharField(max_length=200)
    # 描述
    desc = models.CharField(max_length=200)

 订单(Order) 这张表,这个Order表 包括 创建日期、客户、药品、数量。

客户字段对应的客户 只能是 Customer 中的某个客户记录

Order表里面 一条订单记录的客户 对应 Customer表里面的一条客户记录

而 多条 Order记录里面的客户 是可以对应 Customer 表里面 同一个客户记录的,

反过来说,就是:一个客户记录可以对应多条订单记录

像这种一对多的关系,数据库中是用 外键 来表示的。

如果一个表中 的 某个字段是外键,那就意味着 这个外键字段的记录的取值,只能是它关联表的某

个记录的主键的值。

定义表的 Model类的时候,如果没有指定主键字段,migrate 的时候 Django 会为该Model对应的数

据库表自动生成一个id字段,作为主键。

Customer、Medicine表均没有主键,但是在migrate之后,查看数据库记录就可以发现有一个id字

段,且该字段是 主键 (primary key)

定义订单表Order

其中客户字段就应该是一个外键,对应Customer表的主键,也就是id字段

Django中定义外键的方法就是 Model类的该属性字段 值为 ForeignKey 对象

import datetime
class Order(models.Model):
    # 订单名
    name = models.CharField(max_length=200,null=True,blank=True)

    # 创建日期
    create_date = models.DateTimeField(default=datetime.datetime.now)

    # 客户
    customer = models.ForeignKey(Customer,on_delete=models.PROTECT)

 customer 字段 是外键, 指向 Customer 类。 意思就是告诉Django: Order表的 customer 字段

指向 Customer表的主键 的一个外键。

另外一个参数 on_delete 指定了 当我们想 删除 外键指向的主键记录时, 系统的行为。

on_delete 不同取值对应不同的做法,常见的做法如下

  • CASCADE 删除主键记录和相应的外键表记录。
  • PROTECT 禁止删除记录。
  • SET_NULL 删除主键记录,并且将外键记录中外键字段的值置为null。 当然前提是外键字段要设置为值允许是null。

注意: 外键字段,实际在数据库表中的字段名, 是 Django ForeignKey 定义字段名加上后 

缀 _id 。

一对一

Django 中 用 OneToOneField 对象 实现 一对一的关系

class Student(models.Model):
    # 姓名
    name = models.CharField(max_length=200)
    # 班级
    classname = models.CharField(max_length=200)
    # 描述
    desc = models.CharField(max_length=200)


class ContactAddress(models.Model):
    # 一对一 对应学生 
    student = models.OneToOneField(Student, on_delete=models.PROTECT)
    # 家庭
    homeaddress = models.CharField(max_length=200)
    # 电话号码
    phone = models.CharField(max_length=200)

Django这样一对一定定义,它会在migrate的时候,在数据库中定义该字段为外键的同时, 加

上 unique=True 约束,表示在此表中,所有记录的该字段取值必须唯一,不能重复。

多对多

Order表 和 Medicine表之间就形成了多对多的关系。

Django是通过 ManyToManyField 对象表示多对多的关系的。

import datetime
class Order(models.Model):
    # 订单名
    name = models.CharField(max_length=200,null=True,blank=True)

    # 创建日期
    create_date = models.DateTimeField(default=datetime.datetime.now)

    # 客户
    customer = models.ForeignKey(Customer,on_delete=models.PROTECT)

    # 订单购买的药品,和Medicine表是多对多 的关系
    medicines = models.ManyToManyField(Medicine, through='OrderMedicine')


class OrderMedicine(models.Model):
    order = models.ForeignKey(Order, on_delete=models.PROTECT)
    medicine = models.ForeignKey(Medicine, on_delete=models.PROTECT)

    # 订单中药品的数量
    amount = models.PositiveIntegerField()

指定Order表和 Medicine 表 的多对多关系, 其实Order表中并不会产生一个叫 medicines 的字

段。

Order表和 Medicine 表 的多对多关系 是 通过另外一张表, 也就是 through 参数 指定的

OrderMedicine 表 来确定的。

migrate的时候,Django会自动产生一张新表 (这里就是 common_ordermedicine)来 实现 order

表 和 medicine 表之间的多对多的关系。

python manage.py makemigrations common
python manage.py migrate

实现代码

药品管理

在 mgr 目录下面新建 medicine.py,处理 客户端发过来的 列出药品、添加药品、修改药品、删除

药品 的请求。

from django.http import JsonResponse

# 导入 Medicine 对象定义
from  common.models import  Medicine

import json

def dispatcher(request):
    # 根据session判断用户是否是登录的管理员用户
    if 'usertype' not in request.session:
        return JsonResponse({
            'ret': 302,
            'msg': '未登录',
            'redirect': '/mgr/sign.html'},
            status=302)

    if request.session['usertype'] != 'mgr':
        return JsonResponse({
            'ret': 302,
            'msg': '用户非mgr类型',
            'redirect': '/mgr/sign.html'},
            status=302)


    # 将请求参数统一放入request 的 params 属性中,方便后续处理

    # GET请求 参数 在 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_medicine':
        return listmedicine(request)
    elif action == 'add_medicine':
        return addmedicine(request)
    elif action == 'modify_medicine':
        return modifymedicine(request)
    elif action == 'del_medicine':
        return deletemedicine(request)

    else:
        return JsonResponse({'ret': 1, 'msg': '不支持该类型http请求'})



def listmedicine(request):
    # 返回一个 QuerySet 对象 ,包含所有的表记录
    qs = Medicine.objects.values()

    # 将 QuerySet 对象 转化为 list 类型
    # 否则不能 被 转化为 JSON 字符串
    retlist = list(qs)

    return JsonResponse({'ret': 0, 'retlist': retlist})


def addmedicine(request):

    info    = request.params['data']

    # 从请求消息中 获取要添加客户的信息
    # 并且插入到数据库中
    medicine = Medicine.objects.create(name=info['name'] ,
                            sn=info['sn'] ,
                            desc=info['desc'])


    return JsonResponse({'ret': 0, 'id':medicine.id})


def modifymedicine(request):

    # 从请求消息中 获取修改客户的信息
    # 找到该客户,并且进行修改操作

    medicineid = request.params['id']
    newdata    = request.params['newdata']

    try:
        # 根据 id 从数据库中找到相应的客户记录
        medicine = Medicine.objects.get(id=medicineid)
    except Medicine.DoesNotExist:
        return  {
                'ret': 1,
                'msg': f'id 为`{medicineid}`的药品不存在'
        }


    if 'name' in  newdata:
        medicine.name = newdata['name']
    if 'sn' in  newdata:
        medicine.sn = newdata['sn']
    if 'desc' in  newdata:
        medicine.desc = newdata['desc']

    # 注意,一定要执行save才能将修改信息保存到数据库
    medicine.save()

    return JsonResponse({'ret': 0})


def deletemedicine(request):

    medicineid = request.params['id']

    try:
        # 根据 id 从数据库中找到相应的药品记录
        medicine = Medicine.objects.get(id=medicineid)
    except Medicine.DoesNotExist:
        return  {
                'ret': 1,
                'msg': f'id 为`{medicineid}`的客户不存在'
        }

    # delete 方法就将该记录从数据库中删除了
    medicine.delete()

    return JsonResponse({'ret': 0})

在 mgr\urls.py 里面加上 对 medicine 的请求处理路由设置

from django.urls import path
from mgr import customer,sign_in_out,medicine,order

urlpatterns = [

    path('customers', customer.dispatcher),
    path('medicines', medicine.dispatcher), # 加上这行

    path('signin', sign_in_out.signin),
    path('signout', sign_in_out.signout),

]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值