- 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 })
- 多对多记录操作