Model模型
- 模型是你的数据的唯一的、权威的信息源。它包含你所储存数据的必要字段和行为。
- 通常,每个模型对应数据库中唯一的一张表。
- 每个模型都是
django.db.models.Model
的一个Python 子类。 - 模型的每个属性都表示为数据库中的一个字段。
- Django 提供一套自动生成的用于数据库访问的API;
- 这极大的减轻了开发人员的工作量,不需要面对因数据库变更而导致的无效劳动。
- 备注:本文基于django版本1.11展开, 技术栈会有所升级, 仅供参考!
模型与数据库的关系
- 模型(Model)负责业务对象和数据库的关系映射(ORM)
- ORM是“对象-关系-映射”的简称,主要任务是:
- 根据对象的类型生成表结构
- 将对象、列表的操作,转换为sql语句
- 将sql查询到的结果转换为对象、列表
- 使用ORM,我们就可以不用像以前一样写sql语言了
为什么要用模型
- Model是MVC框架中重要的一部分,主要负责程序中用于处理数据逻辑的部分。通常模型对象负责在数据库中存取数据
- 它实现了数据模型与数据库的解耦,即数据模型的设计不需要依赖于特定的数据库,通过简单的配置就可以轻松更换数据库
配置Mysql数据库
1 ) 在当前环境中安装mysql
- $
sudo apt-get install mysql-server
- $
sudo apt install mysql-client
- $
sudo apt install libmysqlclient-dev
2 ) 在当前python环境中安装 pymysql
- $
pip3 install pymysql
3 ) 在mysql中创建数据库
- $
create databases d_models_db default charset=utf8
4 ) 在Django项目中配置数据库
- 修改settings.py文件中的DATABASE配置项
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'd_models_db', # 选择数据库的名, 请确认你的mysql中有这个库 'USER': 'root', 'PASSWORD': '', 'HOST': 'localhost', 'PORT': '3306', } }
5 ) 告诉Django在接下来的mysql操作中使用pymysql
- 打开主项目目录下的__init__.py,写入以下代码导入pymysql:
import pymysql pymysql.install_as_MySQLdb()
开发流程
1 ) 在models.py中定义模型类,要求继承自models.Model
from django.db import models
from datetime import datetime
# Create your models here.
class Users(models.Model):
name = models.CharField(max_length=32)
age = models.IntegerField(default=20)
phone = models.CharField(max_length=16)
addtime=models.DateTimeField(default=datetime.now)
class Meta:
db_table = "users" # 指定表名
2 ) 把应用加入settings.py文件的installed_app项
-
编辑主项目目录下的settings.py文件,并将项目应用文件名添加到该INSTALLED_APPS设置。
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'app', ]
-
生成迁移文件
- $
python3 manage.py makemigrations
django.core.exceptions.ImproperlyConfigured: Error loading MySQLdb module: No module named 'MySQLdb'. Did you install mysqlclient or MySQL-python? # 安装上面"配置Mysql数据库"的第五步配置,或安装:pip install mysqlclient后就可以了
- 若执行 $
python3 manage.py makemigrations
命令(可能把migrations文件夹给删了),会提示"No changes detected." 可能有用的解决方式如下:- 先 $
python3 manage.py makemigrations --empty yourappname
生成一个空的initial.py - 再 $
python3 manage.py makemigrations
生成原先的model对应的migration file
- 先 $
- $
-
执行迁移
- $
python3 manage.py migrate
- $
-
使用模型类进行crud操作
-
备注:在实际项目中生成迁移文件和执行迁移这两部基本不会出现,因为我们的数据库基本是已经设计好的
定义模型
- 在模型中定义属性,会生成表中的字段
- django根据属性的类型确定以下信息:
- 当前选择的数据库支持字段的类型
- 渲染管理表单时使用的默认html控件
- 在管理站点最低限度的验证
- django会为表增加自动增长的主键列,每个模型只能有一个主键列,如果使用选项设置某属性为主键列后,则django不会再生成默认的主键列
- 属性命名限制
- 不能是python的保留关键字
- 由于django的查询方式,不允许使用连续的下划线
定义属性
- 定义属性时,需要字段类型
- 字段类型被定义在
django.db.models.fields
目录下,为了方便使用,被导入到django.db.models
中 - 使用方式
- 导入
from django.db import models
- 通过
models.Field
创建字段类型的对象,赋值给属性
- 导入
- 对于重要数据都做逻辑删除,不做物理删除,实现方法是定义
isDelete
属性,类型为BooleanField
,默认值为False
1 ) 字段类型
AutoField
:一个根据实际ID自动增长的IntegerField,通常不指定- 如果不指定,一个主键字段将自动添加到模型中
BooleanField
:true/false 字段,此字段的默认表单控制是CheckboxInputNullBooleanField
:支持null、true、false三种值CharField(max_length=字符长度)
:字符串,默认的表单样式是 TextInputTextField
:大文本字段,一般超过4000使用,默认的表单控件是TextareaIntegerField
:整数DecimalField(max_digits=None, decimal_places=None)
:使用python的Decimal实例表示的十进制浮点数DecimalField.max_digits
:位数总数DecimalField.decimal_places
:小数点后的数字位数
FloatField
:用Python的float实例来表示的浮点数DateField[auto_now=False, auto_now_add=False])
:使用Python的datetime.date实例表示的日期- 参数
DateField.auto_now
:每次保存对象时,自动设置该字段为当前时间,用于"最后一次修改"的时间戳,它总是使用当前日期,默认为false - 参数
DateField.auto_now_add
:当对象第一次被创建时自动设置当前时间,用于创建的时间戳,它总是使用当前日期,默认为false - 该字段默认对应的表单控件是一个TextInput. 在管理员站点添加了一个JavaScript写的日历控件,和一个“Today"的快捷按钮,包含了一个额外的invalid_date错误消息键
auto_now_add
,auto_now
这些设置是相互排斥的,他们之间的任何组合将会发生错误的结果
- 参数
TimeField
:使用Python的datetime.time实例表示的时间,参数同DateFieldDateTimeField
:使用Python的datetime.datetime实例表示的日期和时间,参数同DateFieldFileField
:一个上传文件的字段ImageField
:继承了FileField的所有属性和方法,但对上传的对象进行校验,确保它是个有效的image
2 ) 字段选项
- 通过字段选项,可以实现对字段的约束
- 在字段对象时通过关键字参数指定
null
:如果为True,Django 将空值以NULL 存储到数据库中,默认值是 Falseblank
:如果为True,则该字段允许为空白,默认值是 False- 对比:null是数据库范畴的概念,blank是表单验证证范畴的
db_column
:字段的名称,如果未指定,则使用属性的名称db_index
:若值为 True, 则在表中会为此字段创建索引default
:默认值primary_key
:若为 True, 则该字段会成为模型的主键字段unique
:如果为 True, 这个字段在表中必须有唯一值
3 ) 关系
- 关系的类型包括
ForeignKey
:一对多,将字段定义在多端中ManyToManyField
:多对多,将字段定义在两端中OneToOneField
:一对一,将字段定义在任意一端中
- 可以维护递归的关联关系,使用’self’指定,详见“自关联”
- 用一访问多:对象.模型类小写_set
bookinfo.heroinfo_set
- 用一访问一:对象.模型类小写
heroinfo.bookinfo
- 访问id:对象.属性_id
heroinfo.book_id
元选项
-
在模型类中定义类
Meta
,用于设置元信息 -
元信息
db_table
:定义数据表名称,推荐使用小写字母 -
数据表的默认名称:<app_name>_<model_name>
-
例如:
djangoApp_users
-
ordering
:对象的默认排序字段,获取对象的列表时使用,接收属性构成的列表class BookInfo(models.Model): ... class Meta(): ordering = ['id']
-
字符串前加-表示倒序,不加-表示正序
class BookInfo(models.Model): ... class Meta(): ordering = ['-id']
-
排序会增加数据库的开销
示例演示
- 创建test2项目,并创建booktest应用,使用mysql数据库
- 定义图书模型
class BookInfo(models.Model): btitle = models.CharField(max_length=20) bpub_date = models.DateTimeField() bread = models.IntegerField(default=0) bcommet = models.IntegerField(default=0) isDelete = models.BooleanField(default=False)
- 英雄模型
class HeroInfo(models.Model): hname = models.CharField(max_length=20) hgender = models.BooleanField(default=True) isDelete = models.BooleanField(default=False) hcontent = models.CharField(max_length=100) hbook = models.ForeignKey('BookInfo')
- 定义index、detail视图
- index.html、detail.html模板
- 配置url,能够完成图书及英雄的展示
测试数据
-
模型BookInfo的测试数据
insert into booktest_bookinfo(btitle,bpub_date,bread,bcommet,isDelete) values ('射雕英雄传','1980-5-1',12,34,0), ('天龙八部','1986-7-24',36,40,0), ('笑傲江湖','1995-12-24',20,80,0), ('雪山飞狐','1987-11-11',58,24,0)
-
模型HeroInfo的测试数据
insert into booktest_heroinfo(hname,hgender,hbook_id,hcontent,isDelete) values ('郭靖',1,1,'降龙十八掌',0), ('黄蓉',0,1,'打狗棍法',0), ('黄药师',1,1,'弹指神通',0), ('欧阳锋',1,1,'蛤蟆功',0), ('梅超风',0,1,'九阴白骨爪',0), ('乔峰',1,2,'降龙十八掌',0), ('段誉',1,2,'六脉神剑',0), ('虚竹',1,2,'天山六阳掌',0), ('王语嫣',0,2,'神仙姐姐',0), ('令狐冲',1,3,'独孤九剑',0), ('任盈盈',0,3,'弹琴',0), ('岳不群',1,3,'华山剑法',0), ('东方不败',0,3,'葵花宝典',0), ('胡斐',1,4,'胡家刀法',0), ('苗若兰',0,4,'黄衣',0), ('程灵素',0,4,'医术',0), ('袁紫衣',0,4,'六合拳',0)
模型实例
1 ) 类的属性
objects
:是Manager类型的对象,用于与数据库进行交互- 当定义模型类时没有指定管理器,则Django会为模型类提供一个名为objects的管理器
- 支持明确指定模型类的管理器
class BookInfo(models.Model): ... books = models.Manager()
- 当为模型类指定管理器后,django不再为模型类生成名为objects的默认管理器
2 ) 创建对象
- 当创建对象时,django不会对数据库进行读写操作
- 调用
save()
方法才与数据库交互,将对象保存到数据库中 - 使用关键字参数构造模型对象很麻烦,推荐使用下面的两种之式
- 说明:init方法已经在基类
models.Model
中使用,在自定义模型中无法使用
3 ) 实例的属性
DoesNotExist
:在进行单个查询时,模型的对象不存在时会引发此异常,结合try/except使用
4 ) 实例的方法
__str__(self)
:重写object方法,此方法在将对象转换成字符串时会被调用save()
:将模型对象保存到数据表中delete()
:将模型对象从数据表中删除
模型查询
- 查询集表示从数据库中获取的对象集合
- 查询集可以含有零个、一个或多个过滤器
- 过滤器基于所给的参数限制查询的结果
- 从Sql的角度,查询集和select语句等价,过滤器像where和limit子句
- 接下来主要讨论如下知识点
- 查询集
- 字段查询:比较运算符,F对象,Q对象
1 ) 查询集
- 在管理器上调用过滤器方法会返回查询集
- 查询集经过过滤器筛选后返回新的查询集,因此可以写成链式过滤
- 惰性执行:创建查询集不会带来任何数据库的访问,直到调用数据时,才会访问数据库
- 何时对查询集求值:迭代,序列化,与if合用
- 返回查询集的方法,称为过滤器
all()
filter()
exclude()
order_by()
values()
: 一个对象构成一个字典,然后构成一个列表返回
- 写法:
filter(键1=值1,键2=值2) 等价于 filter(键1=值1).filter(键2=值2)
- 返回单个值的方法
get()
:返回单个满足条件的对象- 如果未找到会引发"模型类.DoesNotExist"异常
- 如果多条被返回,会引发"模型类.MultipleObjectsReturned"异常
count()
:返回当前查询的总条数first()
:返回第一个对象last()
:返回最后一个对象exists()
:判断查询集中是否有数据,如果有则返回True
限制查询集
- 查询集返回列表,可以使用下标的方式进行限制,等同于sql中的limit和offset子句
- 注意:不支持负数索引
- 使用下标后返回一个新的查询集,不会立即执行查询
- 如果获取一个对象,直接使用[0],等同于[0:1].get(),但是如果没有数据,[0]引发IndexError异常,[0:1].get()引发DoesNotExist异常
#这会返回前5个对象 LIMIT 5 Entry.objects.all()[:5] #这将返回第六个到第十个对象 OFFSET 5 LIMIT 5 Entry.objects.all()[5:10]
查询集的缓存
- 每个查询集都包含一个缓存来最小化对数据库的访问
- 在新建的查询集中,缓存为空,首次对查询集求值时,会发生数据库查询,django会将查询的结果存在查询集的缓存中,并返回请求的结果,接下来对查询集求值将重用缓存的结果
- 情况一:这构成了两个查询集,无法重用缓存,每次查询都会与数据库进行一次交互,增加了数据库的负载
print([e.title for e in Entry.objects.all()]) print([e.title for e in Entry.objects.all()])
- 情况二:两次循环使用同一个查询集,第二次使用缓存中的数据
querylist=Entry.objects.all() print([e.title for e in querylist]) print([e.title for e in querylist])
- 何时查询集不会被缓存:当只对查询集的部分进行求值时会检查缓存,但是如果这部分不在缓存中,那么接下来查询返回的记录将不会被缓存,这意味着使用索引来限制查询集将不会填充缓存,如果这部分数据已经被缓存,则直接使用缓存中的数据
2 ) 字段查询
- 实现where子名,作为方法filter()、exclude()、get()的参数
- 语法:属性名称__比较运算符=值
- 表示两个下划线,左侧是属性名称,右侧是比较类型
- 对于外键,使用“属性名_id”表示外键的原始值
- 转义:like语句中使用了%与,匹配数据中的%与,在过滤器中直接写,例如:filter(title__contains="%")=>where title like ‘%%%’,表示查找标题中包含%的
比较运算符
-
exact
:表示判等,大小写敏感;如果没有写“ 比较运算符”,表示判等filter(isDelete=False)
-
contains
:是否包含,大小写敏感exclude(btitle__contains='www')
-
startswith
、endswith
:以value开头或结尾,大小写敏感exclude(btitle__endswith='com')
-
isnull
、isnotnull
:是否为nullfilter(btitle__isnull=False)
-
在前面加个i表示不区分大小写,如
iexact
、icontains
、istarswith
、iendswith
-
in
:是否包含在范围内filter(pk__in=[1, 2, 3, 4, 5])
-
gt
、gte
、lt
、lte
:大于、大于等于、小于、小于等于filter(id__gt=3)
-
year
、month
、day
、week_day
、hour
、minute
、second
:对日期间类型的属性进行运算filter(bpub_date__year=1980)
filter(bpub_date__gt=date(1980, 12, 31))
跨关联关系的查询:处理join查询
- 语法:模型类名 <属性名> <比较>
- 注:可以没有__<比较>部分,表示等于,结果同inner join
- 可返向使用,即在关联的两个模型中都可以使用
filter(heroinfo_ _hcontent_ _contains='八')
聚合函数
- 使用
aggregate()
函数返回聚合函数的值 - 函数:
Avg
,Count
,Max
,Min
,Sum
from django.db.models import Max maxDate = list.aggregate(Max('bpub_date'))
count
的一般用法count = list.count()
Model模型的实战
1 ) 创建数据库和表(若是执行表迁移,可跳过下面操作)
- 进入MySQL数据库创建数据库:
d_models_db
- 进入数据库创建数据表:users
CREATE TABLE `myapp_users` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(32) NOT NULL, `age` tinyint(3) unsigned NOT NULL DEFAULT '20', `phone` varchar(16) DEFAULT NULL, `addtime` datetime(6) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8
- 备注:当然也可以通过迁移文件生成users表(需要使用Meta来改名)
- 添加几条测试数据
2 ) 创建项目djangoApp和应用app
- 创建项目框架 djangoApp $
django-admin startproject djangoApp && cd djangoApp
- 在项目中创建一个myapp应用 $
python3 manage.py startapp app
- 创建模板目录 $
mkdir tpls && cd mkdir tpls/app
- 注:最外层的目录
Django-1.11.11-model
是改名而来的 - 项目目录结构:
Django-1.11.11-model ├── djangoApp │ ├── __init__.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── manage.py ├── app │ ├── admin.py │ ├── apps.py │ ├── __init__.py │ ├── models.py │ ├── tests.py │ └── views.py └── tpls └── app
3 ) 执行数据库连接配置,网站配置
-
编辑djangoApp/init.py文件,添加Pymysql的数据库操作支持
import pymysql pymysql.install_as_MySQLdb()
-
编辑djangoApp/settings.py文件,配置数据库连接
# ... #配置自己的服务器IP地址 ALLOWED_HOSTS = ['localhost','127.0.0.1','*'] # ... #添加自己应用 INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'app', ] # ... # 配置模板路径信息 TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(BASE_DIR,'tpls')], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ] # ... # 数据库连接配置 DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'd_models_db', 'USER': 'root', 'PASSWORD': '', # 填写自己的数据库密码 'HOST': 'localhost', 'PORT': '3306', } # ...
4 ) 定义Model类
-
编辑djangoApp/models.py
from django.db import models from datetime import datetime class Users(models.Model): name = models.CharField(max_length=32) age = models.IntegerField(default=20) phone = models.CharField(max_length=20) createTime = models.DateTimeField(default=datetime.now) def __str__(self): return self.name+":"+self.phone class Meta: db_table = "users" # 指定表名
-
测试Model类的使用, 执行 $
python3 manage.py shell
[root@localhost djangoApp]# python3 manage.py shell >>> from app.models import Users >>> Users.objects.all() <QuerySet [<Users: 张三:12345678901>, <Users: aa:13456789023>]> >>> s = Users.objects.get(id=1) >>> s.id 1 >>> s.name '张三' >>> s.age 20 >>> s.phone '12345678901'
5 ) 实现Web端访问
-
编辑djangoApp/settings.py文件.做 ALLOWED_HOSTS 主机访问配置(若之前已做可跳过此步骤)
ALLOWED_HOSTS = ['localhost','127.0.0.1','这里填写主机的ip地址', '*']
-
编写项目主路由urls配置,配置对djangoApp应用路由的访问连接配置
from django.conf.urls import include,url from django.contrib import admin urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^', include('app.urls')), ]
-
配置当前应用djangoApp的路由配置
- 在djangoApp应用目录下创建一个路由文件urls.py文件,注意此文件编码为utf-8(建议复制一个)。
- 编辑应用中的路由配置文件:djangoApp/app/urls.py, 内容如下:
from django.conf.urls import url from . import views urlpatterns = [ url(r'^$', views.index, name="index"), ]
-
编辑视图文件:djangoApp/views.py,内容如下
#from django.shortcuts import render from django.http import HttpResponse from myapp.models import Users def index(request): try: s = Users.objects.get(id=1) return HttpResponse(s) except: return HttpResponse("没有找到对应的信息!")
-
测试环节:在项目根目录下运行 $
python3 manage.py runserver
命令,开启服务 -
打开浏览器,在浏览其中输入网址测试:
127.0.0.1:8000
-
继续上面操作完成Web版的Users信息的crud操作,详见下面示例仓库
6 ) 示例操作代码仓库详见