在MVC或者说MTV设计模式中,模型(M)代表对数据库的操作。那么如何操作数据库呢?
在Python环境下的操作,我们必然是通过写Python代码的方式。但是Python和数据库语言SQL是两码事,它根本无法操作数据库,没关系,我们可以在Python代码中嵌入SQL语句,比如下面的方式:
# 创建连接,这里先忽略创建方法 conn = ...... # 创建游标 cursor = conn.cursor() # 执行SQL,并返回收影响行数 effect_row = cursor.execute("insert into host (hostname,port,ip) values('ubuntu','22','10.0.0.2');") # 提交,不然无法保存新建或者修改的数据 conn.commit() # 关闭游标 cursor.close() # 关闭连接 conn.close()
所以问题来了,Python如何创建和数据库的链接呢?这里我们使用pymysql这一类的第三方模块(针对不同的数据库,有不同的模块),于是我们有下面的连接:
conn = pymysql.connect(host='137.78.5.130', port=3306, user='root', passwd='123456', db='test')
大多数程序员不是专业的DBA,所以SQL写的很烂也正常,那么出错问题如何解决呢?
Python语法可以解决这个问题,我们使用Python语法来写,然后使用一个中间工具将Python代码翻译成原生的SQL语句,而这个中间工具就是所谓的ORM(对象关系映射)!
ORM将一个Python的对象映射为数据库中的一张关系表。它将SQL封装起来,程序员不再需要关心数据库的具体操作,只需要专注于自己本身的代码和业务逻辑的实现。
于是整体的流程就是:Python代码,通过ORM转换成SQL语句,再通过pymysql去实际操作数据库。
Django自带ORM系统,不需要额外的安装别的ORM。当然也可以安装并使用其他的ORM,比如SQLAlchemy。但是不建议这么做,因为Django系统庞大,集成完善,模型层与视图层,模板层结合的比较紧密。
Django的ORM系统体现在框架内就是模型层。要想理解模型层的概念,关键在于理解用Python代码的方式来定义数据库表的做法!一个Python的类,就是一个模型,代表数据库中的一张数据表! Django奉行Python优先的原则,一切基于Python代码的交流,完全封装SQL内部细节。
模型
一个模型(model)就是一个单独的,确定的数据的信息源,包含了数据的字段和操作方法。通常,每个模型映射为一张数据库中的表。
基本的原则如下:
- 每个模型在Django中的存在形式为一个Python类
- 每个模型都是Django.db.models.Model的子类
- 模型的每个字段(属性)代表数据表的某一列
- Django将自动为我们生成数据库访问API
简单示例:
下面的模型定义了一个“人”,它具有first_name 和 last_name 字段:
from django.db import models class Person(models.Model): first_name = models.CharField(max_length=30) last_name = models.CharField(max_length=30)
每个字段都是一个类属性,每个类属性表示数据表中的一个列。
上面的代码,相当于下面的原生SQL语句。
CREATE TABLE myapp_person ( "id" serial NOT NULL PRIMARY KEY, "first_name" varchar(30) NOT NULL, "last_name" varchar(30) NOT NULL );
注意:
- 表明myapp_person 由Django自动生成,默认格式为“项目名称 + 下划线 + 小写类名”,你可以重写这个规则。
- Django默认自动创建自增主键 id ,当然,你也可以自己指定主键。
- 上面的SQL语句基于PostgreSQL语法。
通常,我们会将模型编写在其所属APP下的models.py文件中,没有特别需求时,请坚持这个原则,不要给自己添麻烦。
创建了模型之后,在使用它之前,你需要先在settings文件中的INSTALLED_APPS处,注册models.py 文件所在的myapp。看清楚,是注册APP,不是模型,也不是models.py。如果你以前写过模型,可能已经做过这一步,可以跳过。
INSTALLED_APPS = [ #... 'myapp', #... ]
当你每次对模型进行增,删,修改时,请务必执行python manage.py migrate ,让操作实际应用到数据库上。这里可以选择在执行migrate之前,先执行 python manage.py makemigrations 让修改动作保存到记录文件中。
模型字段fields
字段是模型中最重要的内容之一,也是唯一必须的部分。字段在Python中表现为一个类属性,体现了数据表中的一个列,请不要使用 clean , save , delete 等Django内置的模型API名字,防止命名冲突。下面是一个展示,注意字段的写法:
from django.db import models class Musician(models.Model): first_name = models.CharField(max_length=50) last_name = models.CharField(max_length=50) instrument = models.CharField(max_length=100) class Album(models.Model): artist = models.ForeignKey(Musician, on_delete=models.CASCADE) name = models.CharField(max_length=100) release_date = models.DateField() num_stars = models.IntegerField()
字段命名约束:
Django不允许下面两种字段名:
- 与Python关键字冲突。这会导致语法错误,例如:
class Example(models.Model): pass = models.IntegerField() # 'pass'是Python保留字!
- 字段名中不能有两个以上下划线在一起,因为两个下划线是Django的查询语法,例如:
class Example(models.Model): foo__bar = models.IntegerField() # 'foo__bar' 有两个下划线在一起!
由于你可以自定义表名,列名,上面的规则可能被绕开,但是请养成良好的习惯,一定不要那么起名。
SQL语言的join,where和Select 等保留字可以作为字段名,因为Django对他们都进行了转义。
常用字段类型
字段类型的作用:
- 决定数据库中对应列的数据类型(例如:INTEGER ,VARCHAR,TEXT)
- HTML中对应的表单标签的类型,例如 <input type= "text" />
- 在admin后台和自动生成的表单中最小的数据验证需求
Django内置了许多字段类型,他们都位于django.db.models中,例如models.CharField 。这些类型基本满足需求。如果还不够你也可以自定义字段。
这里有如何上传文件和图片的方法:
1,FileField
class FileField(upload_to=None, max_length=100, **options)[source]
上传文件字段(不能设置为主键)。默认情况下,该字段在HTML中表现为一个ClearableFileInput 标签。在数据库内,我们实际保存的是一个字符串类型,默认最大长度为100,可以通过max_length 参数自定义。真实的文件是保存在服务器的文件系统内的。
重要参数upload_to 用于设置上传地址的目录和文件名。如下例所示:
class MyModel(models.Model): # 文件被传至`MEDIA_ROOT/uploads`目录,MEDIA_ROOT由你在settings文件中设置 upload = models.FileField(upload_to='uploads/') # 或者 # 被传到`MEDIA_ROOT/uploads/2015/01/30`目录,增加了一个时间划分 upload = models.FileField(upload_to='uploads/%Y/%m/%d/')
Django很人性化的帮我们实现了根据日期生成目录的方式!
upload_to 参数也可以接收一个回调函数,该函数返回具体的路径字符串,如下例:
def user_directory_path(instance, filename): #文件上传到MEDIA_ROOT/user_<id>/<filename>目录中 return 'user_{0}/{1}'.format(instance.user.id, filename) class MyModel(models.Model): upload = models.FileField(upload_to=user_directory_path)
例子中,user_directory_path 这种回调函数,必须接收两个参数,然后返回一个Unix风格的路径字符串。参数instace代表一个定义了FileField的模型的实例,说白了就是当前数据记录。filename是原本的文件名。
2,ImageField
class ImageField(upload_to=None, height_field=None, width_field=None, max_length=100, **options)[source]
用于保存图像文件袋额字段。其基本用法和特征与FileField一样,只不过多了两个属性height和width。默认清洗下,该字段在HTML中表现为一个ClearableFileInput 标签。在数据库内,我们实际保存的是一个字符串类型。默认最大长度100,可以通过max_length 参数自定义。真实的图片是保存在服务器的文件系统内的。
- height_field参数:保存有图片高度信息的模型字段名
- width_field参数:保存有图片宽度信息的模型字段名
使用Django的ImageField需要提前安装pillow模块。
使用FileField或者ImageField字段的步骤:
- 1,在settings文件中宏,配置MEDIA_ROOT,作为你上传文件在服务器中的基本路径(为了性能考虑,这些文件不会被存储在数据库中)。再配置个MEDIA_URL ,作为公用URL,指向上传文件的基本路径。请确保Web服务器的用户账号对该目录具有写的权限。
- 2,添加FileField或者ImageField字段到你的模型中,定义好upload_to参数,文件最终会放在MEDIA_ROOT目录的“upload_to”子目录中。
- 3,所有真正被保存在数据库中的,只是指向你上传文件路径的字符串而已。可以通过url属性,在Django的模板中方便的访问这些文件。例如,假设你有一个ImageField字段,名为mug_shot,那么在Django模块的HTML文件中,可以使用{{ object.mug_shot.url }} 来获取该文件。其中的object 用你具体的对象名称代替。
- 4,可以通过name 和 size 属性,获取文件的名称和大小信息。
安全建议
无论你如何保存上传的文件,一定要注意他们的内容和格式,避免安全漏洞!务必对所有上传文件进行安全检查,确保他们不出问题! 如果你不加任何检查就盲目的让任何人上传到你的服务器文档根目录内,比如上传了一个CGI 或者PHP脚本,很可能就会被访问的用户执行,这具有致命的危害。
3,FilePathField
class FilePathField(path=None, match=None, recursive=False, max_length=100, **options)[source]
一种用来保存文件路径信息的子段。在数据表内以字符串的形式存在,默认最大长度100,可以通过max_length参数设置。
他们包含有下面的一些参数:
- path:必须指定的参数。表示一个系统绝对路径
- match:可选参数,一个正则表达式,用于过滤文件名。只匹配基本文件名,不匹配路径。例如foo.*\.txt$ ,只匹配文件名 foo23.txt,不匹配bar.txt 与 foo23.png 。
- recursive:可选参数,只能是True或者FALSE。默认为FALSE。决定是否包含子目录,也就是是否递归的意思。
- allow_files:可选参数,只能是True或者False。默认为True,决定是否应该将文件名包括在内。它和allow_folders其中,必须有一个为True。
- allow_folders:可选参数,只能是True或者Flase。默认为False,决定是否应该将目录名包括在内。
比如:
FilePathField(path="/home/images", match="foo.*", recursive=True)
他只匹配/home/images/foo.png ,但是不匹配 /home/images/foo/bar.png ,因为默认情况,只匹配文件名,而不管路径怎么样的。
4,UUIDField
数据库无法为自己生成uuid,因此需要如下使用default参数:
import uuid # Python的内置模块 from django.db import models class MyUUIDModel(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) # 其它字段
单表操作
1,ORM简介
MVC或者MTV框架中包括一个重要的部分,就是ORM,它实现了数据模型与数据库的解耦,即数据模型的设计不需要依赖于特定的数据库,通过简单的配置就可以轻松更换数据库,这极大的减轻了开发人员的工作量,不需要面对因数据库变更而导致的无效劳动
ORM 就是 “对象——关系——映射”
比如图中birthday DATA 改成DATE ,后期会修改图片的内容
#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, ); #sql中的表纪录 #添加一条表纪录: 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的类 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()
1,创建模型
创建名为book的APP,在book下的models.py中创建模型:
from django.db import models # Create your models here. class Book(models.Model): id = models.AutoField(primary_key = True) title=models.CharField(max_length=32) state=models.BooleanField() pub_date=models.DateField() price=models.DecimalField(max_digits=8,decimal_places=2) publish=models.CharField(max_length=32)
2,更多字段和参数
每个字段有一些特有的参数,例如,CharField需要max_length 参数来指定VARCHAR数据库字段的大小。还有一些适用于所有字段的通过参数。这些参数这文档中有详细定义,这里我们学习一些最常用的:
<1> CharField 字符串字段, 用于较短的字符串. CharField 要求必须有一个参数 maxlength, 用于从数据库层和Django校验层 限制该字段所允许的最大字符数. <2> IntegerField #用于保存一个整数. <3> FloatField 一个浮点数. 必须 提供两个参数: 参数 描述 max_digits 总位数(不包括小数点和符号) decimal_places 小数位数 举例来说, 要保存最大值为 999 (小数点后保存2位),你要这样定义字段: models.FloatField(..., max_digits=5, decimal_places=2) 要保存最大值一百万(小数点后保存10位)的话,你要这样定义: models.FloatField(..., max_digits=19, decimal_places=10) admin 用一个文本框(<input type="text">)表示该字段保存的数据. <4> AutoField 一个 IntegerField, 添加记录时它会自动增长. 你通常不需要直接使用这个字段; 自定义一个主键:my_id=models.AutoField(primary_key=True) 如果你不指定主键的话,系统会自动添加一个主键字段到你的 model. <5> BooleanField A true/false field. admin 用 checkbox 来表示此类字段. <6> TextField 一个容量很大的文本字段. admin 用一个 <textarea> (文本区域)表示该字段数据.(一个多行编辑框). <7> EmailField 一个带有检查Email合法性的 CharField,不接受 maxlength 参数. <8> DateField 一个日期字段. 共有下列额外的可选参数: Argument 描述 auto_now 当对象被保存时,自动将该字段的值设置为当前时间.通常用于表 示 "last-modified" 时间戳. auto_now_add 当对象首次被创建时,自动将该字段的值设置为当前时间. 通常用于表示对象创建时间. (仅仅在admin中有意义...) <9> DateTimeField 一个日期时间字段. 类似 DateField 支持同样的附加选项. <10> ImageField 类似 FileField, 不过要校验上传对象是否是一个合法图片.#它有两个可选参 数:height_field和width_field, 如果提供这两个参数,则图片将按提供的高度和宽度规格保存. <11> FileField 一个文件上传字段. 要求一个必须有的参数: upload_to, 一个用于保存上载文件的本地文件系统路径. 这个路径必须包含 strftime #formatting, 该格式将被上载文件的 date/time 替换(so that uploaded files don't fill up the given directory). admin 用一个<input type="file">部件表示该字段保存的数据(一个文件上传部件) . 注意:在一个 model 中使用 FileField 或 ImageField 需要以下步骤: (1)在你的 settings 文件中, 定义一个完整路径给 MEDIA_ROOT 以便让 Django在此处保存上传文件. (出于性能考虑,这些文件并不保存到数据库.) 定义MEDIA_URL 作为该目录 的公共 URL. 要确保该目录对 WEB服务器用户帐号是可写的. (2) 在你的 model 中添加 FileField 或 ImageField, 并确保定义了 upload_to 选项,以告诉 Django 使用 MEDIA_ROOT 的哪个子目录保存上传文件.你的数据库中要保存的只是 文件的路径(相对于 MEDIA_ROOT). 出于习惯你一定很想使用 Django 提供的 get_<#fieldname>_url 函数.举 例来说,如果你的 ImageField 叫作 mug_shot, 你就可以在模板中以 {{ object.#get_mug_shot_url }} 这样的方式得到图像的绝对路径. <12> URLField 用于保存 URL. 若 verify_exists 参数为 True (默认), 给定的 URL 会预先检查是否 存在( 即URL是否被有效装入且 没有返回404响应). admin 用一个 <input type="text"> 文本框表示该字段保存的数据(一个单行编辑框) <13> NullBooleanField 类似 BooleanField, 不过允许 NULL 作为其中一个选项. 推荐使用这个字段而不要用 BooleanField 加 null=True 选项 admin 用一个选择框 <select> (三个可选择的值: "Unknown", "Yes" 和 "No" ) 来表示这种字段数据. <14> SlugField "Slug" 是一个报纸术语. slug 是某个东西的小小标记(短签), 只包含字母,数字,下划线 和连字符.#它们通常用于URLs 若你使用 Django 开发版本,你可以指定 maxlength. 若 maxlength 未指定, Django 会使用默认长度: 50. #在 以前的 Django 版本,没有任何办法改变50 这个长度. 这暗示了 db_index=True. 它接受一个额外的参数: prepopulate_from, which is a list of fields from which to auto-#populate the slug, via JavaScript,in the object's admin form: models.SlugField (prepopulate_from=("pre_name", "name"))prepopulate_from 不接受 DateTimeFields. <13> XMLField 一个校验值是否为合法XML的 TextField,必须提供参数: schema_path, 它是一个 用来校验文本的 RelaxNG schema #的文件系统路径. <14> FilePathField 可选项目为某个特定目录下的文件名. 支持三个特殊的参数, 其中第一个是必须提供的. 参数 描述 path 必需参数. 一个目录的绝对文件系统路径. FilePathField 据此得到可选项目. Example: "/home/images". match 可选参数. 一个正则表达式, 作为一个字符串, FilePathField 将使用它过滤文件名. 注意这个正则表达式只会应用到 base filename 而不是 路径全名. Example: "foo.*\.txt^", 将匹配文件 foo23.txt 却不匹配 bar.txt 或 foo23.gif. recursive可选参数.要么 True 要么 False. 默认值是 False. 是否包括 path 下面的全部子目录. 这三个参数可以同时使用. match 仅应用于 base filename, 而不是路径全名. 那么,这个例子: FilePathField(path="/home/images", match="foo.*", recursive=True) ...会匹配 /home/images/foo.gif 而不匹配 /home/images/foo/bar.gif <15> IPAddressField 一个字符串形式的 IP 地址, (i.e. "24.124.1.30"). <16> CommaSeparatedIntegerField 用于存放逗号分隔的整数值. 类似 CharField, 必须要有maxlength参数.
更多参数
(1)null 如果为True,Django 将用NULL 来在数据库中存储空值。 默认值是 False. (1)blank 如果为True,该字段允许不填。默认为False。 要注意,这与 null 不同。null纯粹是数据库范畴的,而 blank 是数据验证范畴的。 如果一个字段的blank=True,表单的验证将允许该字段是空值。如果字段的blank=False,该字段就是必填的。 (2)default 字段的默认值。可以是一个值或者可调用对象。如果可调用 ,每有新对象被创建它都会被调用。 (3)primary_key 如果为True,那么这个字段就是模型的主键。如果你没有指定任何一个字段的primary_key=True, Django 就会自动添加一个IntegerField字段做为主键,所以除非你想覆盖默认的主键行为, 否则没必要设置任何一个字段的primary_key=True。 (4)unique 如果该值设置为 True, 这个数据字段的值在整张表中必须是唯一的 (5)choices 由二元组组成的一个可迭代对象(例如,列表或元组),用来给字段提供选择项。 如果设置了choices ,默认的表单将是一个选择框而不是标准的文本框,而且这 个选择框的选项就是choices 中的选项。
3,settings配置
若要想将模型转为MySQL数据库中的表,需要在settings中配置。
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME':'bms', # 要连接的数据库,连接前需要创建好 'USER':'root', # 连接数据库的用户名 'PASSWORD':'', # 连接数据库的密码 'HOST':'127.0.0.1', # 连接主机,默认本级 'PORT':3306 # 端口 默认3306 } }
注意:Name名字即为数据库的名字,在MySQL连接前该数据库必须已经创建,而上面的SQLite数据库下的db.sqlite3则是项目自动创建USER和PASSWORD分布式数据库的用户名和密码。设置完后,再启动我们的Django项目前,我们需要激活我们的MySQL。然后,启动项目,会报错:no module named MySQLdb 。这是因为django默认你导入的驱动是MySQLdb,可是MySQLdb对于py3有很大问题,所以我们需要的驱动是PyMySQL,所以我们只需要找到项目文件下的init,在里面写入:
import pymysql pysql.install_as_MySQLdb()
最后通过两条数据库迁移命令即可在指定数据库中创建表:
python manage.py makemigrations python manage.py migrate
注意:确保配置文件中的INSTALLED_APPS中写入我们创建的APP名称
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', "book" ]
注意:如果报错如下:
django.core.exceptions.ImproperlyConfigured: mysqlclient 1.3.3 or newer is required; you have 0.7.11.None
MySQLclient目前只支持到Python3.4 ,因此如果使用的是更高版本的Python,需要修改如下:
通过查询路径C:\Programs\Python\Python36-32\Lib\site-packages\Django-2.0-py3.6.egg\django\db\backends\mysql 这个路径里的文件把下面注释掉就OK了。
if version < (1, 3, 3): raise ImproperlyConfigured("mysqlclient 1.3.3 or newer is required; you have %s" % Database.__version__)
注意:如果想打印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', }, } }
4,添加表记录(增create ,save)
方式1:
# create方法的返回值book_obj就是插入book表中的python数学分析这本书籍纪录对象 book_obj=Book.objects.create(title="python数学分析",state=True,price=78, publish="机械工业出版社",pub_date="2019-02-12")
当时2:
book_obj=Book(title="python数学分析",state=True,price=100, publish="机器工业出版社",pub_date="2012-12-12") book_obj.save()
示例:
from app01.models import * #create方式一: Author.objects.create(name='Alvin') #create方式二: Author.objects.create(**{"name":"alex"}) #save方式一: author=Author(name="alvin") author.save() #save方式二: author=Author() author.name="alvin" author.save()
那么如何创建存在一对多或者多对多关系的一本书的信息呢?(如何处理外键关系的字段如一对多的publisher和多对多的authors)
#一对多(ForeignKey): #方式一: 由于绑定一对多的字段,比如publish,存到数据库中的字段名叫publish_id,所以我们可以直接给这个 # 字段设定对应值: Book.objects.create(title='php', publisher_id=2, #这里的2是指为该book对象绑定了Publisher表中id=2的行对象 publication_date='2017-7-7', price=99) #方式二: # <1> 先获取要绑定的Publisher对象: pub_obj=Publisher(name='河大出版社',address='保定',city='保定', state_province='河北',country='China',website='http://www.hbu.com') OR pub_obj=Publisher.objects.get(id=1) # <2>将 publisher_id=2 改为 publisher=pub_obj #多对多(ManyToManyField()): author1=Author.objects.get(id=1) author2=Author.objects.filter(name='alvin')[0] book=Book.objects.get(id=1) book.authors.add(author1,author2) #等同于: book.authors.add(*[author1,author2]) book.authors.remove(*[author1,author2]) #------------------- book=models.Book.objects.filter(id__gt=1) authors=models.Author.objects.filter(id=1)[0] authors.book_set.add(*book) authors.book_set.remove(*book) #------------------- book.authors.add(1) book.authors.remove(1) authors.book_set.add(1) authors.book_set.remove(1) #注意: 如果第三张表是通过models.ManyToManyField()自动创建的,那么绑定关系只有上面一种方式 # 如果第三张表是自己创建的: class Book2Author(models.Model): author=models.ForeignKey("Author") Book= models.ForeignKey("Book") # 那么就还有一种方式: author_obj=models.Author.objects.filter(id=2)[0] book_obj =models.Book.objects.filter(id=3)[0] s=models.Book2Author.objects.create(author_id=1,Book_id=2) s.save() s=models.Book2Author(author=author_obj,Book_id=1) s.save()
5,查询表记录(filter , value)
查询API
<1> all(): 查询所有结果 <2> filter(**kwargs): 它包含了与所给筛选条件相匹配的对象 <3> get(**kwargs): 返回与所给筛选条件相匹配的对象,返回结果有且只有一个, 如果符合筛选条件的对象超过一个或者没有都会抛出错误。 <4> exclude(**kwargs): 它包含了与所给筛选条件不匹配的对象 <5> order_by(*field): 对查询结果排序 <6> reverse(): 对查询结果反向排序 <8> count(): 返回数据库中匹配查询(QuerySet)的对象数量。 <9> first(): 返回第一条记录 <10> last(): 返回最后一条记录 <11> exists(): 如果QuerySet包含数据,就返回True,否则返回False <12> values(*field): 返回一个ValueQuerySet——一个特殊的QuerySet,运行后得到的并 不是一系列model的实例化对象,而是一个可迭代的字典序列 <13> values_list(*field): 它与values()非常相似,它返回的是一个元组序列,values返回的 是一个字典序列 <14> distinct(): 从返回结果中剔除重复纪录
补充
#扩展查询,有时候DJANGO的查询API不能方便的设置查询条件,提供了另外的扩展查询方法extra: #extra(select=None, where=None, params=None, tables=None,order_by=None, select_params=None (1) Entry.objects.extra(select={'is_recent': "pub_date > '2006-01-01'"}) (2) Blog.objects.extra( select=SortedDict([('a', '%s'), ('b', '%s')]), select_params=('one', 'two')) (3) q = Entry.objects.extra(select={'is_recent': "pub_date > '2006-01-01'"}) q = q.extra(order_by = ['-is_recent']) (4) Entry.objects.extra(where=['headline=%s'], params=['Lennon'])
6,删除表记录(delete)
删除方法就是delete() 它运行时立即删除对象而不返回任何值。例如:
model_obj.delete()
你可以一次性删除多个对象,每个QuerySet都有一个delete()方法,它一次性删除QuerySet中所有的对象。
例如下面的代码将删除pub_date是2005年的Entry对象:
Entry.objects.filter(pub_date__year=2005).delete()
在Django删除对象时,会模仿SQL约束ON DELETE CASCADE的行为,换句话说,删除一个对象时也会删除与它相关联的外键对象。例如:
b = Blog.objects.get(pk=1) # This will delete the Blog and all of its Entry objects. b.delete()
要注意的是:delete() 方法就是QuerySet上的方法,但并不适用于Manager本身。这是一种保护机制,是为了避免意外的调用Entry.objects.delete() 方法导致所有的记录被误删除。如果你确认要删除所有的对象,那么你必须显示的调用:
Entry.objects.all().delete()
如果不想级联删除,可以设置为:
pubHouse = models.ForeignKey(to='Publisher', on_delete=models.SET_NULL, blank=True, null=True)
如果是多对多的关系:remove() 和 clear()方法
#正向 book = models.Book.objects.filter(id=1) #删除第三张表中和女孩1关联的所有关联信息 book.author.clear() #清空与book中id=1 关联的所有数据 book.author.remove(2) #可以为id book.author.remove(*[1,2,3,4]) #可以为列表,前面加* #反向 author = models.Author.objects.filter(id=1) author.book_set.clear() #清空与boy中id=1 关联的所有数据
7,修改表记录(update和save)
Book.objects.filter(title__startswith="py").update(price=120)
此外,update() 方法对于任何结果集(QuerySet)均有效,这意味着你可以同时更新多条记录update()方法会返回一个整型数值,表示受影响的记录条数。
#---------------- update方法直接设定对应属性---------------- models.Book.objects.filter(id=3).update(title="PHP") ##sql: ##UPDATE "app01_book" SET "title" = 'PHP' WHERE "app01_book"."id" = 3; args=('PHP', 3) #--------------- save方法会将所有属性重新设定一遍,效率低----------- obj=models.Book.objects.filter(id=3)[0] obj.title="Python" obj.save() # SELECT "app01_book"."id", "app01_book"."title", "app01_book"."price", # "app01_book"."color", "app01_book"."page_num", # "app01_book"."publisher_id" FROM "app01_book" WHERE "app01_book"."id" = 3 LIMIT 1; # # UPDATE "app01_book" SET "title" = 'Python', "price" = 3333, "color" = 'red', "page_num" = 556, # "publisher_id" = 1 WHERE "app01_book"."id" = 3;
在这个例子里我们可以看到Django的save()方法更新了不仅仅是title列的值,还有更新了所有的列。若title以外的列有可能会被其他的进程所改动的情况下,只更改title列显然是更加明智的。更改某一指定的列,我们可以调用结果集(QuerySet)对象的update()方法,与之等同的SQL语句变得更加高效,并且不会引起竞态条件。
此外,update()方法对于任何结果集(QuerySet)均有效,这意味着你可以同时更新多条记录update()方法会返回一个整型数值,表示受影响的记录条数。
注意:这里因为update返回的是一个整型,所以没法用query属性;对于每次创建一个对象,想显示对应的raw 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', }, } }
注意:如果是多对多的改:
obj=Book.objects.filter(id=1)[0] author=Author.objects.filter(id__gt=2) obj.author.clear() obj.author.add(*author)
多表操作
1,创建模型
实例:我们来假定下面这些概念,字段和关系
作者模型:一个作者有姓名和年龄
作者详细模型:把作者的详情放到详情表,包含生日,手机号,家庭住址等信息。作者详细模型和作者模型之间是一对一的关系(OneToOne)
出版商模型:出版商有名称,所在城市以及email
书籍模型:书籍有书名和出版日期,一本书可能有多个作者,一个作者也可以写多本书,所以作者和书籍的关系就是多对多的关联关系(MangToMany),一本书只应该由一个出版商出版,所以出版商和书籍是一对多关联关系(OneToMany)。
模型建立如下:
from django.db import models # Create your models here. class Author(models.Model): nid = models.AutoField(primary_key=True) name = models.CharField(max_length=32) age = models.IntegerField() # 与AuthorDetail建立一对一的关系 authorDetail = models.OneToOneField(to='AuthorDetail',on_delete=models.CASCADE) class AuthorDetail(models.Model): nid = models.AutoField(primary_key=True) birthday = models.DateField() telephone = models.BigIntegerField() addr = models.CharField(max_length=64) class Publish(models.Model): nid = models.AutoField(primary_key = True) name = models.CharField(max_length= 32) city = models.CharField(max_length= 32) email = models.EmailField() class Book(models.Model): nid = models.AutoField(primary_key= True) title = models.CharField( max_length= 32) publishDate = models.DateField() price = models.DecimalField(max_digits=5 , decimal_places=2) # 与Publish 建立一对多的关系,外键字段建立在多的一方 publish = models.ForeignKey(to = 'Publish',to_field='nid',on_delete=models.CASCADE) # 与Author 表建立多对多的关系,ManyToField可以建在两个模型中任意一个 # 自动创建第三张表 authors = models.ManyToManyField(to = 'Author')
注意事项:
- 表的名称myapp_modelName,是根据模型中的元数据自动生成的,也可以覆写为别的名字。
- id 字段是自动添加的
- 对于外键字段,Django会在字段名上添加“_id”来创建数据库中的列名
- 这个例子中的CREATE TABLE SQL 语句使用PostgreSQL 语法格式,要注意的是Django会根据settings中指定的数据库类型来使用相应的SQL语句。
- 定义好模型之后,你需要告诉Django_使用_这些模型。你要做的就是修改配置文件中的INSTALL_APPSZ中设置,在其中添加models.py所在应用的名称。
- 外键字段ForeignKey 有一个 null = True的设置(它允许外键接受空值NULL),你可以赋值给它空值None。
2,添加表记录
操作前先简单的录入一些数据
一对多
方式1: publish_obj=Publish.objects.get(nid=1) book_obj=Book.objects.create(title="平凡的世界",publishDate="2019-02- 12",price=100,publish=publish_obj) 方式2: book_obj=Book.objects.create(title="平凡的世界",publishDate="2019-02- 12",price=100,publish_id=1)
多对多
# 当前生成的书籍对象 book_obj=Book.objects.create(title="三体",price=200,publishDate="2019-01-22", publish_id=1) # 为书籍绑定的做作者对象 james = Author.objects.filter(name="james").first() # 在Author表中主键为2的纪录 durant = Author.objects.filter(name="durant").first() # 在Author表中主键为1的纪录 # 绑定多对多关系,即向关系表book_authors中添加纪录 book_obj.authors.add(james , durant) # 将某些特定的 model 对象添加到被关联对象集合中。 ======= book_obj.authors.add(*[])
多对多关系其他常用API:
book_obj.authors.remove() # 将某个特定的对象从被关联对象集合中去除。 #====== book_obj.authors.remove(*[]) book_obj.authors.clear() #清空被关联对象集合 book_obj.authors.set() #先清空再设置
3,补充class RelatedManager
“关联管理器” 是在一对多或者多对多的关联上下文中使用的管理器。它存在于下面两种情况:
ForeignKey关系的“另一边”。像这样:
from django.db import models class Reporter(models.Model): # ... pass class Article(models.Model): reporter = models.ForeignKey(Reporter)
在上面的例子中,管理器reporter.article_set 拥有下面的方法。
ManyToManyField关系的两边:
class Topping(models.Model): # ... pass class Pizza(models.Model): toppings = models.ManyToManyField(Topping)
这个例子中 , toppings.pizza_set 和pizza.toppings 都拥有下面的方法。
obj(obj1 [ , obj2,...])
把指定的模型对象添加到关联对象集中。 例如: >>> b = Blog.objects.get(id=1) >>> e = Entry.objects.get(id=234) >>> b.entry_set.add(e) # Associates Entry e with Blog b. 在上面的例子中,对于ForeignKey关系,e.save()由关联管理器调用,执行更新操作。 然而,在多对多关系中使用add()并不会调用任何 save()方法,而是由 QuerySet.bulk_create()创建关系。 延伸: # 1 *[]的使用 >>> book_obj = Book.objects.get(id=1) >>> author_list = Author.objects.filter(id__gt=2) >>> book_obj.authors.add(*author_list) # 2 直接绑定主键 book_obj.authors.add(*[1,3]) # 将id=1和id=3的作者对象添加到这本书的作者集合中 # 应用: 添加或者编辑时,提交作者信息时可以用到.
create(**kwargs)
创建一个新的对象,保存对象,并将它添加到关联对象集之中。返回新创建的对象: >>> b = Blog.objects.get(id=1) >>> e = b.entry_set.create( ... headline='Hello', ... body_text='Hi', ... pub_date=datetime.date(2005, 1, 1) ... ) # No need to call e.save() at this point -- it's already been saved. 这完全等价于(不过更加简洁于): >>> b = Blog.objects.get(id=1) >>> e = Entry( ... blog=b, ... headline='Hello', ... body_text='Hi', ... pub_date=datetime.date(2005, 1, 1) ... ) >>> e.save(force_insert=True) 要注意我们并不需要指定模型中用于定义关系的关键词参数。在上面的例子中,我们并没有 传入blog参数给create()。Django会明白新的 Entry对象blog 应该添加到b中。
remove(obj1 [, obj2,...])
从关联对象集中移除执行的模型对象: >>> b = Blog.objects.get(id=1) >>> e = Entry.objects.get(id=234) >>> b.entry_set.remove(e) # Disassociates Entry e from Blog b. 对于ForeignKey对象,这个方法仅在null=True时存在。
clear()
从关联对象集中移除一切对象。 >>> b = Blog.objects.get(id=1) >>> b.entry_set.clear() 注意这样不会删除对象 —— 只会删除他们之间的关联。 就像 remove() 方法一样,clear()只能在 null=True的ForeignKey上被调用。
set()
先清空,再设置,编辑书籍时即可用到
注意:对于所有类型的关联字段,add() ,create(),remove(),clear(),set() 都会马上更新数据库。换句话说,在关联的任何一端,都不需要再调用save()方法。
直接赋值:通过赋值一个新的可跌打的对象,关联对象集可以被整体替换掉
>>> new_list = [obj1, obj2, obj3] >>> e.related_set = new_list
如果外键关系满足 null =True,关联管理器会在添加new_list 中的内容之前,首先调用clear()方法来解除关联集中一切已存在对象的关联。否则 , new_list中的对象会在已存在的关联的基础上被添加。
4,查询
4.1 基于双下划线的模糊查询
Django还提供了一种直观而高效的方法在查询(lookups)中表示关联关系,它能自动确认SQL JOIN 联系。要做跨关系查询,就使用两个下划线来链接模型(model)间关联字段的名称,直到最终链接到你想要的model为止。
Book.objects.filter(price__in=[100,200,300]) Book.objects.filter(price__gt=100) Book.objects.filter(price__lt=100) Book.objects.filter(price__range=[100,200]) Book.objects.filter(title__contains="python") Book.objects.filter(title__icontains="python") Book.objects.filter(title__startswith="py") Book.objects.filter(pub_date__year=2012)
4.1.1,一对多查询
# 练习1: 查询苹果出版社出版过的所有书籍的名字与价格(一对多) # 正向查询 按字段:publish queryResult=Book.objects .filter(publish__name="苹果出版社") .values_list("title","price") # 反向查询 按表名:book queryResult=Publish.objects .filter(name="苹果出版社") .values_list("book__title","book__price")
4.1.2,多对多查询
# 练习2: 查询alex出过的所有书籍的名字(多对多) # 正向查询 按字段:authors: queryResult=Book.objects .filter(authors__name="yuan") .values_list("title") # 反向查询 按表名:book queryResult=Author.objects .filter(name="yuan") .values_list("book__title","book__price")
4.1.3,混合使用
# 练习3: 查询人民出版社出版过的所有书籍的名字以及作者的姓名 # 正向查询 queryResult=Book.objects .filter(publish__name="人民出版社") .values_list("title","authors__name") # 反向查询 queryResult=Publish.objects .filter(name="人民出版社") .values_list("book__title","book__authors__age","book__authors__name") # 练习4: 手机号以151开头的作者出版过的所有书籍名称以及出版社名称 queryResult=Book.objects .filter(authors__authorDetail__telephone__regex="151") .values_list("title","publish__name")
注意:反向查询时,如果定义了related_name,则用related_name替换表名,例如:
publish = ForeignKey(Blog, related_name='bookList')
练习:查询人民出版社出版过的所有书籍的名字与价格(一对多)
#反向查询 不再按表名:book,而是related_name:bookList queryResult=Publish.objects .filter(name="人民出版社") .values_list("bookList__title","bookList__price")
5 惰性机制
所谓惰性机制:Publisher.objects.all() 或者.filter()等都只是返回了一个QuerySet(查询结果集对象),它并不会马上执行sql,而是当调用QuerySet的时候才执行。
QuerySet特点:
- 1,可迭代的
- 2,可切片
#objs=models.Book.objects.all()#[obj1,obj2,ob3...] #QuerySet: 可迭代 # for obj in objs:#每一obj就是一个行对象 # print("obj:",obj) # QuerySet: 可切片 # print(objs[1]) # print(objs[1:4]) # print(objs[::-1])
QuerySet的高效使用:
<1>Django的queryset是惰性的 Django的queryset对应于数据库的若干记录(row),通过可选的查询来过滤。 例如,下面的代码会得到数据库中名字为‘Dave’的所有的人:person_set = Person.objects.filter(first_name="Dave")上面的代码并没有运行任何的数据库查询。 你可以使用person_set,给它加上一些过滤条件,或者将它传给某个函数,这些操作都不 会发送给数据库。这是对的,因为数据库查询是显著影响web应用性能的因素之一。 <2>要真正从数据库获得数据,你可以遍历queryset或者使用if queryset,总之你用到 数据时就会执行sql.为了验证这些,需要在settings里加入 LOGGING(验证方式) obj=models.Book.objects.filter(id=3) # for i in obj: # print(i) # if obj: # print("ok") <3>queryset是具有cache的 当你遍历queryset时,所有匹配的记录会从数据库获取,然后转换成Django的model。 这被称为执行(evaluation).这些model会保存在queryset内置的cache中,这样如果你 再次遍历这个queryset, 你不需要重复运行通用的查询。 obj=models.Book.objects.filter(id=3) # for i in obj: # print(i) ## models.Book.objects.filter(id=3).update(title="GO") ## obj_new=models.Book.objects.filter(id=3) # for i in obj: # print(i) #LOGGING只会打印一次 <4> 简单的使用if语句进行判断也会完全执行整个queryset并且把数据放入cache,虽然你并 不需要这些数据!为了避免这个,可以用exists()方法来检查是否有数据: obj = Book.objects.filter(id=4) # exists()的检查可以避免数据放入queryset的cache。 if obj.exists(): print("hello world!") <5>当queryset非常巨大时,cache会成为问题 处理成千上万的记录时,将它们一次装入内存是很浪费的。更糟糕的是,巨大的queryset 可能会锁住系统 进程,让你的程序濒临崩溃。要避免在遍历数据的同时产生queryset cache, 可以使用iterator()方法 来获取数据,处理完数据就将其丢弃。 objs = Book.objects.all().iterator() # iterator()可以一次只从数据库获取少量数据,这样可以节省内存 for obj in objs: print(obj.name) #BUT,再次遍历没有打印,因为迭代器已经在上一次遍历(next)到最后一次了,没得遍历了 for obj in objs: print(obj.name) #当然,使用iterator()方法来防止生成cache,意味着遍历同一个queryset时会重复执行 查询。所以使用iterator()的时候要当心,确保你的代码在操作一个大的queryset时没有重复执行查询 总结: queryset的cache是用于减少程序对数据库的查询,在通常的使用下会保证只有在需要的时候 才会查询数据库。使用exists()和iterator()方法可以优化程序对内存的使用。不过,由于它们并 不会生成queryset cache,可能会造成额外的数据库查询。
对象查询,单表条件查询,多表条件关联查询
#--------------------对象形式的查找-------------------------- # 正向查找 ret1=models.Book.objects.first() print(ret1.title) print(ret1.price) print(ret1.publisher) print(ret1.publisher.name) #因为一对多的关系所以ret1.publisher是一个对象, 而不是一个queryset集合 # 反向查找 ret2=models.Publish.objects.last() print(ret2.name) print(ret2.city) #如何拿到与它绑定的Book对象呢? print(ret2.book_set.all()) #ret2.book_set是一个queryset集合 #---------------了不起的双下划线(__)之单表条件查询---------------- # models.Tb1.objects.filter(id__lt=10, id__gt=1) # 获取id大于1 且 小于10的值 # # models.Tb1.objects.filter(id__in=[11, 22, 33]) # 获取id等于11、22、33的数据 # models.Tb1.objects.exclude(id__in=[11, 22, 33]) # not in # # models.Tb1.objects.filter(name__contains="ven") # models.Tb1.objects.filter(name__icontains="ven") # icontains大小写不敏感 # # models.Tb1.objects.filter(id__range=[1, 2]) # 范围bettwen and # # startswith,istartswith, endswith, iendswith, #----------------了不起的双下划线(__)之多表条件关联查询--------------- # 正向查找(条件) # ret3=models.Book.objects.filter(title='Python').values('id') # print(ret3)#[{'id': 1}] #正向查找(条件)之一对多 ret4=models.Book.objects.filter(title='Python').values('publisher__city') print(ret4) #[{'publisher__city': '北京'}] #正向查找(条件)之多对多 ret5=models.Book.objects.filter(title='Python').values('author__name') print(ret5) ret6=models.Book.objects.filter(author__name="alex").values('title') print(ret6) #注意 #正向查找的publisher__city或者author__name中的publisher,author是book表中绑定的字段 #一对多和多对多在这里用法没区别 # 反向查找(条件) #反向查找之一对多: ret8=models.Publisher.objects.filter(book__title='Python').values('name') print(ret8)#[{'name': '人大出版社'}] 注意,book__title中的book就是Publisher的关联表名 ret9=models.Publisher.objects.filter(book__title='Python').values('book__authors') print(ret9)#[{'book__authors': 1}, {'book__authors': 2}] #反向查找之多对多: ret10=models.Author.objects.filter(book__title='Python').values('name') print(ret10)#[{'name': 'alex'}, {'name': 'alvin'}] #注意 #正向查找的book__title中的book是表名Book #一对多和多对多在这里用法没区别
注意:条件查询即与对象查询对应,是指在filter,values等方法中通过__来明确查询条件。
6 聚合查询和分组查询
1,aggregate(*args , **kwargs):
通过对QuerySet进行计算,返回一个聚合值的字典,aggregate()中每一个参数都指定一个包含在字典中的返回值。即在查询集上生成聚合。
from django.db.models import Avg,Min,Sum,Max 从整个查询集生成统计值。比如,你想要计算所有在售书的平均价钱。Django的查询语法 提供了一种方式描述所有图书的集合。 >>> Book.objects.all().aggregate(Avg('price')) {'price__avg': 34.35} aggregate()子句的参数描述了我们想要计算的聚合值,在这个例子中,是Book模型中 price字段的平均值 aggregate()是QuerySet 的一个终止子句,意思是说,它返回一个包含一些键值对的字典。 键的名称是聚合值的标识符,值是计算出来的聚合值。键的名称是按照字段和聚合函数的名 称自动生成出来的。如果你想要为聚合值指定一个名称,可以向聚合子句提供它: >>> Book.objects.aggregate(average_price=Avg('price')) {'average_price': 34.35} 如果你也想知道所有图书价格的最大值和最小值,可以这样查询: >>> Book.objects.aggregate(Avg('price'), Max('price'), Min('price')) {'price__avg': 34.35, 'price__max': Decimal('81.20'), 'price__min': Decimal('12.99')}
2,annotate( *args , **kwargs)
可以通过计算查询结果中每一个对象所关联的对象集合,从而得到总计值(也可以是平均值或者总和),即为查询集的每一项生成聚合。
查询各个作者出的书的总价格,这里就涉及到分组了,分组条件是authors_name
查询各个出版社最便宜的书价是多少
F查询和Q查询
仅仅靠着单一的关键字参数查询已经很难满足查询要求。此时Django为我们提供了F和Q查询:
# F 使用查询条件的值,专门取对象中某列值的操作 # from django.db.models import F # models.Tb1.objects.update(num=F('num')+1) # Q 构建搜索条件 from django.db.models import Q #1 Q对象(django.db.models.Q)可以对关键字参数进行封装,从而更好地应用多个查询 q1=models.Book.objects.filter(Q(title__startswith='P')).all() print(q1)#[<Book: Python>, <Book: Perl>] # 2、可以组合使用&,|操作符,当一个操作符是用于两个Q的对象,它产生一个新的Q对象。 Q(title__startswith='P') | Q(title__startswith='J') # 3、Q对象可以用~操作符放在前面表示否定,也可允许否定与不否定形式的组合 Q(title__startswith='P') | ~Q(pub_date__year=2005) # 4、应用范围: # Each lookup function that takes keyword-arguments (e.g. filter(), # exclude(), get()) can also be passed one or more Q objects as # positional (not-named) arguments. If you provide multiple Q object # arguments to a lookup function, the arguments will be “AND”ed # together. For example: Book.objects.get( Q(title__startswith='P'), Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)) ) #sql: # SELECT * from polls WHERE question LIKE 'P%' # AND (pub_date = '2005-05-02' OR pub_date = '2005-05-06') # import datetime # e=datetime.date(2005,5,6) #2005-05-06 # 5、Q对象可以与关键字参数查询一起使用,不过一定要把Q对象放在关键字参数查询的前面。 # 正确: Book.objects.get( Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)), title__startswith='P') # 错误: Book.objects.get( question__startswith='P', Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)))
F()查询
F() 的实例可以在查询中引用字段,来比较同一个model实例中两个不同字段的值
# 查询评论数大于收藏数的书籍 from django.db.models import F Book.objects.filter(commnetNum__lt=F('keepNum'))
Django支持F() 对象之间以及 F() 对象和常数之间的加减乘除和取模的操作。
# 查询评论数大于收藏数2倍的书籍 Book.objects.filter(commnetNum__lt=F('keepNum')*2)
修改操作也可以使用F函数,比如将每一本书的价格提高30元:
Book.objects.all().update(price=F("price")+30)
Q() 查询
filter()等方法中的关键字参数查询都是一起“AND”的,如果你需要执行更复杂的查询(例如OR语句),你可以使用Q 对象。
from django.db.models import Q Q(title__startswith='Py')
Q 对象可以使用& 和| 操作符组合起来。当一个操作符在两个Q 对象上使用时,它产生一个新的Q 对象。
bookList=Book.objects.filter(Q(authors__name="james")|Q(authors__name="durant"))
等同于SQL WHERE字句:
WHERE name ="james" OR name ="durant"
你可以组合& 和| 操作符以及使用括号进行分组来编写任意复杂的Q 对象。同时,Q 对象可以使用~ 操作符取反,这允许组合正常的查询和取反(NOT) 查询:
bookList=Book.objects.filter(Q(authors__name="james") & ~Q(publishDate__year=2017)).values_list("title")
查询函数可以混合使用Q 对象和关键字参数。所有提供给查询函数的参数(关键字参数或Q 对象)都将"AND”在一起。但是,如果出现Q 对象,它必须位于所有关键字参数的前面。例如:
bookList=Book.objects.filter(Q(publishDate__year=2016) | Q(publishDate__year=2017), title__icontains="python" )
raw sql
django中models的操作,也是调用了ORM框架来实现的,pymysql或者mysqldb,所以我们也可以使用原生的SQL语句来操作数据库
参考文献:https://www.cnblogs.com/feixuelove1009/p/8413285.html