python-Django入门

一、整体参考资料

博客:https://www.liujiangblog.com/blog/36/,https://www.liujiangblog.com/course/django/2

github(主要):https://github.com/the5fire/django-practice-book与《Django企业开发实战》对应 

二、WSGI(web server Gateway interface)

其实所谓的web框架,比如django,flask,Tornado都是WSGI app部分。比如django创建的框架中,你会发现自带一个文件:wsgi.py,其实就是框架给你写好了,所以说Django是可以直接使用的,不过自带的wsgi.py好像是单线程,具体产品上线应该还需要用其他的。

三、小项目入手实现入门

3.1 参考:https://www.bilibili.com/video/BV1AE41117Up?p=2

3.2 小项目需求

    写一个药企销售管理系统(简称BYSMS),使用者有管理员销售员。系统主要是记录药物被购买情况,客户购买信息,比如:客户名、日期、药品名;管理员可以在 BYSMS 系统 查询、添加 、 修改、删除 所有 的订单, 而销售员只能 查询、添加 、 修改、删除 自己创建 的订单。(详情见:http://www.python3.vip/tut/webdev/django/req_1/)

3.3 项目创建

1)这里直接用pycharm创建吧,项目名就用bysms

 自动生成的是manage.py应该是管理脚本。

比如运行项目:

 当然也可以用pycharm运行。

2)创建APP,这里从管理员与销售员分,将管理员管理的功能放在一个叫mgr的app中,将销售员操作实现放在另一个sales的app中。

3.4 URL设置即将URL与处理的函数对应

1)入门理解:

比如这里希望/sales/orders/这个URL与 listorders这个处理函数对应,只需要两步。

step1:在相应的app的views.py中写这个函数;

step2:在项目中bysms-url.py加上URL与函数的对应关系;

 这样写有一个问题:如果app很多,url很多,这个配置文件就很乱;需要根据app对url进行分类处理。

2)正常解耦写法:

step1:在相应的app的views.py中写这个函数;(同上)

step2:在总的URL配置文件只写到app这个级别。

 step3:在相应的app中创建urls.py文件,这个里面从app写到具体url。其实就是将源url解耦分开写。

3.5 创建数据库

1)说明:为了快速入门,这里使用django自带的sqlite数据库;有关数据库的配置在settings.py的DATABASES字典(ENGINE是数据库引擎)中。

2) 执行# python manage.py migrate创建数据库。创建的表应该与settings.py中INSTALLED_APPS中所有涉及的models有关。具体其实可以下载sqlite 数据库工具 sqlitestudio进行查看。

3)ORM:在我感觉就一种对象关系映射,将数据库增删改查操作映射为对象操作。

4)因为这里两个APP中有许多公共的应用,所以再创建一个叫common的app,并且建立一些表。

step1:# python manage.py startapp common

step2: 在common/models.py创建表一个客户表(三个字段:名字、联系、地址;类型都是varchar)

step3:配置(告诉django怎么找到这个表),即在bysms/settings.py的INSTALLED_APPS加上‘common.app.CommonConfig’

step4:Django知道common这个应用后,执行# python manage.py makemigrations common

这个命令就是告诉Django,去看看common这个app里面的models.py,我们已经修改了数据定义,你现在去产生相应的更新脚本。

 step5: 步骤4后会出现一个0001_initial.py,这个脚本就是相应要进行的数据库操作代码。随机执行# python manage.py migrate真正在数据库中创建这个表。

注意:以后修改这个表要执行# python manage.py makemigrations common与# python manage.py migrate命令,使得数据库同步修改结果。

5)Django框架为我们提供了auth_user表,这个表中存放系统登录用户信息。问题是通常一个系统应该是有管理员的,所以auth_user中应该有超级管理员的记录信息,但是刚创建的表没有。

创建管理员账号:

# python manage.py createsuperuser

按照步骤输入管理员名称,邮箱,密码

管理员登录网址:http://127.0.0.1:8000/admin/

 

登录后发现只有两个表,一个分组表,一个用户信息表。并没有之前创建的Customer表。如果需要自己创建表在管理原界面展示,需要在/admin.py加一行代码将其登记上。

这样开发人员可以直接操作此数据库了。

3.6 读取数据库

1)读取之前要先有数据,因为这一小节重点是读数据,所以直接通过点击后端的Custormers加一些数据。

2)目的:现在写一个函数,在请求sales/customers/时,返回数据库中custormer表中所有记录。

Django 中 对数据库表的操作, 应该都通过 Model对象 实现对数据的读写,而不是通过SQL语句。

Customer.objects.values() 就会返回一个 QuerySet 对象,这个对象是Django 定义的,在这里它包含所有的Customer 表记录。

写完视图函数后,绑定url:

3)目的:过滤条件,有时候请求不是想要所有。根据url请求参数,过滤表记录。

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

 

3.7 前后端分离

1)3.6中前端展示的表格太丑,我们可以用HTML展示,HTML本身其实也是字符串,故可以直接改listcustomer函数的返回:

from django.http import HttpResponse
from common.models import Customer      # 首先导入这个表

# Create your views here.
def listorders(request):
    return HttpResponse('下面是系统的所有订单信息')


# 先定义好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):
    """
    :param request:
    :return: 返回一个QuerySet对象,包含表中所有记录
    """
    qs = Customer.objects.values()

    # 检查url中是否有参数phonenumber,没有就返回none
    ph = request.GET.get('phonenumber',None)
    
    # 如果有,用filter过滤
    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 += '<br>'
    return  HttpResponse(html_template%tableContent)
/sales/views.py

2) 上面是用python代码直接拼接处html内容的,繁琐,可读性差、不好维护等。

 很多后端框架都提供了一种 模板技术, 可以在html 中嵌入编程语言代码片段, 用模板引擎(就是一个专门处理HTML模板的库)来动态的生成HTML代码。

比如JavaEE 里面的JSP。Python 中有很多这样的模板引擎 比如 jinja2 、Mako, Django也内置了一个这样的模板引擎。

我们修改一下代码,使用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>

        {% load static %}
        
        {% 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']     # 模板引擎选择Django这种
template = django_engine.from_string(html_template)       # 生成模板对象template

def listcustomers(request):
    """
    :param request:
    :return: 返回一个QuerySet对象,包含表中所有记录
    """
    qs = Customer.objects.values()

    # 检查url中是否有参数phonenumber,没有就返回none
    ph = request.GET.get('phonenumber',None)
    
    # 如果有,用filter过滤
    if ph:
        qs = qs.filter(phonenumber=ph)

    # 传入渲染模板需要的参数
    endered = template.render({'customers': qs})

    return  HttpResponse(endered)
/sales/views.py

对比:修改html中的%s,将原来函数部分的东西,放在html中写,这样读起来方便。并用模板引擎,将html相当于变成为对象,只要传入渲染模板所需要的参数就可以了。

关于Django模板的详细用法,大家可以参考官方文档

 3)分离:

有了模板引擎,对我们后端开发来说,简化了程序员后端生成HTML的任务,提高了开发效率。

但是,通常后端开发人员的核心任务不是开发前端界面, 而且大部分后端开发人员对前端界面开发还是不熟悉的。

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

只是这个工作在前端执行, 使用前端的 框架库去完成,比如angular,react,vue。

这样 界面完全交给前端开发人员去做, 后端开发只需要提供前端界面所需要的数据就行了。

前端 和 后端 之间的交互就完全是 业务数据了。

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

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

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

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

通常 前后端的 API 接口 是由 架构师 设计的, 有时也可以由经验丰富的前端开发者、或者后端开发者设计。

接下来我们就聚焦在后端,我们的系统前端由另外的团队开发,我们只负责后端业务数据的维护

现在我们的系统,API接口 已经由架构师定义好了, 点击这里查看

我们只需要根据这个接口文档,实现后端系统的部分。

注意:需要Django返回的信息,通常都是所谓的 动态 数据信息。 比如:用户信息,药品信息,订单信息,等等。这些信息通常都是存在数据库中,这些信息是会随着系统的使用发生变化的。

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

这些数据通常都是由其他的 静态资源服务软件,比如 Nginx、Varnish等等,返回给前端。这些软件都会有效的对静态数据进行缓存,大大提高服务效率。在实际的项目中,往往还会直接使用 静态文件 云服务( OSS + CDN )提供静态数据的访问服务。

总之,Django处理并返回的应该是动态业务数据信息。

3.8 对资源的增查改删除处理

1)前提:3.7中实现了前后端分离讲解,并假设已经有架构师定义好了API接口。

目的:后端其实主要是响应前端的请求,对数据资源处理。下面我们就以 BYSMS 系统中 customer 数据为例,看看如何进行 数据的增查改删 操作。

现在我们就根据这个接口文档,来实现后端。

2)实现接口文档中“客户数据”部分:

注意:

接口文档概述中说本接口用于 Bysms 系统 管理员用户 前后端系统 之间的数据 交互。

本接口中,所有请求 ( 除了登录请求之外 ),必须在cookie中携带有登录的成功后,服务端返回的sessionid。

本接口中,所有请求、响应的消息体 均采用 UTF8 编码

因此:

Step1:为管理员用户专门创建一个应用mgr,来处理相关的请求。(已经存在的话跳过)

# python manage.py startapp mgr

此APP中的代码专门处理客户端与管理员之间的相关请求

Step2:常规操作是在views.py中定义函数,处理http请求。但是如果请求很多,views.py就会很庞大,不利于管理。这里建立views文件夹,里面根据客户端对不同数据的操作,建立不同的的py文件。比如,处理客户端对customer数据的操作,则写在customer.py中;处理客户端对订单数据的操作,写在order.py中。

Step3:观察接口文档。观察发现客户数据部分,四个API:列出所有客户、添加一个客户、修改客户信息、删除客户信息。

他们的URL一样。这样,URL.py文件中就没办法根据不同URL映射不同函数来处理;

再发现他们请求参数分别是GET、POST、PUT、DELETE,且请求的参数中都有 action 参数表明这次请求的操作具体是什么。

但是Django 的 url路由功能 不支持 根据 HTTP 请求的方法 和请求体里面的参数 进行路由。也就是说下面写法不行。

 方法:

写一个函数,此函数对应‘/api/mgr/customers’,也就是全部。在函数中在根据请求方式或者请求体内容做不同的回应。通常这叫分发函数dispatcher。

1)在总的urls.py即bysms/urls.py中加上到该app的路由

 2)在mgr  App中建立urls.py,并注明函数响应关系

注意:后期调试时发现这边多打一个/

3)在views/customer.py中写业务代码

import json
from django.http import JsonResponse
from common.models import Customer      # 首先导入这个表
"""
处理客户端对customer数据库的操作
"""


def listcustomers(request):
    """
    列出所有用户,即展示customer数据库中的数据
    :param request: 请求
    :return: 返回json格式的数据
    """
    # 将数据库中的数据以QuerySet对象形式返回
    qs = Customer.objects.values()
    # 将 QuerySet 对象 转化为 list 类型,否则不能转换为json
    retlist = list(qs)

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



def addcustomer(request):
    """
    主要就是从http请求中获取数据,然后插入到Customer表中
    :param request:
    :return: 添加是否成功,若失败,返回原因;若成功,返回插入的id
    """
    info = request.params['data']

    # 返回值 就是对应插入记录的对象
    # Customer.objects.create 方法就可以添加一条Customer表里面的记录。
    record = Customer.objects.create(name=info['name'] ,
                            phonenumber=info['phonenumber'] ,
                            address=info['address'])

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


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


def dispatcher(request):
    # 将请求参数统一放入request 的 params 属性中,方便后续处理
    print(request)

    # 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请求'})
/mgr/views/customer.py

说明:期间为了方便,先临时取消CSRF校验,详情见此

Step4:结合前端调试。注意:实际开发时Django不应该服务任何静态文件,性能很差。这里时开发模式,调试用。

假设这边前端已经开发好对应的前端部分(点击这里下载),我们拉过来调试。

1)将下载好的z_dist安装包,解压在项目的根目录下。

2)添加告知Django,添加静态文件路由。

这样,Django匹配路由,如果‘sales/’等都匹配不上,就会自动匹配上这个静态。

3)启动Django

# python manage.py runserver 80

4) 打开浏览器访问:http://localhost/mgr/index.html

3.9 对3.8中写完的客户数据部分接口进行测试

之前3.8最后假装前端已经写好,直接用Django服务静态文件,联调测试,但是一般前后端是同时进行的。后端写好后可以自己用postman等测试工具测试。这里使用python做API接口测试和自动化。使用python中requests库进行测试。可以参考这里

3.9.1 Requests库简介

Requests 库 是用来发送HTTP请求,接收HTTP响应的一个Python库。经常被用来 爬取 网站信息。 用它发起HTTP请求到网站,从HTTP响应消息中提取信息。也经常被用来做 网络服务系统的Web API 接口测试。因为Web API 接口的消息基本上都是通过HTTP协议传输的。官方文档中文文档在这。

它不是Python标准库,是第三方开发,需要安装一下:pip install requests

3.9.2 抓包工具fiddler

测试时一般会用到抓包工具。因为如果你模拟一个请求测接口,出了问题,你不清楚是本来写得服务器代码有问题,造成的响应的问题,还是你发送的请求就有问题。

fiddler是代理式抓包,也就是说在客户端与服务端之间设置代理,客户端请求先发给代理,代理在转发给服务器;响应也一样。

因此要让代理起作用,需要设置客户端,让它先发给代理。具体使用与安装包在这

如何客户端是浏览器,直接按F12就可以查看。但是如果是手机,或者用Requests库接口测试时,需要。

3.9.3 测试

先运行Django服务,在运行测试脚本。

import requests,pprint


"""列出所有客户接口"""
param = {'action':'list_customer'}
response = requests.get('http://localhost/api/mgr/customers',params=param)
print(response.text)

"""添加一个用户接口"""
# 构建添加 客户信息的 消息体,是json格式
payload1 = {
    "action":"add_customer",
    "data":{
        "name":"武汉市桥西医院",
        "phonenumber":"13345679934",
        "address":"武汉市桥西医院北路"
    }
}
# 发送请求给web服务
response = requests.post('http://localhost/api/mgr/customers',json=payload1)
pprint.pprint(response.json())      # 可以在调用查看,看看有没有添加到表中

"""修改客户信息"""
payload2 = {
    "action":"modify_customer",
    "id": 6,
    "newdata":{
        "name":"武汉市桥北医院",
        "phonenumber":"13345678888",
        "address":"武汉市桥北医院北路"
    }
}
# 发送请求给web服务
response = requests.put('http://localhost/api/mgr/customers',json=payload2)
pprint.pprint(response.json())      # 可以在调用查看,看看有没有添加到表中


"""删除客户信息"""
payload3 = {
    "action":"del_customer",
    "id": 6
}
# 发送请求给web服务
response = requests.put('http://localhost/api/mgr/customers',json=payload3)
pprint.pprint(response.json())      # 可以在调用查看,看看有没有添加到表中

#
# """列出所有客户接口"""
# param = {'action':'list_customer'}
# response = requests.get('http://localhost/api/mgr/customers',params=param)
# print(response.text)
test

3.10 实现登录

1)实现接口文档中“登录”部分,注意:此处的登录应该还是针对管理员的登录。

2)说明:

Auth模块是Django自带的用户认证模块:

我们在开发一个网站的时候,无可避免的需要设计实现网站的用户系统。此时我们需要实现包括用户注册、用户登录、用户认证、注销、修改密码等功能,这还真是个麻烦的事情呢。

Django作为一个完美主义者的终极框架,当然也会想到用户的这些痛点。它内置了强大的用户认证系统--auth,它默认使用 auth_user 表来存储用户数据。同时还有登录名与密码验证的库。可以参考一下这个

 在mgr/views目录下面,创建一个sign_in_out.py,写相关登录接口。

 

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


"""
接口文档登录要求:
POST  /api/mgr/signin  HTTP/1.1
Content-Type:   application/x-www-form-urlencoded
请求参数:username、password

响应要求:
HTTP/1.1 200 OK
Content-Type: application/json
如果成功返回:{“ret”:0}
如果失败,返回失败代号与原因{“ret”:1,"msg":"用户名或者密码错误"}

这里在实际写的时候考虑更多一点
"""


# 登录处理
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:
        # 表中is_active字段表示用户是否是激活状态
        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})
sigin_in_out.py

配置URL映射关系。

之前已经在总的URL中配置了所有‘api/mgr/’开头的到mgr.urls.py中找。

现在只需要在mgr/urls.py中配置如下

3)测试代码:

 理解:这边匹配登录,是匹配管理员账号密码。就是3.5 5)中创建的管理员账号。可以后台User查看。

3.11 加登录校验

1)背景:现在前面分别写了管理员登录API,管理员查看客户数据相关API。但是发现,查看客户数据时,并没有对登录校验,即直接输入登入之后的网址就可以查看。正常逻辑应该是登录上的用户,才有资格调用那些API。

请求消息合法性的验证通常有两种方案:session(会话)与token。

2)django_session

django中有一个session表django_session。session_key是会话ID,session_data是登录相关信息,具体存放哪些可以自定义。比如之前写的登录代码中就有。

 每次登录成功,django会自动创建一条会话。

 简短说明内部逻辑

Step1:浏览器登录成功后,会创建一条session记录,当然也可以往该会话内容中描述一些信息,比如:这个用户等级等等,这样后期就不用查表,直接在session中取。

同时,在登录请求的HTTP响应消息中的头字段Set-Cookie里填入sessionID。

注:根据http协议, 这个Set-Cookie字段的意思就是 要求前端将其中的数据存入 cookie中。 并且随后访问该服务端的时候, 在HTTP请求消息中必须带上 这些 cookie数据。

 

这个cookie是与网站对应,以后每次访问 同一个网站服务, 会在HTTP请求中自动再带上 这些cookie里面的数据。

 

Step2:后端需要时,应该取这个sessionID,去查一下会话,该用户是否有权限等行为操作此接口。

因此在原先的/mgr/views/customer.py中的dispatcher函数加上校验。

 

现在如果不经过登录界面,直接调用查看的URL,就会报错。

3.12 药品与订单API(数据库表的关联)

1)实现接口文档中“药品与订单”部分,注意:此处的登录应该还是针对管理员。

其实后端主要就是玩的对数据库的各种操作。

2)具体步骤如下:

Step1:与客户API一样,在销售员与管理员公共文件common/models.py建立药品与订单表。

 

 客户与订单是1对多关系,药品与订单是多对多的关系。解释:一个用户可以涉及多个订单;一个订单中可以涉及多个药品,一个药品可以涉及多个订单。

from django.db import models

# Create your models here.
class Customer(models.Model):
    # 客户名称
    name = models.CharField(max_length=200)
    # 联系电话
    phonenumber = models.CharField(max_length=200)
    # 地址
    address = models.CharField(max_length=200)

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


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设置为外键,指向Customer类。
    customer = models.ForeignKey(Customer,on_delete=models.PROTECT)
    # 参数 on_delete 指定了 当我们想 删除 外键指向的主键 记录时, 系统的行为。

    # 订单购买的药品,和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()
/common/models.py

然后执行:

 python manage.py makemigrations common(APP名字)

python manage.py migrate

 Step2 : 药品管理API实现,这个与上面的客户API类似。

import json
from django.http import JsonResponse
from common.models import Medicine     # 首先导入这个表

"""
药品管理其实与客户API类似
"""
# 写一个总函数分发管理员药品相关操作:查、增、修改、删除
def dispatcher(request):
    # 校验
    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类型',
            'redirct':'/mgr/sign.html'
        },
            status=302
        )

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

    # 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_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']
    # 返回值 就是对应插入记录的对象
    record = Medicine.objects.create(name=info['name'] ,
                            sn=info['sn'] ,
                            desc=info['desc'])
    return JsonResponse({'ret': 0, 'id':record.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/views/medicine.py

 启动Django     python manage.py runserver 80     打开浏览器访问:http://localhost/mgr/index.html,看一下效果。

Step3:实现订单接口

from django.db import models

# Create your models here.
class Customer(models.Model):
    # 客户名称
    name = models.CharField(max_length=200)
    # 联系电话
    phonenumber = models.CharField(max_length=200)
    # 地址
    address = models.CharField(max_length=200)

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


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设置为外键,指向Customer类。
    customer = models.ForeignKey(Customer,on_delete=models.PROTECT)
    # # 参数 on_delete 指定了 当我们想 删除 外键指向的主键 记录时, 系统的行为。

    # 订单购买的药品,和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()
models
from django.urls import path

from mgr.views import customer,sign_in_out,medicine,order

urlpatterns = [
    path('customers', customer.dispatcher),

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

    path('medicines',medicine.dispatcher),
    path('orders',order.dispatcher)

]
urls
import json

from django.db import transaction
from django.http import JsonResponse, HttpResponse
from common.models import Order,OrderMedicine    # 首先导入这个表
from django.db.models import F

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_order':
        return listorder(request)
    elif action == 'add_order':
        return addorder(request)

    # 订单 暂 不支持修改 和删除
    else:
        return JsonResponse({'ret': 1, 'msg': '不支持该类型http请求'})

def listorder(request):
    # 这边需要展示id,name,creat_date,customer_name,medicines_name;
    # Order表中只有id,name, creat_date
    # annotate等于时从命名,两个下划线取外键,但是接口需要的时单个下划线,重新命名一下
    qs = Order.objects\
            .annotate(
                customer_name=F('customer__name'),
                medicines_name=F('medicines__name')
            )\
            .values(
                'id','name','customer__name','medicines__name'
            )

    # 将 QuerySet 对象 转化为 list 类型
    retlist = list(qs)

    # 可能有 ID相同,药品不同的订单记录, 需要合并
    newlist = []
    id2order = {}
    for one in retlist:
        orderid = one['id']
        if orderid not in id2order:
            newlist.append(one)
            id2order[orderid] = one
        else:
            id2order[orderid]['medicines_name'] += ' | ' + one['medicines_name']

    return HttpResponse(json.dumps({'ret': 0, 'retlist': newlist},ensure_ascii=False),content_type='application/json')
    # return JsonResponse({'ret': 0, 'retlist': newlist},safe=False)


def addorder(request):
    info = request.params['data']

    # 因为订单涉及其他表,为了防止其他表添加一半失败,进行事务操作(要么全成功,要么回滚到操作之前的状态,避免脏数据)
    with transaction.atomic():
        new_order = Order.objects.create(name=info['name'],
                                         customer_id=info['customerid'])

        batch = [OrderMedicine(order_id=new_order.id, medicine_id=mid, amount=1)
                 for mid in info['medicineids']]
        OrderMedicine.objects.bulk_create(batch)

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

3.13 ORM 关联表操作

参考:https://www.bilibili.com/video/av73284083/?p=27

 

posted @ 2020-12-03 17:55  maxiaonong  阅读(224)  评论(0编辑  收藏  举报