人生苦短,我用python。
一、MTV框架 1、MVC框架
大部分开发语言中都有MVC框架。MVC框架的核心思想是:解耦。降低各功能模块之间的耦合性,方便变更,更容易重构代码,最大程度上实现代码的重用。
MVC框架:
Model :数据存取逻辑,主要用于对数据库层的封装。
View :表现逻辑,用于向用户展示结果。
Controller :业务逻辑,是核心,用于处理请求、获取数据、返回结果。
2、MTV框架
Django中 Controller 由框架自行处理,而更关注的是模型(Model)、模板(Template)和视图(Views),所以被称为 MTV 框架 。
MTV框架:
Model :数据存取层,该层处理与数据库相关的交互: 如何存取、如何验证有效。
Template :表现层,该层处理与表现相关的决定: 负责呈现内容到浏览器。
View :业务逻辑层,是核心,负责接收请求、获取数据、返回结果。 可以把它看作模型与模板之间的桥梁。
注意:MTV框架中的 Template 相当于 MVC框架中的 View,MTV框架中的 View 相当于 MVC框架中的 Controller。
Django官方文档 :https://docs.djangoproject.com/zh-hans
二、入门 1、开发环境
创建:mkvirtualenv [虚拟环境名称]
删除:rmvirtualenv [虚拟环境名称]
进入:workon [虚拟环境名称]
退出:deactivate
查看当前的所有虚拟环境:workon [两次tab键/回车键]
查看虚拟环境中已经安装的包:pip list
查看依赖信息保存在requirements文件:pip freeze > requirements.txt
安装Django:pip install django==1.8.2 (建议安装1.8.2版本,这是一个稳定性高、使用广、文档多的版本)
2、创建项目
(1)查看django命令:
django-admin
(2)创建项目
django-admin startproject [项目名称]
BookAndHero/:项目目录
manage.py:一个命令行工具,可以让你在使用 Django 项目时以不同的方式进行交互。
BookAndHero/:内层目录,项目真正的Python包
__init__.py:一个空的文件,用它标识一个目录为 Python 的标准包。
settings.py:Django 项目的配置文件,包括 Django 模块应用配置,数据库配置,模板配置等。
urls.py:Django 项目的 URL 声明。
wsgi.py:项目与 WSGI 兼容的 Web服务器 入口。
(3)查看manage提供的命令:
/项目目录> python manage.py
(4)创建应用:
/项目目录> python manage.py startapp [应用名称]
BookAndHero/
BookAndHero/
book/:应用目录
migrations/:用于记录 models 中数据的变更。
__init__.py:一个空的文件,用它标识一个目录为 Python 的标准包。
admin.py:映射 models 中的数据到 Django 自带的 admin 后台。
apps.py:在新的 Django 版本中新增,用于应用程序的配置。
models.py:创建应用程序数据表模型(对应数据库的相关操作)。
tests.py:创建 Django 测试。
views.py:控制向前端显示哪些数据。
(5)添加应用到项目
BookAndHero/
BookAndHero/
settings.py (在settings.py中添加应用到项目)
1 2 3 4 5 6 7 8 9 INSTALLED_APPS = ( 'django.contrib.admin' , 'django.contrib.auth' , 'django.contrib.contenttypes' , 'django.contrib.sessions' , 'django.contrib.messages' , 'django.contrib.staticfiles' , 'book' , )
(6)启动服务:
python manage.py runserver ip:port
可以不写ip,默认端口为8000
可以修改端口:python manage.py runserver 8080
如果修改文件不需要重启服务器,如果增删文件需要重启服务器
通过ctrl+c停止服务器
3、设计模型
(1)数据表设计:
示例完成“图书-英雄”信息的维护,需要存储两种数据:图书、英雄
图书表结构设计:
表名:BookInfo
图书名称:btitle
图书发布时间:bpub_date
英雄表结构设计:
表名:HeroInfo
英雄姓名:hname
英雄性别:hgender
英雄简介:hcontent
所属图书:hbook
图书-英雄的关系为一对多
(2)数据库配置:
BookAndHero/
BookAndHero/
settings.py (在settings.py中配置默认数据库)
1 2 3 4 5 6 7 8 9 10 11 12 13 DATABASES = { 'default' : { 'ENGINE' : 'django.db.backends.mysql' , 'HOST' : '127.0.0.1' , 'PORT' : '3306' , 'NAME' : 'bookandhero' , 'USER' : 'root' , 'PASSWORD' : '123456' , 'OPTIONS' : { 'init_command' : "SET sql_mode='STRICT_TRANS_TABLES'" , }, } }
注意:Django 默认连接 MySQL 的库是 MySQLdb 库,如果 python3 要使用 pymysql 库需要修改文件:
BookAndHero/
BookAndHero/
__init__.py (在__init__.py中配置默认库)
1 2 3 import pymysqlpymysql.install_as_MySQLdb()
(3)创建模型类:
每一个数据表,都需要创建一个模型类与之对应。模型类继承自 django.db.models.model 。
BookAndHero/
book/
models.py (在models.py中定义模型类)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 from django.db import modelsclass BookInfo (models.Model) : btitle = models.CharField(max_length=20 ) bpub_date = models.DateTimeField() def __str__ (self) : return "%d" % self.pk class HeroInfo (models.Model) : hname = models.CharField(max_length=20 ) hgender = models.BooleanField() hcontent = models.CharField(max_length=100 ) hbook = models.ForeignKey('BookInfo' ) def gender (self) : if self.hgender: return '男' else : return '女' gender.short_description = '性别' def __str__ (self) : return "%d" % self.pk
(4)生成迁移文件(根据模型类生成sql语句):
python manage.py makemigrations
迁移文件被生成到应用的 migrations 目录:
book/
migrations/
__init__.cpython-36.pyc
__init__.py
0001_initial.py
(5)执行迁移(执行sql语句生成数据表):
python manage.py migrate
(6)测试数据操作:
进入 python shell 模式:python manage.py shell
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 from book.models import BookInfo,HeroInfofrom django.utils import timezonefrom datetime import *b = BookInfo(btitle='射雕英雄传' , bpub_date=datetime(1990 ,1 ,10 ,0 ,0 ,0 )) b.save() b = BookInfo() b.btitle = '射雕英雄传' b.bpub_date = datetime(year=1990 , month=1 , day=10 ) b.save() BookInfo.objects.create(btitle='射雕英雄传' , bpub_date=datetime(1990 ,1 ,10 ,0 ,0 ,0 )) b.delete() b.btitle = '天龙八部' b.save() b = BookInfo.objects.get(pk=1 ) b = BookInfo.objects.filter(pk=1 ) b b.id b.btitle BookInfo.objects.all() h = HeroInfo() h.hname = '郭靖' h.hgender = True h.hcontent = '降龙十八掌' h.hbook = b h.save() b.heroinfo_set.all() h = b.heroinfo_set.create(hname=r'黄蓉' , hgender=False , hcontent=r'打狗棒法' )
退出 python shell 模式:quit()
4、添加管理
(1)创建管理员
python manage.py createsuperuser
,然后按提示输入用户名、邮箱、密码。
执行 manage.py 的“migrate”命令时,Django 同时也帮我们生成了 auth_user 表。
启动服务,通过 http://127.0.0.1:8000/admin/ 来访问 Django 自带的 Admin 管理后台。
进入管理站点,默认可以对groups、users进行管理。
(2)管理界面本地化
编辑settings.py文件,设置编码、时区
1 2 LANGUAGE_CODE = 'zh-Hans' TIME_ZONE = 'Asia/Shanghai'
(3)向admin注册数据表模型
BookAndHero/
book/
admin.py (在admin.py中注册模型)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 from django.contrib import adminfrom book.models import BookInfo from book.models import HeroInfoclass HeroInfoInline (admin.StackedInline) : model = HeroInfo extra = 2 class BookInfoAdmin (admin.ModelAdmin) : list_display = ['id' , 'btitle' , 'bpub_date' ] list_filter = ['btitle' ] search_fields = ['btitle' ] list_per_page = 10 fieldsets = [ ('basic' , {'fields' :['btitle' ]}), ('more' , {'fields' :['bpub_date' ]}) ] inlines = [HeroInfoInline] class HeroInfoAdmin (admin.ModelAdmin) : list_display = ['id' , 'hname' , 'gender' , 'hcontent' ] admin.site.register(BookInfo, BookInfoAdmin) admin.site.register(HeroInfo, HeroInfoAdmin)
4、添加视图
视图就是被定义在 views.py 中一个 Python函数,接受Web请求并且返回Web响应。
视图接收 reqeust对象 作为第一个参数,包含了请求的信息。
视图的响应可以是一张网页的HTML内容,一个重定向,一个404错误等等。
(1)定义视图:
BookAndHero/
book/
views.py (在views.py中定义视图)
1 2 3 4 5 6 7 8 9 10 from django.shortcuts import renderfrom django.http import HttpResponsedef index (request) : return HttpResponse('index' ) def detail (request, id) : return HttpResponse('detail %s' % id)
(2)配置urlconf
Django使用正则表达式匹配请求的URL,一旦匹配成功,则调用应用的视图。
1) 可以先在 BookAndHero/urls.py 中插入 book,使 主urlconf 连接到 book.urls模块。
BookAndHero/
BookAndHero/
urls.py (在主urls.py中添加url路由)
1 2 3 4 5 6 7 8 from django.conf.urls import include, urlfrom django.contrib import adminurlpatterns = [ url(r'^admin/' , include(admin.site.urls)), url(r'^' , include('book.urls' )), ]
2) 在 book 中新建 urls.py,添加urlconf
BookAndHero/
1 2 3 4 5 6 7 from django.conf.urls import urlfrom book import viewsurlpatterns = [ url(r'^$' , views.index), url(r'^([0-9]+)/$' , views.detail), ]
5、添加模板
(1)创建模板目录
BookAndHero/
BookAndHero/
book/
templates/ (可以在工程根目录下创建模板目录,存放所有应用的模板)
(2)修改 settings.py 中模板默认路径
BookAndHero/
BookAndHero/
settings.py(在settings.py文件中设置模板默认路径)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 TEMPLATES = [ { 'BACKEND' : 'django.template.backends.django.DjangoTemplates' , 'DIRS' : [[os.path.join(BASE_DIR, 'templates' )], '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' , ], }, }, ]
(3)创建应用的模板
在模板中访问视图传递的数据:
{ { 输出值,可以是变量,也可以是对象.属性 } }
{ % 执行代码段 % }
在模板中访问对象成员时,都以属性的方式访问,即方法也不能加括号
templates/
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <!DOCTYPE html> <html > <head > <title > 首页</title > </head > <body > <h1 > 图书列表</h1 > <ul > {%for book in booklist%} <li > <a href ="{{book.id}}" > {{book.btitle}}</a > </li > {%endfor%} </ul > </body > </html >
templates/
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <!DOCTYPE html> <html > <head > <title > 详细页</title > </head > <body > <h1 > {{book.btitle}}</h1 > <ul > {%for hero in book.heroinfo_set.all%} <li > {{hero.hname}}---{{hero.hcontent}} </li > {%endfor%} </ul > </body > </html >
(4)在视图的函数中调用模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 from django.shortcuts import renderfrom django.http import HttpResponsefrom django.template import RequestContext,loaderfrom book.models import BookInfodef index (request) : booklist = BookInfo.objects.all() template = loader.get_template('book/index.html' ) context = RequestContext(request, {'booklist' :booklist}) return HttpResponse(template.render(context)) def detail (request, id) : book = BookInfo.objects.get(pk=id) template = loader.get_template('book/detail.html' ) context = RequestContext(request, {'book' :book}) return HttpResponse(template.render(context))
三、模型 MVC框架中包括一个重要的部分,就是ORM,它实现了数据模型与数据库的解耦,即数据模型的设计不需要依赖于特定的数据库,通过简单的配置就可以轻松更换数据库。
ORM是“对象-关系-映射”的简称,主要任务是:
根据对象的类型生成表结构
将对象、列表的操作,转换为sql语句
将sql查询到的结果转换为对象、列表
开发流程:
在models.py中定义模型类,要求继承自models.Model
把应用加入settings.py文件的installed_app项
生成迁移文件
执行迁移生成表
使用模型类进行crud操作
使用数据库生成模型类:
python manage.py inspectdb > 应用名称/models.py
1、定义模型
(1)定义模型:
在模型中定义属性,会生成表中的字段
django根据属性的类型确定以下信息:
当前选择的数据库支持字段的类型
渲染管理表单时使用的默认html控件
在管理站点最低限度的验证
(2)定义属性:
定义属性时,需要字段类型
使用方式
导入from django.db import models
通过models.Field创建字段类型的对象,赋值给属性
属性命名限制
不能是python的保留关键字
由于django的查询方式,不允许使用连续的下划线
django会为表增加自动增长的主键列
每个模型只能有一个主键列
如果使用选项设置某属性为主键列后,则django不会再生成默认的主键列
对于重要数据都做逻辑删除,不做物理删除
实现方法是定义 isDelete 属性,类型为 BooleanField ,默认值为 False
(3)字段类型:
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
(4)字段选项
通过字段选项,可以实现对字段的约束 在字段对象时通过关键字参数指定
null :如果为True,Django 将空值以 NULL 存储到数据库中,默认值是 False
blank :如果为True,则该字段允许为空白(表单验证证范畴的),默认值是 False
db_column :字段的名称,如果未指定,则使用属性的名称
db_index :若值为 True, 则在表中会为此字段创建索引
default :默认值
primary_key :若为 True, 则该字段会成为模型的主键字段
unique :如果为 True, 这个字段在表中必须有唯一值
(5)关系
关系的类型:
ForeignKey:一对多,将字段定义在多的端中
ManyToManyField:多对多,将字段定义在两端中
OneToOneField:一对一,将字段定义在任意一端中
可以维护递归的关联关系,使用’self’指定,详见“自关联”
用一访问多:对象.模型类小写_set
用一访问一:对象.模型类小写
访问id:对象.属性_id
(6)元选项
在模型类中定义类Meta,用于设置元信息
2、模型成员
(1)管理器对象
当定义模型类时没有指定管理器,则 Django 会为模型类提供一个名为 objects 的管理器
objects :是 Manager 类型的对象,用于与数据库进行交互(ORM)
支持自定义指定模型类的管理器对象,当为模型类指定管理器后,django不再为模型类生成名为 objects 的默认管理器1 2 3 class BookInfo (models.Model) : ... books = models.Manager()
(2)管理器 Manager
管理器 Manager 是 Django 的模型进行数据库的查询操作的接口,Django应用 的每个模型都拥有至少一个管理器
管理器也支持自定义,自定义管理器类主要用于两种情况:
情况一:向管理器类中添加额外的方法
情况二:修改管理器返回的原始查询集:重写get_queryset()方法
1 2 3 4 5 6 7 class BookInfoManager (models.Manager) : def get_queryset (self) : return super(BookInfoManager, self).get_queryset().filter(isDelete=False ) class BookInfo (models.Model) : ... books = BookInfoManager()
(3)创建对象
当创建对象时,django不会对数据库进行读写操作
调用save()方法才与数据库交互,将对象保存到数据库中
使用关键字参数构造模型对象很麻烦,推荐使用下面的两种之式
说明:_init_方法已经在基类models.Model中使用,在自定义模型中无法使用
1 2 3 4 5 6 7 8 9 10 11 12 13 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 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() 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()
(4)实例的属性
DoesNotExist:在进行单个查询时,模型的对象不存在时会引发此异常,结合try/except使用
(5)实例的方法
str (self) :重写object方法,此方法在将对象转换成字符串时会被调用
save() :将模型对象保存到数据表中
delete() :将模型对象从数据表中删除
3、模型查询
查询集表示从数据库中获取的对象集合
查询集可以含有零个、一个或多个过滤器
过滤器基于所给的参数限制查询的结果
从Sql的角度,查询集和select语句等价,过滤器像where和limit子句
(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
(2)限制查询集
查询集返回列表,可以使用下标的方式进行限制,等同于sql中的limit和offset子句
注意:不支持负数索引
使用下标后返回一个新的查询集,不会立即执行查询
如果获取一个对象,直接使用[0],等同于[0:1].get(),但是如果没有数据,[0]引发IndexError异常,[0:1].get()引发DoesNotExist异常
(3)查询集的缓存
每个查询集都包含一个缓存来最小化对数据库的访问
在新建的查询集中,缓存为空,首次对查询集求值时,会发生数据库查询,django会将查询的结果存在查询集的缓存中,并返回请求的结果,接下来对查询集求值将重用缓存的结果
情况一:这构成了两个查询集,无法重用缓存,每次查询都会与数据库进行一次交互,增加了数据库的负载
1 2 print([e.title for e in Entry.objects.all()]) print([e.title for e in Entry.objects.all()])
情况二:两次循环使用同一个查询集,第二次使用缓存中的数据
1 2 3 querylist=Entry.objects.all() print([e.title for e in querylist]) print([e.title for e in querylist])
何时查询集不会被缓存:当只对查询集的部分进行求值时会检查缓存,但是如果这部分不在缓存中,那么接下来查询返回的记录将不会被缓存,这意味着使用索引来限制查询集将不会填充缓存,如果这部分数据已经被缓存,则直接使用缓存中的数据
(4)字段查询
实现where子名,作为方法filter()、exclude()、get()的参数
语法:属性名称__比较运算符=值
表示两个下划线,左侧是属性名称,右侧是比较类型
对于外键,使用“属性名_id ”表示外键的原始值
转义:like语句中使用了%与,匹配数据中的%与,在过滤器中直接写,例如:filter(title__contains=”%”)=>where title like ‘%\%%’,表示查找标题中包含%的
(5)比较运算符
exact:表示判等,大小写敏感;如果没有写“ 比较运算符”,表示判等
1 exclude(btitle__contains='传' )
startswith、endswith:以value开头或结尾,大小写敏感
1 exclude(btitle__endswith='传' )
1 filter(btitle__isnull=False )
在前面加个i表示不区分大小写,如iexact、icontains、istarswith、iendswith
in:是否包含在范围内
1 filter(pk__in=[1 , 2 , 3 , 4 , 5 ])
gt、gte、lt、lte:大于、大于等于、小于、小于等于
year、month、day、week_day、hour、minute、second:对日期间类型的属性进行运算
1 2 filter(bpub_date__year=1980 ) filter(bpub_date__gt=date(1980 , 12 , 31 ))
跨关联关系的查询:处理join查询
语法:模型类名 <属性名> <比较>
注:可以没有__<比较>部分,表示等于,结果同inner join
可返向使用,即在关联的两个模型中都可以使用
1 filter(heroinfo__hcontent__contains='八' )
查询的快捷方式:pk,pk表示primary key,默认的主键是id
(6)聚合函数
使用aggregate()函数返回聚合函数的值
函数:Avg,Count,Max,Min,Sum
1 2 from django.db.models import MaxmaxDate = list.aggregate(Max('bpub_date' ))
(7)F对象
可以使用模型的字段A与字段B进行比较,如果A写在了等号的左边,则B出现在等号的右边,需要通过F对象构造
1 list.filter(bread__gte=F('bcommet' ))
1 list.filter(bread__gte=F('bcommet' ) * 2 )
F()对象中还可以写作“模型类__列名”进行关联查询
1 list.filter(isDelete=F('heroinfo__isDelete' ))
对于date/time字段,可与timedelta()进行运算
1 list.filter(bpub_date__lt=F('bpub_date' ) + timedelta(days=1 ))
(8)Q对象
过滤器的方法中关键字参数查询,会合并为And进行,需要进行or查询,使用Q()对象
Q对象(django.db.models.Q)用于封装一组关键字参数,这些关键字参数与“比较运算符”中的相同
1 2 from django.db.models import Qlist.filter(Q(pk__lt=6 ))
Q对象可以使用&(and)、|(or)操作符组合起来
当操作符应用在两个Q对象时,会产生一个新的Q对象
1 2 list.filter(pk__lt=6 ).filter(bcommet__gt=10 ) list.filter(Q(pk__lt=6 ) | Q(bcommet__gt=10 ))
1 list.filter(~Q(pk__lt=6 ))
可以使用&|~结合括号进行分组,构造做生意复杂的Q对象
过滤器函数可以传递一个或多个Q对象作为位置参数,如果有多个Q对象,这些参数的逻辑为and
过滤器函数可以混合使用Q对象和关键字参数,所有参数都将and在一起,Q对象必须位于关键字参数的前面
4、自连接
对于地区信息,属于一对多关系,使用一张表,存储所有的信息
类似的表结构还应用于分类信息,可以实现无限级分类
新建模型AreaInfo,生成迁移
1 2 3 class AreaInfo (models.Model) : atitle = models.CharField(max_length=20 ) aParent = models.ForeignKey('self' , null=True , blank=True )
1 2 上级对象:area.aParent 下级对象:area.areainfo_set.all()
加入测试数据
在book/views.py中定义视图area
1 2 3 4 from models import AreaInfodef area (request) : area = AreaInfo.objects.get(pk=130100 ) return render(request, 'book/area.html' , {'area' : area})
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <!DOCTYPE html> <html > <head > <title > 地区</title > </head > <body > 当前地区:{{area.atitle}} <hr /> 上级地区:{{area.aParent.atitle}} <hr /> 下级地区: <ul > { %for a in area.areainfo_set.all%} <li > {{a.atitle}}</li > { %endfor%} </ul > </body > </html >
在book/urls.py中配置一个新的urlconf
1 2 3 urlpatterns = [ url(r'^area/$' , views.area, name='area' ) ]
四、视图 1、URLconf
(1)URLconf
在settings.py文件中通过ROOT_URLCONF指定根级url的配置
urlpatterns是一个url()实例的列表
一个url()对象包括:
编写URLconf的注意:
若要从url中捕获一个值,需要在它周围设置一对圆括号
不需要添加一个前导的反斜杠,如应该写作’test/‘,而不应该写作’/test/‘
每个正则表达式前面的r表示字符串不转义
请求的url被看做是一个普通的python字符串,进行匹配时不包括get或post请求的参数及域名
1 http://www.test.cn/python/1/?i=1&p=new,只匹配“/python/1/”部分
1 url(r'^([0-9]+)/$' , views.detail, name='detail' ),
正则表达式命名组,通过关键字参数传递给视图,本例中关键字参数为id
1 url(r'^(?P<id>[0-9]+)/$' , views.detail, name='detail' ),
参数匹配规则:优先使用命名参数,如果没有命名参数则使用位置参数
每个捕获的参数都作为一个普通的python字符串传递给视图
性能:urlpatterns中的每个正则表达式在第一次访问它们时被编译,这使得系统相当快
(2)包含其它的URLconfs
在应用中创建urls.py文件,定义本应用中的urlconf,再在项目的settings中使用include()
1 2 3 4 from django.conf.urls import include, urlurlpatterns = [ url(r'^' , include('booktest.urls' , namespace='book' )), ]
匹配过程:先与主URLconf匹配,成功后再用剩余的部分与应用中的URLconf匹配
1 2 3 4 5 6 7 请求http://www.test.cn/book/1/ 在sestings.py中的配置: url(r'^book/', include('book.urls', namespace='book')), 在bookt应用urls.py中的配置 url(r'^([0-9]+)/$', views.detail, name='detail'), 匹配部分是:/book/1/ 匹配过程:在settings.py中与“book/”成功,再用“1/”与booktest应用的urls匹配
使用include可以去除urlconf的冗余
参数:视图会收到来自父URLconf、当前URLconf捕获的所有参数
在include中通过namespace定义命名空间,用于反解析
(3)URL的反向解析
如果在视图、模板中使用硬编码的链接,在urlconf发生改变时,维护是一件非常麻烦的事情
解决:在做链接时,通过指向urlconf的名称,动态生成链接地址
视图:使用django.core.urlresolvers.reverse()函数
模板:使用url模板标签
2、视图函数
(1)定义视图
本质就是一个函数
视图的参数
一个HttpRequest实例
通过正则表达式组获取的位置参数
通过正则表达式组获得的关键字参数
在应用目录下默认有views.py文件,一般视图都定义在这个文件中
如果处理功能过多,可以将函数定义到不同的py文件中
1 2 3 4 5 6 7 8 9 新建views1.py #coding:utf-8 from django.http import HttpResponse def index(request): return HttpResponse("你好") 在urls.py中修改配置 from . import views1 url(r'^$', views1.index, name='index'),
(2)错误视图
Django原生自带几个默认视图用于处理HTTP错误
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 并带有一些调试信息
1 2 3 4 5 6 7 8 9 10 11 <!DOCTYPE html> <html > <head > <title > </title > </head > <body > 找不到了 <hr /> {{request_path}} </body > </html >
1 2 DEBUG = False ALLOWED_HOSTS = ['*', ]
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
3、Reqeust对象
服务器接收到http协议的请求后,会根据报文创建HttpRequest对象
视图函数的第一个参数是HttpRequest对象
在django.http模块中定义了HttpRequest对象的API
(1)属性
下面除非特别说明,属性都是只读的:
path:一个字符串,表示请求的页面的完整路径,不包含域名
method:一个字符串,表示请求使用的HTTP方法,常用值包括:’GET’、’POST’
encoding:一个字符串,表示提交的数据的编码方式
如果为None则表示使用浏览器的默认设置,一般为utf-8
这个属性是可写的,可以通过修改它来修改访问表单数据使用的编码,接下来对属性的任何访问将使用新的encoding值
GET:一个类似于字典的对象,包含get请求方式的所有参数
POST:一个类似于字典的对象,包含post请求方式的所有参数
FILES:一个类似于字典的对象,包含所有的上传文件
COOKIES:一个标准的Python字典,包含所有的cookie,键和值都为字符串
session:一个既可读又可写的类似于字典的对象,表示当前的会话,只有当Django 启用会话的支持时才可用,详细内容见“状态保持”
(2)方法
is_ajax():如果请求是通过XMLHttpRequest发起的,则返回True
(3)QueryDict对象
定义在django.http.QueryDict
request对象的属性GET、POST都是QueryDict类型的对象
与python字典不同,QueryDict类型的对象用来处理同一个键带有多个值的情况
方法get():根据键获取值
只能获取键的一个值
如果一个键同时拥有多个值,获取最后一个值
1 2 3 dict.get('键',default) 或简写为 dict['键']
1 dict.getlist('键',default)
(4)GET属性
QueryDict类型的对象
包含get请求方式的所有参数
与url请求地址中的参数对应,位于?后面
参数的格式是键值对,如key1=value1
多个参数之间,使用&连接,如key1=value1&key2=value2
键是开发人员定下来的,值是可变的
示例如下,创建视图getTest1用于定义链接,getTest2用于接收一键一值,getTest3用于接收一键多值
1 2 3 4 5 6 7 8 9 10 11 12 def getTest1 (request) : return render(request,'book/getTest1.html' ) def getTest2 (request) : a=request.GET['a' ] b=request.GET['b' ] context={'a' :a,'b' :b} return render(request,'booktest/getTest2.html' ,context) def getTest3 (request) : a=request.GET.getlist('a' ) b=request.GET['b' ] context={'a' :a,'b' :b} return render(request,'booktest/getTest3.html' ,context)
1 2 3 url(r'^getTest1/$' , views.getTest1), url(r'^getTest2/$' , views.getTest2), url(r'^getTest3/$' , views.getTest3),
1 2 3 4 5 6 7 8 9 10 11 <html > <head > <title > Title</title > </head > <body > 链接1:一个键传递一个值 <a href ="/getTest2/?a=1&b=2" > gettest2</a > <br > 链接2:一个键传递多个值 <a href ="/getTest3/?a=1&a=2&b=3" > gettest3</a > </body > </html >
1 2 3 4 5 6 7 8 9 <html > <head > <title > Title</title > </head > <body > a:{{ a }}<br > b:{{ b }} </body > </html >
1 2 3 4 5 6 7 8 9 10 11 12 <html > <head > <title > Title</title > </head > <body > a:{% for item in a %} {{ item }} {% endfor %} <br > b:{{ b }} </body > </html >
(5)POST属性
QueryDict类型的对象
包含post请求方式的所有参数
与form表单中的控件对应
表单中哪些控件会被提交?
控件要有name属性,则name属性的值为键,value属性的值为键,构成键值对提交
对于checkbox控件,name属性一样为一组,当控件被选中后会被提交,存在一键多值的情况
键是开发人员定下来的,值是可变的
示例如下,定义视图postTest1,创建视图postTest2接收请求的数据
1 2 3 4 5 6 7 8 9 def postTest1 (request) : return render(request,'booktest/postTest1.html' ) def postTest2 (request) : uname=request.POST['uname' ] upwd=request.POST['upwd' ] ugender=request.POST['ugender' ] uhobby=request.POST.getlist('uhobby' ) context={'uname' :uname,'upwd' :upwd,'ugender' :ugender,'uhobby' :uhobby} return render(request,'booktest/postTest2.html' ,context)
1 2 url(r'^postTest1$',views.postTest1) url(r'^postTest2$',views.postTest2)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <html > <head > <title > Title</title > </head > <body > <form method ="post" action ="/postTest2/" > 姓名:<input type ="text" name ="uname" /> <br > 密码:<input type ="password" name ="upwd" /> <br > 性别:<input type ="radio" name ="ugender" value ="1" /> 男 <input type ="radio" name ="ugender" value ="0" /> 女<br > 爱好:<input type ="checkbox" name ="uhobby" value ="胸口碎大石" /> 胸口碎大石 <input type ="checkbox" name ="uhobby" value ="跳楼" /> 跳楼 <input type ="checkbox" name ="uhobby" value ="喝酒" /> 喝酒 <input type ="checkbox" name ="uhobby" value ="爬山" /> 爬山<br > <input type ="submit" value ="提交" /> </form > </body > </html >
1 2 3 4 5 6 7 8 9 10 11 <html > <head > <title > Title</title > </head > <body > {{ uname }}<br > {{ upwd }}<br > {{ ugender }}<br > {{ uhobby }} </body > </html >
注意:使用表单提交,注释掉settings.py中的中间件crsf
4、Response对象
在django.http模块中定义了HttpResponse对象的API
HttpRequest对象由Django自动创建,HttpResponse对象由程序员创建
不调用模板,直接返回数据
1 2 3 4 5 from django.http import HttpResponsedef index (request) : return HttpResponse('你好' )
1 2 3 4 5 6 7 from django.http import HttpResponsefrom django.template import RequestContext, loaderdef index (request) : t1 = loader.get_template('polls/index.html' ) context = RequestContext(request, {'h1' : 'hello' }) return HttpResponse(t1.render(context))
(1)属性
content:表示返回的内容,字符串类型
charset:表示response采用的编码字符集,字符串类型
status_code:响应的HTTP响应状态码
content-type:指定输出的MIME类型
(2)方法
init :使用页内容实例化HttpResponse对象
write(content):以文件的方式写
flush():以文件的方式输出缓存区
set_cookie(key, value=’’, max_age=None, expires=None):设置Cookie
key、value都是字符串类型
max_age是一个整数,表示在指定秒数后过期
expires是一个datetime或timedelta对象,会话将在这个指定的日期/时间过期,注意datetime和timedelta值只有在使用PickleSerializer时才可序列化
max_age与expires二选一
如果不指定过期时间,则两个星期后过期
1 2 3 4 5 6 7 8 9 10 from django.http import HttpResponsefrom datetime import *def index (request) : response = HttpResponse() if request.COOKIES.has_key('h1' ): response.write('<h1>' + request.COOKIES['h1' ] + '</h1>' ) response.set_cookie('h1' , '你好' , 120 ) return response
delete_cookie(key):删除指定的key的Cookie,如果key不存在则什么也不发生
(3)子类HttpResponseRedirect
重定向,服务器端跳转
构造函数的第一个参数用来指定重定向的地址
1 2 3 4 5 6 7 8 9 from django.http import HttpResponse,HttpResponseRedirectdef index (request) : return HttpResponseRedirect('js/' ) def index2 (request,id) : return HttpResponse(id) url(r'^([0-9]+)/$' , views1.index2, name='index2' ),
请求地址
127.0.0.1:8000/
127.0.0.1:8000/js/
推荐使用反向解析
1 2 3 4 from django.core.urlresolvers import reversedef index (request) : return HttpResponseRedirect(reverse('book:index2' , args=(1 ,)))
(4)子类JsonResponse
返回json数据,一般用于异步请求
_init_(data)
帮助用户创建JSON编码的响应
参数data是字典对象
JsonResponse的默认Content-Type为application/json
1 2 3 4 from django.http import JsonResponsedef index2 (requeset) : return JsonResponse({'list' : 'abc' })
5、简写函数
(1)render
render(request, template_name[, context])
结合一个给定的模板和一个给定的上下文字典,并返回一个渲染后的HttpResponse对象
request:该request用于生成response
template_name:要使用的模板的完整名称
context:添加到模板上下文的一个字典,视图将在渲染模板之前调用它
1 2 3 4 from django.shortcuts import renderdef index (request) : return render(request, 'book/index.html' , {'h1' : 'hello' })
(2)重定向
redirect(to)
为传递进来的参数返回HttpResponseRedirect
to推荐使用反向解析
1 2 3 4 5 from django.shortcuts import redirectfrom django.core.urlresolvers import reversedef index (request) : return redirect(reverse('book:index2' ))
(3)得到对象或返回404
get_object_or_404(klass, args, *kwargs)
通过模型管理器或查询集调用get()方法,如果没找到对象,不引发模型的DoesNotExist异常,而是引发Http404异常
klass:获取对象的模型类、Manager对象或QuerySet对象
**kwargs:查询的参数,格式应该可以被get()和filter()接受
如果找到多个对象将引发MultipleObjectsReturned异常
1 2 3 4 5 6 7 8 9 10 11 from django.shortcuts import *def detail (request, id) : try : book = get_object_or_404(BookInfo, pk=id) except BookInfo.MultipleObjectsReturned: book = None return render(request, 'book/detail.html' , {'book' : book})
(4)得到列表或返回404
get_list_or_404(klass, args, *kwargs)
klass:获取列表的一个Model、Manager或QuerySet实例
**kwargs:查寻的参数,格式应该可以被get()和filter()接受
1 2 3 4 5 6 7 8 from django.shortcuts import *def index (request) : list = get_list_or_404(BookInfo, pk__lt=6 ) return render(request, 'booktest/index.html' , {'list' : list})
6、状态保持
http协议是无状态的:每次请求都是一次新的请求,不会记得之前通信的状态
客户端与服务器端的一次通信,就是一次会话
实现状态保持的方式:在客户端或服务器端存储与会话有关的数据
存储方式包括cookie、session,会话一般指session对象
使用cookie,所有数据存储在客户端,注意不要存储敏感信息
推荐使用sesison方式,所有数据存储在服务器端,在客户端cookie中存储session_id
状态保持的目的是在一段时间内跟踪请求者的状态,可以实现跨页面访问当前请求者的数据
注意:不同的请求者之间不会共享这个数据,与请求者一一对应
(1)启用session
使用django-admin startproject创建的项目默认启用
在settings.py文件中
1 2 3 4 5 项INSTALLED_APPS列表中添加: 'django.contrib.sessions', 项MIDDLEWARE_CLASSES列表中添加: 'django.contrib.sessions.middleware.SessionMiddleware',
禁用会话:删除上面指定的两个值,禁用会话将节省一些性能消耗
(2)使用session
启用会话后,每个HttpRequest对象将具有一个session属性,它是一个类字典对象
get(key, default=None):根据键获取会话的值
clear():清除所有会话
flush():删除当前的会话数据并删除会话的Cookie
del request.session[‘member_id’]:删除会话
在views.py文件中创建视图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 from django.shortcuts import render, redirectfrom django.core.urlresolvers import reversedef index (request) : uname = request.session.get('uname' ) return render(request, 'book/index.html' , {'uname' : uname}) def login (request) : return render(request, 'book/login.html' ) def login_handle (request) : request.session['uname' ] = request.POST['uname' ] return redirect(reverse('main:index' )) def logout (request) : request.session.flush() return redirect(reverse('main:index' ))
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from django.conf.urls import include, urlurlpatterns = [ url(r'^' , include('book.urls' , namespace='main' )) ] from django.conf.urls import urlfrom . import viewsurlpatterns = [ url(r'^$' , views.index, name='index' ), url(r'login/$' , views.login, name='login' ), url(r'login_handle/$' , views.login_handle, name='login_handle' ), url(r'logout/$' , views.logout, name='logout' ) ]
1 2 3 4 5 6 7 8 9 10 11 12 13 <!DOCTYPE html> <html > <head > <title > 首页</title > </head > <body > 你好:{{uname}} <hr /> <a href ="{%url 'main:login'%}" > 登录</a > <hr /> <a href ="{%url 'main:logout'%}" > 退出</a > </body > </html >
1 2 3 4 5 6 7 8 9 10 11 12 <!DOCTYPE html> <html > <head > <title > 登录</title > </head > <body > <form method ="post" action ="/login_handle/" > <input type ="text" name ="uname" /> <input type ="submit" value ="登录" /> </form > </body > </html >
(3)会话过期时间
set_expiry(value):设置会话的超时时间
如果没有指定,则两个星期后过期
如果value是一个整数,会话将在values秒没有活动后过期
若果value是一个imedelta对象,会话将在当前时间加上这个指定的日期/时间过期
如果value为0,那么用户会话的Cookie将在用户的浏览器关闭时过期
如果value为None,那么会话永不过期
修改视图中login_handle函数,查看效果
1 2 3 4 5 6 7 def login_handle (request) : request.session['uname' ] = request.POST['uname' ] return redirect(reverse('main:index' ))
(4)存储session
使用存储会话的方式,可以使用settings.py的SESSION_ENGINE项指定
基于数据库的会话:这是django默认的会话存储方式,需要添加django.contrib.sessions到的INSTALLED_APPS设置中,运行manage.py migrate在数据库中安装会话表,可显示指定为
1 SESSION_ENGINE='django.contrib.sessions.backends.db'
基于缓存的会话:只存在本地内在中,如果丢失则不能找回,比数据库的方式读写更快
1 SESSION_ENGINE='django.contrib.sessions.backends.cache'
可以将缓存和数据库同时使用:优先从本地缓存中获取,如果没有则从数据库中获取
1 SESSION_ENGINE='django.contrib.sessions.backends.cached_db'
(5)使用Redis缓存session
会话还支持文件、纯cookie、Memcached、Redis等方式存储,下面演示使用redis存储
安装包
1 pip install django-redis-sessions
1 2 3 4 5 6 SESSION_ENGINE = 'redis_sessions.session' SESSION_REDIS_HOST = 'localhost' SESSION_REDIS_PORT = 6379 SESSION_REDIS_DB = 0 SESSION_REDIS_PASSWORD = '' SESSION_REDIS_PREFIX = 'session'
1 2 3 4 5 6 7 启动:sudo redis-server /etc/redis/redis.conf 停止:sudo redis-server stop 重启:sudo redis-server restart redis-cli:使用客户端连接服务器 keys *:查看所有的键 get name:获取指定键的值 del name:删除指定名称的键
五、模板
作为Web框架,Django提供了模板,可以很便利的动态生成HTML
模版系统致力于表达外观,而不是程序逻辑
模板的设计实现了业务逻辑(view)与显示内容(template)的分离,一个视图可以使用任意一个模板,一个模板可以供多个视图使用
模板包含
Django模板语言,简写DTL,定义在django.template包中
由startproject命令生成的settings.py定义关于模板的值:
DIRS定义了一个目录列表,模板引擎按列表顺序搜索这些目录以查找模板源文件
APP_DIRS告诉模板引擎是否应该在每个已安装的应用中查找模板
常用方式:在项目的根目录下创建templates目录,设置DIRS值1 DIRS=[os.path.join(BASE_DIR,"templates")]
1、模板处理
Django处理模板分为两个阶段
Step1 加载:根据给定的标识找到模板然后预处理,通常会将它编译好放在内存中
1 loader.get_template(template_name),返回一个Template对象
Step2 渲染:使用Context数据对模板插值并返回生成的字符串
1 Template对象的render(RequestContext)方法,使用context渲染模板
1 2 3 4 5 6 7 from django.template import loader, RequestContextfrom django.http import HttpResponsedef index (request) : tem = loader.get_template('temtest/index.html' ) context = RequestContext(request, {}) return HttpResponse(tem.render(context))
为了减少加载模板、渲染模板的重复代码,django提供了快捷函数
render_to_string(“”)
render(request,’模板’,context)
1 2 3 4 from django.shortcuts import renderdef index (request) : return render(request, 'temtest/index.html' )
2、定义模板
模板语言包括:
变量 { { 变量 } }
标签 { % 代码块 % }
过滤器 { { 变量|过滤器 } }
注释{ # 代码或html # }
(1)变量
当模版引擎遇到一个变量,将计算这个变量,然后将结果输出
变量名必须由字母、数字、下划线(不能以下划线开头)和点组成
当模版引擎遇到点(“.”),会按照下列顺序查询:
字典查询,例如:foo[“bar”]
属性或方法查询,例如:foo.bar
数字索引查询,例如:foo[bar]
如果变量不存在, 模版系统将插入’’ (空字符串)
在模板中调用方法时不能传递参数
在模板中调用对象的方法:
1 2 3 4 5 6 from django.db import modelsclass HeroInfo (models.Model) : ... def showName (self) : return self.hname
1 2 3 4 5 6 7 from django.shortcuts import renderfrom models import *def index (request) : hero = HeroInfo(hname='abc' ) context = {'hero' : hero} return render(request, 'temtest/detail.html' , context)
(2)标签
语法:{ % tag % }
作用
在输出中创建文本
控制循环或逻辑
加载外部信息到模板中供以后的变量使用
for标签
1 2 3 4 5 6 7 {% for ... in ... %} 循环逻辑 {{ forloop.counter }}表示当前是第几次循环 {% empty %}给出的列表为或列表不存在时,执行此处 {% endfor %}
1 2 3 4 5 6 7 {% if ... %} 逻辑1 {% elif ... %} 逻辑2 {% else %} 逻辑3 {% endif %}
1 2 3 { % comment % } 多行注释 { % endcomment % }
1 { %include "foo/bar.html" % }
csrf_token:这个标签用于跨站请求伪造保护
布尔标签:and、or,and比or的优先级高
block、extends:详见“模板继承”
autoescape:详见“HTML转义”
(3)过滤器
语法:{ { 变量|过滤器 }},例如{ { name|lower }},表示将变量name的值变为小写输出
使用管道符号 (|)来应用过滤器
通过使用过滤器来改变变量的计算结果
可以在if标签中使用过滤器结合运算符
default:如果一个变量没有被提供,或者值为false或空,则使用默认值,否则使用变量的值
(4)注释
1 {# { % if foo % }bar{ % else % } #}
3、模板继承
(1)模板继承
模板继承可以减少页面内容的重复定义,实现页面内容的重用
典型应用:网站的头部、尾部是一样的,这些内容可以定义在父模板中,子模板不需要重复定义
block标签:在父模板中预留区域,在子模板中填充
extends继承:继承,写在模板文件的第一行
定义父模板base.html
1 2 3 4 {% block block_name %} 这里可以定义默认值 如果不定义默认值,则表示空字符串 {% endblock %}
1 {% extends "base.html" %}
1 2 3 { %block block_name%} 实际填充内容 { %endblock%}
(2)说明
如果在模版中使用extends标签,它必须是模版中的第一个标签
不能在一个模版中定义多个相同名字的block标签
子模版不必定义全部父模版中的blocks,如果子模版没有定义block,则使用了父模版中的默认值
如果发现在模板中大量的复制内容,那就应该把内容移动到父模板中
使用可以获取父模板中block的内容
为了更好的可读性,可以给endblock标签一个名字
1 2 3 { % block block_name %} 区域内容 { % endblock block_name %}
(3)三层继承结构
三层继承结构(头部、尾部都一样)使代码得到最大程度的复用,并且使得添加内容更加简单。
1)创建根级模板
名称为“base.html”
存放整个站点共用的内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <!DOCTYPE html> <html > <head > <title > {%block title%}{%endblock%} 水果超市</title > </head > <body > top--{{logo}} <hr /> {%block left%}{%endblock%} {%block content%}{%endblock%} <hr /> bottom </body > </html >
2)创建分支模版
继承自base.html
名为“base_*.html”
定义特定分支共用的内容
1 2 3 4 5 {%extends 'temtest/base.html'%} {%block title%}商品{%endblock%} {%block left%} <h1 > goods left</h1 > {%endblock%}
1 2 3 4 5 {%extends 'temtest/base.html'%} {%block title%}用户中心{%endblock%} {%block left%} <font color ='blue' > user left</font > {%endblock%}
定义index.html,继承自base.html,不需要写left块
1 2 3 4 {%extends 'temtest/base.html'%} {%block content%} 首页内容 {%endblock content%}
1 2 3 4 {%extends 'temtest/base_goods.html'%} {%block content%} 商品正文列表 {%endblock content%}
1 2 3 4 {%extends 'temtest/base_user.html'%} {%block content%} 用户密码修改 {%endblock content%}
1 2 3 logo='hello world' def index (request) : return render(request, 'temtest/index.html' , {'logo' : logo})
1 2 def goodslist (request) : return render(request, 'temtest/goodslist.html' , {'logo' : logo})
1 2 def userpwd (request) : return render(request, 'temtest/userpwd.html' , {'logo' : logo})
1 2 3 4 5 6 7 from django.conf.urls import urlfrom . import viewsurlpatterns = [ url(r'^$' , views.index, name='index' ), url(r'^list/$' , views.goodslist, name='list' ), url(r'^pwd/$' , views.userpwd, name='pwd' ), ]
4、HTML转义
Django对字符串进行自动HTML转义,如在模板中输出如下值:
1 2 3 4 5 6 7 8 视图代码: def index(request): return render(request, 'temtest/index2.html', { 't1': '<h1>hello</h1>' }) 模板代码: {{t1}}
(1)会被自动转义的字符
html转义,就是将包含的html标签输出,而不被解释执行,原因是当显示用户提交字符串时,可能包含一些攻击性的代码,如js脚本
Django会将如下字符自动转义:
1 2 3 4 5 6 7 8 9 < 会转换为< > 会转换为> ' (单引号) 会转换为' " (双引号)会转换为 " & 会转换为 &
当显示不被信任的变量时使用escape过滤器,一般省略,因为Django自动转义
(2)关闭转义
1 2 3 { % autoescape off %} {{ body }} { % endautoescape %}
标签autoescape接受on或者off参数
自动转义标签在base模板中关闭,在child模板中也是关闭的
(3)字符串字面值
1 { { data|default:"<b>123</b>" }}
1 { { data|default:"<b>123</b>" }}
5、CSRF跨站请求伪造
(1)csrf
全称Cross Site Request Forgery,跨站请求伪造
某些恶意网站上包含链接、表单按钮或者JavaScript,它们会利用登录过的用户在浏览器中的认证信息试图在你的网站上完成某些操作,这就是跨站攻击
演示csrf如下:
创建视图csrf1用于展示表单,csrf2用于接收post请求
1 2 3 4 5 def csrf1 (request) : return render(request,'book/csrf1.html' ) def csrf2 (request) : uname=request.POST['uname' ] return render(request,'book/csrf2.html' ,{'uname' :uname})
1 2 url(r'^csrf1/$', views.csrf1), url(r'^csrf2/$', views.csrf2),
1 2 3 4 5 6 7 8 9 10 11 <html > <head > <title > Title</title > </head > <body > <form method ="post" action ="/crsf2/" > <input name ="uname" > <br > <input type ="submit" value ="提交" /> </form > </body > </html >
1 2 3 4 5 6 7 8 <html > <head > <title > Title</title > </head > <body > {{ uname }} </body > </html >
在浏览器中访问,查看效果,网页提示‘CSRF verification failed. Request aborted’
将settings.py中的中间件代码’django.middleware.csrf.CsrfViewMiddleware’注释
查看csrf1的源代码,复制,在自己的网站内建一个html文件,粘贴源码,访问查看效果
(2)防csrf的使用
在django的模板中,提供了防止跨站攻击的方法,使用步骤如下:
step1:在settings.py中启用’django.middleware.csrf.CsrfViewMiddleware’中间件,此项在创建项目时,默认被启用
step2:在csrf1.html中添加标签
1 2 3 4 <form > {% csrf_token %} ... </form >
step3:测试刚才的两个请求,发现跨站的请求被拒绝了,网页提示‘CSRF verification failed. Request aborted’
(3)取消保护
如果某些视图不需要保护,可以使用装饰器csrf_exempt,模板中也不需要写标签,修改csrf2的视图如下
1 2 3 4 5 6 from django.views.decorators.csrf import csrf_exempt@csrf_exempt def csrf2 (request) : uname=request.POST['uname' ] return render(request,'booktest/csrf2.html' ,{'uname' :uname})
(4)保护原理
1 <input type ='hidden' name ='csrfmiddlewaretoken' value ='nGjAB3Md9ZSb4NmG1sXDolPmh3bR2g59' />
在浏览器的调试工具中,通过network标签可以查看cookie信息
本站中自动添加了cookie信息(csrftoken的信息)
查看跨站的信息,并没有cookie信息,即使加入上面的隐藏域代码,发现又可以访问了
结论:django的csrf不是完全的安全
当提交请求时,中间件’django.middleware.csrf.CsrfViewMiddleware’会对提交的cookie及隐藏域的内容进行验证,如果失败则返回403错误
6、验证码
在用户注册、登录页面,为了防止暴力请求,可以加入验证码功能,如果验证码错误,则不需要继续处理,可以减轻一些服务器的压力
使用验证码也是一种有效的防止crsf的方法
(1)验证码视图
新建viewsUtil.py,定义函数verifycode
此段代码用到了PIL中的Image、ImageDraw、ImageFont模块,需要先安装Pillow(3.4.1)包
Image表示画布对象
ImageDraw表示画笔对象
ImageFont表示字体对象,ubuntu的字体路径为“/usr/share/fonts/truetype/freefont”
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 from django.http import HttpResponsedef verifycode (request) : from PIL import Image, ImageDraw, ImageFont import random bgcolor = (random.randrange(20 , 100 ), random.randrange(20 , 100 ), 255 ) width = 100 height = 25 im = Image.new('RGB' , (width, height), bgcolor) draw = ImageDraw.Draw(im) for i in range(0 , 100 ): xy = (random.randrange(0 , width), random.randrange(0 , height)) fill = (random.randrange(0 , 255 ), 255 , random.randrange(0 , 255 )) draw.point(xy, fill=fill) str1 = 'ABCD123EFGHIJK456LMNOPQRS789TUVWXYZ0' rand_str = '' for i in range(0 , 4 ): rand_str += str1[random.randrange(0 , len(str1))] font = ImageFont.truetype('FreeMono.ttf' , 23 ) fontcolor = (255 , random.randrange(0 , 255 ), random.randrange(0 , 255 )) draw.text((5 , 2 ), rand_str[0 ], font=font, fill=fontcolor) draw.text((25 , 2 ), rand_str[1 ], font=font, fill=fontcolor) draw.text((50 , 2 ), rand_str[2 ], font=font, fill=fontcolor) draw.text((75 , 2 ), rand_str[3 ], font=font, fill=fontcolor) del draw request.session['verifycode' ] = rand_str import cStringIO buf = cStringIO.StringIO() im.save(buf, 'png' ) return HttpResponse(buf.getvalue(), 'image/png' )
(2)配置url
1 2 3 4 5 from . import viewsUtil urlpatterns = [ url(r'^verifycode/$', viewsUtil.verifycode), ]
(3)显示验证码
1 <img id ='verifycode' src ="/verifycode/" alt ="CheckCode" />
启动服务器,查看显示成功
扩展:点击“看不清,换一个”时,可以换一个新的验证码
1 2 3 4 5 6 7 8 9 10 <script type ="text/javascript" src ="/static/jquery-1.12.4.min.js" > </script > <script type ="text/javascript" > $(function(){ $('#verifycodeChange').css('cursor','pointer').click(function() { $('#verifycode').attr('src',$('#verifycode').attr('src')+1) }); }); </script > <img id ='verifycode' src ="/verifycode/?1" alt ="CheckCode" /> <span id ='verifycodeChange' > 看不清,换一个</span >
为了能够实现提交功能,需要增加form和input标签1 2 3 4 5 6 7 <form method ='post' action ='/verifycodeValid/' > <input type ="text" name ="vc" > <img id ='verifycode' src ="/verifycode/?1" alt ="CheckCode" /> <span id ='verifycodeChange' > 看不清,换一个</span > <br > <input type ="submit" value ="提交" > </form >
(4)验证
1 2 3 4 5 6 7 8 from django.http import HttpResponsedef verifycodeValid (request) : vc = request.POST['vc' ] if vc.upper() == request.session['verifycode' ]: return HttpResponse('ok' ) else : return HttpResponse('no' )
1 2 3 urlpatterns = [ url(r'^verifycodeValid/$' , views.verifycodeValid), ]
(5)第三方
可以在网上搜索“验证码”,找到一些第三方验证码提供网站,阅读文档,使用到项目中
六、高级 1、管理静态文件
项目中的CSS、图片、js都是静态文件
(1)配置静态文件
1 2 3 4 STATIC_URL = '/static/' STATICFILES_DIRS = [ os.path.join(BASE_DIR, 'static' ), ]
在项目根目录下创建static目录,再创建当前应用名称的目录
1 /static/my_app/myexample.jpg
1 2 { % load static from staticfiles %} <img src ="{ % static " my_app /myexample.jpg " %}" alt ="My image" />
2、中间件
是一个轻量级、底层的插件系统,可以介入Django的请求和响应处理过程,修改Django的输入或输出
激活:添加到Django配置文件中的MIDDLEWARE_CLASSES元组中
每个中间件组件是一个独立的Python类,可以定义下面方法中的一个或多个
_init_:无需任何参数,服务器响应第一个请求的时候调用一次,用于确定是否启用当前中间件
process_request(request):执行视图之前被调用,在每个请求上调用,返回None或HttpResponse对象
process_view(request, view_func, view_args, view_kwargs):调用视图之前被调用,在每个请求上调用,返回None或HttpResponse对象
process_template_response(request, response):在视图刚好执行完毕之后被调用,在每个请求上调用,返回实现了render方法的响应对象
process_response(request, response):所有响应返回浏览器之前被调用,在每个请求上调用,返回HttpResponse对象
process_exception(request,response,exception):当视图抛出异常时调用,在每个请求上调用,返回一个HttpResponse对象
使用中间件,可以干扰整个处理过程,每次请求中都会执行中间件的这个方法
示例:自定义异常处理
与settings.py同级目录下创建myexception.py文件,定义类MyException,实现process_exception方法
1 2 3 4 from django.http import HttpResponseclass MyException () : def process_exception (request,response, exception) : return HttpResponse(exception.message)
将类MyException注册到settings.py中间件中
1 2 3 4 MIDDLEWARE_CLASSES = ( 'test1.myexception.MyException', ... )
定义视图,并发生一个异常信息,则会运行自定义的异常处理
3、上传图片
当Django在处理文件上传的时候,文件数据被保存在request.FILES
FILES中的每个键为<input type="file" name="" />
中的name
注意:FILES只有在请求的方法为POST 且提交的
使用模型处理上传文件:将属性定义成models.ImageField类型
1 pic=models.ImageField(upload_to='cars/' )
注意:如果属性类型为ImageField需要安装包Pilow
1 pip install Pillow==3.4.1
图片存储路径
在项目根目录下创建media文件夹
图片上传后,会被保存到“/static/media/cars/图片文件”
打开settings.py文件,增加media_root项
1 MEDIA_ROOT=os.path.join(BASE_DIR,"static/media")
使用django后台管理,遇到ImageField类型的属性会出现一个file框,完成文件上传
手动上传的模板代码
1 2 3 4 5 6 7 8 9 10 11 12 <html > <head > <title > 文件上传</title > </head > <body > <form method ="post" action ="upload/" enctype ="multipart/form-data" > <input type ="text" name ="title" > <br > <input type ="file" name ="pic" /> <br > <input type ="submit" value ="上传" > </form > </body > </html >
1 2 3 4 5 6 7 8 9 10 11 12 from django.conf import settingsdef upload (request) : if request.method == "POST" : f1 = request.FILES['pic' ] fname = '%s/cars/%s' % (settings.MEDIA_ROOT,f1.name) with open(fname, 'w' ) as pic: for c in f1.chunks(): pic.write(c) return HttpResponse("ok" ) else : return HttpResponse("error" )
4、Admin站点
(1)Admin站点
通过使用startproject创建的项目模版中,默认Admin被启用
1.创建管理员的用户名和密码
1 2 python manage.py createsuperuser 然后按提示填写用户名、邮箱、密码
2.在应用内admin.py文件完成注册,就可以在后台管理中维护模型的数据
1 2 3 4 from django.contrib import adminfrom models import *admin.site.register(HeroInfo)
查找admin文件:在INSTALLED_APPS项中加入django.contrib.admin,Django就会自动搜索每个应用的admin模块并将其导入
(2)ModelAdmin对象
ModelAdmin类是模型在Admin界面中的表示形式
定义:定义一个类,继承于admin.ModelAdmin,注册模型时使用这个类
1 2 class HeroAdmin (admin.ModelAdmin) : ...
通常定义在应用的admin.py文件里
使用方式一:注册参数
1 admin.site.register(HeroInfo,HeroAdmin)
1 2 @admin.register(HeroInfo) class HeroAdmin (admin.ModelAdmin) :
通过重写admin.ModelAdmin的属性规定显示效果,属性主要分为列表页、增加修改页两部分
1)列表页选项
“操作选项”的位置
actions_on_top、actions_on_bottom:默认显示在页面的顶部
1 2 3 class HeroAdmin (admin.ModelAdmin) : actions_on_top = True actions_on_bottom = True
list_display
出现列表中显示的字段
列表类型
在列表中,可以是字段名称,也可以是方法名称,但是方法名称默认不能排序
在方法中可以使用format_html()输出html内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from django.db import modelsfrom tinymce.models import HTMLFieldfrom django.utils.html import format_htmlclass HeroInfo (models.Model) : hname = models.CharField(max_length=10 ) hcontent = HTMLField() isDelete = models.BooleanField() def hContent (self) : return format_html(self.hcontent) class HeroAdmin (admin.ModelAdmin) : list_display = ['hname' , 'hContent' ]
让方法排序,为方法指定admin_order_field属性
1 2 3 4 def hContent (self) : return format_html(self.hcontent) hContent.admin_order_field = 'hname'
标题栏名称:将字段封装成方法,为方法设置short_description属性
1 2 3 4 5 6 7 8 9 def hName (self) : return self.hname hName.short_description = '姓名' hContent.short_description = '内容' class HeroAdmin (admin.ModelAdmin) : list_display = ['hName' , 'hContent' ]
list_filter
右侧栏过滤器,对哪些属性的值进行过滤
列表类型
只能接收字段
1 2 3 class HeroAdmin (admin.ModelAdmin) : ... list_filter = ['hname' , 'hcontent' ]
1 2 3 class HeroAdmin (admin.ModelAdmin) : ... list_per_page = 10
search_fields
搜索框
列表类型,表示在这些字段上进行搜索
只能接收字段
1 2 3 class HeroAdmin (admin.ModelAdmin) : ... search_fields = ['hname' ]
2)增加与修改页选项
fields:显示字段的顺序,如果使用元组表示显示到一行上
1 2 3 class HeroAdmin (admin.ModelAdmin) : ... fields = [('hname' , 'hcontent' )]
1 2 3 4 5 6 class HeroAdmin (admin.ModelAdmin) : ... fieldsets = ( ('base' , {'fields' : ('hname' )}), ('other' , {'fields' : ('hcontent' )}) )
(3)InlineModelAdmin对象
类型InlineModelAdmin:表示在模型的添加或修改页面嵌入关联模型的添加或修改
子类TabularInline:以表格的形式嵌入
子类StackedInline:以块的形式嵌入
1 2 3 4 5 6 7 class HeroInline (admin.TabularInline) : model = HeroInfo class BookAdmin (admin.ModelAdmin) : inlines = [ HeroInline, ]
(4)重写admin模板
在项目所在目录中创建templates目录,再创建一个admin目录
设置模板查找目录:修改settings.py的TEMPLATES项,加载模板时会在DIRS列表指定的目录中搜索
‘DIRS’: [os.path.join(BASE_DIR, ‘templates’)],
从Django安装的目录下(django/contrib/admin/templates)将模板页面的源文件admin/base_site.html拷贝到第一步建好的目录里
编辑base_site.html文件
刷新页面,发现以刚才编辑的页面效果显示
其它管理后台的模板可以按照相同的方式进行修改
5、分页
Django提供了一些类实现管理数据分页,这些类位于django/core/paginator.py中
(1)Paginator对象
Paginator(列表,int):返回分页对象,参数为列表数据,每面数据的条数
属性
count:对象总数
num_pages:页面总数
page_range:页码列表,从1开始,例如[1, 2, 3, 4]
方法
page(num):下标以1开始,如果提供的页码不存在,抛出InvalidPage异常
异常exception
InvalidPage:当向page()传入一个无效的页码时抛出
PageNotAnInteger:当向page()传入一个不是整数的值时抛出
EmptyPage:当向page()提供一个有效值,但是那个页面上没有任何对象时抛出
(2)Page对象
创建对象
Paginator对象的page()方法返回Page对象,不需要手动构造
属性
object_list:当前页上所有对象的列表
number:当前页的序号,从1开始
paginator:当前page对象相关的Paginator对象
方法
has_next():如果有下一页返回True
has_previous():如果有上一页返回True
has_other_pages():如果有上一页或下一页返回True
next_page_number():返回下一页的页码,如果下一页不存在,抛出InvalidPage异常
previous_page_number():返回上一页的页码,如果上一页不存在,抛出InvalidPage异常
len():返回当前页面对象的个数
迭代页面对象:访问当前页面中的每个对象
(3)示例
1 2 3 4 5 6 7 8 9 10 11 from django.core.paginator import Paginatordef pagTest (request, pIndex) : list1 = AreaInfo.objects.filter(aParent__isnull=True ) p = Paginator(list1, 10 ) if pIndex == '' : pIndex = '1' pIndex = int(pIndex) list2 = p.page(pIndex) plist = p.page_range return render(request, 'booktest/pagTest.html' , {'list' : list2, 'plist' : plist, 'pIndex' : pIndex})
1 url(r'^pag(?P<pIndex>[0-9]*)/$', views.pagTest, name='pagTest'),
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <!DOCTYPE html> <html > <head > <title > </title > </head > <body > <ul > {%for area in list%} <li > {{area.id}}--{{area.atitle}}</li > {%endfor%} </ul > {%for pindex in plist%} {%if pIndex == pindex%} {{pindex}} {%else%} <a href ="/pag{{pindex}}/" > {{pindex}}</a > {%endif%} {%endfor%} </body > </html >
6、Ajax
使用视图通过上下文向模板中传递数据,需要先加载完成模板的静态页面,再执行模型代码,生成最张的html,返回给浏览器,这个过程将页面与数据集成到了一起,扩展性差
改进方案:通过ajax的方式获取数据,通过dom操作将数据呈现到界面上
推荐使用框架的ajax相关方法,不要使用XMLHttpRequest对象,因为操作麻烦且不容易查错
jquery框架中提供了$.ajax、$.get、$.post方法,用于进行异步交互
由于csrf的约束,推荐使用$.get
示例:实现省市区的选择
在views.py视图中添加函数
1 2 3 4 5 6 7 8 9 10 11 12 13 def area (request) : return render(request,'book/area.html' ) def area2 (request,id) : id1=int(id) if id1==0 : data=AreaInfo.objects.filter(parea__isnull=True ) else : data=[{}] list=[] for area in data: list.append([area.id,area.title]) return JsonResponse({'data' :list})
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html > <head > <title > Title</title > <script src ="/static/booktest/jquery-1.12.4.min.js" > </script > <script > $(function () { //查询省信息 pro=$('#pro') $.get('/area/0/',function (dic) {//{data:[[],[],[]]} $.each(dic.data,function (index, item) {//[id,title] pro.append('<option value ="'+item[0]+'" > '+item[1]+'</option > ') }) }) }); </script > </head > <body > <select id ="pro" > <option value ="" > 请选择省</option > </select > <select id ="city" > <option value ="" > 请选择市</option > </select > <select id ="dis" > <option value ="" > 请选择区</option > </select > </body > </html >
1 2 url(r'^area/$',views.area), url(r'^area/(\d+)/$',views.area2),
七、第三方 1、富文本编辑器
借助富文本编辑器,管理员能够编辑出来一个包含html的页面,从而页面的显示效果,可以由管理员定义,而不用完全依赖于前期开发人员
此处以tinymce为例,其它富文本编辑器的使用可以自行学习
(1)下载安装
在网站pypi网站搜索并下载”django-tinymce-2.4.0”
解压
1 tar zxvf django-tinymce-2.4.0.tar.gz
(2)应用到项目中
在settings.py中为INSTALLED_APPS添加编辑器应用
1 2 3 4 INSTALLED_APPS = ( ... 'tinymce' , )
1 2 3 4 5 TINYMCE_DEFAULT_CONFIG = { 'theme': 'advanced', 'width': 600, 'height': 400, }
1 2 3 4 urlpatterns = [ ... url(r'^tinymce/' , include('tinymce.urls' )), ]
1 2 3 4 5 6 from django.db import modelsfrom tinymce.models import HTMLFieldclass HeroInfo (models.Model) : ... hcontent = HTMLField()
在后台管理界面中,就会显示为富文本编辑器,而不是多行文本框
(3)自定义使用
1 2 def editor (request) : return render(request, 'other/editor.html' )
1 2 3 4 urlpatterns = [ ... url(r'^editor/$' , views.editor, name='editor' ), ]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <!DOCTYPE html> <html > <head > <title > </title > <script type ="text/javascript" src ='/static/tiny_mce/tiny_mce.js' > </script > <script type ="text/javascript" > tinyMCE.init({ 'mode':'textareas', 'theme':'advanced', 'width':400, 'height':100 }); </script > </head > <body > <form method ="post" action ="/content/" > <input type ="text" name ="hname" > <br > <textarea name ='hcontent' > 哈哈,这是啥呀</textarea > <br > <input type ="submit" value ="提交" > </form > </body > </html >
定义视图content,接收请求,并更新heroinfo对象
1 2 3 4 5 6 7 8 9 10 def content (request) : hname = request.POST['hname' ] hcontent = request.POST['hcontent' ] heroinfo = HeroInfo.objects.get(pk=1 ) heroinfo.hname = hname heroinfo.hcontent = hcontent heroinfo.save() return render(request, 'other/content.html' , {'hero' : heroinfo})
1 2 3 4 urlpatterns = [ ... url(r'^content/$' , views.content, name='content' ), ]
1 2 3 4 5 6 7 8 9 10 11 12 13 <!DOCTYPE html> <html > <head > <title > </title > </head > <body > 姓名:{{hero.hname}} <hr > {%autoescape off%} {{hero.hcontent}} {%endautoescape%} </body > </html >
2、缓存
对于中等流量的网站来说,尽可能地减少开销是必要的。缓存数据就是为了保存那些需要很多计算资源的结果,这样的话就不必在下次重复消耗计算资源
Django自带了一个健壮的缓存系统来保存动态页面,避免对于每次请求都重新计算
Django提供了不同级别的缓存粒度:可以缓存特定视图的输出、可以仅仅缓存那些很难生产出来的部分、或者可以缓存整个网站
(1)设置缓存
通过设置决定把数据缓存在哪里,是数据库中、文件系统还是在内存中
通过setting文件的CACHES配置来实现
参数TIMEOUT:缓存的默认过期时间,以秒为单位,这个参数默认是300秒,即5分钟;设置TIMEOUT为None表示永远不会过期,值设置成0造成缓存立即失效
1 2 3 4 5 6 CACHES={ 'default' : { 'BACKEND' : 'django.core.cache.backends.locmem.LocMemCache' , 'TIMEOUT' : 60 , } }
可以将cache存到redis中,默认采用1数据库,需要安装包并配置如下:
1 2 3 4 5 6 7 8 9 CACHES = { "default" : { "BACKEND" : "redis_cache.cache.RedisCache" , "LOCATION" : "localhost:6379" , 'TIMEOUT' : 60 , }, }
1 2 3 4 连接:redis-cli 切换数据库:select 1 查看键:keys * 查看值:get 键
(2)单个view缓存
django.views.decorators.cache定义了cache_page装饰器,用于对视图的输出进行缓存
示例代码如下:
1 2 3 4 5 6 from django.views.decorators.cache import cache_page@cache_page(60 * 15) def index (request) : return HttpResponse('hello1' )
cache_page接受一个参数:timeout,秒为单位,上例中缓存了15分钟
视图缓存与URL无关,如果多个URL指向同一视图,每个URL将会分别缓存
(3)模板片断缓存
使用cache模板标签来缓存模板的一个片段
需要两个参数:
示例代码如下:
1 2 3 4 5 {% load cache %} {% cache 500 hello %} hello1 {% endcache %}
(4)底层的缓存API1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from django.core.cache import cache 设置:cache.set(键,值,有效时间) 获取:cache.get(键) 删除:cache.delete(键) 清空:cache.clear() def cache1(request): # return HttpResponse('hello1') # return HttpResponse('hello2') # cache.set('key1','value1',600) # print(cache.get('key1')) # return render(request,'book/cache1.html') cache.clear() return HttpResponse('ok')
3、全文检索
全文检索不同于特定字段的模糊查询,使用全文检索的效率更高,并且能够对于中文进行分词处理
haystack:django的一个包,可以方便地对model里面的内容进行索引、搜索,设计为支持whoosh,solr,Xapian,Elasticsearc四种全文检索引擎后端,属于一种全文检索的框架
whoosh:纯Python编写的全文搜索引擎,虽然性能比不上sphinx、xapian、Elasticsearc等,但是无二进制包,程序不会莫名其妙的崩溃,对于小型的站点,whoosh已经足够使用
jieba:一款免费的中文分词包,如果觉得不好用可以使用一些收费产品
(1)在虚拟环境中依次安装包
1 2 3 pip install django-haystack pip install whoosh pip install jieba
(2)修改settings.py文件
1 2 3 4 INSTALLED_APPS = ( ... 'haystack' , )
1 2 3 4 5 6 7 8 9 HAYSTACK_CONNECTIONS = { 'default' : { 'ENGINE' : 'haystack.backends.whoosh_cn_backend.WhooshEngine' , 'PATH' : os.path.join(BASE_DIR, 'whoosh_index' ), } } HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'
(3)在项目的urls.py中添加url
1 2 3 4 urlpatterns = [ ... url(r'^search/' , include('haystack.urls' )), ]
(4)在应用目录下建立search_indexes.py文件
1 2 3 4 5 6 7 8 9 10 11 12 13 from haystack import indexesfrom models import GoodsInfoclass GoodsInfoIndex (indexes.SearchIndex, indexes.Indexable) : text = indexes.CharField(document=True , use_template=True ) def get_model (self) : return GoodsInfo def index_queryset (self, using=None) : return self.get_model().objects.all()
(5)在目录“templates/search/indexes/应用名称/”下创建“模型类名称_text.txt”文件
1 2 3 4 #goodsinfo_text.txt,这里列出了要对哪些列的内容进行检索 {{ object.gName }} {{ object.gSubName }} {{ object.gDes }}
(6)在目录“templates/search/”下建立search.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <!DOCTYPE html> <html > <head > <title > </title > </head > <body > {% if query %} <h3 > 搜索结果如下:</h3 > {% for result in page.object_list %} <a href ="/{{ result.object.id }}/" > {{ result.object.gName }}</a > <br /> {% empty %} <p > 啥也没找到</p > {% endfor %} {% if page.has_previous or page.has_next %} <div > {% if page.has_previous %}<a href ="?q={{ query }}&page={{ page.previous_page_number }}" > {% endif %}« 上一页{% if page.has_previous %}</a > {% endif %} | {% if page.has_next %}<a href ="?q={{ query }}&page={{ page.next_page_number }}" > {% endif %}下一页 »{% if page.has_next %}</a > {% endif %} </div > {% endif %} {% endif %} </body > </html >
(7)建立ChineseAnalyzer.py文件
保存在haystack的安装文件夹下,路径如“/home/python/.virtualenvs/django_py2/lib/python2.7/site-packages/haystack/backends”
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import jiebafrom whoosh.analysis import Tokenizer, Tokenclass ChineseTokenizer (Tokenizer) : def __call__ (self, value, positions=False, chars=False, keeporiginal=False, removestops=True, start_pos=0 , start_char=0 , mode='' , **kwargs) : t = Token(positions, chars, removestops=removestops, mode=mode, **kwargs) seglist = jieba.cut(value, cut_all=True ) for w in seglist: t.original = t.text = w t.boost = 1.0 if positions: t.pos = start_pos + value.find(w) if chars: t.startchar = start_char + value.find(w) t.endchar = start_char + value.find(w) + len(w) yield t def ChineseAnalyzer () : return ChineseTokenizer()
(8)复制whoosh_backend.py文件,改名为whoosh_cn_backend.py
注意:复制出来的文件名,末尾会有一个空格,记得要删除这个空格
1 2 3 4 5 from .ChineseAnalyzer import ChineseAnalyzer 查找 analyzer=StemmingAnalyzer() 改为 analyzer=ChineseAnalyzer()
(9)生成索引
1 python manage.py rebuild_index
(10)在模板中创建搜索栏
1 2 3 4 <form method ='get' action ="/search/" target ="_blank" > <input type ="text" name ="q" > <input type ="submit" value ="查询" > </form >
4、celery
官方网站:http://www.celeryproject.org/ 中文文档:http://docs.jinkan.org/docs/celery/
示例一:用户发起request,并等待response返回。在本些views中,可能需要执行一段耗时的程序,那么用户就会等待很长时间,造成不好的用户体验
示例二:网站每小时需要同步一次天气预报信息,但是http是请求触发的,难道要一小时请求一次吗?
使用celery后,情况就不一样了
示例一的解决:将耗时的程序放到celery中执行
示例二的解决:使用celery定时执行
(1)名词
任务task:就是一个Python函数
队列queue:将需要执行的任务加入到队列中
工人worker:在一个新进程中,负责执行队列中的任务
代理人broker:负责调度,在布置环境中使用redis
(2)使用
1 2 3 celery==3.1.25 celery-with-redis==3.0 django-celery==3.1.17
1 2 3 4 5 6 7 8 9 10 11 INSTALLED_APPS = ( ... 'djcelery', } ... import djcelery djcelery.setup_loader() BROKER_URL = 'redis://127.0.0.1:6379/0' CELERY_IMPORTS = ('应用名称.task')
1 2 3 4 5 6 7 8 import time from celery import task @task def sayhello(): print('hello ...') time.sleep(2) print('world ...')
1 python manage.py migrate
1 sudo redis-server /etc/redis/redis.conf
1 python manage.py celery worker --loglevel=info
1 function.delay(parameters)
1 2 3 4 5 6 7 8 9 10 11 def sayhello (request) : print('hello ...' ) import time time.sleep(10 ) print('world ...' ) return HttpResponse("hello world" )
5、布署
(1)服务器介绍
服务器:私有服务器、公有服务器
私有服务器:公司自己购买、自己维护,只布署自己的应用,可供公司内部或外网访问
公有服务器:集成好运营环境,销售空间或主机,供其布署自己的应用
私有服务器成本高,需要专业人员维护,适合大公司使用
公有服务器适合初创公司使用,成本低
常用的公有服务器,如阿里云、青云等,可根据需要,按流量收费或按时间收费
此处的服务器是物理上的一台非常高、线路全、运行稳定的机器
(2)服务器环境配置
在本地的虚拟环境中,项目根目录下,执行命令收集所有包
通过ftp软件将开发好的项目上传到此服务器的某个目录
安装并创建虚拟环境,如果已有则跳过此步
1 2 3 sudo apt-get install python-virtualenv sudo easy_install virtualenvwrapper mkvirtualenv [虚拟环境名称]
1 2 workon [虚拟环境名称] pip install -r plist.txt
1 2 DEBUG = False ALLOW_HOSTS=['*',]表示可以访问服务器的ip
(3)WSGI
python manage.py runserver:这是一款适合开发阶段使用的服务器,不适合运行在真实的生产环境中
在生产环境中使用WSGI
WSGI:Web服务器网关接口,英文为Python Web Server Gateway Interface,缩写为WSGI,是Python应用程序或框架和Web服务器之间的一种接口,被广泛接受
WSGI没有官方的实现, 因为WSGI更像一个协议,只要遵照这些协议,WSGI应用(Application)都可以在任何服务器(Server)上运行
命令django-admin startproject会生成一个简单的wsgi.py文件,确定了settings、application对象
application对象:在Python模块中使用application对象与应用服务器交互
settings模块:Django需要导入settings模块,这里是应用定义的地方
此处的服务器是一个软件,可以监听网卡端口、遵从网络层传输协议,收发http协议级别的数据
(4)uWSGI
uWSGI实现了WSGI的所有接口,是一个快速、自我修复、开发人员和系统管理员友好的服务器
uWSGI代码完全用C编写
安装uWSGI
配置uWSGI,在项目中新建文件uwsgi.ini,编写如下配置
1 2 3 4 5 6 7 8 9 10 [uwsgi] socket=外网ip:端口(使用nginx连接时,使用socket) http=外网ip:端口(直接做web服务器,使用http) chdir=项目根目录 wsgi-file=项目中wsgi.py文件的目录,相对于项目根目录 processes=4 threads=2 master=True pidfile=uwsgi.pid daemonize=uswgi.log
启动:uwsgi –ini uwsgi.ini
停止:uwsgi –stop uwsgi.pid
重启:uwsgi –reload uwsgi.pid
使用http协议查看网站运行情况,运行正常,但是静态文件无法加载
(5)nginx
使用nginx的作用
负载均衡:多台服务器轮流处理请求
反射代理:隐藏真实服务器
实现构架:客户端请求nginx,再由nginx请求uwsgi,运行django框架下的python代码
nginx+uwsgi也可以用于其它框架的python web代码,不限于django
到官网下载nginx压缩文件或通过命令安装
1 2 3 4 5 6 7 解压缩: tar zxvf nginx-1.6.3.tar.gz 进入nginx-1.6.3目录依次执行如下命令进行安装: ./configure make sudo make install
默认安装到/usr/local/nginx目录,进入此目录执行命令
查看版本:sudo sbin/nginx -v
启动:sudo sbin/nginx
停止:sudo sbin/nginx -s stop
重启:sudo sbin/nginx -s reload
通过浏览器查看nginx运行结果
指向uwsgi项目:编辑conf/nginx.conf文件
1 2 3 4 5 6 7 sudo conf/nginx.conf 在server下添加新的location项,指向uwsgi的ip与端口 location / { include uwsgi_params;将所有的参数转到uwsgi下 uwsgi_pass uwsgi的ip与端口; }
修改uwsgi.ini文件,启动socket,禁用http
重启nginx、uwsgi
在浏览器中查看项目,发现静态文件加载不正常,接下来解决静态文件的问题
(6)静态文件
静态文件一直都找不到,现在终于可以解决了
所有的静态文件都会由nginx处理,不会将请求转到uwsgi
配置nginx的静态项,打开conf/nginx.conf文件,找到server,添加新location
1 2 3 location /static { alias /var/www/test5/static/; }
在服务器上创建目录结构“/var/www/test5/”
修改目录权限
1 sudo chmod 777 /var/www/test5
创建static目录,注意顺序是先分配权限,再创建目录
1 2 STATIC_ROOT='/var/www/test5/static/' STATIC_URL='/static/'
收集所有静态文件到static_root指定目录:python manage.py collectstatic
重启nginx、uwsgi
持续更新…