date: 2021-11-24title: Django之模型层_单表操作 #标题
tags: #标签
categories: python # 分类
ORM简介
- MVC或者MVC框架中包括一个重要的部分,就是ORM,它实现了数据模型与数据库的解耦,即数据模型的设计不需要依赖于特定的数据库,通过简单的配置就可以轻松更换数据库,这极大的减轻了开发人员的工作量,不需要面对因数据库变更而导致的无效劳动
- ORM是“对象-关系-映射”的简称。(Object Relational Mapping,简称ORM)(和sqlalchemy很像的,但是django的orm没有独立出来让别人去使用,虽然功能比sqlalchemy更强大,但是别人用不了)
- 类对象—->sql—->pymysql—->mysql服务端—->磁盘,orm其实就是将类对象的语法翻译成sql语句的一个引擎,明白orm是什么了,剩下的就是怎么使用orm,怎么来写类对象关系语句。
参考:django模型层ORM
如下是mysql原生语句和ORM的区别:
######### 原生sql
#创建表:
CREATE TABLE employee(
id INT PRIMARY KEY auto_increment ,
name VARCHAR (20),
gender BIT default 1,
birthday DATA ,
department VARCHAR (20),
salary DECIMAL (8,2) unsigned,
);
# 添加一条记录:
INSERT employee (name,gender,birthday,salary,department)
VALUES ("alex",1,"1985-12-12",8000,"保洁部");
# 查询一条表记录:
SELECT * FROM employee WHERE age=24;
# 更新一条表记录:
UPDATE employee SET birthday="1989-10-24" WHERE id=1;
# 删除一条表记录:
DELETE FROM employee WHERE name="alex"
######### python的类
'''
# 创建表
(还需要在终端中的项目目录下执行两条命令:python manage.py makemigrations 和 python manage.py migrate)
'''
class Employee(models.Model):
id=models.AutoField(primary_key=True)
name=models.CharField(max_length=32)
gender=models.BooleanField()
birthday=models.DateField()
department=models.CharField(max_length=32)
salary=models.DecimalField(max_digits=8,decimal_places=2)
#python的类对象
# 添加一条表记录:
emp=Employee(name="alex",gender=True,birthday="1985-12-12",epartment="保洁部")
emp.save()
# 查询一条表记录:
Employee.objects.filter(age=24)
# 更新一条表记录:
Employee.objects.filter(id=1).update(birthday="1989-10-24")
# 删除一条表记录:
Employee.objects.filter(name="alex").delete()
好,现在我们就来看看,如何在django项目中连接mysql数据库(默认django会给我们连接一个sqllite3数据库用作测试,不过一般我们不会使用这种数据库,而是使用mysql或oracle等)。
配置django项目连接mysql数据库
安装mysql数据库
修改settings.py配置文件
# 默认使用sqlite3数据库,注释相关配置
# DATABASES = {
# 'default': {
# 'ENGINE': 'django.db.backends.sqlite3',
# 'NAME': BASE_DIR / 'db.sqlite3',
# }
# }
# 添加mysql为项目数据库
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql', # 指定mysql数据库引擎
'NAME': 'ljz', # 要连接的数据库名称(需要提前手动创建好)
'USER': 'root', # 连接数据库的用户
'PASSWORD': '123.com', # 用户密码
'HOST': '127.0.0.1', # 数据库地址
'PORT': 3306 # 数据库连接端口
}
}
############ 如果要为每个app单独配置数据库,请参考如下配置 #############
'''
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'bms',
'USER': 'root', # 连接数据库的用户名
'PASSWORD': '', # 连接数据库的密码
'HOST': '127.0.0.1', # 连接主机,默认本级
'PORT': 3306 # 端口 默认3306
},
'app01': { # 可以为每个app都配置自己的数据,并且数据库还可以指定别的,也就是不一定就是mysql,也可以指定sqlite等其他的数据库
'ENGINE': 'django.db.backends.mysql',
'NAME': 'bms', # 要连接的数据库,连接前需要创建好
'USER': 'root', # 连接数据库的用户名
'PASSWORD': '', # 连接数据库的密码
'HOST': '127.0.0.1', # 连接主机,默认本级
'PORT': 3306 # 端口 默认3306
}
}
'''
设置完后,再启动我们的Django项目前,我们需要激活我们的mysql。然后,启动项目,会报错:django.core.exceptions.ImproperlyConfigured: Error loading MySQLdb module.Did you install mysqlclient?
。这是因为django默认你导入的驱动是MySQLdb,可是MySQLdb 对于py3有很大问题,所以我们需要的驱动是PyMySQL 所以,我们只需要找到项目名文件下的init,在里面写入:
import pymysql
pymysql.install_as_MySQLdb()
如果你也没有安装pymysql,那么需要在终端下执行如下指令进行安装:
$ pip3 install pymysql
至此,我们就可以正常启动项目了,启动后,控制台输出无异常,表示项目启动成功。
编写moudels.py文件
现在来写下moudels.py
文件:
from django.db import models
# Create your models here.
from django.db import models
class UserInfo(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=16, null=True, blank=True)
age = models.IntegerField()
current_date = models.DateField()
'''
上面定义的UserInfo类,就相当于下面这条sql语句
# create table userinfo(id int primary key auto_increment,name varchar(16),age int,current_date date ))
'''
现在我们运行项目管理工具,将上面的sql写入数据库中。
项目sql写入数据库中
在pycharm界面按下Ctrl+Alt+R
建,运行manage.py
脚本,然后输入如下:
$ makemigrations
$ migrate
上面两条指令,直接执行manage.py脚本的话,需要使用如下指令:
'''生成记录,每次修改了models里面的内容或者添加了新的app,
新的app里面写了models里面的内容,都要执行这两条
'''
python manage.py makemigrations
python manage.py migrate
'''
执行上面这个语句的记录来创建表,生成的表名字前面会自带应用的名字,
例如:你的book表在mysql里面叫做app01_book表
'''
在执行 python manager.py magrations
时django 会在相应的 app 的migration文件夹下面生成 一个python脚本文件,在执行 python manager.py migrte
时 django才会生成数据库表,那么django是如何生成数据库表的呢?django是根据 migration下面的脚本文件来生成数据表的,每个migration文件夹下面有多个脚本,那么django是如何知道该执行那个文件的呢,django有一张django-migrations表,表中记录了已经执行的脚本,那么表中没有的就是还没执行的脚本,则 执行migrate的时候就只执行表中没有记录的那些脚本。有时在执行 migrate 的时候如果发现没有生成相应的表,可以看看在 django-migrations表中看看 脚本是否已经执行了,可以删除 django-migrations 表中的记录 和 数据库中相应的 表 , 然后重新执行。
运行界面如下:
如上所示,运行完成,无报错信息,现在可以自行去数据库中进行查看,moudels.py文件中定义的类,是否正确的在数据库中创建了表,我这边查到的表信息如下:
models字段和参数
字段
- CharField:字符串字段, 用于较短的字符串,CharField 要求必须有一个参数 maxlength, 用于从数据库层和Django校验层限制该字段所允许的最大字符数。
- IntegerField:用于保存一个整数
- DecimalField:一个浮点数。必须 提供两个参数: max_digits(总位数(不包括小数点和符号))和 decimal_places(小数位数),举例来说, 要保存最大值为 999 (小数点后保存2位),你要这样定义字段: models.DecimalField(..., max_digits=5, decimal_places=2);要保存最大值一百万(小数点后保存10位)的话,你要这样定义: models.DecimalField(..., max_digits=17, decimal_places=10) #max_digits大于等于17就能存储百万以上的数了
- AutoField:一个 IntegerField, 添加记录时它会自动增长. 你通常不需要直接使用这个字段; 自定义一个主键:my_id=models.AutoField(primary_key=True), 如果你不指定主键的话,系统会自动添加一个主键字段到你的 model。
- BooleanField:A true/false field. admin 用 checkbox 来表示此类字段.
- TextField:一个容量很大的文本字段.。 admin 用一个 <textarea> (文本区域)表示该字段数据.(一个多行编辑框).
- EmailField:一个带有检查Email合法性的 CharField,不接受 maxlength 参数。
- DateField:一个日期字段. 共有下列额外的可选参数: auto_now( 当对象被保存时(更新或者添加都行),自动将该字段的值设置为当前时间.通常用于表示 "last-modified" 时间戳);auto_now_add( 当对象首次被创建时,自动将该字段的值设置为当前时间.通常用于表示对象创建时间。 )
- DateTimeField:一个日期时间字段. 类似 DateField 支持同样的附加选项.
- ImageField:类似 FileField, 不过要校验上传对象是否是一个合法图片,它有两个可选参数:height_field和width_field, 如果提供这两个参数,则图片将按提供的高度和宽度规格保存。
参数
- null:如果为True,Django 将用NULL 来在数据库中存储空值。 默认值是 False.
- blank:如果为True,该字段允许不填。默认为False。要注意,这与 null 不同。null纯粹是数据库范畴的,而 blank 是数据验证范畴的。如果一个字段的blank=True,表单的验证将允许该字段是空值。如果字段的blank=False,该字段就是必填的。
- default:字段的默认值。可以是一个值或者可调用对象。如果可调用 ,每有新对象被创建它都会被调用,如果你的字段没有设置可以为空,那么将来如果我们后添加一个字段,这个字段就要给一个default值
- primary_key:如果为True,那么这个字段就是模型的主键。如果你没有指定任何一个字段的primary_key=True,Django 就会自动添加一个IntegerField字段做为主键,所以除非你想覆盖默认的主键行为,否则没必要设置任何一个字段的primary_key=True。
- unique:如果该值设置为 True, 这个数据字段的值在整张表中必须是唯一的
- choices:由二元组组成的一个可迭代对象(例如,列表或元组),用来给字段提供选择项。 如果设置了choices ,默认的表单将是一个选择框而不是标准的文本框,
而且这个选择框的选项就是choices 中的选项。- db_index:如果db_index=True 则代表着为此字段设置数据库索引。DatetimeField、DateField、TimeField这个三个时间字段,都可以设置如下属性。
- auto_now_add:配置auto_now_add=True,创建数据记录的时候会把当前时间添加到数据库。
- auto_now:配置上auto_now=True,每次更新数据记录的时候会更新该字段,标识这条记录最后一次的修改时间。
增删改查
在进行增删改查测试时,都是写一个视图函数,增删改查相关操作,都是写在这个视图函数中,然后访问此页面,这样访问一下,就执行了这个视图函数的内容。
增
from django.shortcuts import render
from app01 import models
def index(request):
'''
# 创建记录的方式一(下面的代码写好后,只要访问下index这个视图函数,
#下面的代码就执行了,相应数据也就写入到mysql中了)
userinfo_obj=models.UserInfo(name='吕建钊',age=25)
userinfo_obj.save() # 上面的方式写入后,需要执行save方法,以便写入到数据库中(就是pymysql的那个commit提交)
# 创建记录的方式二
new_obj=models.UserInfo.objects.create(name='尼古拉斯·赵四',age=44)
# 或者可以写成**{'name':'xx','age':18},create方法是用**kwargs进行接收参数的。
print(new_obj) #输出:UserInfo object (2),UserInfo object也叫做 model对象
print(new_obj.name) #输出:尼古拉斯·赵四,点(.)属性,可以获取对应字段的数据
print(new_obj.age) # 输出 44
# 创建方式三(批量创建)
objs_list = []
for i in range(20, 30):
obj = models.UserInfo(
name='张三_'+str(i),
age=10+i,
)
objs_list.append(obj)
models.UserInfo.objects.bulk_create(objs_list)
'''
#创建方法四:update_or_create(有就更新, 没有就创建)
models.UserInfo.objects.update_or_create(
name='张三',
defaults={
'age': 228,
}
)
return render(request, 'index.html')
查
下面有些查询方法,总说在表对象中定义了__str__
方法,怕日后忘记,这里着重说下,就是如下:
如下,是对__str__
方法的解释:
好,接下来,我们来对数据库进行查操作。
from django.shortcuts import render
from app01 import models
def index(request):
'''
####### 条件查询:.filter方法,返回的也是queryset集合,
# 查询不到内容,不会报错,返回一个<QuerySet []>空的queryset
objs = models.UserInfo.objects.filter(id=2) #找id为2的那条记录
for i in objs:
print(i.name)
print(i.age)
如果不想使用for进行取值,可以在modules.py的表对象中定义__str__方法,返回固定的字段值,如下:
def __str__(self):
return self.name
###### filter多条件查询
objs = models.UserInfo.objects.filter(name='吕建钊',id=1)
print(objs)
如果在表类对象中定义了__str__方法,如下:
def __str__(self):
return self.name
则上面的print输入:<QuerySet [<UserInfo: 吕建钊>]>
'''
###### get方法查询:返回的是model对象,而且get方法有且必须只有1个结果,否则报错
obj = models.UserInfo.objects.get(id=13)
print(obj) # 输出 张三(在表对象中定义了__str__方法)
return render(request, 'index.html')
查询接口
- all(): 查询所有结果,结果是queryset类型。
- filter(**kwargs): 它包含了与所给筛选条件相匹配的对象,结果也是queryset类型 Book.objects.filter(title=’linux’,price=100) #里面的多个条件用逗号分开,并且这几个条件必须都成立,是and的关系。
models.Student.objects.filter(id=7,name='大壮哥哥',age=78).update(
name='大壮禅师',
age=78
)
#打散形式传参
models.Student.objects.filter(**{'id':7,'name':'大壮禅师'}).update(age=100)
models.Student.objects.all().filter(id=7) # queryset类型可以调用fitler在过滤
- get(**kwargs): 返回与所给筛选条件相匹配的对象,不是queryset类型,是行记录对象,返回结果有且只有一个,如果符合筛选条件的对象超过一个或者没有都会抛出错误。 Book.objects.get(id=1)。
- exclude(**kwargs): 排除的意思,它包含了与所给筛选条件不匹配的对象,用这个exclude,返回值是queryset类型,如:Book.objects.exclude(id=6)的执行结果,返回id不等于6的所有的对象,或者在queryset基础上调用,如:Book.objects.all().exclude(id=6)。
exclude(**kwargs): # 排除,objects控制器和queryset集合都可以调用,返回结果是queryset类型
query = models.Student.objects.exclude(id=1)
print(query)
query = models.Student.objects.filter(age=38).exclude(id=6)
print(query)
- order_by(*field): queryset类型的数据来调用,对查询结果排序,默认是按照id来升序排列的,返回值还是queryset类型,如:models.Book.objects.all().order_by(‘price’,’id’) ,直接写price,默认是按照price升序排列,如果想按照字段降序排列,就写个负号就行了order_by(‘-price’)。order_by(‘price’,’id’)是多条件排序,按照price进行升序,price相同的数据,按照id进行升序。
- reverse(): queryset类型的数据来调用,对查询结果反向排序,返回值还是queryset类型。
# 排序之后反转
query = models.Student.objects.all().order_by('id').reverse()
print(query)
- count(): queryset类型的数据来调用,返回数据库中匹配查询(QuerySet)的对象数量。
- first(): queryset类型的数据来调用,返回第一条记录 Book.objects.all()[0] = Book.objects.all().first(),得到的都是model对象,不是queryset。
- last(): queryset类型的数据来调用,返回最后一条记录
- exists(): queryset类型的数据来调用,如果QuerySet包含数据,就返回True,否则返回False。空的queryset类型数据也有布尔值True和False,但是一般不用它来判断数据库里面是不是有数据,如果有大量的数据,你用它来判断,那么就需要查询出所有的数据,效率太差了,用count或者exits,例:all_books = models.Book.objects.all().exists() #翻译成的sql是SELECT (1) AS
a
FROMapp01_book
LIMIT 1,就是通过limit 1,取一条来看看是不是有数据。
- values(*field): 用的比较多,queryset类型的数据来调用,返回一个ValueQuerySet——一个特殊的QuerySet,运行后得到的并不是一系列 model的实例化对象,而是一个可迭代的字典序列,只要是返回的queryset类型,就可以继续链式调用queryset类型的其他的查找方法,其他方法也是一样的。
- values_list(*field): 它与values()非常相似,它返回的是一个元组序列,values返回的是一个字典序列。
- distinct(): values和values_list得到的queryset类型的数据来调用,从返回结果中剔除重复记录。
关于上面的values、values_list、distinct方法用法示例如下:
from django.shortcuts import render
from app01 import models
def index(request):
obj = models.UserInfo.objects.values('name', 'age')
print(obj) # 返回 <QuerySet [{'name': '张三', 'age': 67}, {'name': '尼古拉斯·赵四', 'age': 67}]>
obj_2 = models.UserInfo.objects.values('name') # 返回:<QuerySet [{'name': '张三'}, {'name': '尼古拉斯·赵四'}]>
print(obj_2)
obj_3 = models.UserInfo.objects.values_list('name', 'age') # 返回 <QuerySet [('张三', 67), ('尼古拉斯·赵四', 67)]>
print(obj_3)
obj_4 = models.UserInfo.objects.values_list('name')
print(obj_4) # 返回 [('张三',), ('尼古拉斯·赵四',)]>
# 下面有些数据是重复的
obj_5 = models.UserInfo.objects.values_list('name','age').distinct() # 返回<QuerySet [('张三', 67), ('尼古拉斯·赵四', 67), ('吕建钊', 18)]>
print(obj_5)
return render(request, 'index.html')
基于双下划线的模糊查询
注意:我在models.py文件中定义了__str__
方法:
def __str__(self):
return self.name
下面是一些模糊查询的示例:
# 查询id大于41的
querey=models.UserInfo.objects.filter(id__gt=41) # 输出:<QuerySet [<UserInfo: 尼古拉斯·赵四>, <UserInfo: 吕建钊>]>
print(querey)
# 查询id大于或等于41的
querey_2 = models.UserInfo.objects.filter(id__gte=41) # 输出:[<UserInfo: 张三>, <UserInfo: 尼古拉斯·赵四>, <UserInfo: 吕建钊>]>
print(querey_2)
# 查询id小于37的
querey_3 = models.UserInfo.objects.filter(id__lt=37) # 输出:<QuerySet [<UserInfo: 张三>, <UserInfo: 尼古拉斯·赵四>, <UserInfo: 尼古拉斯·赵四>]>
print(querey_3)
# 查询id小于或等于37的
querey_4 = models.UserInfo.objects.filter(id__lte=37) # 输出:<QuerySet [<UserInfo: 张三>, <UserInfo: 尼古拉斯·赵四>, <UserInfo: 尼古拉斯·赵四>, <UserInfo: 尼古拉斯·赵四>]>
print(querey_4)
# 查询id小于等于42并大于或等于39(相当于sql中的between and)
querey_5 = models.UserInfo.objects.filter(id__range=[39,42])
print(querey_5)
# 查询id等于这个列表中的任意一个值得对象,也就是查询id为34或者42、43的记录
querey_6 = models.UserInfo.objects.filter(id__in=[34,42,43])
print(querey_6)
# 查询name列包含“张”字的列
querey_7 = models.UserInfo.objects.filter(name__contains='张')
print(querey_7)
# # 查询class列包含“py”的列(不区分大小写)
# querey_8 = models.UserInfo.objects.filter(name__icontains='py')
# print(querey_8)
# # 查询class列中以“py”开头的列(如果想不区分大小写,请将startswith改为istartswith)
# querey_9 = models.UserInfo.objects.filter(name__startswith='py')
# print(querey_9)
下面来一些关于日期的模糊查询。
可以先使用下面的代码,创建出需要的源数据:
############ models.py文件内容
from django.db import models
class Brithday(models.Model):
name=models.CharField(max_length=13)
date=models.DateField()
def __str__(self):
return self.name
############ views.py文件内容
from django.shortcuts import render
from app01 import models
def index(request):
# import datetime
# current_date=datetime.datetime.now() # 2021-11-17 11:43:20.375682
obj = models.Brithday.objects
# obj.create(name='吕建钊',date=current_date)
# obj.create(name='张三_0', date='2000-11-11')
# obj.create(name='张三_1',date='2000-11-15')
# obj.create(name='张三_2',date='2000-12-15')
# obj.create(name='张三_3',date='2000-12-6')
# obj.create(name='张三_4',date='2000-12-8')
# obj.create(name='张三_5', date='2001-12-6')
# obj.create(name='张三_6', date='2002-12-6')
# obj.create(name='张三_7', date='2003-12-6')
日期查询示例如下:
from django.shortcuts import render
from app01 import models
def index(request):
obj = models.Brithday.objects
# 查询2000年出生的张三
query_1 = obj.filter(date__year='2000')
print(query_1)
# 查询2000年12月出生的张三
query_2 = obj.filter(date__year='2000', date__month='12')
print(query_2)
# 查询大于2000年出生的张三
query_3 = obj.filter(date__year__gt='2000')
print(query_3)
# 查询大于2000年12月6日出生的人
query_4 = obj.filter(date__year='2000', date__month='12', date__day__gt='06')
print(query_4)
return render(request, 'index.html')
删
from django.shortcuts import render
from app01 import models
def index(request):
'''
###### get方法,先查出来,再调用delete方法进行删除
models.UserInfo.objects.get(id=13).delete()
###### filter方法先查出来再删
#(和get方法类似,只是就算没有匹配上也不会报错,
# 而get方法会报错,filter也可以查出来多个,一次删除,
# 而get方法不可以)
# models.UserInfo.objects.filter(id=13).delete()
'''
###### all方法,删除所有数据
models.UserInfo.objects.all().delete()
return render(request, 'index.html')
改
更新 update方法 model对象不能调用更新方法 报错信息’Student’ object has no attribute ‘update’,只能queryset调用,如下:
from django.shortcuts import render
from app01 import models
def index(request):
# filter:先查出来,再update
models.UserInfo.objects.filter(name='尼古拉斯·赵四').update(age=67)
return render(request, 'index.html')
打印orm转换过程中的sql
如果想打印orm转换过程中的sql,需要在settings中进行如下配置:
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'console':{
'level':'DEBUG',
'class':'logging.StreamHandler',
},
},
'loggers': {
'django.db.backends': {
'handlers': ['console'],
'propagate': True,
'level':'DEBUG',
},
}
}
我试过上面的配置,如果是filter查询,则需要打印查询结果,才可以在控制台打印出相关sql查询语句,具体自行琢磨吧。
或者可以使用下面的方法查询:
from django.shortcuts import render
from app01 import models
def index(request):
# 添加记录
obj = models.UserInfo(name='吕建钊', age=18)
obj.save()
from django.db import connection # 通过这种方式也能查看执行的sql语句
print(connection.queries)
return render(request, 'index.html')
外部文件操作django的models
有时候,我们写好项目代码后,可能还想通过单独的py文件去调用项目中的models文件,去连接数据库,这里就写下如何配置它。
# 首先在项目目录下随便新建一个py文件,内容如下:
import os
def main():
# 加载django环境(下面加载环境的代码是从项目目录下的manage.py文件中copy过来的)
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'Books_Admin_system.settings')
import django
# 启动django环境
django.setup()
# 启动django后,就可以引入应用的models模块了
from Books_Manager import models
# 引入模块后,就可以操作数据库了
obj_list = []
for i in range(1, 10):
obj = models.books(
book_name=f'降龙十八掌第{i}式',
price=20 + i,
date=f'198{i}-11-11',
press='张三丰出版社'
)
obj_list.append(obj)
models.books.objects.bulk_create(obj_list)
if __name__ == '__main__':
main()
运行上面的代码,就可以将数据写入到项目的数据库中了。
django项目中的时区问题
不是跨时区的应用,不需要考虑时区问题,就将USE_TZ
这个值改为False,mysql是对时区不敏感,django往mysql里面出数据的时候,如果这个值为True,那么将让mysql强制使用UTC时间,那么我们存储进入的时间,当你查询的时候,你就会发现,时间晚了8小时,也就是说你存时间的时候被改为了UTC时间,本地是东八区,比UTC时间多8小时。
# settings.py文件中的配置
# Django1.9以后 'zh-cn'就被丢弃了,使用'zh-hans'代替。
LANGUAGE_CODE = 'zh-hans'
TIME_ZONE = 'Asia/Shanghai'
USE_I18N = True
USE_L10N = True
USE_TZ = False