django【第五篇】django orm
why
设计orm就是为了
1.简化操作数据的语法(原生sql语句真的有点复杂)
2.不同水平的人写的sql语句执行效率天差地别。
orm与表的关系
ORM的对应关系:
类 ---> 数据表
对象 ---> 数据行
属性 ---> 字段
ORM详细步骤
settings默认使用的数据库是sqlite3,其实orm一套语法可以翻译成很多种数据库的sql语句,你用什么数据库都是一套orm语法(方便吧)
我这里就使用生产中常规的mysql
1. 自己动手创建数据库 create database 数据库名; 2. 在Django项目中设置连接数据库的相关配置(告诉Django连接哪一个数据库) # 数据库相关的配置 DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', # 连接的数据库类型 'HOST': '127.0.0.1', # 连接数据库的地址 'PORT': 3306, # 端口 'NAME': "mingren01", # 数据库名称 'USER': 'root', # 用户 'PASSWORD': 'root' # 密码 } } 3. 告诉Django用pymysql代替默认的MySQLDB 连接MySQL数据库 在项目/__init__.py文件中,写下面两句: import pymysql # 告诉Django用pymysql来代替默认的MySQLdb pymysql.install_as_MySQLdb() 4. 在app下面的models.py文件中定义一个类,这个类必须继承models.Model class 类名(models.Model): ... 5. 执行两个命令 1. python3 manage.py makemigrations 2. python3 manage.py migrate
默认生成表
同步model与数据库
新增表
makemigrations
可以看到,migrations目录下多了一个py文件
migrate
mysql中生成一张表(每次migrate真正调用的的是最新的makemigrations目录下的脚本)
每次migrate后,django_migrations表会新增一条记录(用于记录每次migrate操作:app 名 、本次调用的脚本名、执行时间)
每次migrate前,都会先检测django_migrations表是否执行过makemigrations目录下的当前脚本,未执行过则执行
django就是通过makemigrations把类生成orm语言,migrate是去把orm语句翻译成sql语句
删除表
注释关联的model
然后
makemigrations migrate
修改表
makemigrations migrate
补充
不要随意修改:
1.migrations目录下的orm文件,
2.django_migrations表下的数据记录
因为每次migrate(数据库同步操作),是根据migrations目录下的文件在django_migrations表是否有记录而去决定是否执行migrations目录下的文件
3.数据库只能改表数据,不要动表结构
pycharm数据库界面管理工具
其实pycharm还集成了很多工具,这样你在做python开发的时候,就不需要打开一堆软件了
手动增加一条记录,然后查询
后台管理系统
模板可以从一些模板网站获取(如http://metronic.kp7.cn/ https://v3.bootcss.com/getting-started/#examples),然后修改下
目的:做这个功能,只是为了让大家熟悉orm,其实django自带的admin和更炫酷的xadmin比这个功能更强大,后台管理系统比数据库工具(如navicat)更方便
后台管理系统之单表操作
在app01目录下的models.py新增如下
from django.db import models # Create your models here. # 图书管理系统, 书 作者 出版社 # 出版社 class Publisher(models.Model): id = models.AutoField(primary_key=True) # 自增的ID主键 # 创建一个varchar(64)的唯一的不为空的字段 name = models.CharField(max_length=64, null=False, unique=True) addr = models.CharField(max_length=128) def __str__(self): return "<Publisher Object: {}>".format(self.name) # 书 class Book(models.Model): id = models.AutoField(primary_key=True) # 自增的ID主键 # 创建一个varchar(64)的唯一的不为空的字段 title = models.CharField(max_length=64, null=False, unique=True) # 和出版社关联的外键字段 publisher = models.ForeignKey(to="Publisher") def __str__(self): return "<Book Object: {}>".format(self.title) # 作者表 class Author(models.Model): id = models.AutoField(primary_key=True) name = models.CharField(max_length=16, null=False, unique=True) # 告诉ORM 我这张表和book表是多对多的关联关系,ORM自动帮我生成了第三张表 book = models.ManyToManyField(to="Book") def __str__(self): return "<Author Object: {}>".format(self.name)
makemigrations migrate
可以看到,新建了三个类(从前面的介绍可知,一个类对应一张表)
我们发现,多对多外键关系会多出来一张表,这张表存的是author和book表两条相关记录的id
出版社列表页面
添加数据
<!DOCTYPE html> <!-- saved from url=(0042)https://v3.bootcss.com/examples/dashboard/ --> <html lang="zh-CN"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! --> <meta name="description" content=""> <meta name="author" content=""> <link rel="icon" href="https://v3.bootcss.com/favicon.ico"> <title>Dashboard</title> <!-- Bootstrap core CSS --> <link href="/static/bootstrap/css/bootstrap.min.css" rel="stylesheet"> <!-- Custom styles for this template --> <link href="/static/dashboard.css" rel="stylesheet"> <link rel="stylesheet" href="/static/fontawesome/css/font-awesome.min.css"> </head> <body> <nav class="navbar navbar-inverse navbar-fixed-top"> <div class="container-fluid"> <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="https://v3.bootcss.com/examples/dashboard/#">BMS-S10</a> </div> <div id="navbar" class="navbar-collapse collapse"> <ul class="nav navbar-nav navbar-right"> <li><a href="https://v3.bootcss.com/examples/dashboard/#">Dashboard</a></li> <li><a href="https://v3.bootcss.com/examples/dashboard/#">Settings</a></li> <li><a href="https://v3.bootcss.com/examples/dashboard/#">Profile</a></li> <li><a href="https://v3.bootcss.com/examples/dashboard/#">Help</a></li> </ul> <form class="navbar-form navbar-right"> <input type="text" class="form-control" placeholder="Search..."> </form> </div> </div> </nav> <div class="container-fluid"> <div class="row"> <div class="col-sm-3 col-md-2 sidebar"> <ul class="nav nav-sidebar"> <li class="active"><a href="/publisher_list/">出版社列表页</a></li> <li><a href="/book_list/">书籍列表</a></li> <li><a href="/author_list/">作者列表</a></li> </ul> </div> <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main"> <h1 class="page-header">出版社管理页面</h1> <div class="panel panel-primary"> <!-- Default panel contents --> <div class="panel-heading">出版社列表 <i class="fa fa-thumb-tack pull-right"></i></div> <div class="panel-body"> <div class="row" style="margin-bottom: 15px"> <div class="col-md-4"> <div class="input-group"> <input type="text" class="form-control" placeholder="Search for..."> <span class="input-group-btn"> <button class="btn btn-default" type="button">搜索</button> </span> </div><!-- /input-group --> </div><!-- /.col-md-4 --> <div class="col-md-1 pull-right"> <button class="btn btn-success" data-toggle="modal" data-target="#myModal">新增</button> </div> </div><!-- /.row --> <table class="table table-bordered"> <thead> <tr> <th>#</th> <th>id</th> <th>出版社名称</th> <th>操作</th> </tr> </thead> <tbody> {% for publisher in publisher_list %} <tr> <td>{{ forloop.counter }}</td> <td>{{ publisher.id }}</td> <td>{{ publisher.name }}</td> <td> <a class="btn btn-danger" href="/delete_publisher/?id={{ publisher.id }}">删除</a> <a class="btn btn-info" href="/edit_publisher/?id={{ publisher.id }}">编辑</a> </td> </tr> {% endfor %} </tbody> </table> <nav aria-label="Page navigation" class="text-right"> <ul class="pagination"> <li> <a href="#" aria-label="Previous"> <span aria-hidden="true">«</span> </a> </li> <li><a href="#">1</a></li> <li><a href="#">2</a></li> <li><a href="#">3</a></li> <li><a href="#">4</a></li> <li><a href="#">5</a></li> <li> <a href="#" aria-label="Next"> <span aria-hidden="true">»</span> </a> </li> </ul> </nav> </div> </div> </div> </div> </div> <div class="modal fade" tabindex="-1" role="dialog" id="myModal"> <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span> </button> <h4 class="modal-title">用户信息</h4> </div> <div class="modal-body"> <form class="form-horizontal"> <div class="form-group"> <label for="inputEmail3" class="col-sm-2 control-label">邮箱</label> <div class="col-sm-10"> <input type="email" class="form-control" id="inputEmail3" placeholder="Email"> </div> </div> <div class="form-group"> <label for="inputPassword3" class="col-sm-2 control-label">密码</label> <div class="col-sm-10"> <input type="password" class="form-control" id="inputPassword3" placeholder="Password"> </div> </div> </form> </div> <div class="modal-footer"> <button type="button" class="btn btn-default" data-dismiss="modal">取消</button> <button type="button" class="btn btn-primary">保存</button> </div> </div><!-- /.modal-content --> </div><!-- /.modal-dialog --> </div><!-- /.modal --> <!-- Bootstrap core JavaScript ================================================== --> <!-- Placed at the end of the document so the pages load faster --> <script src="/static/jquery-3.3.1.js"></script> <script src="/static/bootstrap/js/bootstrap.min.js"></script> </body> </html>
前端核心代码
配置url/view
效果图
出版社删除页面
前端核心代码
配置url/view
出版社编辑页面
前端核心代码
配置url/view
编辑页面
这里编辑在一个新页面
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>编辑出版社</title> </head> <body> <h1>编辑出版社</h1> <form action="/edit_publisher/" method="post"> <input type="text" name="id" value="{{ publisher.id }}" style="display: none"> <input type="text" name="publisher_name" value="{{ publisher.name }}"> <input type="submit" value="提交"> <p style="color: red">{{ error }}</p> </form> </body> </html>
效果图
点击编辑后跳到此页面
修改
提交后自动跳到列表页
出版社添加页面
这里通过orm添加数据(也可以去编辑数据库,如果是单表或者数据简单,这两种方式都没有问题,如果复杂就得通过orm或者后台管理系统了)
前端核心代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>添加出版社</title> </head> <body> <h1>添加出版社</h1> <form action="/add_publisher/" method="post"> {% csrf_token %} <input type="text" name="publisher_name"> <input type="submit" value="提交"> <p style="color: red">{{ error }}</p> </form> </body> </html>
配置url/view
效果图
后台管理系统之跨表操作
后端平时做跨表操作的时候也很多
跨表操作前,先回顾一下
业务是这样的
于是我们设计了三张表
from django.db import models # Create your models here. # 图书管理系统, 书 作者 出版社 # 出版社 class Publisher(models.Model): id = models.AutoField(primary_key=True) # 自增的ID主键 # 创建一个varchar(64)的唯一的不为空的字段 name = models.CharField(max_length=64, null=False, unique=True) addr = models.CharField(max_length=128) def __str__(self): return "<Publisher Object: {}>".format(self.name) # 书 class Book(models.Model): id = models.AutoField(primary_key=True) # 自增的ID主键 # 创建一个varchar(64)的唯一的不为空的字段 title = models.CharField(max_length=64, null=False, unique=True) # 和出版社关联的外键字段 publisher = models.ForeignKey(to="Publisher") def __str__(self): return "<Book Object: {}>".format(self.title) # 作者表 class Author(models.Model): id = models.AutoField(primary_key=True) name = models.CharField(max_length=16, null=False, unique=True) # 告诉ORM 我这张表和book表是多对多的关联关系,ORM自动帮我生成了第三张表 book = models.ManyToManyField(to="Book") def __str__(self): return "<Author Object: {}>".format(self.name)
然后,有个ForeignKey和ManyToManyField,这两个就是用来和其它表产生联系的,前者表达的事一对多关系,后者表达的是多对多关系。
OK,那ForeignKey和ManyToManyField这个放在哪个表有要求吗?没有,只是跨表操作的时候语法有点不同。
OK,最后一点就直接告诉你了,你会发现,设计表其实很灵活:1.表只需要两两关联就能一直访问到没有之间关联的表(所以设计的表关联顺序自己定制);2.外键放在哪张表也是自定制
数据准备
这里的书籍列表、作者列表数据就是通过url/view的orm来操作了,因为跨表添加数据有点麻烦
mysql表的数据存储形式
一对多关系的外键字段放在外键表里,多对多关系的外键字段放在新生成的一张表里(这是orm生成表时自动做的)
orm跨表操作
跨表有正向和反向操作,正向指的是从外键所在的表向另一张表操作
一对一就是特殊的一对多,因此不演示,用法和一对多一样。
查询数据
一对多关系正向跨表
多对多关系正向跨表(和一对多一样)
一对多关系反向跨表
如果外键字段加上related_name就可以省略_set
对象跨表
双下划线跨表
多对多关系反向跨表(和一对多一样)
新增数据
一对多
多对多
语法一
语法二
新增多个
修改数据
一对多
多对多
删除数据
级联删除
就是说,如果某张表的某些记录被删除了,那这些记录关联的其它表记录也会删除。django1.11版本的orm默认是均删除,有的版本可能是不级联删除
一对多
一对多不存在跨表删除,只有级联删除
多对多
关联的全部删除
Queryset方法
找到这个类的源码可得
################################################################## # PUBLIC METHODS THAT ALTER ATTRIBUTES AND RETURN A NEW QUERYSET # ################################################################## def all(self) # 获取所有的数据对象 def filter(self, *args, **kwargs) # 条件查询 # 条件可以是:参数,字典,Q def exclude(self, *args, **kwargs) # 条件查询 # 条件可以是:参数,字典,Q def select_related(self, *fields) 性能相关:表之间进行join连表操作,一次性获取关联的数据。 总结: 1. select_related主要针一对一和多对一关系进行优化。 2. select_related使用SQL的JOIN语句进行优化,通过减少SQL查询的次数来进行优化、提高性能。 def prefetch_related(self, *lookups) 性能相关:多表连表操作时速度会慢,使用其执行多次SQL查询在Python代码中实现连表操作。 总结: 1. 对于多对多字段(ManyToManyField)和一对多字段,可以使用prefetch_related()来进行优化。 2. prefetch_related()的优化方式是分别查询每个表,然后用Python处理他们之间的关系。 def annotate(self, *args, **kwargs) # 用于实现聚合group by查询 from django.db.models import Count, Avg, Max, Min, Sum v = models.UserInfo.objects.values('u_id').annotate(uid=Count('u_id')) # SELECT u_id, COUNT(ui) AS `uid` FROM UserInfo GROUP BY u_id v = models.UserInfo.objects.values('u_id').annotate(uid=Count('u_id')).filter(uid__gt=1) # SELECT u_id, COUNT(ui_id) AS `uid` FROM UserInfo GROUP BY u_id having count(u_id) > 1 v = models.UserInfo.objects.values('u_id').annotate(uid=Count('u_id',distinct=True)).filter(uid__gt=1) # SELECT u_id, COUNT( DISTINCT ui_id) AS `uid` FROM UserInfo GROUP BY u_id having count(u_id) > 1 def distinct(self, *field_names) # 用于distinct去重 models.UserInfo.objects.values('nid').distinct() # select distinct nid from userinfo 注:只有在PostgreSQL中才能使用distinct进行去重 def order_by(self, *field_names) # 用于排序 models.UserInfo.objects.all().order_by('-id','age') def extra(self, select=None, where=None, params=None, tables=None, order_by=None, select_params=None) # 构造额外的查询条件或者映射,如:子查询 Entry.objects.extra(select={'new_id': "select col from sometable where othercol > %s"}, select_params=(1,)) Entry.objects.extra(where=['headline=%s'], params=['Lennon']) Entry.objects.extra(where=["foo='a' OR bar = 'a'", "baz = 'a'"]) Entry.objects.extra(select={'new_id': "select id from tb where id > %s"}, select_params=(1,), order_by=['-nid']) def reverse(self): # 倒序 models.UserInfo.objects.all().order_by('-nid').reverse() # 注:如果存在order_by,reverse则是倒序,如果多个排序则一一倒序 def defer(self, *fields): models.UserInfo.objects.defer('username','id') 或 models.UserInfo.objects.filter(...).defer('username','id') #映射中排除某列数据 def only(self, *fields): #仅取某个表中的数据 models.UserInfo.objects.only('username','id') 或 models.UserInfo.objects.filter(...).only('username','id') def using(self, alias): 指定使用的数据库,参数为别名(setting中的设置) ################################################## # PUBLIC METHODS THAT RETURN A QUERYSET SUBCLASS # ################################################## def raw(self, raw_query, params=None, translations=None, using=None): # 执行原生SQL models.UserInfo.objects.raw('select * from userinfo') # 如果SQL是其他表时,必须将名字设置为当前UserInfo对象的主键列名 models.UserInfo.objects.raw('select id as nid from 其他表') # 为原生SQL设置参数 models.UserInfo.objects.raw('select id as nid from userinfo where nid>%s', params=[12,]) # 将获取的到列名转换为指定列名 name_map = {'first': 'first_name', 'last': 'last_name', 'bd': 'birth_date', 'pk': 'id'} Person.objects.raw('SELECT * FROM some_other_table', translations=name_map) # 指定数据库 models.UserInfo.objects.raw('select * from userinfo', using="default") ################### 原生SQL ################### from django.db import connection, connections cursor = connection.cursor() # cursor = connections['default'].cursor() cursor.execute("""SELECT * from auth_user where id = %s""", [1]) row = cursor.fetchone() # fetchall()/fetchmany(..) def values(self, *fields): # 获取每行数据为字典格式 def values_list(self, *fields, **kwargs): # 获取每行数据为元祖 def dates(self, field_name, kind, order='ASC'): # 根据时间进行某一部分进行去重查找并截取指定内容 # kind只能是:"year"(年), "month"(年-月), "day"(年-月-日) # order只能是:"ASC" "DESC" # 并获取转换后的时间 - year : 年-01-01 - month: 年-月-01 - day : 年-月-日 models.DatePlus.objects.dates('ctime','day','DESC') def datetimes(self, field_name, kind, order='ASC', tzinfo=None): # 根据时间进行某一部分进行去重查找并截取指定内容,将时间转换为指定时区时间 # kind只能是 "year", "month", "day", "hour", "minute", "second" # order只能是:"ASC" "DESC" # tzinfo时区对象 models.DDD.objects.datetimes('ctime','hour',tzinfo=pytz.UTC) models.DDD.objects.datetimes('ctime','hour',tzinfo=pytz.timezone('Asia/Shanghai')) """ pip3 install pytz import pytz pytz.all_timezones pytz.timezone(‘Asia/Shanghai’) """ def none(self): # 空QuerySet对象 #################################### # METHODS THAT DO DATABASE QUERIES # #################################### def aggregate(self, *args, **kwargs): # 聚合函数,获取字典类型聚合结果 from django.db.models import Count, Avg, Max, Min, Sum result = models.UserInfo.objects.aggregate(k=Count('u_id', distinct=True), n=Count('nid')) ===> {'k': 3, 'n': 4} def count(self): # 获取个数 def get(self, *args, **kwargs): # 获取单个对象 def create(self, **kwargs): # 创建对象 def bulk_create(self, objs, batch_size=None): # 批量插入 # batch_size表示一次插入的个数 objs = [ models.DDD(name='r11'), models.DDD(name='r22') ] models.DDD.objects.bulk_create(objs, 10) def get_or_create(self, defaults=None, **kwargs): # 如果存在,则获取,否则,创建 # defaults 指定创建时,其他字段的值 obj, created = models.UserInfo.objects.get_or_create(username='root1', defaults={'email': '1111111','u_id': 2, 't_id': 2}) def update_or_create(self, defaults=None, **kwargs): # 如果存在,则更新,否则,创建 # defaults 指定创建时或更新时的其他字段 obj, created = models.UserInfo.objects.update_or_create(username='root1', defaults={'email': '1111111','u_id': 2, 't_id': 1}) def first(self): # 获取第一个 def last(self): # 获取最后一个 def in_bulk(self, id_list=None): # 根据主键ID进行查找 id_list = [11,21,31] models.DDD.objects.in_bulk(id_list) def delete(self): # 删除 def update(self, **kwargs): # 更新 def exists(self): # 是否有结果 QuerySet方法大全