Django学习笔记(7)——单表操作和多表操作
单表操作
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,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 pymysql.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" ]
注意:如果想打印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', }, } }
APP/urls.py
from user9_one import views from django.urls import path urlpatterns = [ path('book/', views.book), ]
ADMIN/urls.py
from django.contrib import admin from django.urls import path from django.conf.urls import url, include urlpatterns = [ path('admin/', admin.site.urls), path('user7_auth/', include('user7_auth.urls')), path('user8_book/', include('user8_book.urls')), path('user9_one/', include('user9_one.urls')), path('user10_many/', include('user10_many.urls')), path('user11_ajax/', include('user11_ajax.urls')), ]
3,添加表记录
3.1 create方法创建记录对象
name=request.POST.get('name') pwd=request.POST.get('password') addr=request.POST.get('addr') user=models.User.objects.create(name=name,password=pwd,address=addr)
3.2 创建对象,save方法保存
name=request.POST.get('name') pwd=request.POST.get('password') addr=request.POST.get('addr') user=models.User(name=name,password=pwd,address=addr) user.save()
3.3 示例说明
from django.shortcuts import render,HttpResponse # Create your views here. from user9_one.models import Book def book(requset): # create 方法的返回值book_obj就是插入book表中的python1这本书的记录 title = 'python1' state = True price = 100 publish = '机械出版社' pub_date = '2019-3-3' book_obj = Book.objects.create(title=title, state=state, price=price, publish=publish, pub_date=pub_date) title = 'python2' state = True price = 200 publish = '机械出版社' pub_date = '2019-2-3' book_obj2 = Book(title=title, state=state, price=price, publish=publish, pub_date=pub_date) book_obj2.save() return HttpResponse("create ok")
4,修改表记录
4.1 update()方法
注意:update方法对于任何结果集(QuerySet)有效,可以同时更新多条记录,使用update()方法会返回一个整数数值,表示受影响的记录条数。
name=request.POST.get('name') pwd=request.POST.get('password') addr=request.POST.get('addr') models.User.objects.filter(id=id).update(name=name,password=pwd,address=addr)
4.2 修改对象,save()保存
# first()是返回第一条记录 user = models.User.objects.filter(name='python').first() user.pwd = '******' user.save()
4.3 示例说明
def book(requset): id = 1 title = 'python3' state = True price = 400 publish = '机械出版社' pub_date = '2019-9-13' book_obj = Book.objects.filter(id=id).update(title=title, state=state, price=price, publish=publish, pub_date=pub_date) id = 2 book_obj2 = Book.objects.filter(id=id).first() book_obj2.title = 'python4' book_obj2.state = True book_obj2.price = 400 book_obj2.publish = '机械出版社' book_obj2.pub_date = '2019-6-13' book_obj2.save() return HttpResponse("update ok")
5,删除表记录
5.1 删除所有对象
删除方法就是delete()。它运行时立即删除对象而不返回任何值。例如:
model_obj.delete()
你也可以一次性删除多个对象,每个QuerySet都有一个delete()方法,它一次性删除QuerySet中所有对象。
要注意的是:delete()方法是QuerySet上的方法,但是并不适用于Manager本身。这是一种保护机制,是为了避免意外地调用Entry.objects.delete()方法导致所有的记录被误删除。如果你确认要删除所有的对象,那么你必须显式的调用:
model.User.objects.all().delete()
如果不想级联删除,可以设置为:
pubHouse = models.ForeignKey(to='Publisher', on_delete=models.SET_NULL, blank=True, null=True)
5.2 根据记录删除
id = request.GET.get('id') ret1 = models.User.objects.filter(id=id).delete() ret = models.Book.objects.filter(name='james').first() ret.delete()
例如,下面的代码将删除pub_date是2019年的Entry对象:
Entry.objects.filter(pub_date__year=2019).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()
5.3 示例说明
def book(requset): id = 7 ret = Book.objects.filter(id=id).delete() Book.objects.all().delete() return HttpResponse("delete ok")
之前:
之后:
6,查询记录操作
6.1 查询API
查询操作是Django的ORM框架中最重要的内容之一,下面是我们常用到与查询相关的API
- python数据精度存在盲点,使用查询数字类型的是,注意转换的SQL语句所传输的数字是否精确
- 可以对QuerySet对象使用.query方法调用,可以查询最终传输到数据的纯生SQL语句
- 查询得到的QuerySet可以进行后续的查询方法,通过.进行调用
注意:一定要区分出object 和 QuerySet 的区别 !!!
QuerySet是查询集,就是传到服务器上的url里面的内容,Django会对查询返回的结果集QuerySet进行缓存,这是是为了提高查询效率。也就是说,在创建一个QuerySet对象的时候,Django并不会立即向数据库发出查询命令,只有在你需要用到这个QuerySet的时候才去数据库查询。
Object是django实现的MVC框架中的数据层(model)M,django中的模型类都有一个object对象,他是django中定义的QuerySet类型的对象,它包含了模型对象的实例。
简单来说,object是单个对象,QuerySet是许多对象。
6.2 基于双下划线的模糊查询
filter(字段名__gt='') ---- 大于 filter(字段名__lt='') ---- 小于 filter(字段名__lte='') ---- 小于等于 filter(字段名__gte='') ---- 大于等于 filter(字段名__in='') ---- 字段存在列表中 filter(字段名__range='') ---- 字段数据在范围内(between and) filter(字段名__contains='') ---- 字段数据包含内容 模糊查询 like % % filter(字段名__icontains='') ---- 字段数据包含内容,并且忽略大小写 模糊查询 like % % filter(字段名__startswith='') ---- 字段数据以内容开头 filter(字段名__endswith='') ---- 字段数据以内容结尾 filter(字段名__year='') ---- 查询字段内容以指定年份查询 filter(字段名__month='') ---- 查询字段内容以指定月份查询 filter(字段名__day='') ---- 查询字段内容以指定日期查询
示例:
def book(requset): # filter(字段名__gt='') ---- 大于 # 查询价格大于200 的书 ret1 = Book.objects.filter(price__gt='200') # filter(字段名__lt='') ---- 小于 # 查询价格小于200 的书 ret2 = Book.objects.filter(price__lt='200') # filter(字段名__lte='') ---- 小于等于 ret3 = Book.objects.filter(price__lte='200') # filter(字段名__gte='') ---- 大于等于 ret4 = Book.objects.filter(price__gte='200') # filter(字段名__in='') ---- 字段存在列表中 ret5 = Book.objects.filter(price__in=['2001', '1000', '1001']) # filter(字段名__range='') ---- 字段数据在范围内(between and) ret6 = Book.objects.filter(price__range=[500, 1500]) # filter(字段名__contains='') ---- 字段数据包含内容 模糊查询 like % % # 查询名字有'%python%'的书 ret7 = Book.objects.filter(title__contains='python') # filter(字段名__icontains='') ---- 字段数据包含内容,并且忽略大小写 模糊查询 like % % # 查询名字带p的书,忽略大小写 ret8 = Book.objects.filter(title__icontains='P') # filter(字段名__startswith='') ---- 字段数据以内容开头 ret9 = Book.objects.filter(title__startswith='p') # filter(字段名__endswith='') ---- 字段数据以内容结尾 ret10 = Book.objects.filter(title__endswith='5') # filter(字段名__year='') ---- 查询字段内容以指定年份查询 ret11 = Book.objects.filter(pub_date__year='2019') # filter(字段名__month='') ---- 查询字段内容以指定月份查询 ret12 = Book.objects.filter(pub_date__month='4') # filter(字段名__day='') ---- 查询字段内容以指定日期查询 ret13 = Book.objects.filter(pub_date__day='3') print(ret1) print(ret2) print(ret3) print(ret4) print(ret5) print(ret6) print(ret7) print(ret8) print(ret9) print(ret10) print(ret11) print(ret12) print(ret13) return HttpResponse("select ok")
结果:
<QuerySet [<Book: Book object (11)>, <Book: Book object (12)>, <Book: Book object (13)>, <Book: Book object (14)>, <Book: Book object (15)>, <Book: Book object (16)>]> <QuerySet [<Book: Book object (9)>]> <QuerySet [<Book: Book object (9)>, <Book: Book object (10)>]> <QuerySet [<Book: Book object (10)>, <Book: Book object (11)>, <Book: Book object (12)>, <Book: Book object (13)>, <Book: Book object (14)>, <Book: Book object (15)>, <Book: Book object (16)>]> <QuerySet [<Book: Book object (11)>, <Book: Book object (13)>, <Book: Book object (14)>]> <QuerySet [<Book: Book object (11)>, <Book: Book object (13)>, <Book: Book object (15)>]> <QuerySet [<Book: Book object (9)>, <Book: Book object (10)>, <Book: Book object (11)>, <Book: Book object (12)>, <Book: Book object (13)>, <Book: Book object (14)>, <Book: Book object (15)>, <Book: Book object (16)>]> <QuerySet [<Book: Book object (9)>, <Book: Book object (10)>, <Book: Book object (11)>, <Book: Book object (12)>, <Book: Book object (13)>, <Book: Book object (14)>, <Book: Book object (15)>, <Book: Book object (16)>]> <QuerySet [<Book: Book object (9)>, <Book: Book object (10)>, <Book: Book object (11)>, <Book: Book object (12)>, <Book: Book object (13)>, <Book: Book object (14)>, <Book: Book object (15)>, <Book: Book object (16)>]> <QuerySet [<Book: Book object (13)>]> <QuerySet [<Book: Book object (9)>, <Book: Book object (10)>, <Book: Book object (11)>, <Book: Book object (12)>, <Book: Book object (13)>, <Book: Book object (14)>, <Book: Book object (15)>, <Book: Book object (16)>]> <QuerySet [<Book: Book object (12)>]> <QuerySet [<Book: Book object (11)>, <Book: Book object (12)>, <Book: Book object (13)>, <Book: Book object (14)>, <Book: Book object (15)>, <Book: Book object (16)>]>
6.3 示例说明
首先给数据库里面添加一些数据,添加后,数据如下:
def book(requset): # 查询机械出版社出版的书,返回单个对象 res = Book.objects.filter(publish="机械出版社").first() print(res) # Book object(9) print(type(res)) # <class 'user9_one.models.Book'> # 支持类列表的查询方式 res1 = Book.objects.filter(publish="机械出版社")[1] print(res1) #Book object (10) # filter内可以传多个参数,用逗号分隔,他们是and的关系,返回QuerySet # 如果你在查询里面,使用字符串,导致精度不准确,则会报错,无法查询 res2 = Book.objects.filter(publish='机械出版社', price='100') print(res2) #<QuerySet [<Book: Book object (9)>]> return HttpResponse("select ok")
多表操作的知识储备
多表关联是模型层的重要功能之一,Django提供了一套基于关联字段独特的解决方案。那就是用OneToOneField,ForeignKey,ManyToMany。
1,区分什么是一对一,一对多,多对多
一对一:子表从母表中选出一条数据一一对应,母表中选出来一条就少一条,子表不可以再选择母表中已被选择的那条数据。
一对多:子表从母表中选出一条数据一一对应,但母表的这条数据还可以被其他子表数据选择。
多对多:比如有多个孩子,多种颜色,每个孩子可以喜欢多种颜色,一种颜色可以被多个孩子喜欢,对于双向均是可以有多个选择。
2,一对一,一对多,多对多的应用场景
一对一(OneToOneField):一般用于某张表的补充,比如用户基本信息是一张表,但并非每个用户都需要有登录的权限,不需要记录用户名和密码,此时,合理的做法就是新建一张记录登录信息的表,与用户信息进行一对一的关联,可以方便的从子表查询母表信息或反向查询
外键(ForeignKey):有很多的应用场景,比如每个员工归属于一个部门,那么就可以让员工表的部门字段与部门表进行一对多关联,可以查询到一个员工归属于哪个部门,也可以反向查询某一部门有哪些员工
多对多(ManyToMany):如有很多公司,一台服务器可能会有很多种用途,归属于多个产品线中,那么服务器与产品线之间就可以做成多对,多对多在A表添加ManyToMany字段或者从B表中添加,效果一样。
3,外键——ForeignKey
ForeignKey 字段接受一个Model类作为参数,类型与被参照的字段完全相同:
blog = models.ForeignKey(Blog)
3.1 ForeignKey.to_field
关联到的关联对象的字段名称。默认的,Django使用关联对象的主键。
blog = models.ForeignKey(Blog, to_field = Blog.name)
3.2 ForeignKey.db_constraint
Django Model的 Foreign 字段的主要功能是维护一个一对多的关系,以进行关联查询。只有在db_constraint = True 时Django model 才会在数据库上建立外键约束,在该值为False时不建立约束。
默认 db_constraint = True
3.3 ForeignKey.related_name
这个名称用于让关联的对象反查到源对象。
如果你不想让DJango创建一个反向关联,请设置related_name 为 ‘+’ 或者 以‘+’结尾。
ForeignKey.related_query_name以ForeignKey.related_name作为默认值。
4,ManyToManyField
4.1 ManyToManyField.through
Django会自动创建一个表来管理多对多关系,若要手动指定关联表则需要使用through关键字参数。
4.2 ManyToManyField.through_fields
上面示例中Membership有两个外键指向Person(person 和 inviter),这使得关联关系含混不清并让Django不知道使用哪一个,在这种情况下,必须使用through_fields明确指定Django应该使用哪些外键。through_fields 接收一个二元组('field1' , 'field2'),其中field1为指向定义ManyToManyField字段的模型的外键名称(本例中为group),field2为指向目标模型的外键的名称(本例中为person)。
4.3 MantToManyField.db_table
默认情况下,关联表的名称使用多对多字段的名称和包含这张表的模型的名称以及Hash值生成,如:memberShip_person_3c1f5,若想要手动指定表的名称,可以使用db_table 关键字参数指定。
5,on_delete的使用方法
对于最新版本的Django2.0 在使用一对一(OneToOneField)和外键(ForeignKey)时,需要加上on_delete 参数,不然就会报错,
如果刚使用django2.0 的是,遇到报错如下,则是没有给外键添加on_delete参数:
TypeError: __init__() missing 1 required positional argument: 'on_delete'
6,一对一(OneToOneField)示例
from django.conf import settings from django.db import models class MySpecialUser(models.Model): user = models.OneToOneField(settings.AUTH_PASSWORD_VALIDATORS, on_delete=models.CASCADE,) supervisor = models.OneToOneField(settings.AUTH_PASSWORD_VALIDATORS, on_delete=models.CASCADE, related_name='supervisor_of',)
7,一对多(ForeignKey)示例
from django.db import models class AbstractCar(models.Model): manufacturer = models.ForeignKey('Manufacturer', on_delete=models.CASCADE) class Meta: abstract = True
8,下面整理的on_delete参数的各个值的含义:
on_delete=None, # 删除关联表中的数据时,当前表与其关联的field的行为 on_delete=models.CASCADE, # 删除关联数据,与之关联也删除 on_delete=models.DO_NOTHING, # 删除关联数据,什么也不做 on_delete=models.PROTECT, # 删除关联数据,引发错误ProtectedError # models.ForeignKey('关联表', on_delete=models.SET_NULL, blank=True, null=True) on_delete=models.SET_NULL, # 删除关联数据,与之关联的值设置为null(前提FK字段需要设置为可空,一对一同理) # models.ForeignKey('关联表', on_delete=models.SET_DEFAULT, default='默认值') on_delete=models.SET_DEFAULT, # 删除关联数据,与之关联的值设置为默认值(前提FK字段需要设置默认值,一对一同理) on_delete=models.SET, # 删除关联数据, a. 与之关联的值设置为指定值,设置:models.SET(值) b. 与之关联的值设置为可执行对象的返回值,设置:models.SET(可执行对象)
多对多不需要on_delete。
多表操作
1,创建模型
1.1 实例:我们来假定下面这些概念,字段和关系
作者模型 : 姓名 年龄 作者详细模型 : 生日 手机号码 家庭住址等等 出版商模型 : 出版商名称 所在城市 email 书籍模型 : 书名 出版日期 (作者模型 和 作者详细模型 是一对一关系 one-to -one) (一本书可能会有多个作者, 一个作者也可以写多本书) (作者 和 书籍 是多对多的关系 many -to -many) (一本书只应该由一个出版商出版, 出版商和书籍是一对多关联关系 one - to - many)
1.2 代码如下
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() # max_digits=None, 总长度 decimal_places=None,小数位长度 price = models.DecimalField(max_digits=5, decimal_places=2) # 与publish建立一对多的关系,外键字段建立在多的一方 publish = models.ForeignKey(to='Publish', to_field='nid',on_delete=models.CASCADE) # 与Author 建立多对多的关系,ManyToManyField可以建在两个模型中的任意一个,自动创建第三张表 authors = models.ManyToManyField(to='Author',)
1.3 结果如下
1.4 注意事项
- 表的名称是Myapp_modelName,是根据模型中的元数据自动生成的额,也可以复写为别的名字。
- ID 字段是自动添加的
- 对于外键字段,Django会在字段名上添加“_id” 来创建数据库中的列名
- 这个例子中的CREATE TABLE SQL 语句使用PostgreSQL 语法格式,要注意的是Django会根据settings中指定的数据库类型来使用相应的SQL语句。
- 定义好模型之后,你需要告诉Django_ 使用 _ 这些模型。你要做的就是修改配置文件中的INSTALL_APPSZ中设置,在其中添加models.py所在应用的名称。
- 外键字段ForeignKey有一个null = True的设置(它允许外键接受空值NULL),你可以赋给他空值None。
2,添加表记录
操作前先简单的录入一些数据
2.1 Publish表
2.2 AuthorDetail表
2.3 Author表
2.4 一对多添加——book表
def insert_data(request): publish_obj = Publish.objects.get(nid=1) book_obj = Book.objects.create(title='秦腔', publishDate='2016-8-28', price=1005, publish=publish_obj) book_obj2 = Book.objects.create(title="追风筝的人", publishDate='2017-4-9', price=124, publish_id=2) return HttpResponse("insert data ok")
2.5 多对多添加——book表,book_authors表
def insert_data(request): # 生成当前的书籍对象 book_obj = Book.objects.create(title='秦腔', publishDate='2016-8-28', price=1005, publish_id=1) # 为书籍绑定的做作者对象 author_james = Author.objects.filter(name='james').first() # 在Author表中主键为1的记录 author_durant = Author.objects.filter(name='durant').first() # 在Author表中主键为2的记录 # 绑定多对多关系,即向关系表book_authors中添加数据 book_obj.authors.add(author_james, author_durant) # 将某些特定的model对象添加到被关联对象集合中 # book_obj.authors.add(*[]) return HttpResponse("insert data ok")
2.6 多对多关系常用API
# 将某个特定的对象从被关联对象集合中去除。 book_obj.authors.remove() book_obj.authors.remove(*[]) #清空被关联对象集合 book_obj.authors.clear() #先清空再设置 book_obj.authors.set()
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中的对象会在已存在的关联的基础上被添加。
2.7 如何处理外键关系的字段(如一对多的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()
2.8 对象查询,单表条件查询,多表条件关联查询
#--------------------对象形式的查找-------------------------- # 正向查找 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等方法中通过__来明确查询条件
3,关联管理器——Class RelatedManager
“关联管理器” 是在一对多或者多对多的关联上下文中使用的管理器。他存在于下面两种情况:ForeignKey关系的“另一边”。像这样:
# models.py from django.db import models class Reporter(models.Model): # ... pass class Article(models.Model): reporter = models.ForeignKey(Reporter)
在上面的例子中,管理器reporter.article_set拥有下面的方法。
MangToManyField关系的两边:
class Manager1(models.Model): # ... pass class Manager2(models.Model): manager1 = models.ManyToManyField(Manager1)
3.1 查询表记录(filter,value)
查询操作是Django的ORM框架中最重要的内容之一,下面是我们常用到与查询相关的API。
注意:一定要区分出object 和QuerySet 的区别
附加SQL查询extra()
#扩展查询,有时候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'])
4,基于对象的跨表查询
4.1 一对一查询(Author和AuthorDetail)
正向查询(按字段:authorDetail)
def select_data(request): # 查询作者名为james的电话 james = Author.objects.filter(name='james').first() print(james) print(james.authorDetail.telephone) return HttpResponse("select data ok")
反向查询(按表名:author)
def select_data(request): # 查询住址在chengdu的作者姓名 authorDetail_list = AuthorDetail.objects.filter(addr='chengdu') for obj in authorDetail_list: print(obj.author.name) return HttpResponse("select data ok")
4.2 一对多查询(Publish和Book)
正向查询(按字段:publish)
def select_data(request): # 查询主键为1的书籍的出版社所在的城市 book_obj = Book.objects.filter(nid=1).first() # book_obj.publish 是主键为1 的书籍对象关联的出版社对象 print(book_obj.publish.city) return HttpResponse("select data ok")
反向查询(按表名:book_set)
def select_data(request): publish = Publish.objects.get(name="机械出版社") # 与机械出版社 关联的所有书籍对象集合 res1 = publish.book_set.all() print(res1) book_list = publish.book_set.all() for book_obj in book_list: print(book_obj.title) return HttpResponse("select data ok")
4.3 多对多查询(Author与Book)
正向查询(按字段:author)
def select_data(request): # 查找平凡的世界所有作者的名字以及手机号码 book_obj = Book.objects.filter(title="秦腔").first() print(book_obj) authors = book_obj.authors.all() print(authors) for author_obj in authors: print(author_obj.name, author_obj.authorDetail.telephone) return HttpResponse("select data ok")
反向查询(按表名:book_set)
def select_data(request): # 查找james出过的所有书籍的名字 author_obj = Author.objects.get(name='james') print(author_obj) # 与james作者相关的所有书籍 book_list = author_obj.book_set.all() print(book_list) for book_obj in book_list: print(book_obj.title) return HttpResponse("select data ok")
4.4 注意
你可以通过在ForeignKey()和ManyToManyField的定义中设置related_name的值来覆写FOO_set的名称。例如,如果Article model中做一下更改:
publish = ForeignKey(Book, related_name='bookList')
那么接下来,我们就会看到这样:
def select_data(request): # 查找人们出版社出版过的所有书籍 publish = Publish.objects.get(name='机械出版社') print(publish) # 与机械出版社关联的所有书籍对象集合 book_list = publish.bookList.all() print(book_list) return HttpResponse("select data ok")
5,基于双下划线的跨表查询
Django还提供了一种直观而高效的方法在查询(lookups)中表示关联关系,它能自动确认SQL JOIN 联系。要做跨关系查询,就使用两个下划线来链接模型(model)间关联字段的名称,直到最终链接到你想要的model为止。
关键点:正向查询按字段,反向查询按表名
5.1 一对多查询
def select_data(request): # 查询机械出版社 出版过的所有书籍的名字与价钱(一对多) # 正向查询 按字段publish queryResult1 = Book.objects.filter(publish_id=1).values_list('publish__book', 'publish__city') print(queryResult1) # 反向查询 按表名:book queryResult2 =Publish.objects.filter(nid=2).values_list("book__title", 'book__price') print(queryResult2) return HttpResponse("select data ok")
查询结果:
<QuerySet [(1, 'xian'), (3, 'xian'), (4, 'xian'), (5, 'xian'), (6, 'xian'), (1, 'xian'), (3, 'xian'), (4, 'xian'), (5, 'xian'), (6, 'xian'), (1, 'xian'), (3, 'xian'), (4, 'xian'), (5, 'xian'), (6, 'xian'), (1, 'xian'), (3, 'xian'), (4, 'xian'), (5, 'xian'), (6, 'xian'), ' ...(remaining elements truncated)...']> <QuerySet [('追风筝的人', Decimal('124.00'))]>
5.2 多对多查询
def select_data(request): # 查询james出版过的所有书籍的名字 (多对多) # 正向查询,按照字段:authors queryResult1 = Book.objects.filter(authors=1).values_list("authors__name",'authors__age','publish__name') print(queryResult1) # 反向查询 按照表名book queryResult2 = Author.objects.filter(name='durant').values_list("book__authors__name",'age','book__publish__name') print(queryResult2) return HttpResponse("select data ok")
结果展示:
<QuerySet [('james', 34, '机械出版社'), ('james', 34, '机械出版社')]> <QuerySet [('james', 30, '机械出版社'), ('james', 30, '机械出版社'), ('durant', 30, '机械出版社'), ('durant', 30, '机械出版社')]>
5.3 一对一查询
# 查询james的手机号 # 正向查询 ret=Author.objects.filter(name="james").values("authordetail__telephone") # 反向查询 ret=AuthorDetail.objects.filter(author__name="james").values("telephone")
5.4 混合使用
def select_data(request): # 查询机械出版社 出版过的所有书籍的名字与作者名字 # 正向查询 按照字段 authors queryResult1 = Book.objects.filter(publish_id=1).values_list('title', 'authors__name') print(queryResult1) # 反向查询 按表名:book queryResult2 =Publish.objects.filter(nid=2).values_list("book__title", 'book__price') print(queryResult2) # 查询手机是以123开头的作者 出版过的所有书籍名称以及出版社名称 queryResult3 = Book.objects.filter(authors__authorDetail__telephone__regex='123').values_list('title', 'publish__name') print(queryResult3) return HttpResponse("select data ok")
结果展示:
<QuerySet [('秦腔', 'james'), ('秦腔', 'james'), ('秦腔', 'durant'), ('秦腔', 'durant'), ('平凡的世界', None), ('平凡的世界2', None), ('秦腔', None)]> <QuerySet [('追风筝的人', Decimal('124.00'))]> <QuerySet [('秦腔', '机械出版社'), ('秦腔', '机械出版社')]>
注意:反向查询时,如果定义了related_name,则用related_name替换表名,例如:
publish = ForeignKey(Blog, related_name='bookList')
5.5 练习:查询人民出版社出版过的所有书籍的名字与价格(一对多)
def select_data(request): # 查询机械出版社 出版过的所有书籍的名字与价格 # 正向查询 按照字段 authors queryResult1 = Book.objects.filter(publish_id=1).values_list('title', 'price') print(queryResult1) # 反向查询 不再按照表名book,而是related_name:bookList queryResult2 = Publish.objects.filter(name='机械出版社').values_list("book__title","book__price") print(queryResult2) return HttpResponse("select data ok")
结果展示:
<QuerySet [('平凡的世界', Decimal('125.00')), ('平凡的世界2', Decimal('105.00')), ('秦腔',Decimal('1005.00')), ('秦腔', Decimal('1005.00')), ('秦腔', Decimal('1005.00'))]> <QuerySet [('平凡的世界', Decimal('125.00')), ('平凡的世界2', Decimal('105.00')), ('秦腔',Decimal('1005.00')), ('秦腔', Decimal('1005.00')), ('秦腔', Decimal('1005.00'))]>
5.6 进阶练习(连续跨表)
# 练习: 查询人民出版社出版过的所有书籍的名字以及作者的姓名 # 正向查询 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") # 练习: 手机号以151开头的作者出版过的所有书籍名称以及出版社名称 # 方式1: queryResult=Book.objects .filter(authors__authorDetail__telephone__regex="151") .values_list("title","publish__name") # 方式2: ret=Author.objects .filter(authordetail__telephone__startswith="151") .values("book__title","book__publish__name")
5.7 related_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")
6 ,聚合查询和分组查询
6.1 aggregate(*args , **kwargs):
通过对QuerySet进行计算,返回一个聚合值的字典,aggregate()中每一个参数都指定一个包含在字典中的返回值。即在查询集上生成聚合。
总结:跨表分组查询的本质就是将关联表join成一张表,再按照单表的思路进行分组查询。
aggregate()字句的参数描述了我们想要计算的聚合值。aggregate() 是QuerySet的一个终止子句,意思是它返回一个包含一些键值对的字典,键的名称是聚合值的标识符,值是计算出来的聚合值。键的名称是按照字段和聚合函数的名称自动生成出来的。如果你想要为聚合值指定一个名称,可以向聚合子句提供它。
from django.db.models import Avg, Min, Sum, Max def select_data(request): # 比如你想要计算所有在售的书的平均价钱 # Django 提供了一种方式描述所有图书的集合 # 在下面的例子中,aggregate()计算的Book模型中price字段的平均值 res1 = Book.objects.all().aggregate(Avg('price')) print(res1) #{'price__avg': 561.5} # 如果想要为聚合值指定一个名称,可以向聚合子句提供它。 res2 = Book.objects.aggregate(avergae_price=Avg('price')) print(res2) # {'avergae_price': 561.5} # 如果你想知道所有图书价格的最大值和最小值,可以这样查询 res3 = Book.objects.aggregate(Avg('price'), Max('price'), Min('price')) print(res3) # {'price__avg': 561.5, 'price__max': Decimal('1005.00'), 'price__min': Decimal('105.00')} return HttpResponse("select data OK")
6.2 annotate( *args , **kwargs)
可以通过计算查询结果中每一个对象所关联的对象集合,从而得到总计值(也可以是平均值或者总和),即为查询集的每一项生成聚合。
单表分组查询的ORM 语法如下:单表模型.objects.values('group by 的字段‘).annotate(聚合函数('统计字段'))
查询各个作者出的书的总价格,这里就涉及到分组了,分组条件是authors_name
查询各个出版社最便宜的书价是多少
from django.db.models import Avg, Min, Sum, Max def select_data(request): # 查询各个作者出的书的总价格,这里涉及分组,分组条件是authors_name res1 = Book.objects.values("authors__name").annotate(Sum('price')) print(res1) # 查询各个出版社最便宜的书架是多少 res2 = Book.objects.values("publish__name").annotate(Min("price")) print(res2) return HttpResponse("select data OK")
结果如下:
<QuerySet [{'authors__name': 'james', 'price__sum': Decimal('2010.00')}, {'authors__name':'durant', 'price__sum': Decimal('2010.00')}, {'authors__name': None, 'price__sum': Decimal('1359.00')}]> <QuerySet [{'publish__name': '机械出版社', 'price__min': Decimal('105.00')}, {'publish__name':'清华出版社', 'price__min':Decimal('124.00')}]>
6.3 查询练习
注意:(框里面的相等)
1,练习:统计每一个出版社里最便宜的书籍
publishList = Publish.objetcs.annotate(minPrice=Min('book__price")) for publish_obj in publishList: print(publish_obj.name, publish_obj.MinPrice)
annotate的返回值是QuerySet,如果不想遍历对象,可以用上 valuelist
queryResult= Publish.objects .annotate(MinPrice=Min("book__price")) .values_list("name","MinPrice") print(queryResult)
对应的SQL代码如下:
''' SELECT "app01_publish"."name", MIN("app01_book"."price") AS "MinPrice" FROM "app01_publish" LEFT JOIN "app01_book" ON ("app01_publish"."nid" = "app01_book"."publish_id") GROUP BY "app01_publish"."nid", "app01_publish"."name", "app01_publish"."city", "app01_publish"."email" '''
2,练习:统计每一本书的作者个数
ret = Book.objects.annotate(authorsNum=Count('authors__name'))
3,统计每一本以py开头的书籍的作者个数
queryResult=Book.objects.filter(title__startswith="Py") .annotate(num_authors=Count('authors'))
4,统计不止一个作者的图书:
queryResult=Book.objects.annotate(num_authors=Count('authors')) .filter(num_authors__gt=1)
5,根据一本图书作者数量的多少对查询集QuerySet进行排序
Book.objects.annotate(num_authors=Count('authors')).order_by('num_authors')
6,查询各个作者出的书的总价格
# 按author表的所有字段 group by queryResult=Author.objects.annotate(SumPrice=Sum("book__price")) .values_list("name","SumPrice") print(queryResult)
7,F查询和Q查询
仅仅靠着单一的关键字参数查询已经很难满足查询要求。此时Django为我们提供了F和Q查询:
7.1 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函数,比如把每一本书的价格提高10元
def update_use_F(request): # F 使用查询条件的值,专门取对象中某列值的操作 from django.db.models import F # models.Tb1.objects.update(num=F('num') + 1) res1 = Book.objects.update(price = F('price') + 10) print(res1) return HttpResponse("update use F OK")
操作之前:
操作之后:
7.2 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" )
7.3 综合运用
# 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)))
8,删除表记录(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()方法
多对多删除的两个重要参数:clear() 清空数据 remove() 删除数据
#正向 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 关联的所有数据
9,修改表记录(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)