前言
该备忘录是基于以下版本开发
Mac OS High sierra 10.13.4
mintoudeMacBook-Pro-2:~ mintou$ python3 -m django --version
2.1
mintoudeMacBook-Pro-2:~ mintou$ ipython3
Python 3.6.3 (v3.6.3:2c5fed86e0, Oct 3 2017, 00:32:08)
Type 'copyright', 'credits' or 'license' for more information
IPython 6.3.1 -- An enhanced Interactive Python. Type '?' for help.
安装
pip3 install Django==2.1
python3
In [1]: import django
In [2]: django.VERSION
Out[2]: (2, 1, 0, 'final', 0)
安装成功
创建项目(模型映射表)
cd到指定目录(例如桌面),mkdir一个文件夹放项目
然后执行
django-admin startproject test1
- manage.py:一个命令行工具,可以使你用多种方式对Django项目进行交互
- 内层的目录:项目的真正的Python包
- _init _.py:一个空文件,它告诉Python这个目录应该被看做一个Python包
- settings.py:项目的配置
- urls.py:项目的URL声明
- wsgi.py:项目与WSGI兼容的Web服务器入口
创建应用
1.cd到manage.py下的目录,然后执行创建一个booktest的应用
mintoudeMacBook-Pro-2:DJangoProject mintou$ pwd
/Users/mintou/Desktop/DJangoProject
mintoudeMacBook-Pro-2:DJangoProject mintou$ ls
test1
mintoudeMacBook-Pro-2:DJangoProject mintou$ cd test1/
mintoudeMacBook-Pro-2:test1 mintou$ ls -a
. booktest manage.py
.. db.sqlite3 test1
mintoudeMacBook-Pro-2:test1 mintou$ python3 manage.py startapp booktest
从这也可以看到Django是默认用的sqlite3编写的
看下应用的目录
2.定义模型类
- 有一个数据表,就有一个模型类与之对应
- 打开models.py文件,定义模型类
- 引入包from django.db import models
- 模型类继承自models.Model类
- 说明:不需要定义主键列,在生成时会自动添加,并且值为自动增长
- 当输出对象时,会调用对象的str方法
from django.db import models
class BookInfo(models.Model):
book_title = models.CharField(max_length=20)
book_publish_date = models.DateTimeField()
def __str__(self):
# 2.1最新版本不需要encode
return self.book_title
class HeroInfo(models.Model):
hero_name = models.CharField(max_length=20)
hero_gender = models.BooleanField()
hero_content = models.CharField(max_length=100)
# 这里和1.x版本不同的是需要增加on_delete CASCADE代表级联操作。主表删除之后和这个关联的都会跟随删除
hero_book = models.ForeignKey('BookInfo', on_delete=models.CASCADE)
def __str__(self):
return self.hero_name
3.生成数据表迁移
- 激活模型:编辑settings.py文件,将booktest应用加入到installed_apps中
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'booktest',
]
- 生成迁移文件:根据模型类生成sql语句
python3 manage.py makemigrations
- 迁移文件被生成到应用的migrations目录
- 执行迁移:执行sql语句生成数据表
python3 manage.py migrate
这个 migrate
命令选中所有还没有执行过的迁移(Django 通过在数据库中创建一个特殊的表 django_migrations
来跟踪执行过哪些迁移)并应用在数据库上 - 也就是将你对模型的更改同步到数据库结构上。
迁移是非常强大的功能,它能让你在开发过程中持续的改变数据库结构而不需要重新删除和创建表 - 它专注于使数据库平滑升级而不会丢失数据。我们会在后面的教程中更加深入的学习这部分内容,现在,你只需要记住,改变模型需要这三步:
- 编辑
models.py
文件,改变模型。 - 运行
python manage.py makemigrations
为模型的改变生成迁移文件。 - 运行
python manage.py migrate
来应用数据库迁移。
数据库迁移被分解成生成和应用两个命令是为了让你能够在代码控制系统上提交迁移数据并使其能在多个应用里使用;这不仅仅会让开发更加简单,也给别的开发者和生产环境中的使用带来方便。
4.测试数据操作
回到manage.py的目录进入shell,导入包然后执行代码查看所有BookInfo的信息
mintoudeMacBook-Pro-2:test1 mintou$ pwd
/Users/mintou/Desktop/DJangoProject/test1
mintoudeMacBook-Pro-2:test1 mintou$ ls
booktest db.sqlite3 manage.py test1
mintoudeMacBook-Pro-2:test1 mintou$ python3 manage.py shell
Python 3.6.3 (v3.6.3:2c5fed86e0, Oct 3 2017, 00:32:08)
Type 'copyright', 'credits' or 'license' for more information
IPython 6.3.1 -- An enhanced Interactive Python. Type '?' for help.
In [1]: from booktest.models import BookInfo, HeroInfo
In [2]: from django.utils import timezone
In [3]: from datetime import *
In [4]: BookInfo.objects.all()
Out[4]: <QuerySet [<BookInfo: 射雕银熊转>, <BookInfo: 水浒传>]>
- 新建图书信息:
In [5]: book1 = BookInfo()
In [6]: book1.book_title = "三国演义"
In [8]: book1.book_publish_date = datetime(year=2018,month=1,day=30)
In [9]: book1.save()
/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/django/db/models/fields/__init__.py:1421: RuntimeWarning: DateTimeField BookInfo.book_publish_date received a naive datetime (2018-01-30 00:00:00) while time zone support is active.
RuntimeWarning)
In [10]: BookInfo.objects.all()
Out[10]: <QuerySet [<BookInfo: 射雕银熊转>, <BookInfo: 水浒传>, <BookInfo: 三国演义>]>
- 查找图书信息:
In [11]: BookInfo.objects.get(pk=3)
Out[11]: <BookInfo: 三国演义>
- 修改图书信息:
In [12]: book2 = BookInfo.objects.get(pk=1)
In [13]: book2.book_title
Out[13]: '射雕银熊转'
In [14]: book2.book_title = "射雕转"
In [15]: book2.save()
- 删除图书信息:
book2.delete()
关联对象的操作
- 对于HeroInfo可以按照上面的操作方式进行
- 添加,注意添加关联对象
In [18]: HeroInfo.objects.all()
Out[18]: <QuerySet [<HeroInfo: 郭靖>, <HeroInfo: 黄蓉>, <HeroInfo: 浪里白条>, <HeroInfo: 小李广>, <HeroInfo: 花和尚>]>
In [19]: h=HeroInfo()
In [20]: h.hero_name = "霹雳火"
In [21]: h.hero_gender = True
In [22]: h.hero_content = "晴明"
In [23]: BookInfo.objects.all()
Out[23]: <QuerySet [<BookInfo: 射雕转>, <BookInfo: 水浒传>, <BookInfo: 三国演义>]>
In [24]: b2=BookInfo.objects.get(pk=2)
In [25]: h.hero_book=b2
In [26]: h.save()
In [27]: HeroInfo.objects.all()
Out[27]: <QuerySet [<HeroInfo: 郭靖>, <HeroInfo: 黄蓉>, <HeroInfo: 浪里白条>, <HeroInfo: 小李广>, <HeroInfo: 花和尚>, <HeroInfo: 霹雳火>]>
- 获得关联集合:返回当前book对象的所有hero
In [28]: b2.heroinfo_set.all()
Out[28]: <QuerySet [<HeroInfo: 浪里白条>, <HeroInfo: 小李广>, <HeroInfo: 花和尚>, <HeroInfo: 霹雳火>]>
- 有一个HeroInfo存在,必须要有一个BookInfo对象,提供了创建关联的数据:
In [29]: h = b2.heroinfo_set.create(hero_name='豹子头',hero_gender=True,hero_con
...: tent="灵宠")
In [30]: HeroInfo.objects.all()
Out[30]: <QuerySet [<HeroInfo: 郭靖>, <HeroInfo: 黄蓉>, <HeroInfo: 浪里白条>, <HeroInfo: 小李广>, <HeroInfo: 花和尚>, <HeroInfo: 霹雳火>, <HeroInfo: 豹子头>]>
Django后台管理系统
服务器
- 运行如下命令可以开启服务器
python3 manage.py runserver
- 可以不写ip,默认端口为8000
- 这是一个纯python编写的轻量级web服务器,仅在开发阶段使用
- 服务器成功启动后,提示如下信息
mintoudeMacBook-Pro-2:test1 mintou$ python3 manage.py runserver
Performing system checks...
System check identified no issues (0 silenced).
August 16, 2018 - 10:07:53
Django version 2.1, using settings 'test1.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
- 默认端口是8000,可以修改端口
python3 manage.py runserver 8080
- 打开浏览器,输入网址“127.0.0.1:8000”可以打开默认页面
- 如果修改文件不需要重启服务器,如果增删文件需要重启服务器
- 通过ctrl+c停止服务器
管理操作
-
站点分为“内容发布”和“公共访问”两部分
- “内容发布”的部分负责添加、修改、删除内容,开发这些重复的功能是一件单调乏味、缺乏创造力的工作。为此,Django会根据定义的模型类完全自动地生成管理模块
使用django的管理
- 创建一个管理员用户
python3 manage.py createsuperuser
按提示输入用户名、邮箱、密码
- 启动服务器,通过“127.0.0.1:8000/admin”访问,输入上面创建的用户名、密码完成登录
- 进入管理站点,默认可以对groups、users进行管理
管理界面本地化
- 编辑settings.py文件,设置编码、时区
LANGUAGE_CODE = 'zh-Hans'
TIME_ZONE = 'Asia/Shanghai'
向admin注册模型
- 打开booktest/admin.py文件,注册模型
from django.contrib import admin
# 这里Python3编写就不能和2一样直接写models,需要在写一个.
from .models import BookInfo, HeroInfo
admin.site.register(BookInfo)
admin.site.register(HeroInfo)
自定义管理页面
- Django提供了admin.ModelAdmin类
- 通过定义ModelAdmin的子类,来定义模型在Admin界面的显示方式
class QuestionAdmin(admin.ModelAdmin):
...
admin.site.register(Question, QuestionAdmin)
列表页属性
- list_display:显示字段,可以点击列头进行排序
list_display = ['pk', 'book_title', 'book_publish_date']
- list_filter:过滤字段,过滤框会出现在右侧
list_filter = ['book_title']
- search_fields:搜索字段,搜索框会出现在上侧
search_fields = ['book_title']
- list_per_page:分页,分页框会出现在下侧
list_per_page = 10
添加、修改页属性
- fields:属性的先后顺序
fields = ['book_publish_date', 'book_title']
- fieldsets:属性分组
fieldsets = [
('basic',{'fields': ['book_title']}),
('more', {'fields': ['book_publish_date']}),
]
关联对象
-
对于HeroInfo模型类,有两种注册方式
- 方式一:与BookInfo模型类相同
- 方式二:关联注册
-
按照BookInfor的注册方式完成HeroInfo的注册
- 接下来实现关联注册
from django.contrib import admin
# Register your models here.
from .models import BookInfo, HeroInfo
# 插入的Book的时候插入Hero 指定三条
class HeroInfoInline(admin.StackedInline):
model = HeroInfo
extra = 3
class BookAdmin(admin.ModelAdmin):
# 展示方式 UI
list_display = ['pk', 'book_title', 'book_publish_date']
# 搜索UI
search_fields = ['book_title']
# 插入BookInfo的时候可以带上Hero子类插入,具体信息看HeroInfoInline的属性
inlines = [HeroInfoInline]
class HeroAdmin(admin.ModelAdmin):
list_display = ['pk', 'hero_name', 'hero_gender', 'hero_content', 'hero_book']
search_fields = ['hero_name']
# 这里把需要的models注册进行 例如 BookInfo 和 HeroInfo 最基本的UI展示
# 如果需要自定义UI就需要写一个继承于admin.ModelAdmin来指定字段编写展示
admin.site.register(BookInfo, BookAdmin)
admin.site.register(HeroInfo, HeroAdmin)
插入BookInfo的时候可以看到底部会附带三个HeroInfo让我们填写
布尔值的显示
- 发布性别的显示不是一个直观的结果,可以使用方法进行封装 在对应的Model里面编写
class HeroInfo(models.Model):
hero_name = models.CharField(max_length=20)
hero_gender = models.BooleanField()
hero_content = models.CharField(max_length=100)
# 这里和1.x版本不同的是需要增加on_delete CASCADE代表级联操作。主表删除之后和这个关联的都会跟随删除
hero_book = models.ForeignKey('BookInfo', on_delete=models.CASCADE)
def __str__(self):
return self.hero_name
def gender(self):
if self.hero_gender:
return "男"
else:
return "女"
gender.short_description = '性别'
- 在admin注册中使用gender代替hero_gender
class HeroAdmin(admin.ModelAdmin):
list_display = ['pk', 'hero_name', 'gender', 'hero_content', 'hero_book']
search_fields = ['hero_name']
视图
- 在django中,视图对WEB请求进行回应
- 视图接收reqeust对象作为第一个参数,包含了请求的信息
- 视图就是一个Python函数,被定义在views.py中
from django.shortcuts import render
from django.http import HttpResponse
def index(request):
return HttpResponse('<h1>Hello Mikejing<h1>')
def detail(request,id):
return HttpResponse('detail page--->%s'%id)
URLconf
- 在Django中,定义URLconf包括正则表达式、视图两部分
- Django使用正则表达式匹配请求的URL,一旦匹配成功,则调用应用的视图
- 注意:只匹配路径部分,即除去域名、参数后的字符串
- 在test1/urls.py插入booktest,使主urlconf连接到booktest.urls模块
from django.contrib import admin
from django.urls import path, include, re_path
urlpatterns = [
path('admin/', admin.site.urls),
re_path(r'^', include('booktest.urls'))
]
这里需要注意两点
1.这个是主入口,需要例如有个应用模块是booktest,我们需要另外再建一个urls,那么根部就需要把它include进来,需要把include包导入
2.2x版本之前是默认支持正则的,Django 2.x之后需要把path和re_path两个包都导入,后者对应正则匹配路径
- 在booktest中的urls.py中添加urlconf
from django.urls import path, re_path
from . import views
urlpatterns = [
re_path(r'^$', views.index, name='index'),
re_path(r'^book/([0-9]+)$', views.detail, name='detail'),
]
模板
- 模板是html页面,可以根据视图中传递的数据填充值
- 创建模板的目录如下图:
- 修改settings.py文件,设置TEMPLATES的DIRS值
'DIRS': [os.path.join(BASE_DIR, 'templates')],
- 在模板中访问视图传递的数据
{{输出值,可以是变量,也可以是对象.属性}}
{%执行代码段%}
index.html模板
<body>
<ul>
{%for book in lists%}
<li><a href="/book/{{book.id}}">{{book.book_title}}</a></li>
{%endfor%}
</ul>
</body>
注意:
这里a标签里面的href中写的路径有两个区别如下图,首先明确一点,无论哪种都是从头根部urls开启重新匹配
带/和不带的区别,一般写全路径都需要带上/
detail.html模板
<body>
<ul>
{%for hero in lists%}
<li>{{hero.hero_name}} {{hero.hero_gender}} {{hero.hero_content}}</li>
{%endfor%}
</ul>
</body>
应用urls
from django.urls import path, re_path
from . import views
urlpatterns = [
re_path(r'^$', views.index, name='index'),
re_path(r'^book/([0-9]+)$', views.detail, name='detail'),
]
这里说的不是根目录下的urls,是每个创建的应用对应的urls,里面如果用了re_path正则来匹配,那么正则里面获取值的方式就是(),加了就能传递到views.detail方法里面作为第二个参数,没有默认还是一个
views里面的id就是正则()里面匹配到的id,根据id查找对应的heros进行模板渲染
from django.shortcuts import render
from django.http import HttpResponse
from .models import *
def index(request):
books = BookInfo.objects.all()
context = {'lists': books}
return render(request, 'booktest/index.html', context)
def detail(request, id):
book = BookInfo.objects.get(pk=id)
heros = book.heroinfo_set.all()
return render(request, 'booktest/detail.html', {'lists': heros})
模型Model详细介绍
- MVC框架中包括一个重要的部分,就是ORM,它实现了数据模型与数据库的解耦,即数据模型的设计不需要依赖于特定的数据库,通过简单的配置就可以轻松更换数据库
- ORM是“对象-关系-映射”的简称,主要任务是:
- 根据对象的类型生成表结构
- 将对象、列表的操作,转换为sql语句
- 将sql查询到的结果转换为对象、列表
- 这极大的减轻了开发人员的工作量,不需要面对因数据库变更而导致的无效劳动
- Django中的模型包含存储数据的字段和约束,对应着数据库中唯一的表
- 在模型中定义属性,会生成表中的字段
- django根据属性的类型确定以下信息:
- 当前选择的数据库支持字段的类型
- 渲染管理表单时使用的默认html控件
- 在管理站点最低限度的验证
- django会为表增加自动增长的主键列,每个模型只能有一个主键列,如果使用选项设置某属性为主键列后,则django不会再生成默认的主键列
- 属性命名限制
- 不能是python的保留关键字
- 由于django的查询方式,不允许使用连续的下划线
定义属性
- 定义属性时,需要字段类型
- 字段类型被定义在django.db.models.fields目录下,为了方便使用,被导入到django.db.models中
- 使用方式
- 导入from django.db import models
- 通过models.Field创建字段类型的对象,赋值给属性
- 对于重要数据都做逻辑删除,不做物理删除,实现方法是定义isDelete属性,类型为BooleanField,默认值为False
字段类型
- AutoField:一个根据实际ID自动增长的IntegerField,通常不指定
- 如果不指定,一个主键字段将自动添加到模型中
- BooleanField:true/false 字段,此字段的默认表单控制是CheckboxInput
- NullBooleanField:支持null、true、false三种值
- CharField(max_length=字符长度):字符串,默认的表单样式是 TextInput
- TextField:大文本字段,一般超过4000使用,默认的表单控件是Textarea
- IntegerField:整数
- 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, and default 这些设置是相互排斥的,他们之间的任何组合将会发生错误的结果
- TimeField:使用Python的datetime.time实例表示的时间,参数同DateField
- DateTimeField:使用Python的datetime.datetime实例表示的日期和时间,参数同DateField
- FileField:一个上传文件的字段
- ImageField:继承了FileField的所有属性和方法,但对上传的对象进行校验,确保它是个有效的image
字段选项
- 通过字段选项,可以实现对字段的约束
- 在字段对象时通过关键字参数指定
- null:如果为True,Django 将空值以NULL 存储到数据库中,默认值是 False
- blank:如果为True,则该字段允许为空白,默认值是 False
- 对比:null是数据库范畴的概念,blank是表单验证证范畴的
- db_column:字段的名称,如果未指定,则使用属性的名称
- db_index:若值为 True, 则在表中会为此字段创建索引
- default:默认值
- primary_key:若为 True, 则该字段会成为模型的主键字段
- unique:如果为 True, 这个字段在表中必须有唯一值
关系
- 关系的类型包括
- ForeignKey:一对多,将字段定义在多的端中
- ManyToManyField:多对多,将字段定义在两端中
- OneToOneField:一对一,将字段定义在任意一端中
- 可以维护递归的关联关系,使用'self'指定,详见“自关联”
- 用一访问多:对象.模型类小写_set
bookinfo.heroinfo_set
- 用一访问一:对象.模型类小写
heroinfo.bookinfo
- 访问id:对象.属性_id
heroinfo.book_id
元选项
- 在模型类中定义类Meta,用于设置元信息
- 元信息db_table:定义数据表名称,推荐使用小写字母,数据表的默认名称
<app_name>_<model_name>
- ordering:对象的默认排序字段,获取对象的列表时使用,接收属性构成的列表
class BookInfo(models.Model):
...
class Meta():
ordering = ['id']
- 字符串前加-表示倒序,不加-表示正序
class BookInfo(models.Model):
...
class Meta():
ordering = ['-id']
- 排序会增加数据库的开销
1.创建项目
cd 到指定目录
django-admin startproject test2
打开配置文件默认用sqlite3引擎,name表示工程中sqlite3的路径
# Database
# https://docs.djangoproject.com/en/2.1/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
这里可以看到默认的数据库引擎是sqlite3,我们现在用Mysql,打开backends路径下的查看如下
/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6
这是我Python 3.6的安装路径
到该路径下有很多python文件,这些都是默认的包,我们安装的第三方包都在site-packages里面
2.修改默认sqlite3为mysql
可以看到,除了sqlite3,还有mysql和oracle等,我们现在用mysql,因此就把上面的配置文件修改成mysql引擎
# Database
# https://docs.djangoproject.com/en/2.1/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'test2',
'USER': 'root',
'PASSWORD': 'mikejing',
'HOST': 'localhost',
'PORT': '3306'
}
}
3.修改后在创建的项目中创建App
mintou$ python3 manage.py startapp booktest
4.编写好Model
from django.db import models
class BookInfo(models.Model):
btitle = models.CharField(max_length=20)
bpub_data = models.DateTimeField(db_column= 'book_publish_data')
bread = models.IntegerField(default=0)
bcommet = models.IntegerField(default=0)
isDelete = models.BooleanField(default=False)
# 用来修改数据库信息 比如表名
class Meta():
db_table = 'bookinfo'
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)
# 注意2.1一定要加上on_delete
hbook = models.ForeignKey('BookInfo',on_delete=models.CASCADE)
5.生成迁移
python3 manage.py makemigrations
注意:当你修改成Mysql后之后,由于不在是2.7 python,我们用的是3.6 python针对Mysql的包名都不同,直接执行直接报错
Did you install mysqlclient or MySQL-python?
我们在booktest--->test2---->__init__.py根目录下的__init__中添加如下
import pymysql
pymysql.install_as_MySQLdb()
再次执行makemigrations即可
mintoudeMacBook-Pro-2:test2 mintou$ python3 manage.py makemigrations
Migrations for 'booktest':
booktest/migrations/0001_initial.py
- Create model BookInfo
- Create model HeroInfo
mintoudeMacBook-Pro-2:test2 mintou$ python3 manage.py migrate
最后执行migrate执行迁移,把我们做的Model数据类型全部迁移到数据库上生成对应的表信息
6.查看Mysql数据
打开终端,链接数据库
mysql -uroot -p
输入密码
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |
| test2 |
+--------------------+
5 rows in set (0.00 sec)
mysql> use test2
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Database changed
mysql> show tables;
+----------------------------+
| Tables_in_test2 |
+----------------------------+
| auth_group |
| auth_group_permissions |
| auth_permission |
| auth_user |
| auth_user_groups |
| auth_user_user_permissions |
| bookinfo |
| booktest_heroinfo |
| django_admin_log |
| django_content_type |
| django_migrations |
| django_session |
+----------------------------+
12 rows in set (0.00 sec)
mysql> desc bookinfo
-> ;
+-------------------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| btitle | varchar(20) | NO | | NULL | |
| book_publish_data | datetime(6) | NO | | NULL | |
| bread | int(11) | NO | | NULL | |
| bcommet | int(11) | NO | | NULL | |
| isDelete | tinyint(1) | NO | | NULL | |
+-------------------+-------------+------+-----+---------+----------------+
6 rows in set (0.00 sec)
mysql> desc booktest_heroinfo;
+----------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| hname | varchar(20) | NO | | NULL | |
| hgender | tinyint(1) | NO | | NULL | |
| isDelete | tinyint(1) | NO | | NULL | |
| hcontent | varchar(100) | NO | | NULL | |
| hbook_id | int(11) | NO | MUL | NULL | |
+----------+--------------+------+-----+---------+----------------+
6 rows in set (0.00 sec)
可以看到我们刚才的model信息变成了mysql表,Django帮我们生成了很多其他的标,主要看
booktest_heroinfo 和 bookinfo 前者是默认表名,后者是通过Meta元类修改自定义的表名
7.类的属性objects
- objects:是Manager类型的对象,用于与数据库进行交互
- 当定义模型类时没有指定管理器,则Django会为模型类提供一个名为objects的管理器
- 支持明确指定模型类的管理器
class BookInfo(models.Model):
...
books = models.Manager()
- 当为模型类指定管理器后,django不再为模型类生成名为objects的默认管理器
管理器Manager
- 管理器是Django的模型进行数据库的查询操作的接口,Django应用的每个模型都拥有至少一个管理器
- 自定义管理器类主要用于两种情况
- 情况一:向管理器类中添加额外的方法:见下面“创建对象”中的方式二
- 情况二:修改管理器返回的原始查询集:重写get_queryset()方法
class BookInfoManager(models.Manager):
def get_queryset(self):
return super(BookInfoManager, self).get_queryset().filter(isDelete=False)
class BookInfo(models.Model):
...
books = BookInfoManager()
该类继承了models.Manager,重写的get_queryset的方法,当我们重新进入
In [3]: BookInfo.objects.all()
Out[3]: <QuerySet [<BookInfo: BookInfo object (1)>, <BookInfo: BookInfo object (2)>, <BookInfo: BookInfo object (3)>, <BookInfo: BookInfo object (4)>]>
In [4]: exit
上半段,默认查出所有,没有重写之前
下半段是重写之后filter了,查出来只有三个了,而且不再是默认的objects,而是我们自己定义的类名books
mintoudeMacBook-Pro-2:test2 mintou$ python3 manage.py shell
Python 3.6.3 (v3.6.3:2c5fed86e0, Oct 3 2017, 00:32:08)
Type 'copyright', 'credits' or 'license' for more information
IPython 6.3.1 -- An enhanced Interactive Python. Type '?' for help.
In [1]: from booktest.models import BookInfo, HeroInfo
In [2]: BookInfo.objects.all()
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-2-182abd3174ec> in <module>()
----> 1 BookInfo.objects.all()
AttributeError: type object 'BookInfo' has no attribute 'objects'
In [3]: BookInfo.books.all()
Out[3]: <QuerySet [<BookInfo: BookInfo object (1)>, <BookInfo: BookInfo object (2)>, <BookInfo: BookInfo object (3)>]>
8.创建对象
- 当创建对象时,django不会对数据库进行读写操作
- 调用save()方法才与数据库交互,将对象保存到数据库中
- 使用关键字参数构造模型对象很麻烦,推荐使用下面的两种之式
- 说明: _init _方法已经在基类models.Model中使用,在自定义模型中无法使用,
- 方式一:在模型类中增加一个类方法
class BookInfo(models.Model):
...
@classmethod
def create(cls, title, pub_date):
book = cls(btitle=title, bpub_date=pub_date)
book.bread=0
book.bcommet=0
book.isDelete = False
return book
引入时间包:from datetime import *
调用:book=BookInfo.create("hello",datetime(1980,10,11));
保存:book.save()
- 方式二:在自定义管理器中添加一个方法
- 在管理器的方法中,可以通过self.model来得到它所属的模型类
class BookInfoManager(models.Manager):
def create_book(self, title, pub_date):
book = self.model()
book.btitle = title
book.bpub_date = pub_date
book.bread=0
book.bcommet=0
book.isDelete = False
return book
class BookInfo(models.Model):
...
books = BookInfoManager()
调用:book=BookInfo.books.create_book("abc",datetime(1980,1,1))
保存:book.save()
- 在方式二中,可以调用self.create()创建并保存对象,不需要再手动save()
class BookInfoManager(models.Manager):
def create_book(self, title, pub_date):
book = self.create(btitle = title,bpub_date = pub_date,bread=0,bcommet=0,isDelete = False)
return book
class BookInfo(models.Model):
...
books = BookInfoManager()
调用:book=Book.books.create_book("abc",datetime(1980,1,1))
查看:book.pk
实例的属性
- DoesNotExist:在进行单个查询时,模型的对象不存在时会引发此异常,结合try/except使用
实例的方法
- str (self):重写object方法,此方法在将对象转换成字符串时会被调用
- save():将模型对象保存到数据表中
- delete():将模型对象从数据表中删除
查询集
- 在管理器上调用过滤器方法会返回查询集
- 查询集经过过滤器筛选后返回新的查询集,因此可以写成链式过滤
- 惰性执行:创建查询集不会带来任何数据库的访问,直到调用数据时,才会访问数据库
- 何时对查询集求值:迭代,序列化,与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
- get():返回单个满足条件的对象
限制查询集
- 查询集返回列表,可以使用下标的方式进行限制,等同于sql中的limit和offset子句
- 注意:不支持负数索引
- 使用下标后返回一个新的查询集,不会立即执行查询
- 如果获取一个对象,直接使用[0],等同于[0:1].get(),但是如果没有数据,[0]引发IndexError异常,[0:1].get()引发DoesNotExist异常
查询集的缓存
- 每个查询集都包含一个缓存来最小化对数据库的访问
- 在新建的查询集中,缓存为空,首次对查询集求值时,会发生数据库查询,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])
- 何时查询集不会被缓存:当只对查询集的部分进行求值时会检查缓存,但是如果这部分不在缓存中,那么接下来查询返回的记录将不会被缓存,这意味着使用索引来限制查询集将不会填充缓存,如果这部分数据已经被缓存,则直接使用缓存中的数据
字段查询
- 实现where子名,作为方法filter()、exclude()、get()的参数
- 语法:属性名称__比较运算符=值
- 表示两个下划线,左侧是属性名称,右侧是比较类型
- 对于外键,使用“属性名_id”表示外键的原始值
- 转义:like语句中使用了%与,匹配数据中的%与,在过滤器中直接写,例如:filter(title__contains="%")=>where title like '%\%%',表示查找标题中包含%的
比较运算符
- exact:表示判等,大小写敏感;如果没有写“ 比较运算符”,表示判等
filter(isDelete=False)
- contains:是否包含,大小写敏感
exclude(btitle__contains='传')
- startswith、endswith:以value开头或结尾,大小写敏感
exclude(btitle__endswith='传')
- isnull、isnotnull:是否为null 这个字段很有用,可以查询类下面所有子类,而不需要对象去查询
In [13]: BookInfo.books.filter(heroinfo__isnull=False)
Out[13]: <QuerySet [<BookInfo: 射雕英雄传>, <BookInfo: 射雕英雄传>, <BookInfo: 射雕英雄传>, <BookInfo: 射雕英雄传>, <BookInfo: 射雕英雄传>, <BookInfo: 天龙八部>, <BookInfo: 天龙八部>, <BookInfo: 天龙八部>, <BookInfo: 天龙八部>, <BookInfo: 笑傲江湖>, <BookInfo: 笑傲江湖>, <BookInfo: 笑傲江湖>, <BookInfo: 笑傲江湖>]>
这里我们第四种书雪山飞狐由于isDelete字段是True,所以没出来,一般我们要查询一个book下面的所有hero,需要拿到book对象,也可以通过这方法来查询所有书的hero
filter(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='八')
- 查询的快捷方式:pk,pk表示primary key,默认的主键是id
filter(pk__lt=6)
聚合函数
- 使用aggregate()函数返回聚合函数的值
- 函数:Avg,Count,Max,Min,Sum
from django.db.models import Max
maxDate = list.aggregate(Max('bpub_date'))
- count的一般用法:
count = list.count()
F对象
-
可以使用模型的字段A与字段B进行比较,如果A写在了等号的左边,则B出现在等号的右边,需要通过F对象构造
list.filter(bread__gte=F('bcommet'))
- django支持对F()对象使用算数运算
list.filter(bread__gte=F('bcommet') * 2)
- F()对象中还可以写作“模型类__列名”进行关联查询
list.filter(isDelete=F('heroinfo__isDelete'))
- 对于date/time字段,可与timedelta()进行运算
list.filter(bpub_date__lt=F('bpub_date') + timedelta(days=1))
Q对象
- 过滤器的方法中关键字参数查询,会合并为And进行
- 需要进行or查询,使用Q()对象
- Q对象(django.db.models.Q)用于封装一组关键字参数,这些关键字参数与“比较运算符”中的相同
from django.db.models import Q
list.filter(Q(pk_ _lt=6))
- Q对象可以使用&(and)、|(or)操作符组合起来
- 当操作符应用在两个Q对象时,会产生一个新的Q对象
list.filter(pk_ _lt=6).filter(bcommet_ _gt=10)
list.filter(Q(pk_ _lt=6) | Q(bcommet_ _gt=10))
- 使用~(not)操作符在Q对象前表示取反
list.filter(~Q(pk__lt=6))
- 可以使用&|~结合括号进行分组,构造做生意复杂的Q对象
- 过滤器函数可以传递一个或多个Q对象作为位置参数,如果有多个Q对象,这些参数的逻辑为and
- 过滤器函数可以混合使用Q对象和关键字参数,所有参数都将and在一起,Q对象必须位于关键字参数的前面
视图详细介绍
- 视图接受Web请求并且返回Web响应
- 视图就是一个python函数,被定义在views.py中
- 响应可以是一张网页的HTML内容,一个重定向,一个404错误等等
- 响应处理过程如下图:
浏览器输入host+port+path---->DJango获取到地址除去host+port解析path---->匹配urls------>传递给views接收request返回response
urlconf
- 在settings.py文件中通过ROOT_URLCONF指定根级url的配置
- urlpatterns是一个url()实例的列表
- 一个url()对象包括:
- 正则表达式
- 视图函数
- 名称name
- 编写URLconf的注意:
- 若要从url中捕获一个值,需要在它周围设置一对圆括号
- 不需要添加一个前导的反斜杠,如应该写作'test/',而不应该写作'/test/'
- 每个正则表达式前面的r表示字符串不转义
- 请求的url被看做是一个普通的python字符串,进行匹配时不包括get或post请求的参数及域名
http://www.google.com/python/1/?i=1&p=new,只匹配“/python/1/”部分
- 正则表达式非命名组,通过位置参数传递给视图
re_path(r'^([0-9]+)/$', views.detail, name='detail'),
- 正则表达式命名组,通过关键字参数传递给视图,本例中关键字参数为id
re_path(r'^(?P<id>[0-9]+)/$', views.detail, name='detail'),
- 参数匹配规则:优先使用命名参数,如果没有命名参数则使用位置参数
- 每个捕获的参数都作为一个普通的python字符串传递给视图
- 性能:urlpatterns中的每个正则表达式在第一次访问它们时被编译,这使得系统相当快
解析流程
新建的项目会有一个和项目同名的文件夹,下面有对应的urls文件,如下
根的urls
"""
from django.contrib import admin
from django.urls import path, re_path, include
urlpatterns = [
path('admin/', admin.site.urls),
re_path(r'^booktest/', include('booktest.urls'))
]
app urls
from django.urls import path, re_path
from . import views
urlpatterns = [
re_path(r'^$', views.index, name='index'),
re_path(r'^book/([0-9]+)$', views.detail, name='detail'),
]
- 匹配过程:先与主URLconf匹配,成功后再用剩余的部分与应用中的URLconf匹配
请求http://www.google.com/booktest/1/
在sesstings.py中的配置:
re_path(r'^booktest/', include('booktest.urls', namespace='booktest')),
在booktest应用urls.py中的配置
re_path(r'^([0-9]+)/$', views.detail, name='detail'),
匹配部分是:/booktest/1/
匹配过程:在settings.py中与“booktest/”成功,再用“1/”与booktest应用的urls匹配
- 使用include可以去除urlconf的冗余
- 参数:视图会收到来自父URLconf、当前URLconf捕获的所有参数
- 在include中通过namespace定义命名空间,用于反解析
错误页面
404 (page not found) 视图
- defaults.page_not_found(request, template_name='404.html')
- 默认的404视图将传递一个变量给模板:request_path,它是导致错误的URL
- 如果Django在检测URLconf中的每个正则表达式后没有找到匹配的内容也将调用404视图
- 如果在settings中DEBUG设置为True,那么将永远不会调用404视图,而是显示URLconf 并带有一些调试信息
- 在templates中创建404.html
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
找不到了
<hr/>
{{request_path}}
</body>
</html>
- 在settings.py中修改调试
DEBUG = False
ALLOWED_HOSTS = ['*', ]
- 请求一个不存在的地址
http://127.0.0.1:8000/test/
500 (server error) 视图
- defaults.server_error(request, template_name='500.html')
- 在视图代码中出现运行时错误
- 默认的500视图不会传递变量给500.html模板
- 如果在settings中DEBUG设置为True,那么将永远不会调用505视图,而是显示URLconf 并带有一些调试信息
400 (bad request) 视图
- defaults.bad_request(request, template_name='400.html')
- 错误来自客户端的操作
- 当用户进行的操作在安全方面可疑的时候,例如篡改会话cookie