首页 > 编程语言 >Python从0到100(五十):深入理解Django ORM与事务处理

Python从0到100(五十):深入理解Django ORM与事务处理

时间:2024-08-07 09:56:56浏览次数:16  
标签:name Python country create ORM request 事务处理 objects id

在这里插入图片描述

前言: 零基础学Python:Python从0到100最新最全教程 想做这件事情很久了,这次我更新了自己所写过的所有博客,汇集成了Python从0到100,共一百节课,帮助大家一个月时间里从零基础到学习Python基础语法、Python爬虫、Web开发、 计算机视觉、机器学习、神经网络以及人工智能相关知识,成为学习学习和学业的先行者!
欢迎大家订阅专栏:零基础学Python:Python从0到100最新最全教程!

一、ORM 对关联表的操作

在Python Web开发中,Django ORM是一个非常强大的工具,它允许开发者以Python代码的形式操作数据库,而无需编写复杂的SQL语句。本节将详细介绍如何使用Django ORM处理外键关联关系。

1. 定义模型

前面我们学过 一对多,一对一,多对多,都是通过外键来实现。接下来,我们通过一个实例演示,Django ORM 如何 操作 外键关联关系。
首先,我们需要在models.py中定义两个模型:CountryStudent。这两个模型通过外键关联,形成一个一对多的关系。在这里插入图片描述

from django.db import models

class Country(models.Model):
    name = models.CharField(max_length=100)

class Student(models.Model):
    name = models.CharField(max_length=100)
    grade = models.PositiveSmallIntegerField()
    country = models.ForeignKey(Country, on_delete=models.CASCADE)

2. 创建迁移

定义好模型后,我们需要创建迁移并应用到数据库中。

python manage.py makemigrations common
python manage.py migrate

这将生成两张表:countrystudent
在这里插入图片描述

3. 创建数据

在数据库中执行:
在这里插入图片描述
此时,我们在数据库中便找到了我们的文件:
在这里插入图片描述
然后,命令行中执行 python manage.py shell ,直接启动Django命令行,输入代码。
在这里插入图片描述

接下来,我们通过Django 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)

在这里插入图片描述

可以看到在我们的国家表和学生表中都产生了我们添加的名单:
在这里插入图片描述
在这里插入图片描述

4. 外键表字段访问

如果我们已经获取了一个student对象,要得到他的国家名称,可以这样操作:

s1 = Student.objects.get(name='白月')
print(s1.country.name)

5. 外键表字段过滤

要查找Student表中所有一年级的学生,可以使用以下代码:

students = Student.objects.filter(grade=1).values()

在这里插入图片描述

如果要查找一年级中国的学生,不能直接这样写:

# 错误方法
students = Student.objects.filter(grade=1, country='中国')

在这里插入图片描述

因为Student表中的country字段是一个外键字段,对应的是Country表中的id字段。正确的方法是:

cn = Country.objects.get(name='中国')
students = Student.objects.filter(grade=1, country=cn).values()

在这里插入图片描述

或者使用Django ORM的更简洁语法:

students = Student.objects.filter(grade=1, country__name='中国').values()

在这里插入图片描述

6. 外键表反向访问

在Django ORM中,关联表的反向关系可以通过表名小写加上_set来获取。例如,获取属于中国的所有学生:

cn = Country.objects.get(name='中国')
students = cn.student_set.all()

在这里插入图片描述
通过 表Model名转化为小写 ,后面加上一个 _set 来获取所有的反向外键关联对象

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

在这里插入图片描述
就可以使用更直观的属性名,像这样

cn = Country.objects.get(name='中国')
cn.students.all()

7. 外键表反向过滤

如果我们要获取所有 具有一年级学生 的国家名,该怎么写?

当然可以这样

# 先获取所有的一年级学生id列表
country_ids = Student.objects.filter(grade=1).values_list('country', flat=True)

# 再通过id列表使用  id__in  过滤
Country.objects.filter(id__in=country_ids).values()

在这里插入图片描述
但是这样同样存在 麻烦 和性能的问题。Django ORM 可以这样写:

Country.objects.filter(students__grade=1).values()

注意, 因为,我们定义表的时候,用 related_name=‘students’ 指定了反向关联名称 students ,所以这里是 students__grade 。 使用了反向关联名字。

1.没有指定related_name, 则应该使用表名转化为小写

如果定义时,没有指定related_name, 则应该使用 表名转化为小写 ,就是这样:

Country.objects.filter(student__grade=1).values()
2.使用 .distinct()去重

但是,我们发现,这种方式,会有重复的记录产生,如下
在这里插入图片描述

<QuerySet [{'id': 1, 'name': '中国'}, {'id': 1, 'name': '中国'}, {'id': 2, 'name': '美国'}, {'id': 2, 'name': '美国'}]>

可以使用 .distinct() 去重:

Country.objects.filter(student__grade=1).values().distinct()

在这里插入图片描述

注意:distinct()对MySQL数据库无效。

二、实现项目代码

1. URL路由更新

现在,我们在 mgr 目录下面新建 order.py 处理 客户端发过来的 列出订单、添加订单 的请求。
同样,先写 dispatcher 函数,代码如下:

from django.http import JsonResponse
from django.db.models import F
from django.db import IntegrityError, transaction

# 导入 Order 对象定义
from  common.models import  Order,OrderMedicine

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

    # 订单 暂 不支持修改 和删除

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

和以前差不多,没有什么好说的。在mgr/urls.py中添加对订单请求处理的路由:

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

urlpatterns = [
    path('customers', customer.dispatcher),
    path('medicines', medicine.dispatcher),
    path('orders', order.dispatcher),
    path('signin', sign_in_out.signin),
    path('signout', sign_in_out.signout),
]

在这里插入图片描述

2. 事务、多对多记录添加

在添加订单时,使用transaction.atomic()确保操作的原子性。

def addorder(request):
    info = request.POST.get('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})

with transaction.atomic() 下面 缩进部分的代码,对数据库的操作,就都是在 一个事务 中进行了。
如果其中有任何一步数据操作失败了, 前面的操作都会回滚。这就可以防止出现 前面的 Order表记录插入成功, 而后面的 订单药品 记录插入失败而导致的数据不一致现象,OrderMedicine 对应的是订单和药品的多对对记录关系表。要在多对多表中加上关联记录,就是添加一条记录, 可以这样:

OrderMedicine.objects.create(order_id=new_order.id,medicine_id=mid,amount=1)

我们这个例子中,一个订单可能会关联多个药品,也就是需要 插入 OrderMedicine 表中的数据 可能有很多条, 如果我们循环用

OrderMedicine.objects.create(order_id=new_order.id,medicine_id=mid,amount=1)

插入的话, 循环几次, 就会执行 几次SQL语句 插入的 数据库操作 这样性能不高。
我们可以把多条数据的插入,放在一个SQL语句中完成, 这样会大大提高性能。
方法就是使用 bulk_create, 参数是一个包含所有 该表的 Model 对象的 列表
就像上面代码这样

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

#  在多对多关系表中 添加了 多条关联记录
OrderMedicine.objects.bulk_create(batch)

3. 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表中就有这些字段,
只需要这样写就可以了:

def listorder(request):
    # 返回一个 QuerySet 对象 ,包含所有的表记录
    qs = Order.objects.values('id','name','create_date')
    return JsonResponse({'ret': 0, 'retlist': newlist})

问题是:‘customer_name’ 和 ‘medicines_name’ 这两个字段的值怎么获取呢? 因为 订单对应的客户名字 和 药品的名字 都不在 Order 表中啊。

Order 这个Model 中 有 ‘customer’ 字段 , 它外键关联了 Customer 表中的一个 记录,这个记录里面 的 name字段 就是我们要取的字段。

取 外键关联的表记录的字段值,在Django中很简单,可以直接通过 外键字段 后面加 两个下划线 加 关联字段名的方式 来获取。

比如 这里我们就可以用 下面的代码来实现

def listorder(request):
    qs = Order.objects\
            .values(
                'id','name','create_date',
                # 两个下划线,表示取customer外键关联的表中的name字段的值
                'customer__name'
            )
    
    # 将 QuerySet 对象 转化为 list 类型
    retlist = list(qs)
    return JsonResponse({'ret': 0, 'retlist': retlist})
3.1双下划线问题

首先,第一个问题, 接口文档需要的名字是 ‘customer_name’ 和 ‘medicines_name’。 里面只有一个下划线, 而我们这里却产生了 两个下划线。

怎么办?

可以使用 annotate 方法将获取的字段值进行重命名,像下面这样
写好后, 大家可以运行服务 , 用我们做好的前端系统添加几条 订单记录, 然后再查看一下数据库里面的数据是否正确。

from django.db.models import F

def listorder(request):
    # 返回一个 QuerySet 对象 ,包含所有的表记录
    qs = Order.objects\
            .annotate(
                customer_name=F('customer__name'),
                medicines_name=F('medicines__name')
            )\
            .values(
                'id','name','create_date',
                'customer_name',
                'medicines_name'
            )

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

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

在这里插入图片描述

3.2多个订单问题

第二个问题,如果一个订单里面有多个药品,就会产生多条记录, 这不是我们要的。根据接口,一个订单里面的多个药品, 用 竖线 隔开。怎么办?

我们可以用python代码来处理,像下面这样

def listorder(request):
    # 返回一个 QuerySet 对象 ,包含所有的表记录
    qs = Order.objects\
            .annotate(
                customer_name=F('customer__name'),
                medicines_name=F('medicines__name')
            )\
            .values(
                'id','name','create_date','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 JsonResponse({'ret': 0, 'retlist': newlist})

通过以上步骤,我们可以在Django项目中有效地处理外键关联关系和事务,确保数据的一致性和完整性。

标签:name,Python,country,create,ORM,request,事务处理,objects,id
From: https://blog.csdn.net/weixin_51390582/article/details/140675028

相关文章

  • 界面控件DevExpress WinForms,支持HTML & CSS提升用户体验(二)
    DevExpressWinForms现在可以利用HTML/CSS强大的功能,帮助受DevExpress驱动的WinForms应用程序引入现代的UI元素和用户体验!P.S:DevExpressWinForms拥有180+组件和UI库,能为WindowsForms平台创建具有影响力的业务解决方案。DevExpressWinForms能完美构建流畅、美观且易于使用的应......
  • python 实现FFT快速傅立叶变换算法
    FFT快速傅里叶变换介绍FFT(快速傅里叶变换)是计算离散傅里叶变换(DFT)及其逆变换的一种高效算法。DFT是一种将信号从时域转换到频域的数学工具,而FFT通过减少计算量来加速这一过程。FFT的基本思想FFT利用了DFT中的对称性和周期性,通过分而治之的策略将DFT分解为更小的DFT,从而显......
  • 8 Python字符串与二进制文本相互转换
     欢迎来到@一夜看尽长安花博客,您的点赞和收藏是我持续发文的动力对于文章中出现的任何错误请大家批评指出,一定及时修改。有任何想要讨论的问题可联系我:[email protected]。发布文章的风格因专栏而异,均自成体系,不足之处请大家指正。   专栏:java全栈C&C++PythonAIP......
  • 7 Python之代码类型提示(Type Hint)
     欢迎来到@一夜看尽长安花博客,您的点赞和收藏是我持续发文的动力对于文章中出现的任何错误请大家批评指出,一定及时修改。有任何想要讨论的问题可联系我:[email protected]。发布文章的风格因专栏而异,均自成体系,不足之处请大家指正。   专栏:java全栈C&C++PythonAIP......
  • Python 内联函数最佳实践
    如果我有一个可以用一行表示的python函数,那么以下哪一个选项通常被认为最适合可读性和一般最佳实践?或者还有其他更好的选择吗?选项2对我来说似乎是最好的,但我是初学者,所以我不想假设任何事情。我尝试过搜索PEP8、StackOverflow和一两个博客,但我找不到任何关于python的明......
  • 在Python中抽象出具有相同接口的真实硬件和模拟设备
    我正在寻找一种更惯用或更简洁的OOP模式来实现与以下内容等效的功能。接口和实现fromabcimportABC,abstractmethodfromtypingimportoverrideclassDevice:"""Maininterfacethathideswhetherthedeviceisarealhardwaredeviceorasimulated......
  • Python Django,使用外部MSSQL数据库
    我正在尝试创建一个连接到外部MSSQL数据库以仅检索信息(只读)的django网站。这个数据库非常庞大,有数百个表。我目前可以通过在django应用程序中创建一个函数来使其工作,该函数使用connectionString并运行原始SQL查询并将其返回到pandas数据帧中。不知何故,我感觉......
  • 使用 Python 中的 Matplotlib 和时间序列索引生成奇怪的图
    我正在尝试使用Python中的Matplotlib绘制一些时间序列数据,但生成的图看起来很奇怪,我不明白为什么。这是我正在使用的代码:filtered_df=df.loc[(df.index>'2010-01-01')&(df.index<='2010-01-08')]#Plottingthedatafig,axs=plt.subplots(1,1,figsize=(12,......
  • Dash Python:通过 @callback 链接选项卡
    这个问题是下面链接的问题的扩展:DashPython:布局函数中的@Callback未被调用我有一个简单的数据框:importpandasaspddf=pd.DataFrame({'Class1':[1,2,3,4,5],'Class2':[6,7,8,9,10]})我创建了一个数据提取函数,该函数根......
  • 如何在 Python 中使用 Langchain 返回已使用的上下文以进行回答
    我已经构建了一个像这样的RAG系统:defformat_docs(docs):return"\n\n".join(doc.page_contentfordocindocs)response_schemas=[ResponseSchema(name="price",description="Price",type="float"),ResponseSchema(......