人生苦短
我用Python

Django027-中间件|缓存|信号

Siffre阅读(53)评论(0)

Django027-中间件|缓存|信号

1. 中间件

Django中的中间件其实就是一个类,在请求到来和结束后,Django会根据自己的规则在合适的时机执行中间件中相应的方法。

1.1 如何使用中间件?

Django中使用中间件真的是很简单的,我们只需要再settings.py文件中注册一下即可:

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
  1. 注意版本问题: 1.10以下的版本中间件的变量名称是MIDDLEWARE_CLASSES,稍有不注意会让人蛋疼的很。。。尤其是在禁用CSRF时,不起作用,仍是Forbidden 403
  2. 上面都是路径,当你撰写Scrapy框架的中间件时会有所体会的
  3. 中间件的顺序,下面会根据流程进行阐述

1.2 中间的执行流程是怎样的?

Django022-打造通用多级评论模型

Siffre阅读(64)评论(0)

Django022-打造通用多级评论模型

多级评论在很多地方都是可以使用的,便于以后的使用,避免重复造轮子,自己写一个通用的模型。

Django自带的多级评论,请参考这里:Django默认多级评论

下面我们要实现的是通用多级评论,当我们脱离使用Django后,一样能够写出多级评论,如:使用轻量级框架FlaskBottle;异步框架Tonardo

在写代码之前,我们需要先分析常见的评论结构,找到一定的规律。市面上有很多免费的评论插件,使用起来也是很方便的,但毕竟是第三方的东西,虽然免费了,不过数据是保存在人家那里,自己没有办法拿到数据,同时第三方的插件也拖慢网站的访问速度,影响用户体验。

第三方评论插件:多说,displus等

简单的评论模型:

这种评论结构很常见,也很通用!

通过模型我们需要简单的构造一些数据,这里我们通过DjangoModel来构建数据库,比较简单快捷!

我们需要这些内容:

  • 文章ID,文章标题
  • 用户ID,用户信息
  • 评论ID,评论内容,评论时间,被评论的ID(也就是被评论的父级ID)

构建简单的评论模型这些数据基本上够用了,若想添加更多信息,只需要添加额外的字段即可。

1. 创建数据库表结构及数据

应用的models.py文件:

from django.db import models

class UserInfo(models.Model):
username = models.CharField(max_length=32, unique=True) #只需要写这个就行,下面的是我之前写的,加不加都可以
password = models.CharField(max_length=32)
email = models.EmailField(max_length=64, unique=True)
current_time = models.DateTimeField()

def __str__(self):
return self.username

# 多级评论测试
class New(models.Model):
title = models.CharField(max_length=32)

class Comment(models.Model):
content = models.CharField(max_length=128)
ctime = models.DateTimeField(auto_now_add=True, null=True)
new = models.ForeignKey('New')
user_info = models.ForeignKey('UserInfo')
parent = models.ForeignKey('self', related_name='redirect', null=True) #外键到自己,获取父级的评论ID

生成的数据库表,并添加数据如下:

用户信息表:

文章表:

评论表:

表结构已经创建,接下来就要写逻辑代码,这里还是通过Django来实现,并不局限于Django哦!只是现在用起来方便而已。。。懒得去写

先写好URL和简单的模板comment.html,然后在视图views.py中写逻辑代码。

将评论表中的数据取出来,做一些简单的分析,缕清楚数据结构:

直接取出的数据内容:

#
obj = models.Comment.objects.values()
print(obj)
#数据库内容如下:
obj_list= [
{'new_id': 1, 'content': 'PHP是世界上最好的语言,因为ta是拍黄片的!', 'parent_id': None, 'id': 1, 'user_info_id': 2, 'ctime': None},
{'new_id': 2, 'content': '法海他不懂爱,不然早就把白娘子XXOO,哪有许仙的机会。。。', 'parent_id': None, 'id': 2, 'user_info_id': 3, 'ctime': None},
{'new_id': 3, 'content': '这个嘛,没法确定。。。', 'parent_id': None, 'id': 3, 'user_info_id': 4, 'ctime': None},
{'new_id': 1, 'content': '这是要开战a,C才是最好的语言,PHP滚逼。。。', 'parent_id': 1, 'id': 4, 'user_info_id': 1, 'ctime': None},
{'new_id': 2, 'content': '法海估计是不能。。。^_^', 'parent_id': 2, 'id': 5, 'user_info_id': 3, 'ctime': None}
]

看着有点乱,整理一下便于分析:

comment_list = [
{'id': 1, 'new_id': 1, 'content': 'PHP是世界上最好的语言,因为ta是拍黄片的!', 'parent_id': None, 'user_info_id': 2, 'ctime': None},
{'id': 2, 'new_id': 2, 'content': '法海他不懂爱,不然早就把白娘子XXOO,哪有许仙的机会。。。', 'parent_id': None, 'user_info_id': 3, 'ctime': None},
{'id': 3, 'new_id': 3, 'content': '这个嘛,没法确定。。。', 'parent_id': None, 'user_info_id': 4, 'ctime': None},
{'id': 4, 'new_id': 1, 'content': '这是要开战a,C才是最好的语言,PHP滚逼。。。', 'parent_id': 1, 'user_info_id': 1, 'ctime': None},
{'id': 5, 'new_id': 2, 'content': '法海估计是不能。。。^_^', 'parent_id': 2, 'user_info_id': 3, 'ctime': None}
]

我们可以看到parent_id为空,是一级评论给,不为空时,存在下级评论(这里是三级评论),但是我们并不知道下面有几级评论,因为它是动态生成的。逻辑我们是很清楚的,但是要让代码能够更好的体现出来,需要更改数据结构。我们有两种方式来实现逻辑:

首先改变数据结构,添加一个children:[]专门作为存放下级评论的所有内容:

comment_list = [
{'id': 1, 'new_id': 1, 'content': 'PHP是世界上最好的语言,因为ta是拍黄片的!', 'parent_id': None, 'user_info_id': 2, 'ctime': None, 'children':[]},
{'id': 2, 'new_id': 2, 'content': '法海他不懂爱,不然早就把白娘子XXOO,哪有许仙的机会。。。', 'parent_id': None, 'user_info_id': 3, 'ctime': None, 'children':[]},
...
]

基本的数据结构已经实现了,接下来就是逻辑的实现:

2. 数据逻辑实现

  • 递归函数实现多级评论

数据结构和逻辑已经清晰,接下就开始实现逻辑:

#视图函数
def comment(request):
#获取数据
obj = models.Comment.objects.values()
#处理后的数据列表
comment_tree = []
#遍历初始数据
for item in obj:
#判断是否为一级评论
if not item['parent_id']:
#添加存放子评论的空列表
item['children'] = []
#将一级评论添加到处理后数据列表
comment_tree.append(item)
else:
#遍历一级评论
for data in comment_tree:
#判断初始数据是否存在和一级评论有相同的id,存在即是字评论,然后添加
if item['parent_id'] == data['id']:
#添加存放子评论列表
item['children'] = []
#将子评论添加到列表中
data['children'].append(item)
else:
#以下是无限重复,因此,我们使用递归函数来处理

return HttpResponse(json.dumps(comment_tree))

递归实现上述反复出现的逻辑:

#为了方便调用,我们使用类进行封装
class NodeList:
#递归处理
@staticmethod
def recursion(comment_tree, item):
for data in comment_tree:
if item['parent_id'] == data['id']:
item['children'] = []
data['children'].append(item)
return
else:
NodeList.recursion(data['children'], item)

@staticmethod
def comment(obj):
comment_tree = []
for item in obj:
if not item['parent_id']:
item['children'] = []
comment_tree.append(item)
else:
NodeList.recursion(comment_tree, item)
return comment_tree

基本的逻辑已经用代码实现了,下面贴一下完整的代码,并在前端显示拿到的数据,判断一下是否正确:

def comment(request):
obj = models.Comment.objects.values()
result = NodeList.comment(obj)
return HttpResponse(json.dumps(result))

class NodeList:

@staticmethod
def recursion(comment_tree, item):
for data in comment_tree:
if item['parent_id'] == data['id']:
item['children'] = []
data['children'].append(item)
return
else:
NodeList.recursion(data['children'], item)


@staticmethod
def comment(obj):
comment_tree = []
for item in obj:
if not item['parent_id']:
item['children'] = []
comment_tree.append(item)
else:
NodeList.recursion(comment_tree, item)
return comment_tree

前端显示:

通过在线JSON工具转化后的结果:

[
{
"new_id": 1,
"ctime": null,
"content": "PHP是世界上最好的语言,因为ta是拍黄片的!",
"children": [
{
"new_id": 1,
"ctime": null,
"content": "这是要开战a,C才是最好的语言,PHP滚逼。。。",
"children": [ ],
"user_info_id": 1,
"parent_id": 1,
"id": 4
}
],
"user_info_id": 2,
"parent_id": null,
"id": 1
},
{
"new_id": 2,
"ctime": null,
"content": "法海他不懂爱,不然早就把白娘子XXOO,哪有许仙的机会。。。",
"children": [
{
"new_id": 2,
"ctime": null,
"content": "法海估计是不能。。。^_^",
"children": [
{
"new_id": 2,
"ctime": null,
"content": "你咋知道呢?也许法海。。。。",
"children": [ ],
"user_info_id": 5,
"parent_id": 5,
"id": 6
}
],
"user_info_id": 3,
"parent_id": 2,
"id": 5
}
],
"user_info_id": 3,
"parent_id": null,
"id": 2
},
{
"new_id": 3,
"ctime": null,
"content": "这个嘛,没法确定。。。",
"children": [ ],
"user_info_id": 4,
"parent_id": null,
"id": 3
}
]

上面可以看出结果是我们想得到的正确数据。

  • 字典特性实现多级评论

仅限于python,其他语言还是使用递归方法吧!

当我们使用这个方法后,就觉得使用递归不是太low,而是Python没有学好的节奏!此法利用的就是Python的基本内存地址引用原理,只需要创建一个额外的字典,然后调用get()方法即可!

啥也别说了,直接上代码:

先来个性能比较差的版本:

def comment(request):
obj = models.Comment.objects.values()
comment_tree = []
for item in obj:
item.update({'children': []})

for item in obj:
current_item = item
current_item_parent_id = current_item['parent_id']
if not current_item_parent_id:
comment_tree.append(item)
else:
for data in obj:
if data['id'] == current_item_parent_id:
data['children'].append(item)
return HttpResponse(json.dumps(comment_tree))

数据结果:

这个方法确实比上面的递归要好很多,但是有一个很明显的问题,每次都要重新去遍历一次原始数据,很是影响性能啊!

高性能代码,也就是前面说的构建新字典,调用get()方法:

def comment(request):
obj = models.Comment.objects.values()
comment_tree = []
comment_tree_dict = {}
for item in obj:
item.update({'children': []})
comment_tree_dict[item['id']] = item

for item in obj:
parent_item = comment_tree_dict.get(item['parent_id'])
if not parent_item:
comment_tree.append(item)
else:
parent_item['children'].append(item)
return HttpResponse(json.dumps(comment_tree))

数据结果:

用在线JSON转换工具得到的结果是一致的!

3. 数据展示

在前端进行数据的展示有两种方式:

  • 后端模板处理
  • 前端处理

3.1 前端处理

前端对数据进行处理,我需要使用JavaScript,推荐用原生语句来写,并不推荐使用jQuery(行业里流传这么一句话:比较老的程序员在使用jQuery),反正我不会!

当然,在前端是有好处的,将服务器压力分布到每个浏览器!

这里我们使用Ajax进行接收数据,然后html进行渲染,CSS进行修饰。

3.1.1 样式模板构建

代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
h1{
text-align: center;
}
.box{
margin-left: 20px;
}
.data-show{
margin-left: 15px;
}
</style>
</head>
<body>
<h1>这里是多级评论演示!</h1>
<p>*************************************************************</p>
<p>文章文章文章文章文章文章文章文章文章文章文章文章文章文章文章</p>
<p>*************************************************************</p>
<span><b>评论</b></span>
<div>
<div class="box">
<a new_id="3" class="data-show">评论一</a>
</div>
<div class="box">
<a new_id="2" class="data-show">评论一</a>
</div>
<div class="box">
<a new_id="1" class="data-show">评论一</a>
</div>
</div>
</body>
</html>

现在开始书写Ajax,我们通过使用DOM0级进行事件绑定:

Django015-模型|QuerySet

Siffre阅读(48)评论(0)

Django015-模型|QuerySet

提到数据库操作就令自己头疼,刚开始接触数据的时候,对这些关系就是完全懵逼的节奏,现在也没有完全将数据库拿下。。。只能边搞边学习啦!

还是先来看看添加吧:

当我们建立数据模型之后,Django会自动为你生成一套数据库抽象的API,可以进行创建(添加)、检索、更新、删除对象。

这里为何要说成对象呢?那是因为使用Django创建的数据库表中的每行都对应一个Django特有的对象。

在进行创建之前,我们需要做些什么?

答:当然是先创建上文中描述过的对象喽!

下面引入官方的实例:

from django.db import models

class Blog(models.Model):
name = models.CharField(max_length=100)
tagline = models.TextField()

def __str__(self): # __unicode__ on Python 2
return self.name

class Author(models.Model):
name = models.CharField(max_length=50)
email = models.EmailField()

def __str__(self): # __unicode__ on Python 2
return self.name

class Entry(models.Model):
blog = models.ForeignKey(Blog)
headline = models.CharField(max_length=255)
body_text = models.TextField()
pub_date = models.DateField()
mod_date = models.DateField()
authors = models.ManyToManyField(Author)
n_comments = models.IntegerField()
n_pingbacks = models.IntegerField()
rating = models.IntegerField()

def __str__(self): # __unicode__ on Python 2
return self.headline

O(∩_∩)O~ 华丽的分割线

在创建对象之前,先来区分几个要点,不然会懵逼的哦!

首先要知道Django ORM用到三个类:

  • Manager

    Manager定义表级方法(就是影响一条或多条记录的方法),可以使用models.Manager为父类,定义自己的Manager,增加表级方法

  • QuerySet

    QuerySet是一个可遍历结构、包含一个或多个元素,每个元素都是一个Model的实例(对象),它里面的方法是表级方法。

  • Model

    继承django.db.models中的Model类,定义我们自己的表。

QuerySet介绍

每个Model都有一个默认的Manager 实例,名为objects,而QuerySet有两种来源:通过Manager的方法得到或QuerySet方法得到。

Manager的方法和QuerySet的方法大部分一致,如filter(),update()等,但也有一些不同,如Managercreate(),get_or_create(),而QuerySetdelete()等。

Manager类的绝大部分是基于QuerySet的,而一个QuerySet包含一个或多个Model对象。

QuerySet创建对象

Django使用一种直观的方式把数据库表中的数据表示成Python对象:一个模型类表示数据库中的一个表,一个模型类的实例(对象)表示数据库表中的一条特定的记录(表中的每行就是一个对象)。

方式一:

>>> blog = models.Blog(name='Siffre', tagline='Siffre@三弗')
>>> blog.save() #单一数据
#查看(属性的调用)
>>> blog.name
'Siffre'
>>> blog.tagline
'Siffre@三弗'
#修改
>>> blog.name = 'Siffre3'
>>> blog.save()
>>> blog.name
'Siffre3'

上述代码执行了SQL的insert语句

通过关键字参数来实例化一个对象,然后调用save()方法保存到数据库中。当然,在未显示的调用之前Django是不会访问数据库的。

方式二:

可以使用create()方法:

>> blog1 = models.Blog.objects.create(name='alex', tagline='天王盖地虎')
>>> blog1.name, blog1.tagline
('alex', '天王盖地虎')

方式三:

使用get_or_create()方法:

>>> blog2 = models.Blog.objects.get_or_create(name='Siffre2', tagline='宝塔镇河妖')
>>> blog2
(<Blog: Siffre2>, True)
>>> blog3 = models.Blog.objects.get_or_create(name='Siffre', tagline='宝塔镇河妖')
>>> blog3
(<Blog: Siffre>, False)
  1. 该方法会进行判断是否已存在要添加的内容,并返回对应的值:True/False
  2. 推荐使用该方法!
  3. 其他方法并没有返回值!

方式四:

>>> blog4 = models.Blog()
>>> blog4.name = 'Siffre4'
>>> blog4.tagline = '法海不懂爱'
>>> blog.save()
>>> blog4.name, blog4.tagline

此种方法和方式基本是一致的!

上面所创建的每个对象均是数据库表中的每行,这说是创建对象,其实是在创建数据库表中的单行单行数据。

先来看看打印的结果“

#单一对象
>>> blog4 = models.Blog()
>>>> print(blog4, type(blog4))
Siffre4 <class 'app01.models.Blog'>

#获取所有对象
>>> obj = models.Blog.objects.all()
>>> print(obj, type(obj))
[<Blog: Siffre3>, <Blog: alex>, <Blog: Siffre>, <Blog: Siffre2>] <class 'django.db.models.query.QuerySet'>

其中,显示的Siffre3,Siffre4,alex等是打印对象本身时,返回的值,还记得上面添加的__str__吗?返回的值就是return值;去掉后,就是返回对象本身了。

如下图所示:

QuerySet获取对象

获取对象的方式很多,主要是通过特定的格式和一些方法:

获取所有对象

Entry.objects.all() # 查询所有,表名+objects+方法

>>> obj = models.Entry.objects.all()
>>> print(obj, type(obj))
[] <class 'django.db.models.query.QuerySet'>
  1. 返回的对象是列表形式
  2. 属于Django特有的QuerySet

QuerySet特性

在内部,我们可以创建、过滤、切片和传递查询集而不是真实操作数据库。在你对查询集求值之前,不会发生任何实际的数据库操作。

特性如下:

  • 迭代

    查询集是可迭代的,在首次迭代查询集时执行实际的数据库查询。

for e in Entry.objects.all():
print(e.headline)

注意:不要使用上面的语句来验证数据库中是否至少存在一条记录,使用exists()更高效。

  • 切片
  • 序列化/缓存
  • repr()
  • len()
  • list()
    调用次方法,强制对查询集求值:
entry_list = list(Entry.objects.all())
  • bool()

QuerySet详细操作将在下一篇进行总结!

Django014-数据模型Model

Siffre阅读(35)评论(0)

Django014-数据模型Model

1. 模型简介

模型是Django中非常重要的部分,对应数据库的操作—ORM(关系对象映射,Object Relational Mapping)。

models.py中,每个模型的类都是django.db.models.Model的子类,每一个类表示一个数据库的表,每一个属性表示数据库中的字段。

如下实例:

from django.db import models

class UserInfo(models.Model):
username = models.CharField(max_length=32, unique=True)
password = models.CharField(max_length=32)

def __str__(self):
return self.username

对应的数据库命令:

CREATE TABLE UserInfo(
"id" serial not null primary key,
'username' varcahr(32) not null,
'password' varchar(32) not null
);
  1. 其中自增ID,Django自动为我们创建,也可以手动创建nid = models.AutoField(primary_key=True)
  2. 模型中的类名就是数据库的表名
  3. Django会根据数据库的类型来使用相应的SQL语句

2. 模型使用

2.1 注册应用

定义好模型后,我们需要将应用在Django中进行注册,也就是在settings.py配置文件中添加应用名称,以便于程序能加载到:

INSTALLED_APPS = (
#...
'myapp', #项目名称
#...
)

在用模型创建数据库表及相应的关系之前,先来学习一下基本知识点:

常用字段(模型的属性)

这里所说的字段是针对哦数据库而言,对应模型中的属性。

字段的名称应该注意到:不要和模型API中的名称冲突!

字段类型

常用:

1. models.AtuoField() 自增列,若不写Django自动添加,若要自定义自增列,必须设置为主键primary_key=True
2. BigAutoField(AutoField)
- bigint自增列,必须填入参数 primary_key=True

注:当model中如果没有自增列,则自动会创建一个列名为id的列
from django.db import models

class UserInfo(models.Model):
# 自动创建一个列名为id的且为自增的整数列
username = models.CharField(max_length=32)

class Group(models.Model):
# 自定义自增列
nid = models.AutoField(primary_key=True)
name = models.CharField(max_length=32)

3. models.CharField() 字符串字段,必填参数max_length,在数据层和表单验证中均起作用,用来限定字段的长度
4. models.IntegerField() 整型
5. models.BigIntegerField() 长整型
对应的变化范围:
integer_field_ranges = {
    'SmallIntegerField': (-32768, 32767),
    'IntegerField': (-2147483648, 2147483647),
    'BigIntegerField': (-9223372036854775808, 9223372036854775807),
    'PositiveSmallIntegerField': (0, 32767),
    'PositiveIntegerField': (0, 2147483647),
  }

#自定义无符号整数字段

class UnsignedIntegerField(models.IntegerField):
def db_type(self, connection):
return 'integer UNSIGNED'

PS: 返回值为字段在数据库中的属性,Django字段默认的值为:
'AutoField': 'integer AUTO_INCREMENT',
'BigAutoField': 'bigint AUTO_INCREMENT',
'BinaryField': 'longblob',
'BooleanField': 'bool',
'CharField': 'varchar(%(max_length)s)',
'CommaSeparatedIntegerField': 'varchar(%(max_length)s)',
'DateField': 'date',
'DateTimeField': 'datetime',
'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)',
'DurationField': 'bigint',
'FileField': 'varchar(%(max_length)s)',
'FilePathField': 'varchar(%(max_length)s)',
'FloatField': 'double precision',
'IntegerField': 'integer',
'BigIntegerField': 'bigint',
'IPAddressField': 'char(15)',
'GenericIPAddressField': 'char(39)',
'NullBooleanField': 'bool',
'OneToOneField': 'integer',
'PositiveIntegerField': 'integer UNSIGNED',
'PositiveSmallIntegerField': 'smallint UNSIGNED',
'SlugField': 'varchar(%(max_length)s)',
'SmallIntegerField': 'smallint',
'TextField': 'longtext',
'TimeField': 'time',
'UUIDField': 'char(32)',

6. models.FloatField() 浮点型
7. models.BooleanField() 布尔型=tinyint(1),不能为空 Blank=True
8. NullBooleanField(Field):
- 可以为空的布尔值
9. TextField(Field)
- 文本类型

10. EmailField(CharField):
- 字符串类型,Django Admin以及ModelForm中提供验证机制

11. IPAddressField(Field)
- 字符串类型,Django Admin以及ModelForm中提供验证 IPV4 机制

12. GenericIPAddressField(Field)
- 字符串类型,Django Admin以及ModelForm中提供验证 Ipv4和Ipv6
- 参数:
protocol,用于指定Ipv4或Ipv6, 'both',"ipv4","ipv6"
unpack_ipv4, 如果指定为True,则输入::ffff:192.0.2.1时候,可解析为192.0.2.1,开启刺功能,需要protocol="both"

13. URLField(CharField)
- 字符串类型,Django Admin以及ModelForm中提供验证 URL

14. SlugField(CharField)
- 字符串类型,Django Admin以及ModelForm中提供验证支持 字母、数字、下划线、连接符(减号)

15. CommaSeparatedIntegerField(CharField)
- 字符串类型,格式必须为逗号分割的数字

16. UUIDField(Field)
- 字符串类型,Django Admin以及ModelForm中提供对UUID格式的验证

17. FilePathField(Field)
- 字符串,Django Admin以及ModelForm中提供读取文件夹下文件的功能
- 参数:
path, 文件夹路径
match=None, 正则匹配
recursive=False, 递归下面的文件夹
allow_files=True, 允许文件
allow_folders=False, 允许文件夹

18. FileField(Field)
- 字符串,路径保存在数据库,文件上传到指定目录
- 参数:
upload_to = "" 上传文件的保存路径
storage = None 存储组件,默认django.core.files.storage.FileSystemStorage

19. ImageField(FileField)
- 字符串,路径保存在数据库,文件上传到指定目录
- 参数:
upload_to = "" 上传文件的保存路径
storage = None 存储组件,默认django.core.files.storage.FileSystemStorage
width_field=None, 上传图片的高度保存的数据库字段名(字符串)
height_field=None 上传图片的宽度保存的数据库字段名(字符串)

20. DateTimeField(DateField)
- 日期+时间格式 YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]

21. DateField(DateTimeCheckMixin, Field)
- 日期格式 YYYY-MM-DD

22. TimeField(DateTimeCheckMixin, Field)
- 时间格式 HH:MM[:ss[.uuuuuu]]

23. DurationField(Field)
- 长整数,时间间隔,数据库中按照bigint存储,ORM中获取的值为datetime.timedelta类型

24. FloatField(Field)
- 浮点型

25. DecimalField(Field)
- 10进制小数
- 参数:
max_digits,小数总长度
decimal_places,小数位长度

26. BinaryField(Field)
- 二进制类型

字段选项(参数)

1、null=True
  数据库中字段是否可以为空
2、primary_key = False
  主键,对AutoField设置主键后,就会代替原来的自增 id 列
3、auto_now 和 auto_now_add
  auto_now 自动创建---无论添加或修改,都是当前操作的时间
  auto_now_add 自动创建---永远是创建时的时间
4、choices 性别选择常用
GENDER_CHOICE = (
(u'M', u'Male'),
(u'F', u'Female'),
)
gender = models.CharField(max_length=2,choices = GENDER_CHOICE)
5、max_length
6、default  默认值
7、name|db_column  数据库中字段的列名
8、unique=True  不允许重复,自建索引
9、db_index = True  数据库索引
10、editable=True  在Admin里是否可编辑
11、error_messages=None  错误提示
12、auto_created=False  自动创建
13、upload-to 上传到哪个位置,更多与image,filepath配合使用

#Admin相关
1、blank=True
  django的 Admin 中添加数据时是否可允许空值
2、help_text  在Admin中提示帮助信息
3、blank Admin中是否允许用户输入为空
4、editable Admin中是否可以编辑
5、verbose_name  Admin中字段的显示名称
6、 error_messages 自定义错误信息(字典类型),从而定制想要显示的错误信息;
字典健:null, blank, invalid, invalid_choice, unique, and unique_for_date
如:{'null': "不能为空.", 'invalid': '格式错误'}
7、validators=[]
自定义错误验证(列表类型),从而定制想要的验证规则
from django.core.validators import RegexValidator
from django.core.validators import EmailValidator,URLValidator,DecimalValidator,\
MaxLengthValidator,MinLengthValidator,MaxValueValidator,MinValueValidator
如:
test = models.CharField(
max_length=32,
error_messages={
'c1': '优先错信息1',
'c2': '优先错信息2',
'c3': '优先错信息3',
},
validators=[
RegexValidator(regex='root_\d+', message='错误了', code='c1'),
RegexValidator(regex='root_112233\d+', message='又错误了', code='c2'),
EmailValidator(message='又错误了', code='c3'), ]
)

元信息

class UserInfo(models.Model):
nid = models.AutoField(primary_key=True)
username = models.CharField(max_length=32)
class Meta:
# 数据库中生成的表名称 默认 app名称 + 下划线 + 类名
db_table = "table_name"

# 联合索引
index_together = [
("pub_date", "deadline"),
]

# 联合唯一索引
unique_together = (("driver", "restaurant"),)

# admin中显示的表名称
verbose_name

# verbose_name加s
verbose_name_plural

更多:https://docs.djangoproject.com/en/1.10/ref/models/options/
注:
1.触发Model中的验证和错误提示有两种方式:
a. Django Admin中的错误信息会优先根据Admiin内部的ModelForm错误信息提示,如果都成功,才来检查Model的字段并显示指定错误信息
b. 调用Model对象的 clean_fields 方法,如:
# models.py
class UserInfo(models.Model):
nid = models.AutoField(primary_key=True)
username = models.CharField(max_length=32)

email = models.EmailField(error_messages={'invalid': '格式错了.'})

# views.py
def index(request):
obj = models.UserInfo(username='11234', email='uu')
try:
print(obj.clean_fields())
except Exception as e:
print(e)
return HttpResponse('ok')

# Model的clean方法是一个钩子,可用于定制操作,如:上述的异常处理。

2.Admin中修改错误提示
# admin.py
from django.contrib import admin
from model_club import models
from django import forms


class UserInfoForm(forms.ModelForm):
username = forms.CharField(error_messages={'required': '用户名不能为空.'})
email = forms.EmailField(error_messages={'invalid': '邮箱格式错误.'})
age = forms.IntegerField(initial=1, error_messages={'required': '请输入数值.', 'invalid': '年龄必须为数值.'})

class Meta:
model = models.UserInfo
# fields = ('username',)
fields = "__all__"


class UserInfoAdmin(admin.ModelAdmin):
form = UserInfoForm

admin.site.register(models.UserInfo, UserInfoAdmin)

连表关系

Django提供了常见的连表关系:

  • 多对一(many-to-one
  • 多对多(many-to-many
  • 一对一(one-to-one

简单应用场景

  • 多对一:单选下拉框

    如:创建用户信息时,需要选择一个用户类型【普通用户】【金牌用户】【银牌用户】

  • 多对多:多选下拉框

    如:创建用户信息,需要为用户提供多个爱好

  • 一对一:信息记录

    如:一般用于某张表的补充,用户信息是一张表,但并非每一个用户都需要有登录的权限,不需要记录用户名和密码,此时,合理的做法就是新建一张记录登录信息的表,与用户进行一对一的关联,可以方便的从子表查询母表信息或反向查询

其实,一对一,多对多的关系是由多对一衍生而来的,缕清这些关系,只要从多对一入手即可!

实例:

from django.db import models

# Create your models here.

class Colors(models.Model):
colors=models.CharField(max_length=10) #蓝色
def __str__(self):
return self.colors

class Ball(models.Model):
color=models.OneToOneField("Colors") #与颜色表为一对一,颜色表为母表
description=models.CharField(max_length=10) #描述
def __str__(self):
return self.description

class Clothes(models.Model):
color=models.ForeignKey("Colors") #与颜色表为外键,颜色表为母表
description=models.CharField(max_length=10) #描述
def __str__(self):
return self.description

class Child(models.Model):
name=models.CharField(max_length=10) #姓名
favor=models.ManyToManyField('Colors') #与颜色表为多对多

  1. __str__表示:用一个自段替代类本身作为返回值
  2. 在设置ForeignKey时,参数中第一个参数必须为表名,而且该参数有两种方式:

  1. 加引号:不需要考虑建表顺序(推荐)
  2. 不加引号:必须严格按照建表顺序(主表在前,从表在后—有models.ForeignKey的在后面)

其中,多对多关系表有两种创建方式:

  • 自建式
#自建第三张表
class B2G(models.Model):
boy = models.ForeignKey('Boy')
girl = models.ForeignKey('Girl')

class Boy(models.Model):
name = models.CharField(max_length=32)

class Girl(models.Model):
name = models.CharField(max_length=32)

多对多可以理解为是多对一的组合

  • 自动式
class Boy(models.Model):
name = models.CharField(max_length=32)

class Girl(models.Model):
name = models.CharField(max_length=32)
b2g = models.ManyToMany('Boy')

上面简单的介绍了模型的一些基本知识,现在就该来用模型创建数据库表及对应关系了。

2.2 创建数据库表及对应关系

通过上面的基础知识的学习,现在创建表结构应该比较容易啦!

from django.db import models

class Person(models.Model):
name = models.CharField(max_length=128)

def __str__(self): # __unicode__ on Python 2
return self.name

class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(Person, through='Membership')

def __str__(self): # __unicode__ on Python 2
return self.name

class Membership(models.Model):
person = models.ForeignKey(Person)
group = models.ForeignKey(Group)
date_joined = models.DateField()
invite_reason = models.CharField(max_length=64)ult=0)

上面是官方文档的一个实例

2.3 迁移、同步数据库

创建完表和关系后,就该将进行如下操作:

python manage.py syncdb

注意:Django 1.7.1及以上的版本需要用以下命令
python manage.py makemigrations
python manage.py migrate

到此,模型的基本操作流程就结束了!至于数据库的表操作将在下面单独进行复述。

Django023-分页模型

Siffre阅读(48)评论(0)

Django023-分页模型

自设计通用分页模型!

代码如下:

#__author:  Administrator
#date: 2016/12/7

class PagerHelper:
def __init__(self,total_count,current_page,base_url,per_page=10):
self.total_count = total_count
self.current_page = current_page
self.base_url = base_url
self.per_page = per_page
#起始页
@property
def db_start(self):
return (self.current_page -1) * self.per_page
#终止页
@property
def db_end(self):
return self.current_page * self.per_page
#页面总数
def total_page(self):
v, a = divmod(self.total_count, self.per_page)
if a != 0:
v += 1
return v

def pager_str(self):

v = self.total_page()

pager_list = []
if self.current_page == 1:
pager_list.append('<a href="javascript:void(0);">上一页</a>')
else:
pager_list.append('<a href="%s?p=%s">上一页</a>' % (self.base_url, self.current_page - 1,))

# 6,1:12
# 7,2:13
if v <= 11:
pager_range_start = 1
pager_range_end = v
else:
if self.current_page < 6:
pager_range_start = 1
pager_range_end = 11 + 1
else:
pager_range_start = self.current_page - 5
pager_range_end = self.current_page + 5 + 1
if pager_range_end > v:
pager_range_start = v - 10
pager_range_end = v + 1

for i in range(pager_range_start, pager_range_end):
if i == self.current_page:
pager_list.append('<a class="active" href="%s?p=%s">%s</a>' % (self.base_url, i, i,))
else:
pager_list.append('<a href="%s?p=%s">%s</a>' % (self.base_url, i, i,))

if self.current_page == v:
pager_list.append('<a href="javascript:void(0);">下一页</a>')
else:
pager_list.append('<a href="%s?p=%s">下一页</a>' % (self.base_url, self.current_page + 1,))

pager = "".join(pager_list)
return pager

Django006-路由系统

Siffre阅读(74)评论(0)

Django006-路由系统

1.路由介绍

Django程序中,可以同过urls.py文件对所有的url进行任务分配,根据实际业务定制路由规则,交给特定的函数进行处理。

from django.conf.urls import url
from django.contrib import admin
#默认情况
urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(正则表达式,视图函数,参数,别名),
]

参数说明:

  1. 正则表达式匹配路径
  2. 可调用的对象-视图函数
  3. 可选的默认参数(字典形式)
  4. 别名参数

2.路由规则

我将Django的路由机制可分为以下几种:

  • 静态路由
  • 动态路由
  • 路由分发

2.1静态路由

静态路由,这里我们也可以称为精准匹配,也就是说将路由规则写死!

在创建Django项目的时,默认的admin就是静态路由。

匹配规则:http://127.0.0.1:8080/admin/*

from django.conf.urls import url
from django.contrib import admin
#静态路由
urlpatterns = [
    url(r'^admin/', admin.site.urls),
]

自己写一个写一个静态路由:

from django.conf.urls import url
from  blog import views

urlpatterns = [
    url(r'^index/', views.index),
]

2.2动态路由

动态路由,不局限于特定的得url,可根据匹配规则实现大量的url,达到多内容匹配效果,即:利用正则表达式实现分页效果

(1)将最后面的数字作为参数传递

匹配规则:http://127.0.0.1:8080/content/2343

from django.conf.urls import url
from  blog import views

urlpatterns = [
    url(r'^content1/(\d+)', views.content1),
]

(\d+)匹配所有的数字

(2)将后两位数字作为参数传递

匹配规则:http://127.0.0.1:8080/content/2343/23

from django.conf.urls import url
from  blog import views

urlpatterns = [
    url(r'^content2/(\d+)/(\d+)', views.content2),
]
(3)将匹配到的数字的自定义名称作为参数传递

匹配规则:http://127.0.0.1:8080/content/2343/23

from django.conf.urls import url
from  blog import views

urlpatterns = [
    url(r'^content3/(?P<int1>\d+)/(?P<int2>\d+)', views.content3),
]

2.3 路由分发

随着发展变化,网站变得越来越庞大,在一个项目里面虎存在大量的路由规则,这样耦合性太高,容易引发很多不必要的问题,如:

  • 新增加的url规则可能包含旧的url,导致旧的匹配不到
  • 添加错误的url导致站点出现问题
  • app代码间的松耦合问题
  • 。。。

因此,我们将路由进行分层转发,构建二层路由。

(1)在应用中添加urls.py二层路由文件
(2)在全局路由中导入二层路由

全局路由文件

from django.conf.urls import url,include
from blog import urls #导入二层路由

urlpatterns = [
    url(r'^blog/', include(urls)),
]

应用路由文件

from   django.conf.urls import url
from blog import views

urlpatterns = {
    url(r'^page_index/', views.page_index)
}

1.路由分层之后,同样可以实现动态和静态规则
2.Django的路由系统和其他语言的框架不同,每一个url都要有一个路由映射,然后传递给视图函数处理,其他的Web框架多数是对一类的url做一条映射,使路由变得简洁。

注意:路由还有一些琐碎的知识点(别名,附加参数等),不是很常用就不在赘述,看看官网了解即可!

补充:

Django URL逆向解析

Django中提供了一个关于URL映射的解决方案,可以做两个方向的使用:

  • 有客户端的浏览器发起一个URL请求,Django根据URL中的参数捕获,调用响应的视图,获取相应的数据,然后返回给客户端显示
  • 通过一个视图的名字,再加上一些参数和值,逆向获取相应的URL

第一个方向就是平常使用的请求方式;第二个可以叫做:URL逆向解析,URL逆向匹配,URL逆向查阅等

Django提供了不同层级的URL逆向处理工具:

  • 在模板templates中,使用标记,{% url %}
  • 在Python代码中,使用django.core.urlresolvers.reverse()方法
  • 在更高一层级的处理中,使用get_absolute_url()方法

这里只学习模板的使用:

通过URL的命名空间和命名模式解决模板中的href=...超链接的low比问题。

先来看一个示例:

工程的urls.py

from django.conf.urls import url, include
from django.contrib import admin

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'', include('blog.urls', namespace='blog', app_name='blog'))
]

项目的urls.py

from django.conf.urls import url
from blog import views

urlpatterns = [
    url(r'^$', views.IndexView.as_view(), name='index'),
    url(r'^article/(?P<article_id>\d+)$', views.ArticleDetailView.as_view(), name='detail'),
    url(r'^category/(?P<cate_id>\d+)$', views.CategoryView.as_view(), name='category'),
    url(r'^tag/(?P<tag_id>\d+)$', views.TagView.as_view(), name='tag'),
    url(r'^archive/(?P<year>\d+)/(?P<month>\d+)$', views.ArchiveView.as_view(), name='archive'),
    url(r'^article/(?P<article_id>\d+)/comment/$', views.CommentPostView.as_view(), name='comment'),
]

模板文件中的代码:

<div class="post-ft">
     <a href="{% url 'blog:detail' article.id %}" class="more">阅读全文>></a>
     <i class="icon-date"></i>
     <span class="date">{{ article.last_modified_time|date:"Y年n月d日" }}</span>
</div>

上面就是Django URL逆向解析中,命名空间的应用。

URL命名空间

URL命名空间允许你反查到唯一的命名URL,即使在不同应用中使用相同的URL名称。(也就是说,可以在不同 的app中使用相同的名称,为命名困难症的程序员带来了福音)

根据经验,第三方应用应该始终使用带命名空间的URL。类似的,它允许你在一个应用有多个实例部署的情况下反查URL。换句话说,因为一个应用的多个实例共享相同的URL,命名空间将提供一种区分这些命名URL的方法。

在一个站点上,正确使用URL命名空间的Django应用可以部署多次。例如:django.contrib.admin具有一个AdminSite类,它允许你很容易地部署多个管理站点的实例。

下面的例子中,将在两个不同的地方部署应用,这样我们可以为两种不同的用户提供相同的功能。

1.名空间介绍

一个URL命名空间有两个部分,它们都是字符串:

  • 应用命名空间
    它表示正在部署的应用名称。一个应用的每个实例具有相同的应用命名空间。如上例中的namespace='blog', app_name='blog'前者是实例命名空间,后者是应用命名空间
  • 实例命名空间
    它表示应用的一个特定的实例。实例的命名空间在你的全部应用中是唯一的。但是,一个实例的命名空间可以和应用的命名空间相同。它用于表示一个应用的默认实例。例如:上文中的namespace='blog', app_name='blog'

2.命名空间使用

URL得命名空间使用:操作符指定。例如:上例中的模板代码:<a href="{% url 'blog:detail' article.id %}" class="more">阅读全文>></a>,其中blog表示表示实例的命名空间(namespace),detail表示应用文件urls.py定义的URL中的name值。

当然,命名空间也是可以嵌套的。如:URL: "sport:polls:index"将在命名空间polls中查找index,而polls定义在顶层的命名空间sports中。

再来看一个例子:

工程中的urls.py

from django.conf.urls import include, url

urlpatterns = [
    url(r'^author-polls/', include('polls.urls', namespace='author-polls', app_name='polls')),
    url(r'^publisher-polls/', include('polls.urls', namespace='publisher-polls', app_name='polls')),
]

应用的urls.py

from django.conf.urls import url
from . import views

urlpatterns = [
    url(r'^$', views.IndexView.as_view(), name='index'),
    url(r'^(?P<pk>\d+)/$', views.DetailView.as_view(), name='detail'),
    ...
]

上述实例中,polls命名空间设置了两行,那polls下的index到底指的是哪个?

这个时候要看Django的查找顺序:

  1. 如果当前有实例,也就是说,我们通过URL访问到了视图对应的功能函数,这个函数进行反查的时候,例如:我访问的是autho-polls/,这个URL对应的处理函数进行反解析,此时它要解析'polls:detail'。那么将解析到author-polls/(?<pk>\d+)/$中,也就是有实例的优先在实例空间中查询。
  2. 如果没有实例,但是有默认的实例空间,如之前的实例:namespace='blog', app_name='blog',和应用空间同名,这样的就叫做默认的实例空间。在没有实例空间的时候,就匹配到默认的实例空间。
  3. 如果既没有实例也没有默认的实例空间,那么谁在最后注册的就选谁,如例子中的namespace='publisher-polls'就是最后注册的,也就是下面的。

注意:
因为实例空间要是唯一的,所以使用 namespace:name 的模式应该也是唯一匹配的,例如这里的’author-polls:index’ 将永远解析到 ‘author-polls’ 实例的主页(’publisher-polls’ 类似)。

扩展:

关于模板中如何定义{% url %}有多种方式,推荐使用上述实例中的方式。详情请看源码函数中的注释吧:

@register.tag
def url(parser, token):#详细请看函数说明
    """  
    Returns an absolute URL matching given view with its parameters.

    This is a way to define links that aren't tied to a particular URL
    configuration::

        {% url "path.to.some_view" arg1 arg2 %}

        or

        {% url "path.to.some_view" name1=value1 name2=value2 %}

    The first argument is a path to a view. It can be an absolute Python path
    or just ``app_name.view_name`` without the project name if the view is
    located inside the project.

    Other arguments are space-separated values that will be filled in place of
    positional and keyword arguments in the URL. Don't mix positional and
    keyword arguments.

    All arguments for the URL should be present.

    For example if you have a view ``app_name.client`` taking client's id and
    the corresponding line in a URLconf looks like this::

        ('^client/(\d+)/$', 'app_name.client')

    and this app's URLconf is included into the project's URLconf under some
    path::

        ('^clients/', include('project_name.app_name.urls'))

    then in a template you can create a link for a certain client like this::

        {% url "app_name.client" client.id %}

    The URL will look like ``/clients/client/123/``.

    The first argument can also be a named URL instead of the Python path to
    the view callable. For example if the URLconf entry looks like this::

        url('^client/(\d+)/$', name='client-detail-view')

    then in the template you can use::

        {% url "client-detail-view" client.id %}

    There is even another possible value type for the first argument. It can be
    the name of a template variable that will be evaluated to obtain the view
    name or the URL name, e.g.::

        {% with view_path="app_name.client" %}
        {% url view_path client.id %}
        {% endwith %}

        or,

        {% with url_name="client-detail-view" %}
        {% url url_name client.id %}
        {% endwith %}
"""
代码省略
...

上述说明中提供了使用{% url %}的几种方式:

Django011-Session 爱上Cookie

Siffre阅读(73)评论(0)

Django011-Session 爱上Cookie

上篇中,单独介绍了CookieCookie能够独立实现用户的身份认证、信息传递等的功能。个人觉得,随着技术的不断迭代更新,Cookie这种不是很安全的机制,也迎来的新的春天(单身狗寂寞久了)。既然将信息放在客户端容易暴露敏感信息,那就将敏感信息放在服务端,于是乎,就有了Session机制。

小插曲:

SessionCookie的机制,让我联想到了牛郎和织女的故事:牛郎(Cookie)寂寞久了,在某种原因下,和妹纸-织女(Session)喜结连理,由于王母(NetWorking)的阻隔,两人相距在两端(客户端和服务端),只能通过特定的方式联系(HTTP),为了不让其他人知道两人之间的交流信息,织女(Session)给牛郎(Cookie)一串加密的钥匙来证明他的身份,只要拿着正确的钥匙才能沟通,其他任何人都不行,为了确保安全,还添加一些特殊的限制(max_age,expires,...)。因此,Session爱上了Cookie

可能有的朋友会问:哪来那么多对的牛郎(Cookie)和织女(Sess)?
答:现在都什么年代了,只能说牛郎和织女烂大街啦。。。小三儿都有了。。。

1. Session概述

1.1 Session是什么?

Session是一种服务器端的信息管理机制,并将信息以键值对的形式存储在服务器端。

1.2 为什么需要Session

以前技术实现上,只是单独的使用Cookie,很容易将敏感信息暴露,当客户端禁用Cookie时,服务器端就无法确定来访的用户身份,同样也造成一定的数据上的丢失。为了更加安全和方便的确定来访用户的身份,在服务器端添加了Session机制,同Cookie共同来验证来访用户的身份。

1.3 Session的限制是什么?

Session很明显的相对限制就是Cookie,它是相对依赖于Cookie实现的机制,没有Cookie它就是失去了载体的病毒

这里的相对值得注意,下文还有简单的介绍。

1.4 Session原理是什么?

Session并不是协议层的事物,基本原理是服务器端为每一个Session维护一份会话数据,而客户端和服务端依靠一个全局唯一标识来访问会话数据。用户访问Web应用时,服务器端程序决定何时创建Session,创建Session可以分为以下步骤:

  • 用户第一次访问服务端时,服务端会检测Cookie中是否存在全局唯一标识,没有就会生成全局唯一标识(sessionid,默认值,可以更改)
  • 在服务端某处(可设定)开辟数据存储空间。

    Django中有三种方式,将在下面介绍!

  • 将全局唯一标识(sessionid)发送给客户端
  • 用户再次访问服务端时,凭借全局唯一标识就可以直接登录,无须再次验证,直到关闭当前客户端。

此图借用了Java实现的流程,但原理是一样的并不影响(实现代码会有不同)!

在上述流程中有一个 很关键的问题:

服务端如何将sessionid发送到客户端上的?

想到HTTP协议,无非是将数据放到请求行、头域或body里,因此,一般有两种常用的方式:CookieURL重写。

这里就能解释上文的相对了,Session并不是唯一发依赖于Cookie

  1. Cookie实现

服务端只需要设置Set-cookie头就可以将Session的全局唯一标识传递到客户端,客户端此后的每一次请求都会带上这个全局唯一标识,由于Cookie可以设置失效时间,所以一般包含Session信息的Cookie会设置失效时间为0,即浏览器进程有效时间(一般体现在新建浏览器窗口)。

  1. URL重写

所谓的URL重写就是重写URL。在返回用户请求页面之前,将页面内所有的URL后面全部以GET方式加上Session全局唯一标识(或加在path info部分等等),换句话说,就是URL拼接,只要能带上sessionid即可。这样用户在收到响应后,无论点击那个链接或提交表单,都会带上sessionid,从而实现了会话的保持。当客户端禁用了Cookie时,URL重写是首选的方式。

URL重写,可以想象成牛郎(Cookie)的小三儿,哦不,应该是织女(Session)的 外遇

1.4 SessionCookie对比

1. 应用场景

Cookie的典型应用场景是记录服务,即用户的账户信息通过Cookie的形式保存在客户端,当用户再次请求匹配的URL时,账户信息会被传送到服务器端,交由相应的程序完成自动登录等功能。也可以保存一些客户端信息,比如页面布局以及搜索历史等等。

Session的典型应用场景是用户登录某网站后,将其登录信息放入Session,在以后的每次请求中查询响应的登录信息来确保该用户合法。也可以是购物车等经典场景。

2. 安全性

Cookie将信息保存在客户端,如果不进行加密的话,无疑会暴露一些隐私信息,安全性很差,一般情况下敏感信息是经过加密后存储在Cookie中,但仍旧是很容易被窃取、伪造,而Session只会将信息存储在服务端,如果存储文件或数据库中,也有窃取问题,可能性比Cookie小太多了。

Session安全性方面比较突出的是存在会话劫持问题,这是一种安全威胁,但总体来说,安全性明显高于Cookie

3. 性能

Cookie存储在客户端,消耗的是客户端的I/O和内存,而Session 是存储在服务端,消耗的是服务服务端的资源。Session造成的压力比较集中,而Cookie很好的分散压力,但这点来说,Cookie的性能优于Session

4. 时效性

Cookie可以通过设置有效期使其较长时间内存储在客户端, 而Session一般只有比较短的有效期(用户主动销毁或关闭浏览器后引发超时)。

5. 便捷性

Cookie的处理在开发中没有Session方便。

6. 数量和大小

Cookie在客户端的存储有数量(一般200)和大小(4k)限制,而Session的大小和数量以硬件为限制。

2. SessionDjango中的操作

获取Session中的数据

request.session['key']
request.session.get('key', None)

设置Session中的数据

request.session['key'] = value
#存在则不设置
request.session.setdefault('key', 123)

删除Session中的数据

del request.session['key']

清空Session中的数据(服务器端)

request.session.clear()

删除当前用户的所有Session中的数据

request.session.delete("session_key")

获取用户Session中的随机字符串

request.session.session_key

Session中失效日期小于当前日期的数据删除

request.session.clear_expired()

检查用户Session的随机字符串 在数据库中是否

 request.session.exists("session_key")

3. SessionDjango中的应用

这里结合Cookie使用SessionURL的重写略过。

先通过下面实例来了解如何在Django进行登录验证的:

在视图views.py中创建登录验证函数:

def login(request):
message = ''
if request.method == 'GET':
return render(request, 'login.html', {'message': message})
elif request.method == 'POST':
request.session['is_login'] = True
username = request.POST.get('username', None)
password = request.POST.get('password', None)
if username == 'root' and password == 'root':
request.session['username'] = username
return redirect('/index.html')
else:
message = '用户名密码错误!'
return render(request, 'index.html', {'message': message})

创建首页函数:

def index(request):
login_id = request.session.get('is_login', None)
username = request.session.get('username')
if login_id:
return render(request, 'index.html', {'user_name': username})
else:
return redirect('/login.html')

路由和模板自行解决!

功能函数我们写好,在执行之前,我们需要先查看一下客户端的Cookie情况:

上图中可以看到是没有sessionid的!

现在来登录后,再看看:

错误密码:

同样会生成sessionid,用户数据均为空

正确密码:

  1. 跳转情况:

  1. 不跳转页面情况:

修改代码如下:

def login(request):
message = ''
if request.method == 'POST':
request.session['login_id'] = True
username = request.POST.get('username', None)
password = request.POST.get('password', None)
if username == 'root' and password == 'root':
request.session['username'] = username
return HttpResponse('ok') #只需要修改这里即可!
else:
message = '用户名密码错误!'
return render(request, 'login.html', {'message': message})

响应头接收sessionid,同时发到请求头,之后的访问都会携带啦!

直接登录首页:

请求头中携带了sessionid

4. SessionDjango中的配置

默认配置文件setting.pySession相关内容:

...
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions', #Session应用
'django.contrib.messages',
'django.contrib.staticfiles',
'app01',
]

MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware', #session中间件
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

...

Django中默认支持Session,其内部提供了5种类型的Session供开发者使用:

  • 数据库(默认)
  • 缓存
  • 文件
  • 缓存+数据库
  • 加密Cookie

4.1 数据库Session

Django中默认支持Session,并且默认将Session数据存储在数据库中,即:django_session表中。

这里默认使用SqlLite3

配置settings.py

只需要再末尾处直接添加即可!

 SESSION_ENGINE = 'django.contrib.sessions.backends.db'   # 引擎(默认)

SESSION_COOKIE_NAME = "sessionid" # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串(默认)
SESSION_COOKIE_PATH = "/" # Session的cookie保存的路径(默认)
SESSION_COOKIE_DOMAIN = None # Session的cookie保存的域名(默认)
SESSION_COOKIE_SECURE = False # 是否Https传输cookie(默认)
SESSION_COOKIE_HTTPONLY = True # 是否Session的cookie只支持http传输(默认)
SESSION_COOKIE_AGE = 1209600 # Session的cookie失效日期(2周)(默认)
SESSION_EXPIRE_AT_BROWSER_CLOSE = False # 是否关闭浏览器使得Session过期(默认)
SESSION_SAVE_EVERY_REQUEST = False # 是否每次请求都保存Session,默认修改之后才保存(默认)

4.2 缓存Session

缓存比较流行的是使用redis或者memcache

配置settings.py

SESSION_ENGINE = 'django.contrib.sessions.backends.cache'  # 引擎
SESSION_CACHE_ALIAS = 'default' # 使用的缓存别名(默认内存缓存,也可以是memcache),此处别名依赖缓存的设置


SESSION_COOKIE_NAME = "sessionid" # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串
SESSION_COOKIE_PATH = "/" # Session的cookie保存的路径
SESSION_COOKIE_DOMAIN = None # Session的cookie保存的域名
SESSION_COOKIE_SECURE = False # 是否Https传输cookie
SESSION_COOKIE_HTTPONLY = True # 是否Session的cookie只支持http传输
SESSION_COOKIE_AGE = 1209600 # Session的cookie失效日期(2周)
SESSION_EXPIRE_AT_BROWSER_CLOSE = False # 是否关闭浏览器使得Session过期
SESSION_SAVE_EVERY_REQUEST = False # 是否每次请求都保存Session,默认修改之后才保存

4.3 文件Session

配置settings.py

 SESSION_ENGINE = 'django.contrib.sessions.backends.file'    # 引擎
SESSION_FILE_PATH = None # 缓存文件路径,如果为None,则使用tempfile模块获取一个临时地址tempfile.gettempdir() # 如:/var/folders/d3/j9tj0gz93dg06bmwxmhh6_xm0000gn/T


SESSION_COOKIE_NAME = "sessionid" # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串
SESSION_COOKIE_PATH = "/" # Session的cookie保存的路径
SESSION_COOKIE_DOMAIN = None # Session的cookie保存的域名
SESSION_COOKIE_SECURE = False # 是否Https传输cookie
SESSION_COOKIE_HTTPONLY = True # 是否Session的cookie只支持http传输
SESSION_COOKIE_AGE = 1209600 # Session的cookie失效日期(2周)
SESSION_EXPIRE_AT_BROWSER_CLOSE = False # 是否关闭浏览器使得Session过期
SESSION_SAVE_EVERY_REQUEST = False # 是否每次请求都保存Session,默认修改之后才保存

4.4 缓存+数据库Session

数据库用于持久化,缓存用于提高效率!

配置settings.py

 SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'        # 引擎

4.5 加密CookieSession

配置settings.py

SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies'   # 引擎

小结:

  1. Session概述,操作,应用,Django中 的配置
  2. SessionCookie对比和共同使用
  3. tonardo中将自定义Session

补充:

暂无。。。

Django010-浅谈Cookie

Siffre阅读(84)评论(2)

Django010-浅谈Cookie

Cookie看起来是个很简单的东西,但是在Web开发中是一个很重要的客户端数据来源,而且它可以实现扩展性很好的会话状态,因此,三哥必须要对它有一个清晰的认识。

三哥指的是本菜鸡博主 !

1. Cookie概述

1.1 Cookie是什么?

Cookie是以键值对形式存储在客户端的一小段文本信息,伴随着用户请求和页面Web服务器与浏览器之间的传递。Cookie包含每次用户访问站点时,Web应用程序都可以读取的信息。

1.2 为什么需要Cookie

由于我们使用HTTP协议是无状态的,对于一个浏览器发出的多次请求,Web服务器无法区分是不是来源于同一个浏览器。所以,需要添加一个额外的数据用于维护会话,换句话说,就是要添加一个证明身份的标识数据。Cookie正是这样的一段伴随HTTP请求一起被传递的额外数据。

区分HTTP一些概念:http://blog.csdn.net/tennysonsky/article/details/44562435

1.3 Cookie有什么限制?

大多数浏览器支持最大为4096字节的Cookie。由于这限制了Cookie的大小,最好用Cookie来存储少量的数据,如:存储用户ID之类的标识符。用户ID随后就可以用于标识用户,以及从数据库或其他数据源中读取用户信息。浏览器还可以限制站点在用户计算机上存储的Cookie数量。大多数浏览器只允许每个站点存储20个Cookie,如果试图存储更多的Cookie,最旧的Cookie会被丢弃。部分浏览器还会对接受的所有站点的Cookie总数做出绝对限制,通常为300个。

1.4 Cookie的流程

通过前面的知识,我们了解到Cookie用于服务端会话状态,通常由服务端写入,然后在以后的请求中携带Cookie,供服务端读取。

简单流程如下:

  1. 客户端首次发送请求,服务端接收客户端发来的请求,并生成响应信息,响应信息中携带Cookie
  2. 服务端将携带Cookie的响应信息发送给用客户端,客户端接收携带Cookie的响应信息
  3. 客户端再次发送请求,该请求携带从首次接收的Cookie,服务端接收该请求,读取请求信息里面的Cookie进行识别,获取对应的信息
  4. 之后的所有请求均重复第三部的流程,直到客户端关闭(浏览器退出)为止

2. Cookie的实现过程

Django中,读写Cookie我们需要使用HttpResponse类来完成。

2.1 Cookie写入浏览器的过程:

1.服务端

Django代码中,通过views.py里面的函数进行设置:

from  django.shortcuts import HttpResponse
def set_cookie(request):
#创建HTTPResponse对象
response = HttpResponse()
#设置Cookie的key,value
response.set_cookie('CookieKey', 'CookieValue')
return response

Cookie是如何发送到浏览器的呢?又如何发送给服务器的呢?我们使用Google浏览器的F12抓包看看:

注意:

  1. Path=/表示全局,也就是该站点的所有页面
  2. 首次请求Cookie是空的,没有键值对,以后请求和响应中都会存在Cookie,如下图:

从上面两图中,可以发现,Cookie在响应头中被接收,放在请求头中发送到服务端。如果一直刷新页面,就能发现每次HTTP请求,Cookie都会被发送。浏览器也不是发送接收到的所有Cookie,它会自动检查当前请求的域名以及目录,只要这两项与Cookie对应的DomainPath匹配,才会发送。

这样,就引出下面两个小问题了:

(1)跨域名Cookie不共享

对于Domain则是按照尾部匹配的原则进行的。所以,当访问lanzhishi.org时,浏览器并不会将我在浏览www.baidu.com所接收到的Cookie发出去,也就是跨域名Cookie不共享。

(2)二级域名Cookie处理

那同一域名下是二级域名该怎么办呢?

先看图,再解释:

当我们访问耳机域名时,如果该路径下存在Cookie,会取当前的Cookie,若没有,会去上一级的域名路径中搜寻,直到找到Cookie为止。

2.客户端

无论是在服务端还是客户端都可以修改Cookie,在客户端通过JS操作:document.cookie

2.2 Cookie读取的过程:

同样可以在服务端和客户端读取到Cookie,上幅图中,提及到了再客户端如何获取和设置Cookie,这里不在重复。

服务端的获取

Django中获取Cookie

def get_cookie(request):
#判断是否存在cookie
if "CookieKey" in request.COOKIES:
#获取cookie
val_cookie = request.COOKIES['CookieKey']
else:
val_cookie = 'Cookie 不存在!'
return HttpResponse(val_cookie)

结果如下图:

3. Cookie的使用方法和参数

3.1 获取Cookie

  1. 普通Cookie
request.COOKIES['key']

查看request.COOKIES下的所有属性:

['__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'clear', 'copy', 'fromkeys', 'get', 'items', 'keys', 'pop', 'popitem', 'setdefault', 'update', 'values']
  1. 签名Cookie
request.get_signed_cookie(key, default=RAISE_ERROR, salt='', max_age=None)

参数:
default: 默认值
salt: 加密盐
max_age: 后台控制过期时间)

3.2 设置Cookie

  1. 普通Cookie
#创建对象
rep = HttpResponse(...) 或 rep = render(request, ...)
#设置Cookie
rep.set_cookie(key,value,...)

源码,查看该方法下的参数:

def set_cookie(self, key, value='', max_age=None, expires=None, path='/',
domain=None, secure=False, httponly=False)

参数描述:

参数 默认值 描述
max_age None Cookie生存时间,默认情况,这个Cookie延续到浏览器关闭为止
expires None Cookie失效的实际日期/时间,格式必须是:Wdy,DD-Mth-YY HH:MM:SS GMT,这个参数会被max_age参数替代
path "/" Cookie生效路径,默认为全局路径
domain None 这个是Cookie生效的站点,默认情况下,表示当前站点domain='.example.com',则所有带.example.com的二级域名都可以读到Cookie
secure Fasle 安全性处理,默认情况下,为HTTP协议;设置为True,浏览器将通过HTTPS协议传输Cookie
httponly False 在支持httponly的浏览器上,设置成httponly的cookie只能在http(https)请求上传递。也就是说httponly cookie对客户端脚本语言(javascript)无效,从而避免了跨站攻击时JS偷取cookie的情况。当你使用javascript在设置同样名字的cookie时,只有原来的httponly值会传送到服务器。
  1. 签名Cookie
#创建对象
rep = HttpResponse(...) 或 rep = render(request, ...)
#设置Cookie
rep.set_signed_cookie(key,value,salt='',...)

参数和上面的意义是一样的,就是增加了一个加密盐 salt

    def set_signed_cookie(self, key, value, salt='', **kwargs):
value = signing.get_cookie_signer(salt=key + salt).sign(value)
return self.set_cookie(key, value, **kwargs)
#这里的**kwargs表示下面可选的参数

参数:
key, 键
value='', 值
max_age=None, 超时时间
expires=None, 超时时间(IE requires expires, so set it if hasn't been already.)
path='/', Cookie生效的路径,/ 表示根路径,特殊的:跟路径的cookie可以被任何url的页面访问
domain=None, Cookie生效的域名
secure=False, https传输
httponly=False 只能http协议传输,无法被JavaScript获取(不是绝对,底层抓包可以获取到也可以被覆盖)

3.3 删除Cookie

删除Cookie是没有普通和签名之分的哦!

#创建对象
rep = HttpResponse(...) 或 rep = render(request, ...)
#设置Cookie
rep.delete_cookie(key,...)

源码,了解参数:

    def delete_cookie(self, key, path='/', domain=None):
self.set_cookie(key, max_age=0, path=path, domain=domain,
expires='Thu, 01-Jan-1970 00:00:00 GMT')

知道怎么使用这个方法就可以啦!

3.4 其他Cookie操作

其他的Cookie操作方法,自己看源码就知道啦!

4. Cookie的应用

说了这么多,Cookie主要应用在哪里?

  • 会话管理

    1. 记录用户的登录状态是Cookie最常用的用途。通常Web服务器会在用户登录成功后下发一个标识数据来标记会话的有效性,这样免去了用户多次认证和登录网站。
    2. 记录用户的访问状态,如:导航,注册等
  • 个性化信息

    1. Cookie也经常用来记忆用户的信息,方便用户在使用和自己相关的站点服务。例如:QQ登录时,会记忆上一次的登录所用的QQ号码,下次登录时默认填好这个QQ号码。
    2. Cookie也被用来记忆用户自定义的一些功能。用户在设置自定义特征的时候,仅仅是保存在用户的浏览器中,在下一次访问的时候,服务器会根据用户本地的Cookie来表现用户的设置。例如:Google浏览器将搜索设置(语言,显示数量,打开方式等)保存在一个Cookie里。
  • 记录用户的行为
    最典型的是公司的TCSS系统。它使用Cookie来记录用户的点击流和某个产品或商业行为的操作率和流失率。当然,这些功能可以通过IP或Http Header中的referer实现,但Cookie更精准一些。

上面的内容可以通过Fiddler抓包查看到。

以下为代码实现功能:

4.1 用户登录认证

用户的登录认证,会涉及到敏感的信息,放在Cookie很容易泄露,若是放在数据库,就要频繁的操作数据库,用户多的时候可能导致数据库压力大,影响到网站的体验。以下是两种常用的方式:

1. 普通Cookie
普通的Cookie,就是直接将用户信息保存在Cookie中,不考虑安全问题。

#设置Cookie
def set_cookie(request):
#创建HTTPResponse对象
response = HttpResponse()
#设置Cookie的key,value
response.set_cookie('CookieKey', 'CookieValue')
return response

#获取Cookie
def get_cookie(request):
#判断是否存在cookie
if "CookieKey" in request.COOKIES:
#获取cookie
val_cookie = request.COOKIES['CookieKey']
else:
val_cookie = 'Cookie 不存在!'
return HttpResponse(val_cookie)

这些都是普通Cookie的操作,将敏感信息直接暴露在外!

简单的用户认证:

#登录认证
def login(request):
message = ''
if request.method == 'POST':
user = request.POST.get('user', None)
pwd = request.POST.get('pwd', None)
if user == 'root' and pwd == 'root':
rep = redirect('/index.html')
rep.set_cookie('username', 'Siffre')
return rep
else:
message = '用户名或密码错误'
return render(request, 'login.html', {'msg': message})

#使用Cookie,登录该页面
def index(request):
check = request.COOKIES['username']
if check == "Siffre":
return render(request, 'index.html', {"username": check})
else:
return redirect('/login.html')
  1. 首先在浏览器打开http://127.0.0.1:8000/index.html页面,查看Cookie

先不用管报错!这里是没有想要的Cookie,同样再看看login.html页面:

同样是没有认证的Cookie标识数据。

  1. 登录之后,在来看看,下面是登录之后, 跳转到index.html页面的结果:

  1. 在新的浏览器窗口进入到index.html页面

这时,就无需登录,自动获取到之前的用户名!(同样不会出现刚刚的报错情况~(≧▽≦)/~啦啦啦!)

2. 签名Cookie
签名Cookie,就是从安全的角度考虑,将敏感信息进行加密处理。

代码实现功能的流程和上面基本上是一致的,只是调用的方法不同:

#登录认证
def login(request):
message = ''
if request.method == 'POST':
user = request.POST.get('user', None)
pwd = request.POST.get('pwd', None)
if user == 'root' and pwd == 'root':
rep = redirect('/index.html')
rep.set_signed_cookie('username_sign', 'Siffre')
return rep
else:
message = '用户名或密码错误'
return render(request, 'login.html', {'msg': message})

#使用Cookie,登录该页面
def index(request):
check = request.get_signed_cookie('username_sign')
if check == "Siffre":
return render(request, 'index.html', {"username": check})
else:
return redirect('/login.html')

流程看上面的,最终的显示结果:

从上面可以知道,Cookie中的敏感信息变成了加密的随机字符串,防止敏感信息的直接暴露!

当然没有绝对的安全,同样可以破解!

推荐资源

以下为菜鸟必看内容:

  1. C#细说Cookiehttp://www.cnblogs.com/fish-li/archive/2011/07/03/2096903.html
  2. 别让Cookie泄露隐私http://www.qikan.com/article/D8DAB081-599D-4D01-8880-B65DAD277525
  3. 解读CookieSession区别与联系http://aoyouzi.iteye.com/blog/2275329
  4. 全面解读HTTP Cookiehttp://www.webryan.net/2011/08/wiki-of-http-cookie/

小结:

  • 简单的介绍了DjangoCookie
  • Cookie流程,操作,应用
  • 下一篇将介绍CookieSession二者的联系与区别及使用

Django009-模板继承

Siffre阅读(92)评论(0)

Django009-模板继承

当我们写大量的页面时,会写大量的重复性代码!因此,django模板系统,为我们提供了模板的继承能力!

在开发中常见的问题:怎样减少常见页面区域的重复和冗余(如:导航栏)?

解决这个问题的经典方式是使用服务器端引入和导向,可以在模板中嵌套另一个页面。

Django中,确实是支持这样的方式,如下:

1. 模板标签include

该标签,允许你引入另一个模板的内容,标签的参数是引入的模板的名称,名称可以是变量,也可以是单引号或双引号表示的字符串。

单双引号

{% include 'nav.html' %}
{% include "nav.html" %}

模板引入

{% include 'includes/nav.html' %}

变量

{% include templateName %}

2.模板继承

上述方式虽好,但是Django为我们提供了更好的方式:模板继承。

Django的模板继承以块的方式实现,将公共的部分搭建成骨架,非公共部分以的形式进行插拔继承! 哎呦,不小心意淫了一下。。。

2.1模板继承划分

模板的划分十分的简单明了,根据继承的方式:

  • 母板(基础模板)
  • 子模板

2.2模板继承使用

1.创建母板:layout.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
    <link rel="stylesheet" href="..." />
    <style>
        .pg-header{
            height: 48px;
            background-color: cadetblue;
        }
        .pg-body{
            min-height: 500px;
        }
        .pg-body .body-menu{
            width: 20%;
            float: left;
        }
        .pg-body .body-content{
            width: 80%;
            float: left;
        }
        .pg-footer{
            height: 100px;
            background-color: brown;
        }
        .active{
            background-color: aquamarine;
            color: white;
        }
    </style>

    {% block css %}{% endblock %} {# 继承CSS样式 #}
</head>
<body>
    <div class="pg-header">
        后台系统V1
    </div>
    <div class="pg-body">
        <div class="body-menu">
            <ul>
                <li><a href="/web/userinfo">用户管理</a></li>
                <li><a href="/web/assets">资产管理</a></li>
            </ul>
        </div>
        <div class="body-content">
            {% block body1 %}{% endblock %}{# 继承模板骨架 #}
            {% block body2 %}{% endblock %}
            {% block body3 %}{% endblock %}
        </div>

    </div>
    <div class="pg-footer"></div>
    <script src="xxx"></script>
    {% block js %}{% endblock %} {# 继承JS #}
</body>
</html>

上述模板中,包括了三部分继承内容:

  • CSS样式
  • 母板骨架
  • JS

值得注意的地方:

继承的{% block 名称 %} {% endblock %}块,可以使用多个,这里会造成一些小小的混淆:多个子模板继承后,内容会在同一页面显示还是在多个页面显示。

该如何辨别?区分如下图:

上述两种继承的方式都会在同一页面显示三个子模板的内容,但是二者有很大的区别:

左侧: 三个子模板均继承同一个,在母板骨架下,分别显示独立的子模板内容,生成三个独立的页面。

右侧:三个子模板分别继承一个对应的,在母板骨架下,显示三个子模板的全部内容,根据继承顺序依次向下,生成一个独立的页面。

2.创建子模板:“

{% extends 'layout.html' %} {# 继承模板的标签 #}

{% block css %} {# 继承CSS的标签 #}

    <style></style>
{% endblock %}

{% block body %} {# 继承body的标签 #}
    <ul>
    {% for item in user_list %}
        <li>{{ item.username }},{{ item.salary }}</li>
    {% endfor %}
    </ul>

{% endblock %}

{% block js %} {# 继承JS的标签 #}
    <script>
        document.getElementById('userinfo').className = 'active';
    </script>
{% endblock %}

子模板中必须的标签:

  • {% extends '母板名称' %}
  • 标签
  1. 子模板的继承标签,需要与母板中的标签对应
  2. 子模板和母板一样,都是独立的文件

2.3模板继承工作方式

Django载入模板时,模板引擎发现{% extends '名称' %}标签,知道这个是子模板标签,然后模板引擎加载其对应的母板。

此时,模板引擎会注意到母板中有{% block 名称 %}{% endblock %}标签,用子模板对应的标签内容进行替换。

继承并不影响模板的上下文,也就是说,任何在继承上的模板都可以访问到传递模板中的每个模板变量。

说到这里,还有一个地方要提示一下,上面的示例中,

我们将CSSJS代码在母板和子模板文件中都有存在,那么要是全部放在特定的一个静态文件中,该怎么处理呢?

其实很简单,将全部CSSJS内容分别放在一个文件即可,并不影响母板和子模板的修饰效果!

Django008-模板基础

Siffre阅读(76)评论(0)

Django008-模板基础

1. 模板系统介绍

模板基本由两部分组成:

  • HTML
  • 逻辑控制代码

逻辑控制代码又由一下三部分构成:

  1. 变量的使用 {{ username }} #通过大括号来引用变量

  2. tag的使用 {% if use %}{% endif %}{% for i in item_list %}{% endfor %}的配套标签,使用大括号和百分号来表示Django的模板标签

  3. filter的使用 {{ date_time|data:"F j, Y" }}date_time变量传给date过滤器,date过滤器通过使用"F j, Y"这几个参数来格式化日期数据。|代表管道操作。

还有很多内置的tag

2.如何使用模板语言?

其实先要在Python代码中使用模板语言,只需要遵循下面两个步骤:

  1. 可以用原始的模板代码字符串创建一个Template对象,Django同样支持用指定模板文件路径方式来创建Template对象
  2. 调用Template对象的render()方法并提供给它变量,它将返回一个完整的模板字符串内容,包含了所有标签块与变量解析后的内容

3.模板使用

Template不仅可以和views进行配合,也可以独立使用。

最基本的使用方法:

  1. 使用模板 代码字符串作为参数,创建一个Template
  2. 创建模板代码中所需要的上下文Context对象,包含所需要的各个引用参数的值
  3. 调用render()方法,进行模板渲染

3.1 创建模板对象

直接在console下进行,最简单方式是直接初始化它,Template类在django.template模块中,初始化方法需要一个参数,如下:

>>> from django.template  import Template
>>> t = Template("my name is {{ my_namr }}")
#信息如下:
>>> print(t)
<django.template.base.Template object at 0x7f6e8eb76d68>

内存地址每次都会改变,并不受影响,它是Template对象的Python identity

语法错误会触发异常:

>>> t = Template("my  name is {% notag %}")
Traceback (most recent call last):
File "/home/siffre/.local/lib/python3.5/site-packages/django/template/base.py", line 510, in parse
compile_func = self.tags[command]
KeyError: 'notag'

系统触发Template Syntax Error异常可能出现的情况:

  • 不合法的标签
  • 合法的标签接收不合法的参数
  • 不合法的过滤器
  • 合法过滤器接收不合法的参数
  • 不合法的模板语法
  • 块标签没有闭合

3.2 渲染模板

当创建完Template对象,可以通过context来传递数据,context是一个变量及赋值的集合,模板使用它得到变量的值,或者对于块标签求值这个contextdjango.template中的Context类来表示,它的初始化函数有一个可选的参数:一个映射变量名和变量值的字典。

通过context调用Template对象的render()方法进行渲染模板,如下:

>>> from  django.template  import  Template, Context
>>> t = Template("My name is {{ name }}")
>>> c = Context({"name": "Siffre"})
>>> t.render(c)
'My name is Siffre'

渲染多个context

>>> from  django.template  import  Context, Template
>>> t = Template("Hello {{ name }}")
>>> print(t.render(Context({"name":"John"})))
Hello John
>>> print(t.render(Context({"name":"Pat"})))
Hello Pat
>>> print(t.render(Context({"name":"Siffre"})))
Hello Siffre

高效的渲染方式:

创建一次Template对象然后调用render()进行渲染.

#bad
>>> from django.template import Context, Template
>>> for name in ('john', 'Pat', 'Siffre'):
... t = Template('Hello {{ name }}')
... print(t.render(Context({"name": name})))
...
Hello john
Hello Pat
Hello Siffre

这种方式创建了多次的Template对象,相对效率比较低

>>> from  django.template  import  Context, Template
>>> t = Template('Hello {{ name }}')
>>> for name in ('john', 'Pat', 'Siffre'):
... print(t.render(Context({"name": name})))
...
Hello john
Hello Pat
Hello Siffre

这种方式只需要创建一次Template对象,相对上面的效率较高

4. Context使用

4.1Context变量查询

上面我们大部分使用的是字符串,模板系统还可以处理更复杂的数据结构,如:列表,字典和自定义对象。在Django模板系统中处理复杂的数据结构的关键是使用.字符(万能的句点号)。使用小数点来得到字典的key,属性,对象的索引和方法。

访问字典的key

>>> from  django.template  import  Template, Context
>>> person = {'name':"Sally", "age": 25}
>>> t = Temaplate("{{ person.name }} is {{ person.age }} years old.")
>>> c = Context({ "person": person })
>>> t.render(c)
'Sally is 25 years old.'

访问对象属性

>>> from   django.template  import  Template, Context
>>> import datetime
>>> d = datetime.date(1991, 9, 2)
>>> d.year
1991
>>> d.month
9
>>> d.day
2
>>> t = Template('The month is {{ date.month }} and year is {{ date.year }}')
>>> c = Context({'date': d})
>>> t.render(c)
'The month is 9 and year is 1991'

访问列表索引

>>> t = Template('Item 2 is {{ items.2 }} and Item 0 is {{ items.0 }}')
>>> c = Context({'items': ['apples', 'bananas', 'pears']})
>>> t.render(c)
'Item 2 is pears and Item 0 is apples'

索引依旧从零开始,负数的索引是不允许的,会触发语法错误!

访问自定义类的属性(字段)

>>> from django.template import Template, Context
>>> class Person(object):
def __init__(self, first_name, last_name):
self.first_name, self.last_name = first_name, last_name
>>> t = Template('Hello, {{ person.first_name }} {{ person.last_name }}.')
>>> c = Context({'person': Person('John', 'Smith')})
>>> t.render(c)
'Hello, John Smith.'

访问对象的方法

>>> t = Template('{{ var }} -- {{ var.upper }} -- {{  var.isdigit }}')
>>> t.render(Context({'var': 'hello'}))
'hello -- HELLO -- False'
>>> t.render(Context({'var': '123'}))
'123 -- 123 -- True'

上面的操作和Python字符串的操作一样的

多级纵深查询

>>> person = {"name": 'Siffre', "age": 34}
>>> t = Template('{{ person.name.upper }} is {{ person.age }} years old')
>>> c = Context({"person": person})
>>> t.render(c)
'SIFFRE is 34 years old'

方法的调用

方法的调用比其他的查询复杂一些,需要注意以下几点:

  1. 在方法查询的时候,如果一个方法触发了异常,这个异常会传递致使模板渲染失败,但是如果异常有一个值为Truesilent_variable_failure属性,这个变量会渲染成空的字符串:
#触发异常
>>> class PersonClass3:
... def first_name(self):
... raise AssertionError
...
>>> p = PersonClass3()
>>> t.render(Context({'person': p}))
Traceback (most recent call last):
...
AssertionError

#异常处理
>>> class SilentAssertionError(AssertionError):
... silent_variable_failure = True
...
>>> class PersonClass4:
... def first_name(self):
... raise SilentAssertionError
...
>>> p = PersonClass4()
>>> t.render(Context({'person': p}))
'My name is '
  1. 方法的调用仅仅在没有参数时起作用,否则系统将继续查找下一个类型(列表的索引查询)
  2. 一些特殊的方法会导致系统安全性问题
    当你调用对象的delete()方法,模板系统不应该允许做下面 事情:I will now delete this valuable data. {{ account.delete }},为了防止这种情况的发生,可以在方法里设置一个属性alters_data,该属性设置alters_data=True后,模板系统就不在执行这个方法。
def delete(self):
pass
delete.alters_data = True

4.2 Context不合法变量处理

当我们遇到不合法的变量该如何处理??

默认情况下,若变量不存在,模板系统会把它渲染成空的字符串,如下:

>>> t = Template('Your name is {{  name }}')
>>> t.render(Context({'var':'hello'}))
'Your name is '
>>> t.render(Context({'Name':'hello'}))
'Your name is '
>>> t.render(Context({'NAME':'hello'}))
'Your name is

Django会静静的显示错误页面,而不是产生一个异常,这些通常是人为的错误而非异常!

在实际情况中,一个Web站点因为一个模板代码语法的错误而变得不可访问,是无法忍受的!我们可以通过设置Django配置更改其的缺省行为来限制错误!

这个问题的具体情况会在后面涉及到!

4.3 Context对象

大多数的情况下,我们会将字典传给Context(),一旦初始化后,就可以使用标准的Python字典语法操作Context对象:

>>> c = Context({'foo': 'bar'})
>>> c['foo']
'bar'
>>> del c['foo']
>>> c['foo']
Traceback (most recent call last):
File "/usr/lib/python3.5/code.py", line 91, in runcode
exec(code, self.locals)
File "<input>", line 1, in <module>
File "/home/siffre/.local/lib/python3.5/site-packages/django/template/context.py", line 75, in __getitem__
raise KeyError(key)
KeyError: 'foo'
>>> c['name'] = 'Siffre'
>>> c['name']
'Siffre'

这样的操作是基于___dict__,是对象的属性操作等同于字典

Context对象是一个栈(stack),可以使用push()pop()方法。

>>> c = Context()
>>> c['foo'] = 'first'
>>> c.push()
{}
>>> c['foo'] = 'second'
>>> c['foo']
'second'
>>> c.pop()
{'foo': 'second'}
>>> c['foo']
'first'
>>> c['foo'] = 'third'
>>> c['foo']
'third'
>>> c.pop()
Traceback (most recent call last):
File "/usr/lib/python3.5/code.py", line 91, in runcode
exec(code, self.locals)
File "<input>", line 1, in <module>
File "/home/siffre/.local/lib/python3.5/site-packages/django/template/context.py", line 63, in pop
raise ContextPopException
django.template.context.ContextPopException

pop()方法使用太多会触发异常!

5. 模板标签和过滤器

5.1 模板标签

条件判断语句

{% if %} {% elif %} {% else %} {% endif %}

该标签计算一个变量值,如果结果为True表示存在,并执行条件,反之不执行。

{% if athlete_list %}
Number of athlete_list: {{ athlete_list|length }}
{% elif athlete_in_locker_root_list %}
Athlets should be out of the locker room soon!
{% else %}
No athletes.
{% endif %}
  1. 上述语句的执行顺序和Python中的条件判断语句执行顺序一致!
  2. {% elif %}是近期版本才支持的!
  3. 判断条件可以是表达式
  4. 可以嵌套使用标签
  5. andor同样可以灵活运用
  6. 此标签是闭合标签,必须成对出现,否则Django触发TemplateSyntaxError

Django中还提供了类似条件语句功能的标签:

ifequal/ifnotequal

此标签同样是闭合标签,需要配套使用,使用如下:

#参数均可为变量
{% ifequ{}al user currentuser %}
<h1>Welcome</h1>
{% endifequal %}

#参数可为硬编码
{% ifequal user ‘Siffre’ %}
<h1>Welcome</h1>
{% endifequal %}

#支持else
{% ifequal user currentuser %}
<h1>Welcome</h1>
{% else %}
<h1>Go Out</h1>
{% endifequal %}
  1. 除了上述用法,还有小数,整数
  2. 字典,列表,布尔值不能硬编码在标签内

循环语句

{% for %} {% endfor %}

该标签会遍历条件中的可迭代对象,每次循环模板系统都会渲染标签之间的所有内容。

<ul>
{%for athlete in athlete_list %}
<li>{{ athlete.name }}</li>
{% endfor %}
</ul>

该标签同样是闭合标签,需要配套使用

循环语句的一些特殊方法

  1. 变迁中添加reversed来反序可迭代对象
{% for athlete in athlete_list reversed %}
...
{% endfor %}
  1. 嵌套循环
{% for country in countries %}
<h1>{{ country.name }}</h1>
<ul>
{% for city in country.city_list %}
<li>{{ city }}</li>
{% endfor %}
</ul>
{% endfor %}
  1. 循环计数

forloop.counter表示循环的次数,从1开始计数,第一次循环设为1,如下:

{% for item in todo_list %}
<p>{{ forloop.counter }}: {{ item }}</p>
{% endfor %}

其他循环计数的用法也是类似的:

  • forloop.counter0,从0开始计数,第一次循环设为0
  • forloop.revcounter表示循环中剩下的items数量,第一次循环时设为items总数,最后一次设为1
  • forloop.revcounter0表示`items总数少一个,最后一次循环设置为0
  • forloop.first表示当第一次循环时值为True,在特别情况下很有用:
{% for object in objects %}
{% if forloop.first %}
<li class='first'>
{% else %}
<li>
{% endif %}
{{ object }}
</li>
{% endfor %}
  • `forloop.last表示当最后一次循环时值为True
  • forloop。parentioop表示在嵌套循环中表示父级循环的forloop

在模板系统中,循环语句中是不能使用中断语句(breakcontinue)的!

注释

{# This is a comment #}

5.2 过滤器

Django模板系统中的|,相当于Unix系统中的管道符,对前面的结果进行二次处理,也可以多次使用,进行结果的错层次处理。

将变量值转化

#转化Wie小写
{{ name|lower }}

带参数

{{ bio|truncatewords: '30' }}

截断,显示前30个字

比较常用的过滤器

#根据格式化参数来格式化date或datetime对象
{{ pub_date|date:"F j, Y " }}

addslashed,在任何后斜线,单引号,双引号前添加一个后斜线

6. 在视图中使用模板

根据上面的基础知识,我们会这样来写:

from django.http import  HttpResponse
import datetime

def current_datetime(request):
now = datetime.datetime.now()
html = "<html><body>It is now %s.</body</html>" %now
return HttpResponse(html)

改成Django模板系统的方式:

def current_datetime(request):
now = datetime.datetime.now()
t = Template("<html><body>It is now {{ current_date }}.</body</html>")
html = t.render(Context({"current_date": now}))
return HttpResponse(html)

随着模板内容的逐渐增多,将模板放在一个文件里来解决它,然后读取文件得到模板内容:

from django.template import Template, Context  
from django.http import HttpResponse
import datetime

def current_datetime(request):
now = datetime.datetime.now()
# Simple, "dumb" way of saving templates on the filesystem.
# This doesn't account for missing files!
fp = open('/home/siffre/templates/mytemplate.html')
t = Template(fp.read())
fp.close()
html = t.render(Context({'current_date': now}))
return HttpResponse(html)

这种方式很明显不够优雅:

  • 不能处理丢失的文件,如果mytemplate.html不存在或者不可读,调用open()将触发IOError异常
  • 硬编码了模板地址,处理视图方法时,就会重复复制模板地址
  • 引入大量无效代码,多次输入open(), fp.read(), fp.close()

为了解决上面的问题,引入了模板载入和模板目录

7. 模板载入

Django提供了方便和强大的API来从硬盘载入模板,从而减少调用模板和模板本身的冗余。

为了使用Django的模板载入API,首先我们要在settings文件中添加模板路径

使用PyCharm创建项目,系统会自动为我们添加模板路径:

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',
],
},
},
]

近期版本为我们已经准备好了要添加的位置,只需要添加目录名称即可。

这时,我们在视图中的代码就可以这样来写:

from django.template.loader import get_template  
from django.template import Context
from django.http import HttpResponse
import datetime

def current_datetime(request):
now = datetime.datetime.now()
t = get_template('current_datetime.html')
html = t.render(Context({'current_date': now}))
return HttpResponse(html)

8. render

上面的代码依旧是太长。。。更加简洁的方式如下(推荐):

from django.shortcuts import render
from django.http import HttpResponse
import datetime

def current_datetime(request):
now = datetime.datetime.now()
return render(request, 'current_datetime.html', {'current_date': now})

9. locals()技巧

是聪明程序员的专利代言词!

在上面的例子中:

def current_datetime(request):  
now = datetime.datetime.now()
return render(request, 'current_datetime.html', {'current_date': now})

还要将变量now在写一遍,甚至还要在添加字典, 我勒个艹,太麻烦了!

这时,我们就可以使用Python内置的函数locals(),它返回的字典对所有局部变量的名称与值进行映射。所以,我们可以这样写:

def current_datetime(request):
current_date = datetime.datetime.now()
return render_to_response('current_datetime.html', locals())

这样,我们并没有像之前那样需要手工去添加字典和变量,而是使用了locals()方法,它包括了该函数执行到时间点时所定义的所有局部变量。对此,将now改为模板变量current_date

locals()的使用时方便了,但是同时带来了一些弊端,不推荐使用,还是老老实实敲键盘去吧!

小结:

  1. 上面的模板基础,在Django的使用中基本上是足够了!
  2. 还有一些模板继承相关的知识,在后面单独写一篇博文来重点介绍
  3. 模板进阶知识也会在后面写到,敬请期待!