11 ORM关联表、事务

  • ORM对关联表的操作
  • 在models.py中定义两个Model,对应两张表

 

 1 # common.models.py
 2 # 国家表
 3 class Country(models.Model):
 4     name = models.CharField(max_length=100)
 5 
 6 # 学生表, country 字段是国家表的外键,形成一对多的关系
 7 class Student(models.Model):
 8     name    = models.CharField(max_length=100)
 9     grade   = models.PositiveSmallIntegerField()
10     country = models.ForeignKey(Country,
11                                 on_delete=models.PROTECT)

 

  • 执行python manage.py makemigrations common
  • 执行python manage.py migrate
  • 执行python manage.py shell
  • 输入代码
    from common.models import *
    c1 = Country.objects.create(name='中国')
    c2 = Country.objects.create(name='美国')
    c3 = Country.objects.create(name='法国')
    Student.objects.create(name='白月', grade=1, country=c1)
    Student.objects.create(name='黑羽', grade=2, country=c1)
    Student.objects.create(name='大罗', grade=1, country=c1)
    Student.objects.create(name='真佛', grade=2, country=c1)
    Student.objects.create(name='Mike', grade=1, country=c2)
    Student.objects.create(name='Gus',  grade=1, country=c2)
    Student.objects.create(name='White', grade=2, country=c2)
    Student.objects.create(name='Napolen', grade=2, country=c3)
  • 外键表字段访问(Student->Country)  
    • s1 = Student.objects.get(name = '白月'):获取到一个学生对象,怎么得到他的国家名称?
    • s1.country.name:获取国家的名称
  • 外键表字段过滤  
    • 获取Student表中所有一年级的学生:Student.objects.filter(grade=1).values()
    • 查找Student表中所有一年级中国学生:
    • cn = Country.objects.get(name='中国')   Student.objects.filter(grade=1,country_id=cn.id).values()   
    • cn = Country.objects.get(name='中国')   Student.objects.filter(grade=1,country=cn).values()   
    • 上面的方法,写起来麻烦,有两步操作,需要发送两次数据请求给数据库服务器,性能不高
    • 中间方案:Student.objects.filter(grade=1,country__name='中国').values('name','country__name')
    • 选择出来的记录中,国家名是country__name,两个下划线比较怪
    • 有时候前后端接口的设计者,定义好了接口格式,如果要求一定是countryname这个怎么办呢?
    • 最终方案:
      from django.db.models import F
      
      # annotate 可以将表字段进行别名处理
      Student.objects.annotate(
          countryname=F('country__name'),
          studentname=F('name')
          ).filter(grade=1,countryname='中国').values('studentname','countryname')
  • 外键表反向访问
    • 外键表字段访问时通过表外键字段表示,比如Student表的country字段
    • 反向关系时通过表Model名转化成小写表示的
    • 如果已经获取了一个Country对象,如何获取到所有属于这个国家的学生呢?:通过表Model名转化为小写,后面加上_set来获取所有的反向外键关联对象
    • cn = Country.objects.get(name='中国')cn.student_set.all()
    • django给出了一个方法,可以更直观的反应关联关系,在定义Model的时候,外键字段使用related_name参数

 

# 国家表
class Country(models.Model):
    name = models.CharField(max_length=100)

# country 字段是国家表的外键,形成一对多的关系
class Student(models.Model):
    name    = models.CharField(max_length=100)
    grade   = models.PositiveSmallIntegerField()
    country = models.ForeignKey(Country,
                on_delete = models.PROTECT,
                # 指定反向访问的名字
                related_name='students')

 

    • 可能会遇到AttributeError: 'Country' object has no attribute 'students':关闭django shell,重启启动即可。
  • 反向过滤
    • 如果我们要获取所有具有一年级学生的国家名?
    • country_ids = Student.objects.filter(grade=1).values_list('country',flat=True)  Country.objects.filter(id__in=country_ids).values()
    • 使用distinct()消除重复:
    • # 先获取去重后的国家ID
      country_ids = Student.objects.filter(grade=1).values_list('country', flat=True).distinct()
      # 再查询
      Country.objects.filter(id__in=country_ids).values()
    • Country.objects.filter(students__grade=1).values()
    • Country.objects.filter(students__grade=1).values().distinct()
    • 据说.distinct()对MySQL数据库无效
  • 事务、多对多记录添加
    • 添加函数addorder,来处理添加订单请求
    • 添加一条订单记录,需要在两张表(Order和OrderMedicine)中添加记录,意味着我们要有两次数据库操作
    • 第一次插入成功,第二次插入失败,就会造成处理只做了一半
    • 脏数据:数据库中出现数据不一致
    • 脏数据处理办法:事务机制(把一批数据库操作放在事务中,该事务中的任何一次数据库操作失败了,数据库系统就会让整个事务发生回滚,撤销前面的操作,数据库回滚到这事务操作之前的状态)
    • django实现事务操作:with transaction.atomic()
  • orm外键关联
    • 添加函数listorder,来处理列出订单请求
    • # 根据接口文档,我们返回订单记录格式如下:
      [
          {
              id: 1, 
              name: "华山医院订单001", 
              create_date: "2018-12-26T14:10:15.419Z",
              customer_name: "华山医院",
              medicines_name: "青霉素"
          },
          {
              id: 2, 
              name: "华山医院订单002", 
              create_date: "2018-12-27T14:10:37.208Z",
              customer_name: "华山医院",
              medicines_name: "青霉素 | 红霉素 "
          }
      ]
    • 'id','name','create_date' 这些字段的内容获取很简单,order表中就有这些字段
    • customer_name','medicines_name'这两个字段怎么获取呢?
    • Order 这个Model中有'customer'字段,它外键关联了Customer表中的一个记录,这个记录里面的'name'字段就是我们要取的字段
    • 取外键关联表记录的字段值,django中很简单,可以通过外键字段后面加两个下划线加关联字段名的方式来获取'customer__name'
    • 订单对应的药品名字段,是多对多关联,也同样可以用两个下划线获取关键字段的值'medicines__name'
    • 问题1:接口文档需要的名字是'customer_name'和'medicines_name',里面只有一个下划线,而我们产生了两个下划线,可以使用annotate方法将获取的字段重命名
    • 问题2:如果一个订单中有多个药品,就会产生多条记录,这不是我们想要的,根据接口文档,一个订单里面多个药品用竖线隔开
    •  1 # bysms.urls.py
       2 from django.contrib import admin
       3 from django.urls import path, include
       4 # 静态文件服务声明
       5 from django.conf.urls.static import static
       6 
       7 urlpatterns = [
       8                   path('admin/', admin.site.urls),
       9                   # 全路由
      10                   # path('sales/orders/', list_orders),
      11                   # 路由子表
      12                   path('sales/', include('sales.urls')),
      13                   path('api/mgr/', include('mgr.urls'))
      14               ] + static('/', document_root='./z_dist')
    •  1 # mgr.urls.py
       2 from django.urls import path
       3 from mgr import customer, sign_in_out, medicine, order
       4 
       5 urlpatterns = [
       6     path('customers', customer.dispatcher),
       7     path('medicines', medicine.dispatcher),
       8     path('orders', order.dispatcher),
       9     path('signin', sign_in_out.signin),
      10     path('signout', sign_in_out.signout),
      11 ]
    •  1 #mgr.order.py
       2 import json
       3 
       4 from django.db import transaction
       5 from django.db.models import F
       6 from django.http import JsonResponse
       7 
       8 from common.models import Order, OrderMedicine
       9 
      10 
      11 def mgr_login_required(view_func):
      12     def wrapper(request, *args, **kwargs):
      13         if 'usertype' not in request.session:
      14             return JsonResponse({
      15                 'ret': 302,
      16                 'msg': '未登录',
      17                 'redirect': '/mgr/sign.html'
      18             }, status=302)
      19 
      20         if request.session['usertype'] != 'mgr':
      21             return JsonResponse({
      22                 'ret': 302,
      23                 'msg': '用户非mgr类型',
      24                 'redirect': '/mgr/sign.html'
      25             }, status=302)
      26 
      27         return view_func(request, *args, **kwargs)
      28 
      29     return wrapper
      30 
      31 
      32 @mgr_login_required
      33 def dispatcher(request):
      34     if request.method == 'GET':
      35         request.params = request.GET
      36 
      37     elif request.method in ['POST', 'PUT', 'DELETE']:
      38         request.params = json.loads(request.body)
      39 
      40     action = request.params['action']
      41     if action == 'list_order':
      42         return listorders(request)
      43     elif action == 'add_order':
      44         return addorder(request)
      45     # 订单暂时不支持修改和删除
      46     else:
      47         return JsonResponse({'ret': 1, 'msg': '不支持该类型http请求'})
      48 
      49 
      50 def listorders(request):
      51     # 返回一个QuerySet对象,包含所有的表记录
      52     qs = Order.objects.annotate(
      53         customer_name=F('customer__name'),
      54         medicines_name=F('medicines__name')
      55     ).values('id', 'name', 'create_date', 'customer_name', 'medicines_name')
      56 
      57     retlist = list(qs)
      58 
      59     # retlist里的一个订单中,多个药品,会有多条记录,需要合并
      60     newlist = []
      61     id2order = {}
      62     for one in retlist:
      63         orderid = one['id']
      64         if orderid not in id2order:
      65             newlist.append(one)
      66             id2order[orderid] = one
      67         else:
      68             id2order[orderid]['medicines_name'] += '|' + one['medicines_name']
      69 
      70     return JsonResponse({'ret': 0, 'retlist': newlist})
      71 
      72 
      73 def addorder(request):
      74     info = request.params['data']
      75     # 从请求消息中 ,获取要添加订单的信息
      76     # 并且插入到数据库中
      77 
      78     with transaction.atomic():
      79         new_order = Order.objects.create(name=info['name'], customer_id=info['customerid'])
      80 
      81         batch = [OrderMedicine(order_id=new_order.id, medicine_id=mid, amount=1) for mid in info['medicineids']]
      82 
      83         # 在多对多关系表中,添加了多条关联记录
      84         OrderMedicine.objects.bulk_create(batch)
      85     return JsonResponse({
      86         "ret": 0,
      87         "id": new_order.id
      88     })
  • 多对多记录操作
posted @ 2025-12-07 17:52  理想赵雷  阅读(6)  评论(0)    收藏  举报