我们在使用Django框架开发web应用的过程中,不可避免地会涉及到数据的管理操作(如增、删、改、查),而一旦谈到数据的管理操作,就需要用到数据库管理软件,例如mysql、oracle、Microsoft SQL Server等。
如果应用程序需要操作数据(比如将用户注册信息永久存放起来),那么我们需要在应用程序中编写原生sql语句,然后使用pymysql模块远程操作mysql数据库
但是直接编写原生sql语句会存在两方面的问题,严重影响开发效率,如下:
为了解决上述问题,django引入了ORM的概念,ORM全称Object Relational Mapping,即对象关系映射,是在pymysq之上又进行了一层封装,对于数据的操作,我们无需再去编写原生sql,取代代之的是基于面向对象的思想去编写类、对象、调用相应的方法等,ORM会将其转换/映射成原生SQL然后交给pymysql执行
原生SQL与ORM的对应关系示例如下
如此,开发人员既不用再去考虑原生SQL的优化问题,也不用考虑数据库迁移的问题,ORM都帮我们做了优化且支持多种数据库,这极大地提升了我们的开发效率
自增列,可以将其理解为ID主键字段,注意的是必须填入参数 primary_key=True
当model中如果没有自增列,则自动会创建一个列名为id的列
id = models.AutoField(primary_key=True)
整形字段,围在 -2147483648 to 2147483647。(一般不用它来存手机号(位数也不够),直接用字符串存,)
num = models.IntegerField() # 可以不指定参数
字符字段,必须提供max_length参数, max_length表示字符长度。
title = models.CharField(max_length=255)
# 注意:Django中的CharField对应的MySQL数据库中的varchar类型
小数字段,必须提供max_digits参数和decimal_places参数
price = models.DecimalField(max_digits=8,decimal_places=2)
日期字段,日期格式 YYYY-MM-DD,相当于Python中的datetime.date()实例。
date = models.DateField(auto_now_add=True)
日期时间字段,格式 YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ],相当于Python中的datetime.datetime()实例。
给字段命名
用于表示某个字段可以为空。null=True
如果设置为unique=True 则该字段在此表中必须是唯一的 。
如果db_index=True 则代表着为此字段设置索引
为该字段设置默认值
配置auto_now_add=True,创建数据记录的时候会把当前时间添加到数据库。
配置上auto_now=True,每次更新数据记录的时候会更新该字段。
外键类型在ORM中用来表示外键关联关系,一般把ForeignKey字段设置在 '一对多'中'多'的一方。
ForeignKey可以和其他表做关联关系同时也可以和自身做关联关系。
参数
to 设置要关联的表
publish = models.ForeignKey(to='Publish')
# 默认是跟publish表的主键字段做的一对多外键关联
to_field
设置要关联的表的字段
on_delete
当删除关联表中的数据时,当前表与其关联的行的行为。
通常一对一字段用来扩展已有字段。(通俗的说就是一个人的所有信息不是放在一张表里面的,简单的信息一张表,隐私的信息另一张表,之间通过一对一外键关联)
ps:可以用 ForeignKey(unique=True) 代替
参数
to 设置要关联的表。
author_detail = models.OneToOneField(to='AuthorDetail')
to_field
设置要关联的字段。
on_delete
当删除关联表中的数据时,当前表与其关联的行的行为。
多对多字段
是虚拟字段,表中不会显式,
作用:
1.告诉orm自动创建第三种表
2.帮助orm跨表查询
authors = models.ManyToManyField(to='Author')
class Employee(models.Model): # 必须是models.Model的子类
id=models.AutoField(primary_key=True)
name=models.CharField(max_length=16)
gender=models.BooleanField(default=1)
birth=models.DateField()
department=models.CharField(max_length=30)
salary=models.DecimalField(max_digits=10,decimal_places=1)
django的orm支持多种数据库,如果想将上述模型转为mysql数据库中的表,需要settings.py中配置
# 删除\注释掉原来的DATABASES配置项,新增下述配置
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql', # 使用mysql数据库
'NAME': 'db1', # 要连接的数据库
'USER': 'root', # 链接数据库的用于名
'PASSWORD': '', # 链接数据库的用于名
'HOST': '127.0.0.1', # mysql服务监听的ip
'PORT': 3306, # mysql服务监听的端口
'CHARSET':'utf8',
'ATOMIC_REQUEST': True, #设置为True代表同一个http请求所对应的所有sql都放在一个事务中执行
#(要么所有都成功,要么所有都失败),这是全局性的配置,如果要对某个
#http请求放水(然后自定义事务),可以用non_atomic_requests修饰器
'OPTIONS': {
"init_command": "SET storage_engine=INNODB", #设置创建表的存储引擎为INNODB
}
}
}
在链接mysql数据库前,必须事先创建好数据库
mysql> create database db1; # 数据库名必须与settings.py中指定的名字对应上
其实python解释器在运行django程序时,django的orm底层操作数据库的python模块默认是mysqldb而非pymysql,然而对于解释器而言,python2.x解释器支持的操作数据库的模块是mysqldb,而python3.x解释器支持的操作数据库的模块则是pymysql,,毫无疑问,目前我们的django程序都是运行于python3.x解释器下,于是我们需要修改django的orm默认操作数据库的模块为pymysql,具体做法如下
确保配置文件settings.py中的INSTALLED_APPS中添加我们创建的app名称,django2.x与django1.x处理添加方式不同
# django1.x版本,在下述列表中新增我们的app名字即可
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'app01',
# 'app02' # 若有新增的app,依次添加即可
]
# django2.x版本,可能会帮我们自动添加app,只是换了一种添加方式
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'app01.apps.App01Config', # 如果默认已经添加了,则无需重复添加
# 'app02.apps.App02Config', # 若有新增的app,按照规律依次添加即可
]
如果想打印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',
},
}
}
# 可以看到sql语句,以及语句执行的之间
# 其他方式:
1.单独查看某个orm转化的sql语句,可以用以下方法
如: 在视图中的orm语句为:
user_obj = User.objects.get(username=username)
print(user_obj.query)
2.查看某个视图内orm转化的sql语句,可以用以下方法
from django.db import connection
def myviews(request)
...
代码块
...
print(connection.queries)
return HttpResponse('...')
说明: print(connection.queries)会把它所在位置之前的sql语句都打印出来(包括sql执行时间)
最后在命令行中执行两条数据库迁移命令,即可在指定的数据库db1中创建表 :
python manage.py makemigrations
python manage.py migrate
注意:
1、makemigrations只是生成一个数据库迁移记录的文件,而migrate才是将更改真正提交到数据库执行
2、数据库迁移记录的文件存放于app01下的migrations文件夹里
3、了解:使用命令python manage.py showmigrations可以查看没有执行migrate的文件
补充:
在使用的是django1.x版本时,如果报如下错误
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\base.py
这个路径里的文件(mac或linux请在终端执行pip show django查看django的安装路径,找到base.py即可)
# 注释下述两行内容即可
if version < (1, 3, 3):
raise ImproperlyConfigured("mysqlclient 1.3.3 or newer is required; you have %s" % Database.__version__)
当我们直接去数据库里查看生成的表时,会发现数据库中的表与orm规定的并不一致,这完全是正常的,事实上,orm的字段约束就是不会全部体现在数据库的表中,比如我们为字段gender设置的默认值default=1,去数据库中查看会发现该字段的default部分为null
mysql> desc app01_employee; # 数据库中标签前会带有前缀app01_
+------------+---------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+---------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(16) | NO | | NULL | |
| gender | tinyint(1) | NO | | NULL | |
| birth | date | NO | | NULL | |
| department | varchar(30) | NO | | NULL | |
| salary | decimal(10,1) | NO | | NULL | |
+------------+---------------+------+-----+---------+----------------+
虽然数据库没有增加默认值,但是我们在使用orm插入值时,完全为gender字段插入空,orm会按照自己的约束将空转换成默认值后,再提交给数据库执行
补充:
1.对于类型性别,假否,婚否的字段,有固定字段数据的字段可以用choices参数
2.节省空间,因为存的是数字
# models.py文件中:
class User(models.Model):
username = models.CharField(max_length=32)
age = models.IntegerField()
choices = (
(1,'男'),(2,'女'),(3,'其他')
)
gender = models.IntegerField(choices=choices) # 指定choices参数
"""
1 存choice里面罗列的数字与中文对应关系
print(user_obj.get_gender_display())
只要是choices字段 在获取数字对应的注释 固定语法
get_字段名_display()
2 存没有罗列出来的数字
不会报错 还是展示数字
"""
# views.py文件中
def userlist(request):
user_obj = models.User.objects.filter(pk=1).first()
gender = user_obj.get_gender_display() # 得到对应的性别注释,如'男'
class MyChar(models.Field):
def __init__(self,max_length,*args,**kwargs):
self.max_length = max_length
super().__init__(max_length=max_length,*args,**kwargs)
def db_type(self, connection):
return 'char(%s)'%self.max_length
orm内所有的语句操作 都是惰性查询:只会在你真正需要数据的时候才会走数据库,如果你单单只写orm语句时不会走数据库的
这样设计的好处 在于 减轻数据库的压力
当你点击一个是only括号内指定的字段的时候,只走一次数据库查询,
当你点击一个不是only括号内指定的字段的时候不会报错,而是会频繁的走数据库查询
res = models.Book.objects.only('title')
for r in res:
print(r.price)
defer与only是相反的,defer会将不是括号内的所有的字段信息 全部查询出来封装对象中
当你点击了括号内指定的字段,就会频繁的周数据库
当你点击了不是括号内指定的字段,不再走数据库
res1 = models.Book.objects.defer('title')
for r in res1:
print(r.price) # 不在走数据库
print(r.title) # 将频繁走数据
select_related:会将括号内外键字段所关联的那张表 直接全部拿过来(可以一次性拿多张表)跟当前表拼接操作
从而降低你跨表查询 数据库的压力
注意:select_related括号只能放外键字段(一对一表关系和一对多表关系可以使用)
res = models.Book.objects.all().select_related('publish')
for r in res:
print(r.publish.name)
res = res = models.Book.objects.all().select_related('外键字段1__外键字段2__外键字段3__外键字段4') # 只要有外键就可以一直连表
特点: 不主动连表
res = models.Book.objects.prefetch_related('publish')
for r in res:
print(r.publish.name)
"""
不主动连表操作(但是内部给你的感觉像是连表操作了) 而是将book表中的publish全部拿出来 在取publish表中将id对应的所有的数据取出
res = models.Book.objects.prefetch_related('publish')
括号内有几个外键字段 就会走几次数据库查询操作
"""
from django.db import transaction
with transaction.atomic():
"""
数据库操作
在该代码块中书写的操作 同属于一个事务
"""
models.Book.objects.create()
models.Publish.objects.create()
# 添加书籍和出版社 就是同一个事务 要么一起成功要么一起失败
print('出了 代码块 事务就结束')
import os
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "day58.settings")
import django
django.setup()
from app01 import models # 这一句话必须在这下面导入