Django ORM学习
一、介绍
对象关系映射(Object Relational Mapping,简称ORM)模式是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术。简单的说,ORM是通过使用描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系数据库中。ORM在业务逻辑层和数据库层之间充当了桥梁的作用。
二、Model
在Django中model是你数据的单一、明确的信息来源。它包含了你存储的数据的重要字段和行为。通常,一个模型(model)映射到一个数据库表,
基本情况:
每个模型都是一个Python类,它是django.db.models.Model的子类。
模型的每个属性都代表一个数据库字段。
综上所述,Django为您提供了一个自动生成的数据库访问API,详询官方文档链接。
三、数据库配置
1、django默认支持sqlite,mysql, oracle,postgresql数据库。
<1> sqlite django默认使用sqlite的数据库,默认自带sqlite的数据库驱动 , 引擎名称:django.db.backends.sqlite3 <2> mysql 引擎名称:django.db.backends.mysql
2、mysql驱动程序
MySQLdb(mysql python)
mysqlclient
MySQL
PyMySQL(纯python的mysql驱动程序)
3、Django项目使用MySQL数据库
1. 在Django项目的settings.py文件中,配置数据库连接信息:
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'ops', 'USER': 'root', 'PASSWORD': '1qazXDR%', 'HOST': '127.0.0.1', 'PORT': '3306', } }
2. 在Django项目的__init__.py文件中写如下代码,告诉Django使用pymysql模块连接MySQL数据库:
import pymysql pymysql.install_as_MySQLdb()
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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() 问题解决!
四、表(模型)创建
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
我们来假定下面这些概念,字段和关系
作者模型:一个作者有姓名。
作者详细模型:把作者的详情放到详情表,包含性别,email地址和出生日期,作者详情模型和作者模型之间是一对一的关系(one-to-one)(类似于每个人和他的身份证之间的关系),在大多数情况下我们没有必要将他们拆分成两张表,这里只是引出一对一的概念。
出版商模型:出版商有名称,地址,所在城市,省,国家和网站。
书籍模型:书籍有书名和出版日期,一本书可能会有多个作者,一个作者也可以写多本书,所以作者和书籍的关系就是多对多的关联关系(many-to-many),一本书只应该由一个出版商出版,所以出版商和书籍是一对多关联关系(one-to-many),也被称作外键。
在models.py文件中定义自己的模型
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from django.db import models # Create your models here. class Publisher(models.Model): name = models.CharField(max_length=30, verbose_name="名称") address = models.CharField("地址", max_length=50) city = models.CharField('城市', max_length=60) state_province = models.CharField(max_length=30) country = models.CharField(max_length=50) website = models.URLField() class Meta: verbose_name = '出版商' verbose_name_plural = verbose_name def __str__(self): return self.name class Author(models.Model): name = models.CharField(max_length=30) def __str__(self): return self.name class AuthorDetail(models.Model): sex = models.BooleanField(max_length=1, choices=((0, '男'),(1, '女'),)) email = models.EmailField() address = models.CharField(max_length=50) birthday = models.DateField() #django 升级到2.0之后,表与表之间关联的时候,必须要写on_delete参数.外键、OneToOne字段等on_delete为必须参数. # 级联删除:models.CASCADE 当关联表中的数据删除时,该外键也删除 author = models.OneToOneField(Author,on_delete=models.CASCADE) #在数据库中对应的字段django会自动加上_id 如:author_id class Book(models.Model): title = models.CharField(max_length=100) authors = models.ManyToManyField(Author) #ManyToMany会在数据库中建立第三张表 publisher = models.ForeignKey(Publisher,on_delete=models.CASCADE) #在数据库中对应的字段django会自动加上_id 如:publisher_id publication_date = models.DateField() price=models.DecimalField(max_digits=5,decimal_places=2,default=10) def __str__(self): return self.title
同步数据到数据库
生成同步数据库的脚本
python manage.py makemigrations
同步数据库
python manage.py migrate
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
AutoField(Field):int自增列;必须填入参数primary_key=True BooleanField(Field):布尔值类型 CharField(Field):字符类型;必须提供max_length参数,max_length表示字符长度 TextField(Field):文本类型 EmailField(CharField):字符串类型;Django Admin以及ModelForm中提供验证机制 IPAddressField(Field):字符串类型;Django Admin以及ModelForm中提供验证 IPV4 机制 URLField(CharField):字符串类型;Django Admin以及ModelForm中提供验证 URL FilePathField(Field):字符串,Django Admin以及ModelForm中提供读取文件夹下文件的功能; 参数: path, 文件夹路径 match=None, 正则匹配 recursive=False, 递归下面的文件夹 allow_files=True, 允许文件 allow_folders=False, 允许文件夹 FileField(Field):字符串,路径保存在数据库,文件上传到指定目录; 参数: upload_to = "" 上传文件的保存路径 storage = None 存储组件,默认django.core.files.storage.FileSystemStorage ImageField(FileField):字符串,路径保存在数据库,文件上传到指定目录; 参数: upload_to = "" 上传文件的保存路径 storage = None 存储组件,默认django.core.files.storage.FileSystemStorage width_field=None, 上传图片的高度保存的数据库字段名(字符串) height_field=None 上传图片的宽度保存的数据库字段名(字符串) DateTimeField(DateField):日期+时间格式 YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ] DateField(DateTimeCheckMixin, Field):日期格式 YYYY-MM-DD TimeField(DateTimeCheckMixin, Field):时间格式 HH:MM[:ss[.uuuuuu 字段参数: null:表示这个字段可以为空 unique:表示该字段必须唯一 db_index:表示设置数据库索引 default:设置默认值 auto_now_add:创建数据记录的时候会把当前时间添加到数据库 auto_now:每次更新数据记录的时候会更新该字段
ForeignKey、ManyToManyField 和 OneToOneField的区别
item | 关系 | 例子 | 在数据库中的体现 |
ForeignKey | 一对多 | 每个用户可能有多个评论,但某一个评论中只对应到一个用户 | 建立个字段,保存对方的主键 |
ManyToManyField | 多对多 | 每个文章有多个作者,每个用户可以发表多个文章 | 建立一个表。维护两个model数据的关系 |
OneToOneField | 一对一 | 每个用户只能有一个“安全邮箱" | 建立个字段,保存对方的主键,并建立约束 |
4、1、表的操作(增删改查):
通过python manage.py shell进入交互式命令行
from blog.models import *
1、增(create , save)
create方式一: Author.objects.create(name='test') create方式二(推荐): #先定义字典 info={'sex':0,'email':'127.0.0.1@qq.com','address':'上海','author_id':1} AuthorDetail.objects.create(**info) save方式一: author=Author(name='aaaa') author.save() save方式二: author=Author() author.name="bbb" author.save()
增加一对多与多对多字段信息:
一对多(ForeignKey): #方式一: 由于绑定一对多的字段,比如author,存到数据库中的字段名叫author_id,所以我们可以直接给这个字段设定对应值: AuthorDetail.objects.create(**{'sex':0, 'email':'127.0.0.1@qq.com', 'address':'上海', 'author_id':1}) #这里的1是指为该AuthorDetail对象绑定了Author表中id=2的行对象 #方式二: 1、先获取要绑定的Author对象: obj=Author.objects.get(id=1) 2、将'author_id':1改为 'author':obj AuthorDetail.objects.create(**{'sex':0, 'email':'127.0.0.1@qq.com', 'address':'上海', 'author':obj}) 多对多(ManyToManyField()) 通过Book对象添加: author1=Author.objects.get(id=1) author2=Author.objects.filter(name='test')[0] book=Book.objects.get(id=1)
book.authors.add(author1,author2)等同于book.authors.add(*[author1,author2]) 删除book.authors.remove(*[author1,author2]) 通过Author对象添加: book=Book.objects.filter(id__gt=0) authors=Author.objects.filter(id=1)[0] authors.book_set.add(*book) authors.book_set.remove(*book) 注意: 如果第三张表是通过models.ManyToManyField()自动创建的,那么绑定关系只有上面一种方式 如果第三张表是自己创建的还可以通过create方式 例如: 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、删(delete)
Book.objects.filter(id=1).delete() (2, {'blog.Book_authors': 1, 'blog.Book': 1}) 我们表面上删除了一条信息,实际上其他表中和这本书有关的信息都会删除,这种删除方式就是django默认的级联删除 如果是多对多的关系: remove()和clear()方法: 正向 book = Book.objects.filter(id=1) book.author.clear() #清空与book中id=1 关联的所有数据 book.author.remove(2) #可以为id book.author.remove(*[1,2,3,4]) #可以为列表,前面加* 反向 author = Author.objects.filter(id=1) author.book_set.clear() #清空与book中id=1 关联的所有数据
3、改(update和save)
save方法: author=Author.objects.get(id=1) author.name='yang' author.save() update方法: author=Author.objects.filter(id=1).update(name='yqh')#不能使用get 或者 author=Author.objects.filter(id=1) author.update(name='yang') 注意: 1、第二种方式修改不能用get的原因是:update是QuerySet对象的方法,get返回的是一个model对象,它没有update方法,而filter返回的是一个QuerySet对象(filter里面的条件可能有多个条件符合,比如name='alvin',可能有两个name='alvin'的行数据)。 2、save()方法,这个方法会更新一行里的所有列,即使某些数据没改变,因此效率比较低。 而某些情况下,我们只需要更新行里的某几列。推荐使用update 如果是多对多的改: obj=Book.objects.filter(id=1)[0] author=Author.objects.filter(id__gt=2) obj.author.clear() obj.author.add(*author)
4、查(filter,value等)
查询操作返回值
返回QuerySet对象
all()
filter()
exclude()
order_by()
reverse()
distinct()
返回特殊QuerySet
values():返回一个可迭代的字典序列
values_list():返回一个可迭代的元祖序列
返回具体对象
get()
first()
last()
返回布尔值
exists()
返回数字
count()
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
查询相关API: <1>filter(**kwargs): 它包含了与所给筛选条件相匹配的对象 例子:Author.objects.filter(name='aaaa') <QuerySet [<Author: aaaa>]> <2>all(): 查询所有结果 例子:Author.objects.all() <QuerySet [<Author: yang>, <Author: test>, <Author: aaaa>, <Author: bbb>]> <3>get(**kwargs): 返回与所给筛选条件相匹配的对象,返回结果有且只有一个,如果符合筛选条件的对象超过一个或者没有都会抛出错误 例子:Author.objects.get(name='aaaa') <Author: aaaa> -----------下面的方法都是对查询的结果再进行处理:比如 objects.filter.values()-------- <4>values(*field): 返回一个ValueQuerySet——一个特殊的QuerySet,运行后得到的并不是一系列 model的实例化对象,而是一个可迭代的字典序列 例子:Book.objects.all().values('title') <QuerySet [{'title': 'shell'}, {'title': 'python'}]> <5>exclude(**kwargs): 它包含了与所给筛选条件不匹配的对象 例子:Book.objects.all().exclude(title='shell') <QuerySet [<Book: python>]> Book.objects.exclude(title='shell') <QuerySet [<Book: python>]> <6>order_by(*field): 对查询结果排序 <7>reverse(): 对查询结果反向排序 <8>distinct(): 从返回结果中剔除重复纪录 <9>values_list(*field): 它与values()非常相似,它返回的是一个元组序列,values返回的是一个字典序列 <10>count(): 返回数据库中匹配查询(QuerySet)的对象数量。 <11>first(): 返回第一条记录 <12>last(): 返回最后一条记录 <13>exists(): 如果QuerySet包含数据,就返回True,否则返回False。
对象查询,单表条件查询,多表条件关联查询
--------------------对象形式的查找-------------------------- 正向查找 obj=Book.objects.first() print(obj.title) #shell print(obj.price) #50.00 print(obj.publisher) #上海出版社 #通Book对象获取书对应的出版社的名字 print(obj.publisher.name)#上海出版社 注: #因为一对多的关系所以obj.publisher是一个对象,而不是一个queryset集合 反向查找 obj=Publisher.objects.last() print(obj.name)#北京出版社 print(obj.city) #北京 #获取与该出版社绑定的Book对象 print(obj.book_set.all()) #<QuerySet [<Book: python>]> #obj.book_set是一个queryset集合 print(obj.book_set.filter(title='python')) #<QuerySet [<Book: python>]>
了不起的双下划线(__)
---------------了不起的双下划线(__)之单表条件查询---------------- Book.objects.filter(id__lt=4,id__gt=0) # 获取id大于1 且 小于10的值 #<QuerySet [<Book: shell>, <Book: python>]> Book.objects.filter(id__in=[11, 22, 33]) # 获取id等于11、22、33的数据 Book.objects.exclude(id__in=[11, 22, 33]) # not in Book.objects.filter(title__contains="p") #title中包含'p' 区分大小写 Book.objects.filter(title__icontains="p") # icontains大小写不敏感 Book.objects.exclude(id__range=[1, 2]) # 范围bettwen and startswith,istartswith, endswith, iendswith,
----------------了不起的双下划线(__)之多表条件关联查询--------------- 正向查询按字段,反向查询按表明小写 正向查找(条件) #从含有外键表查询其外键对应的表的字段值 Book.objects.all().values('id') #<QuerySet [{'id': 2}, {'id': 4}, {'id': 3}]> #查询php这本书的id Book.objects.filter(title='php').values('id') #<QuerySet [{'id': 4}]> 正向查找(条件)之一对多 #查询书籍为php的出版社的名字 Book.objects.filter(title='php').values('publisher__name')#<QuerySet [{'publisher__name': '上海出版社'}]> 正向查找(条件)之多对多 #查询书籍为php的作者的姓名 Book.objects.filter(title='php').values('authors__name')#<QuerySet [{'authors__name': 'yang'}]> 注意: 正向查找的publisher__name或者authors__name中的publisher,authors是book表中绑定的字段。一对多和多对多在这里用法没区别 反向查找(条件)#从外键不在本表开始查询对应关系表的数据 反向查找之一对多: #查询上海出版社出版的书名 Publisher.objects.filter(name='上海出版社').values('book__title'))#<QuerySet [{'book__title': 'shell'}, {'book__title': 'php'}]> #根据Publisher查询书籍为Python的出版社的名字 Publisher.objects.filter(book__title='Python').values('name')#<QuerySet [{'name': '北京出版社'}]> Publisher.objects.filter(book__title='php').values('book__authors')#<QuerySet [{'book__authors': 1}]> 反向查找之多对多 #查询书籍为php作者的名字 Author.objects.filter(book__title='php').values('name')#<QuerySet [{'name': 'yang'}]> 注意: 反向查找的book__title中的book是表名Book,一对多和多对多在这里用法没区别
注意:条件查询即与对象查询对应,是指在filter,values等方法中的通过__来明确查询条件。
查询操作补充
----------------------------ForeignKey操作---------------------------- ------------------正向查找------------------------------ ---------对象查找(跨表)------- 语法: 对象.关联字段.字段 示例: book_obj = Book.objects.first() # 第一本书对象 print(book_obj.publisher) # 得到这本书关联的出版社对象 print(book_obj.publisher.name) # 得到出版社对象的名称 ------------字段查找(跨表)----------- 语法: 关联字段__字段 备注:双下划线就表示跨一张表。 示例: print(Book.objects.values_list("publisher__name")) print(Book.objects.values("publisher__name")) ------------------反向查找------------------------------ ---------对象查找------- 语法: obj.表名_set 示例: publisher_obj = Publisher.objects.first() # 找到第一个出版社对象 books = publisher_obj.book_set.all() # 找到第一个出版社出版的所有书 titles = books.values_list("title") # 找到第一个出版社出版的所有书的书名 ------------字段查找----------- 语法: 表名__字段 示例: titles = Publisher.objects.values_list("book__title") 备注: 1.外键查找时,不管是正向查找还是反向查找,在使用对象查找时,必须得是一个具体的对象(使用get、first、last),有什么属性就:对象.属性;在使用字段查找时,必须是一个QuerySet对象(使用filter),才能使用 .values()和.values_list()方法。 2.使用related_name='xx'和related_query_name='xx'时,related_name用于反向查询时代替 : 表名_set,而related_query_name用于反向双下划线跨表查询时,代替表名的。也用于,当反向查询的表是动态的(多个时),用来代替动态的表名。 3.使用外键创建一对多表关系时,外键通常设置在多的一边。 使用ManyToMany创建多对多表关系时,通常设置在正向查询多的那一边 4.使用基于对象查询时,内部的SQL语句是一个子查询形式的SQL语句,基于跨表的双下查询(基于queryset)时,内部的SQL语句是一个连表的查询(join ... on ...),所以对于查询效率来说,做好还是使用基于双下划线的查询,少用基于对象的查询。
聚合查询和分组查询
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
aggregate(*args,**kwargs) 说明:通过对QuerySet进行计算,返回一个聚合值的字典。aggregate()中每一个参数都指定一个包含在字典中的返回值。即在查询集上生成聚合。 从整个查询集生成统计值。比如,你想要计算所有在售书的平均价钱。Django的查询语法提供了一种方式描述所有 图书的集合。 from django.db.models import Avg,Min,Sum,Max Book.objects.all().aggregate(Avg('price')) #{'price__avg': 43.333333} aggregate()子句的参数描述了我们想要计算的聚合值,在这个例子中,是Book模型中price字段的平均值 aggregate()是QuerySet 的一个终止子句,意思是说,它返回一个包含一些键值对的字典。键的名称是聚合值的标识符,值是计算出来的聚合值。键的名称是按照字段和聚合函数的名称自动生成出来的。如果你想要为聚合值指定一个名称,可以向聚合子句提供它: Book.objects.aggregate(average_price=Avg('price')) #{'average_price': 43.333333} 如果查询所有图书价格的最大值和最小值,可以这样查询: Book.objects.aggregate(Avg('price'), Max('price'), Min('price')) {'price__avg': 43.333333, 'price__max': Decimal('50.00'), 'price__min': Decimal('40.00')}
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
annotate(*args,**kwargs) 说明:可以通过计算查询结果中每一个对象所关联的对象集合,从而得出总计值(也可以是平均值或总和),即为查询集的每一项生成聚合 查询yang出的书总价格 可以先看下yang出了哪些书 Book.objects.filter(authors__name='yang').values('title') #<QuerySet [{'title': 'python'}, {'title': 'php'}]> 查询书总价 Book.objects.filter(authors__name='yang').aggregate(Sum('price')) #{'price__sum': Decimal('80.00')} 查询各个作者出的书的总价格,这里就涉及到分组了,分组条件是authors__name 可以先查书对应的作者 Book.objects.values('authors__name') # <QuerySet [{'authors__name': 'yang'}, {'authors__name': 'yang'}, {'auth ors__name': 'test'}, {'authors__name': 'aaaa'}]> 查询各个作者出的书的总价格 Book.objects.values('authors__name').annotate(Sum('price')) #<QuerySet [{'authors__name': 'yang', 'price__sum': Decimal('80.00')}, { 'authors__name': 'test', 'price__sum': Decimal('40.00')}, {'authors__name': 'aaa a', 'price__sum': Decimal('50.00')}]> 查询各个出版社最便宜的书价是多少 Book.objects.values('publisher__name').annotate(Min('price')) #<QuerySet [{'publisher__name': '上海出版社', 'price__min': Decimal('40. 00')}, {'publisher__name': '北京出版社', 'price__min': Decimal('40.00')}]>
F查询和Q查询
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
F 可以获取对象中的字段的属性(列),并对其进行操作 from django.db.models import F #对图书馆里的每一本书的价格 上调1块钱 Book.objects.all().update(price=F('price')+1)
Q()可以使orm的fifter()方法支持, 多个查询条件,使用逻辑关系(&、|、~)包含、组合到一起进行多条件查询; 语法: fifter(Q(查询条件1)| Q(查询条件2)) fifter(Q(查询条件2)& Q(查询条件3)) fifter(Q(查询条件4)& ~Q(查询条件5)) fifter(Q(查询条件6)| Q(Q(查询条件4)& ~ Q(Q(查询条件5)& Q(查询条件3)))包含 注意:Q查询条件和非Q查询条件混合使用注意,不包Q()的查询条件一点要放在Q(查询条件)后面
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from django.db.models import Q 1、 Q对象(django.db.models.Q)可以对关键字参数进行封装,从而更好地应用多个查询 q1=Book.objects.filter(Q(title__startswith='p')).all() #<QuerySet [<Book: python>, <Book: php>]> 2、可以组合使用&,|操作符,当一个操作符是用于两个Q的对象,它产生一个新的Q对象。 Q(title__startswith='P') | Q(title__startswith='J') 3、Q对象可以用~操作符放在前面表示否定,也可允许否定与不否定形式的组合 Q(title__startswith='P') | ~Q(pub_date__year=2005) 4、Q对象可以与关键字参数查询一起使用,不过一定要把Q对象放在关键字参数查询的前面。 Book.objects.get( Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)), title__startswith='P')
QuerySet与惰性机制
所谓惰性机制:Publisher.objects.all()或者.filter()等都只是返回了一个QuerySet(查询结果集对象),它并不会马上执行sql,而是当调用QuerySet的时候才执行。 QuerySet特点: 1、可迭代的 2、可切片 3、惰性计算和缓存机制
五、admin简单使用
1、admin主要功能
1、提供数据库管理功能 基于admin模块,可以实现类似数据库客户端的功能,对数据进行增删改查 2、二次开发 基于该模块的数据管理功能,可以二次定制一些实用的功能。
2、注册medel类到admin的两种方式
1、 使用register的方法 admin.site.register(Book,MyAdmin) 2、使用register的装饰器 @admin.register(Book)
3、常用设置
list_display: 指定要显示的字段
list_per_page 指定每页显示多少条记录,默认是100条
search_fields: 指定搜索的字段
list_filter: 指定列表过滤器
ordering: 指定排序字段
4、使用示例
vim blog/admin.py
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from django.contrib import admin from blog.models import * # Register your models here. # @admin.register(Book)#----->单给某个表加一个定制 class MyAdmin(admin.ModelAdmin): list_display = ("title","price","publisher") search_fields = ("title","publisher__name") #publisher外键,直接取是取不到的会报错,应用跨表去取ForeignKey表里面的字段。publisher__name双下线来查Publisher表里面的具体字段内容。 list_filter = ("publisher",) ordering = ("price",) fieldsets = [ (None, {'fields': ['title']}), ('price information', {'fields': ['price', "publisher"], 'classes': ['collapse']}), ] admin.site.register(Book,MyAdmin) admin.site.register(Publisher) admin.site.register(Author)
浏览器中访问http://127.0.0.1:18080/admin