# 静态文件和ORM
# 静态文件
当我们要在html中引用js、css、img……(静态文件时),就需要进行静态文件的规范并使用。下面我们就讲django的静态文件进行配置
第一步:在setting进行修改
STACIC_URL = '/xxx/' #别名随便写,在下面我们拼接后路径后,这个别名就代表下面拼接的路径了,但是前端html、和外界以后都是通过这个别名来访问我们拼接的路径,这样对外就隐藏了我们静态文件的名字。 STATICFILES_DIRS = [ os.path.join(BASE_DIR,'jingtai'), #注意别忘了写逗号,第二个参数就是项目中你存放静态文件的文件夹名称(与应用同级) ]
第二步:具体的使用,在static.html文件中(第一种方式)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="/static/css/1.css"> //引用css文件 {# <link rel="stylesheet" href="/jintai/css/1.css"> 失效#} </head> <body> <div class="c1"> <p >如果引用了别名,通过jintai引入css,反而不行(失效)</p> </div> <script src="/static/js/1.js"> //引用js文件 </script> </body> </html>
配置URL和视图函数后:
第二种引入方式:上面,我们通过在setting中设置别名,将所有静态文件路径与之拼接。其实,之前拼接路径的列表可以存储多条路径,这样我们可以实现,讲多个静态文件夹整合在一个别名下,然后通过具体的下一级目录进行拼接查找,即可找到相应的静态文件。下面我们将使用:
{% load static %} 来动态加载别名(常用),也就是说即使你的配置的static的名字随便变化了,此方法会自动找别名
{% load static %} <img src="{% static "/images/hi.jpg" %}" alt="Hi!" /> //引入img #引用js文件 {% load static %} <script src="{% static "/mytest.js" %}"></script> #某个文件多处被用到可以存为一个变量,使用as取别名 {% load static %} {% static "/images/hi.jpg" as myphoto %} <img src="{{ myphoto }}"></img>
修改static.html文件:注意{% load static %} 必须在引用之前就可以
<head> <meta charset="UTF-8"> <title>Title</title> {% load static %} <link rel="stylesheet" href="{% static "/css/1.css" %}"> </head>
方式三: 使用{% get_static_prefix %} 来获取别名前缀,再拼接路径,和第二种方法类似
<head> <meta charset="UTF-8"> <title>Title</title> {% load static %} <link rel="stylesheet" href="{% get_static_prefix%}css/1.css"> //这主意中间没有空格 </head> //或则取别名 <head> <meta charset="UTF-8"> <title>Title</title> {% load static %} {% get_static_prefix as STATIC_PREFIX %} <link rel="stylesheet" href="{{ STATIC_PREFIX }}css/1.css"> </head>
# 多个静态文件的配置和查找顺序
当我们需要多个静态文件时,需要注意统一的别名只能时static,这个别名代表了所有 你的已经写在列表里(配置)的文件名,相当于给你的静态文件装进一个黑箱中,用的时候django给你取。
静态文件是有执行顺序的,依次查找黑箱中的静态文件,如果我用到一个图片名,多个静态文件都有,此时就会触发最前一个静态文件里的此图片。
# ORM (Object relation mapping)
ORM,全称是object relation mapping。翻译过来,就是对象关系映射。
要知道:再程序开发一个领域可以分为:应用开发人员/数据库管理人员;前者通过编程语言实现应用,而后者是通过sql语句进行数据库操作,那么要求一个应用开发人员去写sql语句,就会显得很吃力,但是我们可以写程序啊,那么ORM就是一个映射数据库的一个模型。
官方:MVC框架中包括一个重要的部分,就是ORM,它实现了数据模型与数据库的解耦,即数据模型的设计不需要依赖于特定的数据库,通过简单的配置就可以轻松更换数据库,这极大的减轻了开发人员的工作量,不需要面对因数据库变更而导致的无效劳动 ORM是“对象-关系-映射”的简称。(Object Relational Mapping,简称ORM)(将来会学一个sqlalchemy,是和他很像的,但是django的orm没有独立出来让别人去使用,虽然功能比sqlalchemy更强大,但是别人用不了)
ORM的作用:
ORM可以实现不写sql语句,就可以实现操作数据库。 通过pymysql,就是Mysql给Python提供的接口。早期的接口叫mysqldb (python3.4后就没有者模块了)
ORM的核心:用python类操作表,用对象操作记录。
ORM把相应的类和属性操作,转换为sql语句,来操作数据库。它做了一个翻译的过程!学习ORM,就是用什么语法,能翻译成什么样的sql语句。
# ORM的优点
1.符合python语法。 2.自己写的sql语句,效率不够高。因为它直接传参,就可以使用了。 3. 不需要自己写SQL,对于类的操作,会转换成相应的SQL语句,来操作数据库。(核心功能) 4.它不属于硬编码,它不针对于MySQL。比如公司发展了,需要换成oracle数据库。(可以对多种数据库进行操作) 如果项目中的sql语句写死了(或者数据库迁移),那么项目中的所有的sql语句,都得更换。如果用了ORM,只需要改一处, 直接修改setting.py中的数据库引擎,换成oracle,就可以了。这样,方便数据库迭代,这是它最大的优点。
# ORM的缺点
1.执行效率低。看上图,它是从上层到下层的操作。它有一个翻译的过程。所以不如直接写SQL(原生sql),操作的执行效率低。 但是影响不大。真遇到这种请情况,优化SQL就可以了。效率问题,跟SQL有很大的关系。 2、不用担心:ORM还提供了,执行原生SQL的接口。可以直接执行原生SQL。
建议:刚开始写ORM时,一定要对应SQL语句,去写ORM
从上图中可以知道,class的类属性和字段是对应的。 [decimal(10,2)表示最大长度为10位。小数点2位。那么整数部分,最大只能有8位。]
ORM引擎 python ------- --------> sql 类名 表名 属性变量 字段 属性值 约束条件 对象 一条记录 比如save(),就可以增加一条记录。所以是对象对应一条记录。
名言警句:
早期,会经历很多碰壁的过程,这样你就能成长! 先创建表,分模块来创建表。 到了写代码的时候,反而是最简单的。 比如一个商城项目,设计表,梳理流程,花了2个月。写代码,几个星期就完成了。
# 具体操作
第一步:配置环境:Pycharm的Mysql环境 + setting的数据库配置
Pycharm的Mysql环境 :
之后点击Mysql,进入输入连接的库、密码、用户名……(就可以连接了)
设置好Pycharm后,修改setting文件的databases,默认django使用的是sqlite3数据库,我们现在要设置为Mysql数据库。
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', # 数据库引擎mysql 'NAME': 'db1', # 你要存储数据的库名,事先要创建之 'USER': 'root', # 数据库用户名 'PASSWORD': '123', # 密码 'HOST': 'localhost', # 主机 'PORT': '3306', # 数据库使用的端口 } }
安装pymsql模块:需要安装两个模块
首先是注册app:修改mysite下的settings.py文件里面的**
INSTALLED_APPS
**,添加自己的项目名字到里面,否则数据库不知道给哪个项目添加表。#django连接MySQL,使用的是pymysql模块,必须得安装2个模块。否则后面会创建表不成功!或者提示no module named MySQLdb pip3 install pymysql pip3 install mysqlclient
在应用目录下的_init_.py文件中添加以下代码(我们这里是在应用 app文件下,设置的)
import pymysql pymysql.install_as_MySQLdb() #启动项目,会报错:no module named MySQLdb 。这是因为django默认你导入的驱动是MySQLdb,可是MySQLdb 对于py3有很大问题,所以我们需要的驱动是PyMySQL。(也就是收到设置驱动式mysql)
第二步:在应用下找到主角---models.py,在里面创建模型
from django.db import models class Book(models.Model): #注销此类,在makemigrations,再提交就会删除ci'biao id=models.AutoField(primary_key=True) # 如果表里面没有写主键,表里面会自动生成一个自增主键字段,叫做id字段,orm要求每个表里面必须要写一个主键 title=models.CharField(max_length=32) # 和varchar(32)是一样的,32个字符 state=models.BooleanField() pub_date=models.DateField() # 必须存这种格式"2018-12-12" price=models.DecimalField(max_digits=8,decimal_places=2) # max_digits最大位数,decimal_places小数部分占多少位 publish=models.CharField(max_length=32) #ORM没法创建数据库,它只能操作表!
接下来要在pycharm的teminal中通过命令创建数据库的表了。有2条命令,分别是:
> python manage.py makemigrations #生成记录,每次修改了models里面的内容或者添加了新的app,新的app里面写了models里面的内容,都要执行这两条,这将在应用migrations文件下生成:0001_initial.py文件,这里面就是记录你本次操作。 > python manage.py migrate #执行上面这个语句的记录来创建表,生成的表名字前面会自带应用的名字,例如:你的book表在mysql里面叫做app01_book表,提交执行数据库创建
关于同步指令的执行简单原理:
在执行python manager.py makemigrations 时django 会在相应的 app 的migration文件夹下面生成 一个python脚本文件,在执行 python manager.py migrate 时 django才会生成数据库表,那么django是如何生成数据库表的呢? django是根据 migration下面的脚本文件来生成数据表的 每个migration文件夹下面有多个脚本,那么django是如何知道该执行那个文件的呢,django有一张django-migrations表,表中记录了,已经执行的脚本,那么表中没有的就是还没执行的脚本,则执行migrate的时候就只执行表中没有记录的那些脚本。 1、有时在执行 migrate 的时候如果发现没有生成相应的表,可以看看在 django-migrations表中看看 脚本是否已经执行了, 2、可以删除 django-migrations 表中的记录 和 数据库中相应的 表 , 然后重新 执行
通过pycharm提供的功能来执行manage.py相关的指令:
在这个终端里,你就可以不用输入:python manage.py -opt了,直接输入:makemigrations/migrate就行了
注意: 如果想打印orm转换过程中的sql,需要在settings中进行如下配置:
#在setting中添加这一段,就会让所有的提交数据库时,出现sql语句 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语句
from app01 import models def add_book(request): book_obj = models.Book(title='python',price=123,pub_date='2012-12-12',publish='人民出版社') book_obj.save() from django.db import connection #通过这种方式也能查看执行的sql语句 print(connection.queries) return HttpResponse('ok')
# 更多字段和参数
每个字段有一些特有的参数,例如,CharField需要max_length参数来指定
VARCHAR
数据库字段的大小。还有一些适用于所有字段的通用参数。 这些参数在文档中有详细定义,这里我们只简单介绍一些最常用的。# 常用字段
<1> CharField 字符串字段, 用于较短的字符串. CharField 要求必须有一个参数 max_length, 用于从数据库层和Django校验层限制该字段所允许的最大字符数. <2> IntegerField #用于保存一个整数.范围是-2147483648 ~ 2147483648 <3> FloatField 一个浮点数. 必须 提供两个参数: 参数 描述 max_digits 总位数(不包括小数点和符号) decimal_places 小数位数 举例来说, 要保存最大值为 999 (小数点后保存2位),你要这样定义字段: models.FloatField(..., max_digits=5, decimal_places=2) 要保存最大值一百万(小数点后保存10位)的话,你要这样定义: models.FloatField(..., max_digits=17, decimal_places=10) #max_digits大于等于17就能存储百万以上的数 admin 用一个文本框(<input type="text">)表示该字段保存的数据. <4> AutoField 一个 IntegerField, 添加记录时它会自动增长. 你通常不需要直接使用这个字段; 自定义一个主键:my_id=models.AutoField(primary_key=True) 如果你不指定主键的话,系统会自动添加一个主键字段到你的 model,也就是id. 一个models类中不能有两个AutoField <5> BooleanField A true/false field. admin 用 checkbox 来表示此类字段. <6> TextField 一个容量很大的文本字段. admin 用一个 <textarea> (文本区域)表示该字段数据.(一个多行编辑框). <7> EmailField 一个带有检查Email合法性的 CharField,不接受 maxlength 参数. <8> DateField 一个日期字段. 共有下列额外的可选参数: Argument 描述 auto_now 当对象被保存时,自动将该字段的值设置为当前时间.通常用于表示 "last-modified" 时间戳. auto_now_add 当对象首次被创建时,自动将该字段的值设置为当前时间.通常用于表示对象创建时间.都是设置为True(仅仅在admin中有意义...) <9> DateTimeField 一个日期时间字段. 类似 DateField 支持同样的附加选项.它是继承DateField,也就是说也可以使用auto_now、auto_now_add <10> ImageField 类似 FileField, 不过要校验上传对象是否是一个合法图片.#它有两个可选参数:height_field和width_field, 如果提供这两个参数,则图片将按提供的高度和宽度规格保存. <11> FileField 一个文件上传字段. 要求一个必须有的参数: upload_to, 一个用于保存上载文件的本地文件系统路径. 这个路径必须包含 strftime #formatting, 该格式将被上载文件的 date/time 替换(so that uploaded files don't fill up the given directory). admin 用一个<input type="file">部件表示该字段保存的数据(一个文件上传部件) . 注意:在一个 model 中使用 FileField 或 ImageField 需要以下步骤: (1)在你的 settings 文件中, 定义一个完整路径给 MEDIA_ROOT 以便让 Django在此处保存上传文件. (出于性能考虑,这些文件并不保存到数据库.) 定义MEDIA_URL 作为该目录的公共 URL. 要确保该目录对 WEB服务器用户帐号是可写的. (2) 在你的 model 中添加 FileField 或 ImageField, 并确保定义了 upload_to 选项,以告诉 Django 使用 MEDIA_ROOT 的哪个子目录保存上传文件.你的数据库中要保存的只是文件的路径(相对于 MEDIA_ROOT). 出于习惯你一定很想使用 Django 提供的 get_<#fieldname>_url 函数.举例来说,如果你的 ImageField 叫作 mug_shot, 你就可以在模板中以 {{ object.#get_mug_shot_url }} 这样的方式得到图像的绝对路径. <12> URLField 用于保存 URL. 若 verify_exists 参数为 True (默认), 给定的 URL 会预先检查是否存在( 即URL是否被有效装入且 没有返回404响应). admin 用一个 <input type="text"> 文本框表示该字段保存的数据(一个单行编辑框) <13> NullBooleanField 类似 BooleanField, 不过允许 NULL 作为其中一个选项. 推荐使用这个字段而不要用 BooleanField 加 null=True 选项 admin 用一个选择框 <select> (三个可选择的值: "Unknown", "Yes" 和 "No" ) 来表示这种字段数据. <14> SlugField "Slug" 是一个报纸术语. slug 是某个东西的小小标记(短签), 只包含字母,数字,下划线和连字符.#它们通常用于URLs 若你使用 Django 开发版本,你可以指定 maxlength. 若 maxlength 未指定, Django 会使用默认长度: 50. #在 以前的 Django 版本,没有任何办法改变50 这个长度. 这暗示了 db_index=True. 它接受一个额外的参数: prepopulate_from, which is a list of fields from which to auto-#populate the slug, via JavaScript,in the object's admin form: models.SlugField (prepopulate_from=("pre_name", "name"))prepopulate_from 不接受 DateTimeFields. <13> XMLField 一个校验值是否为合法XML的 TextField,必须提供参数: schema_path, 它是一个用来校验文本的 RelaxNG schema #的文件系统路径. <14> FilePathField 可选项目为某个特定目录下的文件名. 支持三个特殊的参数, 其中第一个是必须提供的. 参数 描述 path 必需参数. 一个目录的绝对文件系统路径. FilePathField 据此得到可选项目. Example: "/home/images". match 可选参数. 一个正则表达式, 作为一个字符串, FilePathField 将使用它过滤文件名. 注意这个正则表达式只会应用到 base filename 而不是 路径全名. Example: "foo.*\.txt^", 将匹配文件 foo23.txt 却不匹配 bar.txt 或 foo23.gif. recursive可选参数.要么 True 要么 False. 默认值是 False. 是否包括 path 下面的全部子目录. 这三个参数可以同时使用. match 仅应用于 base filename, 而不是路径全名. 那么,这个例子: FilePathField(path="/home/images", match="foo.*", recursive=True) ...会匹配 /home/images/foo.gif 而不匹配 /home/images/foo/bar.gif <15> IPAddressField 一个字符串形式的 IP 地址, (i.e. "24.124.1.30"). <16> CommaSeparatedIntegerField 用于存放逗号分隔的整数值. 类似 CharField, 必须要有maxlength参数.
# 参数说明
(1)null 如果为True,Django 将用NULL 来在数据库中存储空值。 默认值是 False. (1)blank 如果为True,该字段允许不填(admin管理表单中,表单允许不填)。默认为False。 要注意,这与 null 不同。null纯粹是数据库范畴的,而 blank 是数据验证范畴的。 如果一个字段的blank=True,表单的验证将允许该字段是空值。如果字段的blank=False,该字段就是必填的。 (2)default 字段的默认值。可以是一个值或者可调用对象。如果可调用 ,每有新对象被创建它都会被调用,如果你的字段没有设置可以为空,那么将来如果我们后添加一个字段,这个字段就要给一个default值 (3)primary_key 如果为True,那么这个字段就是模型的主键。如果你没有指定任何一个字段的primary_key=True, Django 就会自动添加一个IntegerField字段做为主键,所以除非你想覆盖默认的主键行为, 否则没必要设置任何一个字段的primary_key=True。 (4)unique 如果该值设置为 True, 这个数据字段的值在整张表中必须是唯一的 (5)choices 由二元组组成的一个可迭代对象(例如,列表或元组),用来给字段提供选择项。 如果设置了choices ,默认的表单将是一个选择框而不是标准的文本框,<br>而且这个选择框的选项就是choices 中的选项。 (6)db_index 如果db_index=True 则代表着为此字段设置数据库索引。DatetimeField、DateField、TimeField这个三个时间字段,都可以设置如下属性。 (7)auto_now_add 配置auto_now_add=True,创建数据记录的时候会把当前时间添加到数据库。 (8)auto_now 配置上auto_now=True,每次更新数据记录的时候会更新该字段,标识这条记录最后一次的修改时间。 (9) db_column 数据库中字段的列名 (10) unique_for_date 数据库中字段【日期】部分是否可以建立唯一索引 (11) unique_for_month 数据库中字段【月】部分是否可以建立唯一索引
# 自定义一个char类型字段
#自定义有char类型 class MyCharField(models.Field): 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 # 返回的是自己写的名字char(25),这里可以也可以返回return 'varchar(%s)'%self.max_length #使用自定义char类型在字段 class Class(models.Model): id = models.AutoField(primary_key=True) title = models.CharField(max_length=25) # 使用自定义的char类型的字段 cname = MyCharField(max_length=25)
Model Meta参数:
class UserInfo(models.Model): nid = models.AutoField(primary_key=True) username = models.CharField(max_length=32) class Meta: # 数据库中生成的表名称 默认 app名称 + 下划线 + 类名 db_table = "table_name" # admin中显示的表名称 verbose_name = '个人信息' # verbose_name加s verbose_name_plural = '所有用户信息' # 联合索引 index_together = [ ("pub_date", "deadline"), # 应为两个存在的字段 ] # 联合唯一索引 unique_together = (("driver", "restaurant"),) # 应为两个存在的字段 #补充:配置元信息的重要性?当我们在别的项目中需要使用到我们之前项目的数据库时,知道了数据库密码、接口等还不都,在models中的类默认在执行迁移的时候会生成的项目名_表名,显然和我们数据库中的要使用的表名不一致,会自动创建新表,此时,我们就需要使用元信息了,让我们的表名和数据库中的一致即可。(不需要迁移,就可以使用)
使用choices参数:该参数接收一个可迭代的列表或元组(基本单位为二元组)。如果指定了该参数,在实例化该模型时,该字段只能取选项列表中的值。且需要注意的是这个可选列表的第一个值会被显示,要想显示第二个值,需要使用
get_FOO_display()
#models.py文件 from django.db import models class Person(models.Model): SHIRT_SIZES = ( ('S', 'Small'), ('M', 'Medium'), ('L', 'Large'), ) name = models.CharField(max_length=60) shirt_size = models.CharField(max_length=1, choices=SHIRT_SIZES) -------------------------------------------- >>> p = Person(name="Fred Flintstone", shirt_size="L") >>> p.save() >>> p.shirt_size 'L' >>> p.get_shirt_size_display() 'Large'
备注名:(verbose_name:常用于admin中显示字段)
除了
ForeignKey
,ManyToManyField
和OneToOneField
,任何字段类型都接收一个可选的参数verbose_name
,如果未指定该参数值, Django 会自动使用该字段的属性名作为该参数值,并且把下划线转换为空格。first_name = models.CharField("person's first name", max_length=30) #在该例中,备注名为 "person's first name",这个备注名也就是之前每个字段配置的'verbose_name',可以查看Field的源码的init,默认在第一个位置上传参 first_name = models.CharField(max_length=30) # 在该例中:备注名为 "first name"
ForeignKey
,ManyToManyField
andOneToOneField
接收的第一个参数为模型的类名,后面可以添加一个verbose_name
参数:poll = models.ForeignKey( Poll, on_delete=models.CASCADE, verbose_name="the related poll", ) sites = models.ManyToManyField(Site, verbose_name="list of sites") place = models.OneToOneField( Place, on_delete=models.CASCADE, verbose_name="related place", )
# 多表关系和参数
ForeignKey(ForeignObject) # ForeignObject(RelatedField) to, # 要进行关联的表名 to_field=None, # 要关联的表中的字段名称 on_delete=None, # 当删除关联表中的数据时,当前表与其关联的行的行为 - models.CASCADE,删除关联数据,与之关联也删除 - models.DO_NOTHING,删除关联数据,引发错误IntegrityError - models.PROTECT,删除关联数据,引发错误ProtectedError - models.SET_NULL,删除关联数据,与之关联的值设置为null(前提FK字段需要设置为可空) - models.SET_DEFAULT,删除关联数据,与之关联的值设置为默认值(前提FK字段需要设置默认值) - models.SET,删除关联数据, a. 与之关联的值设置为指定值,设置:models.SET(值) b. 与之关联的值设置为可执行对象的返回值,设置:models.SET(可执行对象) def func(): return 10 class MyModel(models.Model): user = models.ForeignKey( to="User", to_field="id" on_delete=models.SET(func),) related_name=None, # 反向操作时,使用的字段名,用于代替 【表名_set】 如: obj.表名_set.all() related_query_name=None, # 反向操作时,使用的连接前缀,用于替换【表名】 如: models.UserGroup.objects.filter(表名__字段名=1).values('表名__字段名') limit_choices_to=None, # 在Admin或ModelForm中显示关联数据时,提供的条件: # 如: - limit_choices_to={'nid__gt': 5} - limit_choices_to=lambda : {'nid__gt': 5} from django.db.models import Q - limit_choices_to=Q(nid__gt=10) - limit_choices_to=Q(nid=8) | Q(nid__gt=10) - limit_choices_to=lambda : Q(Q(nid=8) | Q(nid__gt=10)) & Q(caption='root') db_constraint=True # 是否在数据库中创建外键约束 parent_link=False # 在Admin中是否显示关联数据 OneToOneField(ForeignKey) to, # 要进行关联的表名 to_field=None # 要关联的表中的字段名称 on_delete=None, # 当删除关联表中的数据时,当前表与其关联的行的行为 ###### 对于一对一 ###### # 1. 一对一其实就是 一对多 + 唯一索引 # 2.当两个类之间有继承关系时,默认会创建一个一对一字段 # 如下会在A表中额外增加一个c_ptr_id列且唯一: class C(models.Model): nid = models.AutoField(primary_key=True) part = models.CharField(max_length=12) class A(C): id = models.AutoField(primary_key=True) code = models.CharField(max_length=1) ManyToManyField(RelatedField) to, # 要进行关联的表名 related_name=None, # 反向操作时,使用的字段名,用于代替 【表名_set】 如: obj.表名_set.all() related_query_name=None, # 反向操作时,使用的连接前缀,用于替换【表名】 如: models.UserGroup.objects.filter(表名__字段名=1).values('表名__字段名') limit_choices_to=None, # 在Admin或ModelForm中显示关联数据时,提供的条件: # 如: - limit_choices_to={'nid__gt': 5} - limit_choices_to=lambda : {'nid__gt': 5} from django.db.models import Q - limit_choices_to=Q(nid__gt=10) - limit_choices_to=Q(nid=8) | Q(nid__gt=10) - limit_choices_to=lambda : Q(Q(nid=8) | Q(nid__gt=10)) & Q(caption='root') symmetrical=None, # 仅用于多对多自关联时,symmetrical用于指定内部是否创建反向操作的字段 # 做如下操作时,不同的symmetrical会有不同的可选字段 models.BB.objects.filter(...) # 可选字段有:code, id, m1 class BB(models.Model): code = models.CharField(max_length=12) m1 = models.ManyToManyField('self',symmetrical=True) # 可选字段有: bb, code, id, m1 class BB(models.Model): code = models.CharField(max_length=12) m1 = models.ManyToManyField('self',symmetrical=False) through=None, # 自定义第三张表时,使用字段用于指定关系表 through_fields=None, # 自定义第三张表时,使用字段用于指定关系表中那些字段做多对多关系表 from django.db import models class Person(models.Model): name = models.CharField(max_length=50) class Group(models.Model): name = models.CharField(max_length=128) members = models.ManyToManyField( Person, through='Membership', through_fields=('group', 'person'), ) class Membership(models.Model): group = models.ForeignKey(Group, on_delete=models.CASCADE) person = models.ForeignKey(Person, on_delete=models.CASCADE) inviter = models.ForeignKey( Person, on_delete=models.CASCADE, related_name="membership_invites", ) invite_reason = models.CharField(max_length=64) db_constraint=True, # 是否在数据库中创建外键约束 db_table=None, # 默认创建第三张表时,数据库中表的名称
# auto_new 你需要知道的事
#当需要更新时间的时候,我们尽量通过datetime模块来创建当前时间,并保存或者更新到数据库里面,看下面的分析: 假如我们的表结构是这样的 class User(models.Model): username = models.CharField(max_length=255, unique=True, verbose_name='用户名') is_active = models.BooleanField(default=False, verbose_name='激活状态') #那么我们修改用户名和状态可以使用如下两种方法: #方法一:update() 批量修改表记录 User.objects.filter(id=1).update(username='nick',is_active=True) #方法二:原始办法,相对慢,因为会重新赋值该记录每个字段值 _t = User.objects.get(id=1) _t.username='nick' _t.is_active=True _t.save() #方法一适合更新一批数据,类似于mysql语句update user set username='nick' where id = 1 #方法二适合更新一条数据,也只能更新一条数据,当只有一条数据更新时推荐使用此方法,另外此方法还有一个好处,我们接着往下看 具有auto_now属性字段的更新 我们通常会给表添加三个默认字段 - 自增ID,这个django已经默认加了,就像上边的建表语句,虽然只写了username和is_active两个字段,但表建好后也会有一个默认的自增id字段 - 创建时间,用来标识这条记录的创建时间,具有auto_now_add属性,创建记录时会自动填充当前时间到此字段 - 修改时间,用来标识这条记录最后一次的修改时间,具有auto_now属性,当记录发生变化时填充当前时间到此字段 就像下边这样的表结构: class User(models.Model): create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间') username = models.CharField(max_length=255, unique=True, verbose_name='用户名') is_active = models.BooleanField(default=False, verbose_name='激活状态') 当表有字段具有auto_now属性且你希望他能自动更新时,必须使用上边方法二的更新,不然auto_now字段不会更新,也就是: _t = User.objects.get(id=1) _t.username='nick' _t.is_active=True _t.save() json/dict类型数据更新字段 目前主流的web开放方式都讲究前后端分离,分离之后前后端交互的数据格式大都用通用的jason型,那么如何用最少的代码方便的更新json格式数据到数据库呢?同样可以使用如下两种方法: 方法一: data = {'username':'nick','is_active':'0'} User.objects.filter(id=1).update(**data) 同样这种方法不能自动更新具有auto_now属性字段的值 通常我们再变量前加一个星号(*)表示这个变量是元组/列表,加两个星号表示这个参数是字典 方法二: data = {'username':'nick','is_active':'0'} _t = User.objects.get(id=1) _t.__dict__.update(**data) _t.save() 方法二和方法一同样无法自动更新auto_now字段的值 注意这里使用到了一个__dict__方法 方法三: _t = User.objects.get(id=1) _t.role=Role.objects.get(id=3) _t.save()
# 附ORM与数据库(sql)实际字段对应关系
'AutoField': 'integer AUTO_INCREMENT', 'BigAutoField': 'bigint AUTO_INCREMENT', 'BinaryField': 'longblob', 'BooleanField': 'bool', 'CharField': 'varchar(%(max_length)s)', 'CommaSeparatedIntegerField': 'varchar(%(max_length)s)', 'DateField': 'date', 'DateTimeField': 'datetime', 'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)', 'DurationField': 'bigint', 'FileField': 'varchar(%(max_length)s)', 'FilePathField': 'varchar(%(max_length)s)', 'FloatField': 'double precision', 'IntegerField': 'integer', 'BigIntegerField': 'bigint', 'IPAddressField': 'char(15)', 'GenericIPAddressField': 'char(39)', 'NullBooleanField': 'bool', 'OneToOneField': 'integer', 'PositiveIntegerField': 'integer UNSIGNED', 'PositiveSmallIntegerField': 'smallint UNSIGNED', 'SlugField': 'varchar(%(max_length)s)', 'SmallIntegerField': 'smallint', 'TextField': 'longtext', 'TimeField': 'time', 'UUIDField': 'char(32)',
# 注意
我们通过与mysql建立连接,操作mysql数据时,其实是创建了一个临时对话框,就比如我们通过Pycharm来操作数据库(migrate操作等时,会打印一个警告:
这涉及到mysql的sql_mode设置的相关知识:详细解释
sql_mode是个很容易被忽视的变量,默认值是空值,在这种设置下是可以允许一些非法操作的,比如允许一些非法数据的插入。在生产环境必须将这个值设置为严格模式,所以开发、测试环境的数据库也必须要设置,这样在开发测试阶段就可以发现问题. sql model 常用来解决下面几类问题: (1) 通过设置sql mode, 可以完成不同严格程度的数据校验,有效地保障数据准备性。 (2) 通过设置sql model 为宽松模式,来保证大多数sql符合标准的sql语法,这样应用在不同数据库之间进行迁移时,则不需要对业务sql 进行较大的修改。 (3) 在不同数据库之间进行数据迁移之前,通过设置SQL Mode 可以使MySQL 上的数据更方便地迁移到目标数据库中。 解决模式设置和修改(以解决上述问题为例): 方式一:先执行select @@sql_mode,复制查询出来的值并将其中的NO_ZERO_IN_DATE,NO_ZERO_DATE删除,然后执行set sql_mode = '修改后的值'或者set session sql_mode='修改后的值';,例如:set session sql_mode='STRICT_TRANS_TABLES';改为严格模式 此方法只在当前会话中生效,关闭当前会话就不生效了。 方式二:先执行select @@global.sql_mode,复制查询出来的值并将其中的NO_ZERO_IN_DATE,NO_ZERO_DATE删除,然后执行set global sql_mode = '修改后的值'。 此方法在当前服务中生效,重新MySQL服务后失效 方法三:在mysql的安装目录下,或my.cnf文件(windows系统是my.ini文件),新增 sql_mode = ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION, 添加my.cnf如下: [mysqld] sql_mode=ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER 然后重启mysql。从方法永久有效
在Django中也可以通过设置数据库配置来完成解决上述问题:setting中
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'mxshop', 'HOST': '127.0.0.1', 'PORT': '3306', 'USER': 'root', 'PASSWORD': '123', 'OPTIONS': { #设置严格模式 "init_command": "SET default_storage_engine='INNODB'", #'init_command': "SET sql_mode='STRICT_TRANS_TABLES'", } } } DATABASES['default']['OPTIONS']['init_command'] = "SET sql_mode='STRICT_TRANS_TABLES'"
# ORM 操作
基本操作
# 增 models.Tb1.objects.create(c1='xx', c2='oo') # 增加一条数据,可以接受字典类型数据 **kwargs obj = models.Tb1(c1='xx', c2='oo') #法二 obj.save() # 查 models.Tb1.objects.get(id=123) # 获取单条数据,不存在则报错(不建议) models.Tb1.objects.all() # 获取全部 models.Tb1.objects.filter(name='seven') # 获取指定条件的数据 models.Tb1.objects.exclude(name='seven') # 去除指定条件的数据 # 删 # models.Tb1.objects.filter(name='seven').delete() # 删除指定条件的数据 # 改 models.Tb1.objects.filter(name='seven').update(gender='0') # 将指定条件的数据更新,均支持 **kwargs obj = models.Tb1.objects.get(id=1) obj.c1 = '111' obj.save() # 修改单条数据 --------------------------------------进阶------------------------------------ # 获取个数 models.Tb1.objects.filter(name='seven').count() # 大于,小于 models.Tb1.objects.filter(id__gt=1) # 获取id大于1的值 models.Tb1.objects.filter(id__gte=1) # 获取id大于等于1的值 models.Tb1.objects.filter(id__lt=10) # 获取id小于10的值 models.Tb1.objects.filter(id__lte=10) # 获取id小于10的值 models.Tb1.objects.filter(id__lt=10, id__gt=1) # 获取id大于1 且 小于10的值 # 成员判断in models.Tb1.objects.filter(id__in=[11, 22, 33]) # 获取id等于11、22、33的数据 models.Tb1.objects.exclude(id__in=[11, 22, 33]) # not in # 是否为空 isnull models.Tb1.objects.filter(pub_date__isnull=True) # 包括contains models.Tb1.objects.filter(name__contains="ven") models.Tb1.objects.filter(name__icontains="ven") # icontains大小写不敏感 models.Tb1.objects.exclude(name__icontains="ven") # 范围range models.Tb1.objects.filter(id__range=[1, 2]) # 范围bettwen and # 其他类似 startswith,istartswith, endswith, iendswith, # 排序order by models.Tb1.objects.filter(name='seven').order_by('id') # asc models.Tb1.objects.filter(name='seven').order_by('-id') # desc # 分组group by from django.db.models import Count, Min, Max, Sum models.Tb1.objects.filter(c1=1).values('id').annotate(c=Count('num')) #SELECT "app01_tb1"."id", COUNT("app01_tb1"."num") AS "c" FROM "app01_tb1" WHERE "app01_tb1"."c1" = 1 GROUP BY "app01_tb1"."id" 筛选出c1=1的按照id进行分组,最后求出有多少个'num' # limit 、offset models.Tb1.objects.all()[10:20] # regex正则匹配,iregex 不区分大小写 models.Tb1.objects.get(title__regex=r'^(An?|The) +') models.Tb1.objects.get(title__iregex=r'^(an?|the) +') # date models.Tb1.objects.filter(pub_date__date=datetime.date(2005, 1, 1)) models.Tb1.objects.filter(pub_date__date__gt=datetime.date(2005, 1, 1)) # year models.Tb1objects.filter(pub_date__year=2005) models.Tb1objects.filter(pub_date__year__gte=2005) # month models.Tb1.objects.filter(pub_date__month=12) models.Tb1.filter(pub_date__month__gte=6) # day models.Tb1.objects.filter(pub_date__day=3) models.Tb1.objects.filter(pub_date__day__gte=3) # week_day models.Tb1.objects.filter(pub_date__week_day=2) models.Tb1.objects.filter(pub_date__week_day__gte=2) # hour models.Tb1.objects.filter(timestamp__hour=23) models.Tb1.objects.filter(time__hour=5) models.Tb1.objects.filter(timestamp__hour__gte=12) # minute models.Tb1.objects.filter(timestamp__minute=29) models.Tb1.objects.filter(time__minute=46) models.Tb1.objects.filter(timestamp__minute__gte=29) # second models.Tb1.objects.filter(timestamp__second=31) models.Tb1.objects.filter(time__second=2) models.Tb1.objects.filter(timestamp__second__gte=31)
# 高级操作
# extra # 在QuerySet的基础上继续执行子语句: extra(self, select=None, where=None, params=None, tables=None, order_by=None, select_params=None) # select和select_params是一组,where和params是一组,tables用来设置from哪个表 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']) #举个例子: models.UserInfo.objects.extra( select={'newid':'select count(1) from app01_usertype where id>%s'}, select_params=[1,], where = ['age>%s'], params=[18,], order_by=['-age'], tables=['app01_usertype'] ) """ select app01_userinfo.id, (select count(1) from app01_usertype where id>1) as newid from app01_userinfo,app01_usertype where app01_userinfo.age > 18 order by app01_userinfo.age desc """ # 执行原生SQL # 更高灵活度的方式执行原生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()
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): # 是否有结果