Django5模型定义与使用
6431字约21分钟
2025-02-16
Django5对各种数据库提供了很好的支持,包括PostgreSQL、MySQL、SQLite和 Oracle,而且为这些 数据库提供了统一的API方法,这些API统称为ORM框架。通过使用Django5内置的ORM框架可以实现数 据库连接和读写操作。
Django5模型定义
ORM框架是一种程序技术,用于实现面向对象编程语言中不同类型系统的数据之间的转换。 从效果上说,它创建了一个可在编程语言中使用的“虚拟对象数据库”,通过对虚拟对象数据库的操作从 而实现对目标数据库的操作,虚拟对象数据库与目标数据库是相互对应的。在 Django5中,虚拟对象数 据库也称为模型,通过模型实现对目标数据库的读写操作,实现方法如下:
- 配置目标数据库,在settings.py中设置配置属性
- 构建虚拟对象数据库,在App 的models.py文件中以类的形式定义模型。
- 通过模型在目标数据库中创建相应的数据表。
- 在其他模块(如视图函数)里使用模型来实现目标数据库的读写操作。
settings.py下我们配置mysql数据库:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'db_python222',
'USER': 'root',
'PASSWORD': '123456',
'HOST': 'localhost',
'PORT': '3308'
}
}
然后我们在models.py里新建两个模型类,分别是图书模型BookInfo和图书类别模型BookTypeInfo,他 们是多对一的关系;
class BookTypeInfo(models.Model):
id = models.AutoField(primary_key=True)
bookTypeName = models.CharField(max_length=20)
class Meta:
db_table = "t_bookType"
verbose_name = "图书类别信息" # 给模型取个直观的名字
class BookInfo(models.Model):
id = models.AutoField(primary_key=True)
bookName = models.CharField(max_length=20)
price = models.FloatField()
publishDate = models.DateField()
bookType = models.ForeignKey(BookTypeInfo, on_delete=models.PROTECT)
class Meta:
db_table = "t_book"
verbose_name = "图书信息" # 给模型取个直观的名字
模型字段类型如下:
- AutoField:自增长类型,数据表的字段类型为整数,长度为11位。
- BigAutoField:自增长类型,数据表的字段类型为bigint,长度为20位。
- CharField:字符类型。
- BooleanField:布尔类型。
- CommaSeparatedIntegerField:用逗号分割的整数类型。DateField:日期( Date)类型。
- DateTimeField:日期时间( Datetime)类型。Decimal:十进制小数类型。
- EmailField:字符类型,存储邮箱格式的字符串。
- FloatField:浮点数类型,数据表的字段类型变成Double类型。IntegerField:整数类型,数据表的字 段类型为11位的整数。
- BigIntegerField:长整数类型。
- IPAddressField:字符类型,存储Ipv4地址的字符串。
- GenericIPAddressField:字符类型,存储Ipv4和Ipv6地址的字符串。NullBooleanField:允许为空的 布尔类型。
- PositiveIntegerFiel:正整数的整数类型。
- PositiveSmallIntegerField:小正整数类型,取值范围为0~32767。SlugField:字符类型,包含字 母、数字、下画线和连字符的字符串。
- SmallIntegerField:小整数类型,取值范围为-32,768~+32,767。
- TextField:长文本类型。
- TimeField:时间类型,显示时分秒HH:MM[ :ss[.uuuuuu]]。URLField:字符类型,存储路由格式的 字符串。
- BinaryField:二进制数据类型。
- FileField:字符类型,存储文件路径的字符串。ImageField:字符类型,存储图片路径的字符串。
- FilePathField:字符类型,从特定的文件目录选择某个文件。
模型字段参数如下:
- verbose_name:默认为None,在 Admin站点管理设置字段的显示名称。
- primary_key:默认为False,若为True,则将字段设置成主键。
- max_length:默认为None,设置字段的最大长度。
- unique:默认为False,若为True,则设置字段的唯一属性。
- blank:默认为False,若为True,则字段允许为空值,数据库将存储空字符串。null:默认为False, 若为True,则字段允许为空值,数据库表现为NULL。
- db_index:默认为False,若为True,则以此字段来创建数据库索引。default:默认为 NOT_PROVIDED对象,设置字段的默认值。
- editable:默认为True,允许字段可编辑,用于设置Admin的新增数据的字段。serialize:默认为 True,允许字段序列化,可将数据转化为JSON格式。
- unique_for_date:默认为None,设置日期字段的唯一性。
- unique_for_month:默认为None,设置日期字段月份的唯一性。unique_for_year:默认为None, 设置日期字段年份的唯一性。choices:默认为空列表,设置字段的可选值。
- help_text:默认为空字符串,用于设置表单的提示信息。
- db_column:默认为None,设置数据表的列名称,若不设置,则将字段名作为数据表的列名。
- db_tablespace:默认为None,如果字段已创建索引,那么数据库的表空间名称将作为该字段的索 引名。注意:部分数据库不支持表空间。
- auto_created:默认为False,若为True,则自动创建字段,用于一对一的关系模型。validators:默 认为空列表,设置字段内容的验证函数。
- error_messages:默认为None,设置错误提示。
ForeignKey方法参数如下:
参数名 | 参数说明 |
---|---|
to | 指定关联的目标模型类。可以使用字符串表示模型类的路径,也可以直接使用模型类的引用。 |
on_delete | 指定当关联对象被删除时的行为。CASCADE、PROTECT、SET_NULL、 SET_DEFAULT、SET0、DO_NOTHING |
related_name | 指定反向关联的名称,默认为模型类名_set。 |
to_field | 指定关联的目标模型类中用于关联的字段名称。默认为主键字段。 |
db_index | 如果为True,则在目标模型的关联字段上创建索引。 |
null | 指定关联字段是否可以为空。如果 null=True,则数据库中该字段将允许NULL值。 |
blank | 指定关联字段是否可以为空。如果blank=True,则表单中该字段可以为空。 |
limit_choices_to | 指定关联对象的过滤条件。可以是一个字典、一个 QuerySet或一个函数。 |
verbose_name | 用于在 Django Admin后台中显示字段名称。 |
help_text | 用于在 Django Admin后台中显示帮助文本。 |
on_delete的models属性有下面设置选项;
- CASCADE:这就是默认的选项,级联删除,你无需显性指定它。
- PROTECT: 保护模式,如果采用该选项,删除的时候,会抛出ProtectedError错误。
- SET_NULL: 置空模式,删除的时候,外键字段被设置为空,前提就是blank=True, null=True,定义 该字段的时候,允许为空。
- SET_DEFAULT: 置默认值,删除的时候,外键字段设置为默认值,所以定义外键的时候注意加上一 个默认值。
- SET(): 自定义一个值,该值当然只能是对应的实体了
Django5数据迁移
然后我们执行: python manage.py makemigrations 生成数据库迁移文件
所谓的迁移文件, 是类似模型类的迁移类,主要是描述了数据表结构的类文件;
这个生成的迁移文件在migrations目录下;每执行一次,都会生成一个新文件。
再执行: python manage.py migrate 执行迁移文件,同步到数据库中;
数据库里就会生成t_book和t_bookType两个表;
最后我们再搞一些测试数据;
INSERT INTO `t_booktype` VALUES (1, '计算机类');
INSERT INTO `t_booktype` VALUES (2, '数学类');
INSERT INTO `t_book` VALUES (1, 'Java编程思想', 100, '2004-03-16', 1);
INSERT INTO `t_book` VALUES (2, 'Head First设计模式', 88, '2020-03-16', 1);
INSERT INTO `t_book` VALUES (3, '数学的秘密', 50, '2019-03-06', 2);
Django5模型查询(上)
我们知道数据库设有多种数据查询方式,如单表查询、多表查询、子查询和联合查询等,而 Django 的 ORM框架对不同的查询方式定义了相应的API方法。下面我们通过实例来深入学习下;
我们来实现下图书信息的查询,顺便通过外键关联配置,把图书类别信息也级联查询出来。我们通过 all()方法查询出所有图书信息;
views.py里我们加下bookList方法:
def bookList(request):
"""
图书列表查询
"""
# 查询所有信息
bookList = BookInfo.objects.all()
print(bookList)
content_value = {"title": "图书列表", "bookList": bookList}
return render(request, 'book/list.html', context=content_value)
urls.py里加下映射配置:
path('book/list', helloWorld.views.bookList)
templates下新建book目录,book目录下新建list.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{ title }}</title>
</head>
<body>
<h3>{{ title }}</h3>
<table border="1">
<tr>
<th>编号</th>
<th>图书名称</th>
<th>价格</th>
<th>出版日期</th>
<th>图书类别</th>
</tr>
{% for book in bookList %}
<tr>
<td>{{ book.id }}</td>
<td>{{ book.bookName }}</td>
<td>{{ book.price }}</td>
<td>{{ book.publishDate | date:'Y-m-d' }}</td>
<td>{{ book.bookType.bookTypeName }}</td>
</tr>
{% endfor %}
</table>
</body>
</html>
测试运行,浏览器输入: http://127.0.0.1:8000/book/list
查询出了所有的图书信息;
Django5模型查询(下)
前面实例我们用了ORM框架提供的all方法,查询所有数据。
下面我们继续学习下ORM框架给我们提供的一些其他常用方法;
# 查询所有信息
bookList = BookInfo.objects.all()
# 获取数据集的第一条数据的bookName属性值
print(bookList[0].bookName)
# 返回前2条数据 select * from t_book limit 2
bookList = BookInfo.objects.all()[:2]
# 查询指定字段
bookList = BookInfo.objects.values("bookName", "price")
# 查询指定字段 数据以列表方式返回,列表元素以元组表示
bookList = BookInfo.objects.values_list("bookName", "price")
ORM框架提供了get()方法,返回满足条件的单个数据:
# 获取单个对象,一般是根据id查询
book = BookInfo.objects.get(id=2)
print(book.bookName)
ORM框架提供了filter()方法,返回满足条件的数据:
# 返回满足条件id=2的数据,返回类型是列表
bookList = BookInfo.objects.filter(id=2)
bookList = BookInfo.objects.filter(price=100, id=1)
# filter的查询条件可以设置成字典格式
d = dict(price=100, id=1)
bookList = BookInfo.objects.filter(**d)
# SQL的or查询,需要引入Q,from django.db.models import Q
# 语法格式:Q(field=value)|Q(field=value) 多个Q之间用"|"隔开
bookList = BookInfo.objects.filter(Q(id=1) | Q(price=88))
# SQL的不等于查询,在Q查询中用“~”即可
# SQL select * from t_book where not (id=1)
bookList = BookInfo.objects.filter(~Q(id=1))
ORM框架提供了exclude()方法,返回不满足条件的数据:
# 也可以使用exclude 返回满足条件之外的数据 实现不等于查询
bookList = BookInfo.objects.exclude(id=1)
ORM框架提供了count()方法,返回满足查询条件后的数据量:
# 使用count()方法,返回满足查询条件后的数据量
t = BookInfo.objects.filter(id=2).count()
print(t)
ORM框架提供了distinct()方法,返回去重后的数据:
# distinct()方法,返回去重后的数据
bookList = BookInfo.objects.values('bookName').distinct()
print(bookList)
ORM框架提供了order_by()方法,对结果进行排序;默认是升序;如果需要降序,只需要在字段前面加 “-”即可;
# 使用order_by设置排序
# bookList = BookInfo.objects.order_by("price")
bookList = BookInfo.objects.order_by("-id")
ORM框架提供了annotate方法来实现聚合查询,比如数据值求和,求平均值等。
# annotate类似于SQL里面的GROUP BY方法
# 如果不设置values,默认对主键进行GROUIP BY分组
# SQL: select bookType_id,SUM(price) AS 'price_sum' from t_book GROUP BY
bookType_id
r = BookInfo.objects.values('bookType').annotate(Sum('price'))
# SQL: select bookType_id,AVG(price) AS 'price_sum' from t_book GROUP BY
bookType_id
r2 = BookInfo.objects.values('bookType').annotate(Avg('price'))
Django5模型分页查询
在Django中实现分页通常使用 Paginator 类。以下是一个简单的示例,展示了如何在Django视图中实 现分页功能:
bookList = BookInfo.objects.all()
# Paginator(object_list ,per_page)
# object_list 结果集/列表
# per_page 每页多少条记录
p = Paginator(bookList, 2)
# 获取第几页的数据
bookListPage = p.page(2)
print("总记录数:", BookInfo.objects.count())
Django5高级查询匹配符
前面讲了开发中常用的数据查询方法,但有时需要设置不同的查询条件来满足多方面的查询要求。上述 的查询条件 filter和 get是使用等值的方法来匹配结果。若想使用大于、不等于或模糊查询的匹配方法, 则可在查询条件filter和 get里使用下表的匹配符实现。
匹配符 | 使用 | 说明 |
---|---|---|
__exact | filter(job__exact='开发') | 精确等于,如SQL的like'开发'。 |
__iexact | filter(job__iexact='开发') | 精确等于并忽略大小写。 |
__contains | filter(job__contains='开发') | 模糊匹配,如SQL的like'%荣耀%'。 |
__icontains | filter(job__icontains='开发') | 模糊匹配,忽略大小写。 |
__gt | filter(job__gt=5) | 大于。 |
__gte | filter(job__gte=5) | 大于等于。 |
__lt | filter(job__lt=5) | 小于。 |
__lte | filter(job__lte=5) | 小于等于。 |
__in | filter(job__in=[1,2,3]) | 判断是否在列表内。 |
__startswith | filter(job__startswith='开发') | 以。。。开头。 |
__istartswith | filter(job__istartswith='开发') | 以。。。开头并忽略大小写。 |
__endswith | filter(job__endswith='开发') | 以。。。结尾。 |
__iendswith | filter(job__iendswith='开发') | 以。。。结尾并忽略大小写。 |
__range | filter(job__range='开发') | 在。。。范围内。 |
__year | filter(job__year='2018') | 日期字段的年份。 |
__month | filter(job__month='12') | 日期字段的月份。 |
__day | filter(job__day=30) | 日期字段的天数。 |
__isnull | filter(job__isnull=True/False) | 判断是否为空。 |
实例代码:
# 模糊查询图书名称含有"编程"的所有数据
# bookList = BookInfo.objects.filter(bookName__contains='编程')
# 查询图书价格大于等于50的所有数据
bookList = BookInfo.objects.filter(price__gte=50)
在查询数据时可以使用查询条件get或filter实现,但是两者的执行过程存在一定的差异,说明如下。
- 查询条件get:查询字段必须是主键或者唯一约束的字段,并且查询的数据必须存在,如果查询的字 段有重复值或者查询的数据不存在,程序就会抛出异常信息。
- 查询条件filter:查询字段没有限制,只要该字段是数据表的某一字段即可。查询结果以列表形式返 回,如果查询结果为空(查询的数据在数据表中找不到),就返回空列表。
Django5模型多表查询
我们在日常的开发中,常常需要对多张数据表同时进行数据查询。多表查询需要在数据表之间建立表关 系才能够实现。一对多或一对一的表关系是通过外键实现关联的,而多表查询分为正向查询和反向查询。
以模型BookInfo和BokkTypeInfo为例,如果查询主题是BookInfo,通过外键bookType_id去查询 BooKTypeInfo的关联数据,那么该查询称为正向查询;如果查询对象的主题是模型BookTypeInfo,要 查询它与模型BookInfo的关联数据,那么该查询称为反向查询;
下面是一个实例:
def bookList2(request):
"""
多表查询 正常查询 和反向查询
:param request:
:return:
"""
# 正向查询
book: BookInfo = BookInfo.objects.filter(id=2).first()
print(book.bookType.bookTypeName)
# 反向查询
bookType = BookTypeInfo.objects.filter(id=1).first()
print(bookType.bookinfo_set.first().bookName)
print(bookType.bookinfo_set.all())
content_value = {"title": "图书列表"}
return render(request, 'book/list.html')
Django5模型数据新增
Django对数据库的数据进行增、删、改操作是借助内置ORM框架所提供的API方法实现的,简单来说,它 在模型基础类 Model里定义数据操作方法,通过类继承将这些操作方法传给开发者自定义的模型对象, 再由模型对象调用即可实现数据操作。
添加操作通过模型的save方法实现,添加下可以返回主键id值。
我们在前面实例的基础上,来实现这个例子。
因为添加页面是需要图书类别的数据,我们用下拉框实现。所以这里需要一个预处理操作。
先在views.py里定义一个添加预处理方法preAdd
def preAdd(request):
"""
预处理,添加操作
:param request:
:return:
"""
bookTypeList = BookTypeInfo.objects.all()
print(bookTypeList)
content_value = {"title": "图书添加", "bookTypeList": bookTypeList}
return render(request, 'book/add.html', context=content_value)
urls.py里加下映射:
path('book/preAdd', helloWorld.views.preAdd),
原先的list.html,加上添加的链接:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{ title }}</title>
</head>
<body>
<h3>{{ title }}</h3>
<a href="/book/preAdd">添加</a><br/><br/>
<table border="1">
<tr>
<th>编号</th>
<th>图书名称</th>
<th>价格</th>
<th>出版日期</th>
<th>图书类别</th>
</tr>
{% for book in bookList %}
<tr>
<td>{{ book.id }}</td>
<td>{{ book.bookName }}</td>
<td>{{ book.price }}</td>
<td>{{ book.publishDate | date:'Y-m-d' }}</td>
<td>{{ book.bookType.bookTypeName }}</td>
</tr>
{% endfor %}
</table>
</body>
</html>
再创建下图书添加页面add.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{ title }}</title>
</head>
<body>
<h3>{{ title }}</h3>
<form action="/book/add" method="post">
{% csrf_token %}
<table>
<tr>
<td>图书名称:</td>
<td>
<input type="text" name="bookName">
</td>
</tr>
<tr>
<td>出版日期:</td>
<td>
<input type="text" name="publishDate">
</td>
</tr>
<tr>
<td>图书类别:</td>
<td>
<select name="bookType_id">
{% for bookType in bookTypeList %}
<option value="{{ bookType.id }}">{{bookType.bookTypeName }}</option>
{% endfor %}
</select>
</td>
</tr>
<tr>
<td>图书价格:</td>
<td>
<input type="text" name="price">
</td>
</tr>
<tr>
<td colspan="2">
<input type="submit" value="提交">
</td>
</tr>
</table>
</form>
</body>
</html>
最后在views.py里创建图书添加函数add:
def add(request):
"""
图书添加
:param request:
:return:
"""
# print(request.POST.get("bookName"))
# print(request.POST.get("publishDate"))
# print(request.POST.get("bookType_id"))
# print(request.POST.get("price"))
book = BookInfo()
book.bookName = request.POST.get("bookName")
book.publishDate = request.POST.get("publishDate")
book.bookType_id = request.POST.get("bookType_id")
book.price = request.POST.get("price")
book.save()
# 数据添加后,获取新增数据的主键id
print(book.id)
return bookList(request)
运行测试:浏览器输入: http://127.0.0.1:8000/book/list
点击添加链接,
进入图书添加页面,输入图书信息,点提交:
页面显示最新数据;
Django5模型数据修改
模型数据修改和添加都是用的save方法。
我们结合案例先实现下;
我们在views.py里先定义preUpdate方法,修改预处理,根据id获取图书信息,以及获取图书类别列表;
def preUpdate(request, id):
"""
预处理,修改操作
:param request:
:return:
"""
print("id:", id)
book = BookInfo.objects.get(id=id)
print(book)
bookTypeList = BookTypeInfo.objects.all()
print(bookTypeList)
content_value = {"title": "图书修改", "bookTypeList": bookTypeList, "book":
book}
return render(request, 'book/edit.html', context=content_value)
urls.py里加下映射:
path('book/preUpdate/<int:id>', helloWorld.views.preUpdate),
book/list.html修改下,加下修改操作链接:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{ title }}</title>
</head>
<body>
<h3>{{ title }}</h3>
<a href="/book/preAdd">添加</a><br/><br/>
<table border="1">
<tr>
<th>编号</th>
<th>图书名称</th>
<th>价格</th>
<th>出版日期</th>
<th>图书类别</th>
<th>操作</th>
</tr>
{% for book in bookList %}
<tr>
<td>{{ book.id }}</td>
<td>{{ book.bookName }}</td>
<td>{{ book.price }}</td>
<td>{{ book.publishDate | date:'Y-m-d' }}</td>
<td>{{ book.bookType.bookTypeName }}</td>
<td>
<a href="/book/preUpdate/{{ book.id }}">修改</a>
</td>
</tr>
{% endfor %}
</table>
</body>
</html>
新建编辑页面edit.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{ title }}</title>
</head>
<body>
<h3>{{ title }}</h3>
<form action="/book/update" method="post">
{% csrf_token %}
<table>
<tr>
<td>图书名称:</td>
<td>
<input type="text" name="bookName" value="{{ book.bookName }}">
</td>
</tr>
<tr>
<td>出版日期:</td>
<td>
<input type="text" name="publishDate" value="{{ book.publishDate| date:'Y-m-d' }}">
</td>
</tr>
<tr>
<td>图书类别:</td>
<td>
<select name="bookType_id">
{% for bookType in bookTypeList %}
<option value="{{ bookType.id }}"
{% if book.bookType.id == bookType.id%}selected{% endif %}>
{{ bookType.bookTypeName }}
</option>
{% endfor %}
</select>
</td>
</tr>
<tr>
<td>图书价格:</td>
<td>
<input type="text" name="price" value="{{ book.price }}">
</td>
</tr>
<tr>
<td colspan="2">
<input type="hidden" name="id" value="{{ book.id }}">
<input type="submit" value="提交">
</td>
</tr>
</table>
</form>
</body>
</html>
再写一个update方法,保存图书信息
def update(request):
"""
图书修改
:param request:
:return:
"""
book = BookInfo()
book.id = request.POST.get("id")
book.bookName = request.POST.get("bookName")
book.publishDate = request.POST.get("publishDate")
book.bookType_id = request.POST.get("bookType_id")
book.price = request.POST.get("price")
book.save()
return bookList(request)
urls.py里再加下映射:
path('book/update', helloWorld.views.update),
我们来测试下,浏览器输入: http://127.0.0.1:8000/book/list
我们点修改,修改id是5的图书,
修改信息后,点提交;
测试成功!
Django5模型数据删除
Django5 ORM框架提供了delete()方法来实现数据删除操作,下面是一些常用的方式,删除所有数据, 删除指定id数据,根据filter条件删除删除。
# 删除所有数据
BookInfo.objects.all().delete()
# 删除指定id数据
BookInfo.objects.get(id=1).delete()
# 根据条件删除多条数据
BookInfo.objects.filter(price__gte=90).delete()
我们来完善下前面的实例:
views.py里先定义delete方法。
def delete(request, id):
"""
图书删除
:param request:
:return:
"""
# 删除所有数据
# BookInfo.objects.all().delete()
# 删除指定id数据
BookInfo.objects.get(id=id).delete()
# 根据条件删除多条数据
# BookInfo.objects.filter(price__gte=90).delete()
return bookList(request)
urls.py里加下映射:
path('book/delete/<int:id>', helloWorld.views.delete),
book/list.html里加下删除操作:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{ title }}</title>
</head>
<body>
<h3>{{ title }}</h3>
<a href="/book/preAdd">添加</a><br/><br/>
<table border="1">
<tr>
<th>编号</th>
<th>图书名称</th>
<th>价格</th>
<th>出版日期</th>
<th>图书类别</th>
<th>操作</th>
</tr>
{% for book in bookList %}
<tr>
<td>{{ book.id }}</td>
<td>{{ book.bookName }}</td>
<td>{{ book.price }}</td>
<td>{{ book.publishDate | date:'Y-m-d' }}</td>
<td>{{ book.bookType.bookTypeName }}</td>
<td>
<a href="/book/preUpdate/{{ book.id }}">修改</a>
<a href="/book/delete/{{ book.id }}">删除</a>
</td>
</tr>
{% endfor %}
</table>
</body>
</html>
我们测试下,浏览器输入 http://127.0.0.1:8000/book/list
点击删除链接,
数据直接被删除,说明测试成功!
Django5 ORM执行SQL语句
Django在查询数据时,大多数查询都能使用ORM提供的API方法,但对于一些复杂的查询可能难以使用 ORM的API方法实现,因此Django引入了SQL语句的执行方法,有以下3种实现方法。
- extra:结果集修改器,一种提供额外查询参数的机制。
- raw:执行原始SQL并返回模型实例对象。
- execute:直接执行自定义SQL。
extra适合用于ORM难以实现的查询条件,将查询条件使用原生SQL语法实现,此方法需要依靠模型对 象,在某程度上可防止SQL注入。它一共定义了6个参数,每个参数说明如下:
- select:添加新的查询字段,即新增并定义模型之外的字段。where:设置查询条件。
- params:如果where设置了字符串格式化%s,那么该参数为where提供数值。tables:连接其他数据 表,实现多表查询。
- order_by:设置数据的排序方式。
- select_params:如果select设置字符串格式化%s,那么该参数为select提供数值。
参考实例:
bookList = BookInfo.objects.extra(where=["price>%s"], params=[90])
raw只能实现数据查询操作,并且也要依靠模型对象,它一共定义了4个参数,每个参数说明如下:
- raw_query: sQL语句。
- params:如果raw_query设置字符串格式化%s,那么该参数为raw_query提供数值。
- translations:为查询的字段设置别名。
- using:数据库对象,即 Django 所连接的数据库。
参考实例:
bookList = BookInfo.objects.raw("select * from t_book where price>%s", params=[90])
execute的语法,它执行SQL语句无须经过Django的ORM框架。我们知道Django连接数据库需要借助第 三方模块实现连接过程,如 MySQL的mysqlclient模块和SQLite 的sqlite3模块等,这些模块连接数据库 之后,可通过游标的方式来执行SQL语句,而 execute就是使用这种方式执行SQL语句,实例如下:
cursor: CursorDebugWrapper = connection.cursor()
cursor.execute("select count(*) from t_book where price>90")
print(cursor.fetchone())
Django5 ORM数据库事务
事务是指作为单个逻辑执行的一系列操作,这些操作具有原子性,即这些操作要么完全执行,要么完全 不执行。事务处理可以确保事务性单元内的所有操作都成功完成,否则不会执行数据操作。 事务应该具有4个属性:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation),持久性 (Durability),这4个属性通常称为ACID特性,说明如下。
- 原子性:一个事务是一个不可分割的工作单位,事务中包括的操作要么都做,要么都不做。 一致性:事务必须使数据库从某个一致性状态变到另一个一致性状态,一致性与原子性是密切相关 的。
- 隔离性:一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对其他事务是隔 离的,各个事务之间不能互相干扰。
- 持久性:持久性也称永久性(Permanence),指一个事务一旦提交,它对数据库中数据的改变应该是
- 永久性的,其他操作或故障不应该对其有任何影响。
我们先来做一个用户转账的事例:
models.py下新建AccountInfo账户信息模型
class AccountInfo(models.Model):
user = models.CharField(max_length=20)
account = models.FloatField()
class Meta:
db_table = "t_account"
verbose_name = "用户账户信息" # 给模型取个直观的名字
然后我们执行: python manage.py makemigrations 生成数据库迁移文件,再执行: python manage.py migrate 执行迁移文件,同步到数据库中;生成了t_account表
表里,我们加两个测试数据:
views.py里,我们写一个测试转账测试实例:
def transfer2(request):
"""
模拟转账
:param request:
:return:
"""
a1 = AccountInfo.objects.filter(user='张三')
a1.update(account=F('account') + 100)
a2 = AccountInfo.objects.filter(user='李四')
a2.update(account=F('account') - 100)
return HttpResponse("OK")
urls.py里配置下映射:
path('transfer2/', helloWorld.views.transfer2),
测试,浏览器输入: http://127.0.0.1:8000/transfer2/ ,测试成功。
这里涉及到2个数据库操作,假如第二个数据库操作失败,那张三就多了100,但是李四钱没少,这时候 金融账务就对不上了,除了问题。
我们来模拟下吧,转出代码,除以0:
再运行测试下,页面报错,
李四的钱也没被扣,
这时候,Django5提供的事务功能就派上用场了。
Django5主要有4个事务方法:
- atomic():在视图函数或视图类里使用事务。
- savepoint():开启事务。
- savepoint_rollback():回滚事务。
- savepoint_commit():提交事务。
我们用上事务,改下代码:
@transaction.atomic
def transfer(request):
"""
模拟转账
:param request:
:return:
"""
# 开启事务
sid = transaction.savepoint()
try:
a1 = AccountInfo.objects.filter(user='张三')
a1.update(account=F('account') + 100)
a2 = AccountInfo.objects.filter(user='李四')
a2.update(account=F('account') - 100 / 0)
# 提交事务 (如不设置,当程序执行完成后,会自动提交事务)
transaction.savepoint_commit(sid)
except Exception as e:
print("异常信息:", e)
# 事务回滚
transaction.savepoint_rollback(sid)
return HttpResponse("OK")
我们再测试下,发现回滚了,钱都没有变化;