• Python 框架学习 Django篇 (六) 数据表关联、ORM关联


    在后端服务器开发中,特别是前后端分离的架构中数据库是非常重要的,后端主要就是负责管理数据,而我们经常使用的mysql、oracle 都是关系型数据库,什么是关系型数据库?就是建立在关系模型基础上的数据库,而最难处理的就是各个表之间的关联关系,一般这种关系分为三种: 一对一 、一对多、多对多

     一、数据表关联

    1、一对多

    表之间以对多的关系就是数据库中的 "外键"  ,下面我们举个例子,比如一个医药系统中肯定会有客户的信息吧,我们先定义一个客户的基本信息(客户名称、联系电话、居住地址)

      vi Django_demo/paas/models.py

    1. class Customer(models.Model):
    2. # 客户名称
    3. name = models.CharField(max_length=200)
    4. # 联系电话
    5. phonenumber = models.CharField(max_length=200)
    6. # 地址
    7. address = models.CharField(max_length=200)

    我们是一个医药系统,肯定存在很多不同的药品类型,同样也需要定义一个药品类型的表

    Medicine药品表 ,包含一些(药品名称、编号、描述信息)

      vi Django_demo/paas/models.py

    1. class Medicine(models.Model):
    2. # 药品名
    3. name = models.CharField(max_length=200)
    4. # 药品编号
    5. sn = models.CharField(max_length=200)
    6. # 描述
    7. desc = models.CharField(max_length=200)

    有了药品信息、客户信息,那么只要存在销售的话就一定会有订单信息

    想一想,我们订单的信息是不是和上面的两张表多少有一些关联,比如订单中需要用到客户信息和药品信息

    在实际观察中,我们发现订单表里面会同时需要拿到上面两张表中的数据,在下图中我们可以看到一个客户同时可能会有多个订单,这种情况就是一对多,或者说多对一

     

    像是这种一对多的关系,在数据库中是以外键形式表示的,如果说一个表中的字段是外键,那么他的值一定来源与其他表的主键

    另外,我们定义表的 Model类的时候,如果没有指定主键字段,migrate 的时候 Django 会为该Model对应的数据库表自动生成一个id字段,作为主键

    1. #导入数据库
    2. python manage.py makemigrations
    3. python manage.py migrate
    4. #查看
    5. desc paas_customer;

    现在我们要生成订单表,按照实际情况我们订单表的字段里面也会有客户的信息表示谁下的订单,而用户的信息需要使用外键去关联客户的主键,而客户表也就是customer表的主键就是id字段,Django中定义外键 的方法就是 Model类的该属性字段 值为(ForeignKey)

     vi Django_demo/paas/models.py

    1. import datetime
    2. class Order(models.Model):
    3. # 订单名
    4. name = models.CharField(max_length=200,null=True,blank=True)
    5. # 创建日期
    6. create_date = models.DateTimeField(default=datetime.datetime.now)
    7. # 客户
    8. customer = models.ForeignKey(Customer,on_delete=models.PROTECT)

    上面定义的customer是外键,让他去找Customer表的主键获取数据,而这里设置了一个on_delete的参数,这个意思是当主键被删除了那么外键这个数据还要不要了

    1、CASCADE: 跟随主机一起把外键数据删除

    2、PROTECT   禁止删除,如果非要删除就先清除外键数据后,才能删除对应主键

    3、SET_NULL   删除后外键数据修改为null

    注意

          外键字段,实际在数据库表中的字段名是DjangoForeignKey定义字段名加上后缀"_id"

    比如上面,在执行了 migrate 命令更新数据库后,customer 这个外键字段实际上在 数据库表中的字段名 是 customer_id

    1. python manage.py makemigrations
    2. python manage.py migrate
    3. #查看
    4. desc paas_info;

    返回

    1. mysql> desc paas_order;
    2. +-------------+--------------+------+-----+---------+----------------+
    3. | Field | Type | Null | Key | Default | Extra |
    4. +-------------+--------------+------+-----+---------+----------------+
    5. | id | bigint | NO | PRI | NULL | auto_increment |
    6. | name | varchar(200) | YES | | NULL | |
    7. | create_date | datetime(6) | NO | | NULL | |
    8. | customer_id | bigint | NO | MUL | NULL | |
    9. +-------------+--------------+------+-----+---------+----------------+
    10. 4 rows in set (0.00 sec)

    2、一对一

    上面的外键案例,可以说是一对多或者多对一,而有时候是一对一的情况

    比如,某个学校的学生表 和学生的地址表,就形成一对一的关系,即 一条主键所在表的记录 只能对应一条 外键所在表的记录,而Django 中 用 OneToOneField 对象 实现 一对一 的关系

     vi Django_demo/paas/models.py

    1. class Student(models.Model):
    2. # 姓名
    3. name = models.CharField(max_length=200)
    4. # 班级
    5. classname = models.CharField(max_length=200)
    6. # 描述
    7. desc = models.CharField(max_length=200)
    8. class ContactAddress(models.Model):
    9. # 一对一 对应学生
    10. student = models.OneToOneField(Student, on_delete=models.PROTECT)
    11. # 家庭
    12. homeaddress = models.CharField(max_length=200)
    13. # 电话号码
    14. phone = models.CharField(max_length=200)

     Django发现这样一对一定定义,它会在migrate的时候,在数据库中定义该字段为外键的同时, 加上 unique=True 约束,表示在此表中,所有记录的该字段 取值必须唯一,不能重复

     3、多对多

    数据库中还存在一种多对多的关系,在order订单表中

    一个订单可以采购多种药品,就对应 Medicine表里面的多种药品;

    而一种药品也可以被多个订单采购, 那么Order表 和 Medicine表 之间就形成了多对多的关系

     

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

     vi Django_demo/paas/models.py

    1. import datetime
    2. class Order(models.Model):
    3. # 订单名
    4. name = models.CharField(max_length=200,null=True,blank=True)
    5. # 创建日期
    6. create_date = models.DateTimeField(default=datetime.datetime.now)
    7. # 客户
    8. customer = models.ForeignKey(Customer,on_delete=models.PROTECT)
    9. # 订单购买的药品,和Medicine表是多对多 的关系
    10. medicines = models.ManyToManyField(Medicine, through='OrderMedicine')
    11. class OrderMedicine(models.Model):
    12. #添加外键
    13. order = models.ForeignKey(Order, on_delete=models.PROTECT)
    14. medicine = models.ForeignKey(Medicine, on_delete=models.PROTECT)
    15. # 订单中药品的数量 一种特殊的类型,表示非负整数
    16. amount = models.PositiveIntegerField()

    我们上面通过medicines = models.ManyToManyField(Medicine, through='OrderMedicine')

    去指定Order表和Medicine表的对应关系,其实不会在Order表上面创建medicines的字段

    1. python manage.py makemigrations
    2. python manage.py migrate
    3. #查看
    4. desc paas_OrderMedicine;

     

    4、管理药品实现

    我们在 mgr 目录下面新建 medicine.py,处理 客户端发过来的 列出药品、添加药品、修改药品、删除药品 的请求,需要运用前面的数据库增删改查的方法

    vi Django_demo/mgr/medicine.py

    1. from django.http import JsonResponse
    2. # 导入 Medicine 对象定义(这块可能显示模块导入不正常,忽略)
    3. from paas.models import Medicine
    4. import json
    5. def Orderdispatcher(request):
    6. # 根据session判断用户是否是登录的管理员用户
    7. if 'usertype' not in request.session:
    8. return JsonResponse({
    9. 'ret': 302,
    10. 'msg': '未登录',
    11. 'redirect': '/mgr/sign.html'},
    12. status=302)
    13. if request.session['usertype'] != 'mgr':
    14. return JsonResponse({
    15. 'ret': 302,
    16. 'msg': '用户非mgr类型',
    17. 'redirect': '/mgr/sign.html'},
    18. status=302)
    19. # 将请求参数统一放入request 的 params 属性中,方便后续处理
    20. # GET请求 参数 在 request 对象的 GET属性中
    21. if request.method == 'GET':
    22. request.params = request.GET
    23. # POST/PUT/DELETE 请求 参数 从 request 对象的 body 属性中获取
    24. elif request.method in ['POST','PUT','DELETE']:
    25. # 根据接口,POST/PUT/DELETE 请求的消息体都是 json格式
    26. request.params = json.loads(request.body)
    27. # 根据不同的action分派给不同的函数进行处理
    28. action = request.params['action']
    29. if action == 'list_medicine':
    30. return listmedicine(request)
    31. elif action == 'add_medicine':
    32. return addmedicine(request)
    33. elif action == 'modify_medicine':
    34. return modifymedicine(request)
    35. elif action == 'del_medicine':
    36. return deletemedicine(request)
    37. else:
    38. return JsonResponse({'ret': 1, 'msg': '不支持该类型http请求'})
    39. def listmedicine(request):
    40. # 返回一个 QuerySet 对象 ,包含所有的表记录
    41. qs = Medicine.objects.values()
    42. # 将 QuerySet 对象 转化为 list 类型
    43. # 否则不能 被 转化为 JSON 字符串
    44. retlist = list(qs)
    45. return JsonResponse({'ret': 0, 'retlist': retlist})
    46. def addmedicine(request):
    47. info = request.params['data']
    48. # 从请求消息中 获取要添加客户的信息
    49. # 并且插入到数据库中
    50. medicine = Medicine.objects.create(name=info['name'] ,
    51. sn=info['sn'] ,
    52. desc=info['desc'])
    53. return JsonResponse({'ret': 0, 'id':medicine.id})
    54. def modifymedicine(request):
    55. # 从请求消息中 获取修改客户的信息
    56. # 找到该客户,并且进行修改操作
    57. medicineid = request.params['id']
    58. newdata = request.params['newdata']
    59. try:
    60. # 根据 id 从数据库中找到相应的客户记录
    61. medicine = Medicine.objects.get(id=medicineid)
    62. except Medicine.DoesNotExist:
    63. return {
    64. 'ret': 1,
    65. 'msg': f'id 为`{medicineid}`的药品不存在'
    66. }
    67. if 'name' in newdata:
    68. medicine.name = newdata['name']
    69. if 'sn' in newdata:
    70. medicine.sn = newdata['sn']
    71. if 'desc' in newdata:
    72. medicine.desc = newdata['desc']
    73. # 注意,一定要执行save才能将修改信息保存到数据库
    74. medicine.save()
    75. return JsonResponse({'ret': 0})
    76. def deletemedicine(request):
    77. medicineid = request.params['id']
    78. try:
    79. # 根据 id 从数据库中找到相应的药品记录
    80. medicine = Medicine.objects.get(id=medicineid)
    81. except Medicine.DoesNotExist:
    82. return {
    83. 'ret': 1,
    84. 'msg': f'id 为`{medicineid}`的客户不存在'
    85. }
    86. # delete 方法就将该记录从数据库中删除了
    87. medicine.delete()
    88. return JsonResponse({'ret': 0})

    添加路由

    vi Django_demo/mgr/urls.py

    1. from django.urls import path
    2. from .k8s import dispatcher
    3. from .sign_in_out import signin,signout
    4. from .medicine import orderdispatcher #添加
    5. urlpatterns = [
    6. path('customers/', dispatcher),
    7. path('medicines/', orderdispatcher), #添加 必须带斜杠
    8. path('signin', signin),
    9. path('signout', signout),
    10. ]

    5、添加药品

    vi main.py

    1. import requests,pprint
    2. #添加认证
    3. payload = {
    4. 'username': 'root',
    5. 'password': '12345678'
    6. }
    7. #发送登录请求
    8. response = requests.post('http://127.0.0.1:8000/api/mgr/signin',data=payload)
    9. #拿到请求中的认证信息进行访问
    10. set_cookie = response.headers.get('Set-Cookie')
    11. # 构建添加 客户信息的 消息体,是json格式
    12. payload = {
    13. "action":"add_medicine",
    14. "data":{
    15. "name":"板蓝根",
    16. "sn":"133",
    17. "desc":"感冒药"
    18. }
    19. }
    20. url='http://127.0.0.1:8000/api/mgr/medicines/'
    21. if set_cookie:
    22. # 将Set-Cookie字段的值添加到请求头中
    23. headers = {'Cookie': set_cookie}
    24. # 发送请求给web服务
    25. response = requests.post(url,json=payload,headers=headers)
    26. pprint.pprint(response.json())

    json类型说明

    刚才上面我们使用了查询和添加数据,但是发现一个问题,两个请求传参的时候稍有不同

    1. #data=payload 表示这个请求携带的参数是以表单的形式也就是字符串形式传输给后端的
    2. requests.post(url,data=payload)
    3. #json=payload 表示参数是以json的形式传输给后端的
    4. requests.post(url,json=payload)

    在使用时要特别注意,我卡了半天才看到。。

    6、查询药品

    vi main.py

    1. import requests,pprint
    2. payload = {
    3. 'username': 'root',
    4. 'password': '12345678'
    5. }
    6. #发送登录请求
    7. response = requests.post('http://127.0.0.1:8000/api/mgr/signin',data=payload)
    8. #拿到请求中的认证信息进行访问
    9. set_cookie = response.headers.get('Set-Cookie')
    10. if set_cookie:
    11. # 将Set-Cookie字段的值添加到请求头中
    12. headers = {'Cookie': set_cookie}
    13. # 发送带有Cookie的新请求 修改url到新的路由
    14. response = requests.get('http://127.0.0.1:8000/api/mgr/medicines/?action=list_medicine',headers=headers)
    15. pprint.pprint(response.json())

    返回

    1. {'ret': 0,
    2. 'retlist': [{'desc': '192.168.1.2', 'id': 1, 'name': 'abc', 'sn': '133'},
    3. {'desc': '感冒药', 'id': 2, 'name': '板蓝根', 'sn': '133'}]}

    第一行是我写错了添加上的,一会当作删除的案例

    遇到的问题

    在访问url的时候,要确定url访问时是否需要带上/  如果定义的urls上有/,那边必须要带上斜杠不然会报错

    7、修改药品

    vi main.py

    1. import requests,pprint
    2. #添加认证
    3. payload = {
    4. 'username': 'root',
    5. 'password': '12345678'
    6. }
    7. #发送登录请求
    8. response = requests.post('http://127.0.0.1:8000/api/mgr/signin',data=payload)
    9. #拿到请求中的认证信息进行访问
    10. set_cookie = response.headers.get('Set-Cookie')
    11. # 构建添加 客户信息的 消息体,是json格式
    12. payload = {
    13. "action":"modify_medicine",
    14. "id": "1",
    15. "newdata":{
    16. "name":"诺氟沙星",
    17. "sn":"141",
    18. "desc":"无"
    19. }
    20. }
    21. url='http://127.0.0.1:8000/api/mgr/medicines/'
    22. if set_cookie:
    23. # 将Set-Cookie字段的值添加到请求头中
    24. headers = {'Cookie': set_cookie}
    25. # 发送请求给web服务
    26. response = requests.post(url,json=payload,headers=headers)
    27. pprint.pprint(response.json())

    再次查询

    1. {'ret': 0,
    2. 'retlist': [{'desc': '无', 'id': 1, 'name': '诺氟沙星', 'sn': '141'},
    3. {'desc': '感冒药', 'id': 2, 'name': '板蓝根', 'sn': '133'}]}

    8、删除药品

    1. import requests,pprint
    2. #添加认证
    3. payload = {
    4. 'username': 'root',
    5. 'password': '12345678'
    6. }
    7. #发送登录请求
    8. response = requests.post('http://127.0.0.1:8000/api/mgr/signin',data=payload)
    9. #拿到请求中的认证信息进行访问
    10. set_cookie = response.headers.get('Set-Cookie')
    11. # 构建添加 客户信息的 消息体,是json格式
    12. payload = {
    13. "action":"del_medicine",
    14. "id":"1",
    15. }
    16. url='http://127.0.0.1:8000/api/mgr/medicines/'
    17. if set_cookie:
    18. # 将Set-Cookie字段的值添加到请求头中
    19. headers = {'Cookie': set_cookie}
    20. # 发送请求给web服务
    21. response = requests.post(url,json=payload,headers=headers)
    22. pprint.pprint(response.json())

     返回

    {'ret': 0, 'retlist': [{'desc': '感冒药', 'id': 2, 'name': '板蓝根', 'sn': '133'}]}

    可以看到除了查询以外,增加、修改、删除的操作基本是一致的,只需要修改携带参数中的动作以及传入的参数值即可

    二、数据库关联操作(sql)

    1、一对多

    1. #先查询用户的id,然后基于id查询外键对应的订单
    2. select * from paas_order where customer_id = (select id from paas_customer where name = "zhangsan");

    一对多,我们查询指定的一个客户id,然后基于id去订单表中获取所以用户相关的订单

    2、多对多

    我们上面使用的时候添加过这么一个表  OrderMedicine 

    1. class OrderMedicine(models.Model):
    2. #添加外键
    3. order = models.ForeignKey(Order, on_delete=models.PROTECT)
    4. medicine = models.ForeignKey(Medicine, on_delete=models.PROTECT)
    5. # 订单中药品的数量 一种特殊的类型,表示非负整数
    6. amount = models.PositiveIntegerField()

    多对多,表示什么意思呢,我们建立的上面的表是将order订单表 和Medicine药品表进行关联的一个中间表,我们在给前面两张表添加数据的时候,还需要单独去给中间表添加一次数据,用来声明订单和药品的关系,通过这张中间表,我们就能查询药品是那些客户进行购买,也可以查询那些客户购买了那些药品,我们做一个案例

     Django_demo/mgr/admin.py

    1. from paas.models import Customer,Medicine,Order,OrderMedicine
    2. admin.site.register([Customer,Medicine,Order,OrderMedicine])

     访问admin页面,前面我们添加了很多的数据除了客户表信息以外都先删除一下

     

    我们先给Medicines药品表  和orders 订单表 添加一条信息

     

    上面给药品添加了感冒颗粒,订单表中给张三用户添加了一个test的订单 ,现在是没有任何关联机制的,这两张表也没有交互,我们在order medicines表中添加下两个订单的关联

     我们关联了第一条数据,将刚才的订单信息和药品信息以及数量一并放入到了中间表中

    1. mysql> select * from paas_ordermedicine;
    2. +----+--------+-------------+----------+
    3. | id | amount | medicine_id | order_id |
    4. +----+--------+-------------+----------+
    5. | 1 | 10 | 6 | 5 |
    6. +----+--------+-------------+----------+
    7. 1 row in set (0.00 sec)

     这里因为存放了药品的id和订单的id,我们就可以根据这个表来查询关联的数据信息,比如我想要看看订单表里面test订单对应下单了什么药品,买了多少

    1. #找到订单中的id
    2. select id from paas_order where name = "test";
    3. #基于id查询关系表中药品
    4. select medicine_id,amount from paas_ordermedicine where order_id = 5;
    5. #命令组合
    6. mysql> select medicine_id,amount from paas_ordermedicine where order_id = (select id from paas_order where name = "test");
    7. +-------------+--------+
    8. | medicine_id | amount |
    9. +-------------+--------+
    10. | 6 | 10 |
    11. +-------------+--------+
    12. 1 row in set (0.00 sec)

  • 相关阅读:
    MAVROS发送自定义话题消息给PX4
    Mysql-varchar和text的区别
    网络应用的基本原理
    ubuntu中/etc/rc.local和/etc/init.d/rc.local的区别是什么
    Java学习 --- lambda表达式
    LC-1774. 最接近目标价格的甜点成本(DFS、01背包、三进制状态枚举)
    Windows桌面便笺 - 置顶任务TODO - 便利贴工具
    Metabase学习教程:模型-2
    游戏设计模式专栏(一):工厂方法模式
    Linux awk命令
  • 原文地址:https://blog.csdn.net/qq_42883074/article/details/133982094