小谈Django中的ORM
ORM
概述
ORM : 对象关系映射
对应关系
- 类 --> 表
- 对象 --> 数据行
- 属性 --> 字段
优点与缺点
- 优点 : 同样的语句可以对应不同的数据库 ;
- 缺点 : 实际运行时Python代码需要转化为数据库语言 , 影响效率 ;
Django中使用Mysql数据库
settings.py中的数据库配置
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.数据库类型',
'NAME': '创建的数据库名称',
'USER': '数据库用户名',
'PASSWORD': '密码',
'HOST': '数据库IP',
'PORT': 数据库端口,
}
}
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'bookmanager',
'USER': 'root',
'PASSWORD': '3232',
'HOST': '127.0.0.1',
'PORT': 3306,
}
}
在Django项目名目录下的__init__.py
(与settins.py同级的)
import pymysql
pymsql.install_as_MySQLdb()
models.py
字段
AutoField
自增字段
DatetimeField
- auto_now_add=True 新增数据时,填写上当前的时间 ;
- auto_now=True 新增或者修改数据时,都会填写当前的时间 ;
- auto_now_add , auto_now 和 default 三个参数是互斥的 , 不能同时设置 ;
BooleanField
# choices=[(数据库中存储的内容1, form组件显示的名字1),(数据库中存储的内容2, form组件显示的名字2)]
# choices最外面的[],用()也可以
sex = models.BooleanField(choices=[(True,'男'),(False,'女')])
sex = models.CharField(choices=(('male','男'),('female','女'),('buxiang','未知')))
TextField
文本类型 : 大字符串
ImageField
上传图片 , 在数据库中只存储路径
DecimalField
十进制小数
from django.db import models
class Person(models.Model):
# 主键 : primary_key=True : 如果没有这个字段会自动生成,而且这个字段只可以有一个
id = models.AutoField(primary_key=True)
name = models.CharField()
age = models.IntegerField() # -21亿 ~ 21亿 十位数,不能存储手机号,手机号可以用CharField
sex = models.BooleanField()
# auto_now_add=True 新增数据时,填写上当前的时间;
# auto_now=True 新增或者修改数据时,都会填写当前的时间;
# 在数据库中操作时还是需要输入的,因为'自动填写当前时间'指的是Django而不是数据库;如果用models.Person.objects.create(name=xxx,age=xxx)就可以了
birth = models.DatetimeField(auto_now_add=True)
自定义字段
下面以自定义一个char类型字段为例 :
- 如果把长度设置为11 , 可以用来存储手机号 , 向数据库中存储时可以存储小于11位的 , 但是大于11位的无法存储 ;
class MyCharField(models.Field):
"""
自定义的char类型的字段类
"""
def __init__(self, max_length, *args, **kwargs):
self.max_length = max_length
super(MyCharField, self).__init__(max_length=max_length, *args, **kwargs)
def db_type(self, connection):
"""
限定生成数据库表的字段类型为char,长度为max_length指定的值
"""
return 'char(%s)' % self.max_length
字段参数
null=True
允许数据库中该字段可以为空
blank=True
Django中提供的form组件允许用户输入可以为空 ; Admin中允许用户输入为空( 因为Admin使用了form组件 )
verbose_name
在form组件中( admin页面中 )显示的字段名字 ; 在CharField字段中指定时 , 可以写在第一参数位置 , 这样可以不写verbose_name , 直接写内容 , 因为在CharField再上一层的class CharField中的双下init中指定的第一参数就是verbose_name ; 但是其他字段的第一参数可能不是verbose_name
db_column
在数据库中显示的字段名字 ;
name = models.CharField(max_length=32, db_column='username')
# 只是数据库显示为username,实际在Django项目中写代码还是用name
default
默认值
primary_key
主键
db_index=True
创建索引 , 在数据库中查询时速度变快
unique
唯一约束
choices
让用户选择的数据
# choices=[(数据库中存储的内容1, form组件显示的名字1),(数据库中存储的内容2, form组件显示的名字2)]
# choices最外面的[],用()也可以
sex = models.BooleanField(choices=[(True,'男'),(False,'女')])
sex = models.CharField(choices=(('male','男'),('female','女'),('buxiang','未知')))
表参数
在models.py的类中再加上一个类 class Meta对数据库中的表进行一些其他配置
class Person(models.Model)
...
class Meta:
# 表在数据库中的名字,默认是add名称+下划线+类名小写
db_table = 'table_name'
# admin中显示的表名称
verbose_name = '个人信息'
# verbose_name加s
verbose_name_plural = '所有用户信息'
# 联合索引:在数据库中查询速度会变快,只查name变快,查name+age也变快,但是只查age不会变快 --> 数据库中的遵循最左前缀
index_together = [
("name", "age"), # 应为两个存在的字段
]
# 联合唯一索引:在联合索引的基础上再加上唯一约束,两个字段不能同时重复
unique_together = (
('name', 'age'), # 应为两个存在的字段
)
Admin的使用
1. 创建超级用户
python manage.py createsuperuser
# 1.提示输入Username,如果输入为空就用计算机用户的名字
# 2.提示输入Email address:可以不写,直接回车
# 3.提示输入Password:不能纯数字也不能纯英文,也不显示
# 然后就可以用浏览器打开ip:端口号/admin
2. 注册model
在app下的admin.py中输入
from django.contrib import admin
from app名字 import models
# Register your models here.
admin.site.register(models.字段名)
3. 访问网站登录操作
有些字段参数涉及到在form组件中的显示效果 , 在admin中也有效果 , 是因为admin使用了Django的form组件
必知必会13条
在Django中新建的脚本需要使用Django环境
# 脚本中
# 前两句来自于manage.py
import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "项目名.settings")
# 后两句: Django初始化
import django
django.setup()
__str__
和__repr__
model里的对象直接print的内容 : 先查找这个类的双下str方法 , 没有就去父类找双下str ; 再没有的话就找双下repr方法 , 再没有就去父类找双下repr方法 ;
model里的对象列表直接print的内容 : 先查找这个类的双下repr方法 , 没有就去父类找双下repr ; 再没有的话就找双下str方法 , 再没有就去父类找双下str方法 ;
# models.py
class Person:
...
def __str__(self):
return self.name
# 脚本中
print(models.Person.objects.all()) # 如果这个想输出<QuerySet [bigbao]>;就在model中加上 __repr__ = __str__ 或者重新定义def __repr__(self)就可以了;
必知必会13条
import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "about_orm.settings")
import django
django.setup() # 初始化
from app01 import models
# all() 查询所有的数据 QuerySet 对象列表
ret = models.Person.objects.all()
# filter 获取满足条件的所有的对象 QuerySet 对象列表
ret = models.Person.objects.filter(name='alex')
# exclude 获取不满足条件的所有的对象 QuerySet 对象列表
ret = models.Person.objects.exclude(name='alex')
# values()
# 不指定字段 获取所有字段名和值 QuerySet 对象列表 [ {},{} ]
# 指定字段 values('pid','name') 获取指定字段名和值 QuerySet 对象列表 [ {},{} ]
ret = models.Person.objects.all().values()
ret = models.Person.objects.filter(name='alex').values('pid', 'name')
# values_list()
# 不指定字段 获取所有的值 QuerySet 对象列表 [ (),() ]
# 指定字段 values_list('pid','name') 获取指定的值 QuerySet 对象列表 [ (),() ]
ret = models.Person.objects.all().values_list()
ret = models.Person.objects.filter(name='alex').values_list('name', 'pid', )
# order_by 排序 默认升序 -降序 可以多个字段排序
ret = models.Person.objects.all().order_by('age', '-pid')
# reverse 对已经排序的QuerySet做的反转
ret = models.Person.objects.all().order_by('pid').reverse()
# get 获取满足条件的一个的对象 对象
ret = models.Person.objects.get(name='alex')
# first 获取第一个元素 对象 获取不到的时候是none
ret = models.Person.objects.filter(name='xxx').first()
# last 获取最后一个元素 对象 获取不到的时候是none
ret = models.Person.objects.filter(name='xxx').last()
# count 计数
ret = models.Person.objects.all().filter(age=84).count()
# exists 数据是否存在
ret = models.Person.objects.filter(age=84).exists()
# distinct 去重 数据时完全一样才去重
ret = models.Person.objects.filter(age=84).values('age').distinct()
单表的双下划线
import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "about_orm.settings")
import django
django.setup() # 初始化
from app01 import models
ret = models.Person.objects.filter(pk__gt=3) # greater than where pk > 3
ret = models.Person.objects.filter(pk__gte=3) # greater than equal where pk >= 3
ret = models.Person.objects.filter(pk__lt=3) # less than where pk < 3
ret = models.Person.objects.filter(pk__lte=3) # less than equal where pk <= 3
ret = models.Person.objects.filter(pk__range=[1,3]) # 1 <= pk <= 3
ret = models.Person.objects.filter(pk__in=[1,3,7,10,100]) # 成员判断
ret = models.Person.objects.filter(name__contains='bigbao') # like 不忽略大小写
ret = models.Person.objects.filter(name__icontains='bigbao') # like ignore 忽略大小写
ret = models.Person.objects.filter(name__startswith='b') # 以什么开头 不忽略大小写
ret = models.Person.objects.filter(name__istartswith='b') # 以什么开头 忽略大小写
ret = models.Person.objects.filter(name__endswith='o') # 以什么结尾 不忽略大小写
ret = models.Person.objects.filter(name__iendswith='o') # 以什么结尾 忽略大小写
ret = models.Person.objects.filter(age__isnull=False) # 字段是否为空
ret = models.Person.objects.filter(birth__year='2019') # 按照年份
ret = models.Person.objects.filter(birth__contains='2019-12-19') # 时间包含
print(ret)
外键的操作
# models.py
class Publisher(models.Model):
name = models.CharField(max_length=32)
def __str__(self):
return self.name
class Book(models.Model):
name = models.CharField(max_length=32)
# related_name("有关联的名字"),related_name就是让Publisher反向查询时要用的名字,去查一个出版社关联所有它出版的书的对象,所以是books
pub = models.ForeignFey(Publisher, on_delete=models.CASCADE, related_name='books')
def __str__(self):
return self.name
# 脚本中
import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "项目名.settings")
import django
django.setup()
from app01 import models
# 一, 基于对象的查询
# 1. 正向查询(拿到书的对象后,直接点外键拿到出版社对象)
# 1.1 直接通过书的类查询书的对象
book_obj = models.Book.objects.get(pk=3)
# 1.2 通过书的对象查询出版社对象
print(book_obj.pub)
# 2. 反向查询(拿到出版社对象后,再去拿出版社出的多有的书)
# 2.1 直接通过出版社的类查询出版社的对象
pub_obj = models.Publisher.objects.get(pk=1)
# 2.2 通过出版社的对象去拿该出版社出版的所有书
# 2.2.1 没有添加related_name时可以这样操作(外键所在表的小写_set),加上related_name后就不能用这个了,因为related_name已经替换了book_set
print(pub_obj.book_set) # 关系管理对象 book_set相当于related_name
print(pub_obj.book_set.all()) # 拿到所有关联的对象
# 2.2.2 添加related_name后(直接点related_name),直接点related_name就好;因为related_name相当于book_set
print(pub_obj.books.all())
# 二, 基于字段的查询
# 1. 正向查询,从Book中查询一个出版社出版的所有书籍
models.Book.objects.filter(pub__name='清华大学出版社')
# 2. 反向查询
# 2.1 没有related_name的情况:类名小写__其他字段;从Publisher中查询书籍所属出版社
models.Publisher.objects.filter(book__name='Python从入门到放弃')
# 2.2 有related_name的情况:related_name小写__其他字段;从Publisher中查询书籍所属出版社
models.Publisher.objects.filter(books__name='Python从入门到放弃')
多对多的操作
# models.py
class Publisher(models.Model):
name = models.CharField(max_length=32)
def __str__(self):
return self.name
class Book(models.Model):
name = models.CharField(max_length=32)
pub = models.ForeignFey(Publisher, on_delete=models.CASCADE, related_name='books')
def __str__(self):
return self.name
class Author(models.Model):
name = models.CharField(max_length=32)
books = models.ManyToManyField(Book)
def __str__(self):
return self.name
# 脚本中
import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "项目名.settings")
import django
django.setup()
from app01 import models
# 一, 基于对象查询
author_obj = models.Author.objects.get(pk=2)
# 1. 正向查询:这个作者写了几本书
print(author_obj.all())
# 2. 反向查询:书的作者是谁
book_obj = models.Book.objects.get(pk=1)
print(book_obj.author_set.all())
# 二, 基于字段查询
# 1. 正向查询
models.Author.objects.filter(books__name='Python从入门到放弃')
# 2. 反向查询
models.Books.objects.filter(author__name='bigbao')
# 三, set([pk1/对象1, pk2/对象2,...]) 设置多对多关系:类似于新增或者重新设置的意思,设置完成后,以前的多对多关系就取消了
# 3.1
author_obj.books.set([3,]) # 列表中放入 要和作者的对象有多对多关系的 书的pk即可;这里是设置为只有pk=3的书是这个作者写的
# 3.2
author_obj.books.set(models.Book.object.all()) # 所有的书都设置为是这个作者写的;set(是一个QuerySet,QuerySet就是一个列表,列表里面都是对象)
# 四, add(pk1/对象1, pk2/对象2,...) 新增多对多关系
author_obj.books.add(1, 2)
author_obj.books.add(对象1, 对象2)
author_obj.books.add(*models.Book.object.all()) # 不能是列表,所以是列表时要打散
# 五, remove(pk1/对象1, pk2/对象2,...) 删除多对多关系
author_obj.books.remove(1, 2)
author_obj.books.remove(对象1, 对象2)
author_obj.books.remove(*models.Book.object.filter(pk__in=[1,2])) # 不能是列表,所以是列表时要打散
# 六, clear() 清空多对多关系,相当于删除所有多对多关系
author_obj.books.clear()
# 七, create() 新增
# 新增一本书并与作者设置多对多关系: 相当于两步操作(先新增一本书,再设置多对多关系)
author_obj.books.create(name='爬虫从入门到入狱', pub_id=1)
# 八, 反之亦然
# 上面那些操作都有
book_obj.author_set.set()
book_obj.author_set.create()
# 九, 一对多反向查询时只能选择对象,pk不起作用;还有clear时,是清空关系而不是清空数据库中的数据,所以一对多时用clear()必须把外键加上null=True才可以,要不然关系清空了,外键所在的表少了一个字段,无法写入数据库