Django 知识点

Django是一个开放源代码的Web应用框架,由Python写成。采用了MVT的软件设计模式,即模型Model,视图View和模板Template。

内容整理自自强学堂

数据迁移

导出的方法

比如我们有一个项目叫 mysite, 里面有一个 app 叫 blog ,我们想导出 blog 的所有数据。

1
python manage.py dumpdata blog > blog_dump.json
## 数据导入,不需要指定 appname

1
python manage.py loaddata blog_dump.json

多数据库连用

每个app都可以单独设置一个数据库

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
# Database
# https://docs.djangoproject.com/en/1.8/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
},
'db1': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': 'dbname1',
'USER': 'your_db_user_name',
'PASSWORD': 'yourpassword',
"HOST": "localhost",
},
'db2': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': 'dbname2',
'USER': 'your_db_user_name',
'PASSWORD': 'yourpassword',
"HOST": "localhost",
},
}

# use multi-database in django
# add by WeizhongTu
DATABASE_ROUTERS = ['project_name.database_router.DatabaseAppsRouter']
DATABASE_APPS_MAPPING = {
# example:
#'app_name':'database_name',
'app1': 'db1',
'app2': 'db2',
}

使用指定的数据库来执行操作

在查询的语句后面用 using(dbname) 来指定要操作的数据库即可

1
2
3
4
5
6
7
# 查询
YourModel.objects.using('db1').all()
或者 YourModel.objects.using('db2').all()

# 保存 或 删除
user_obj.save(using='new_users')
user_obj.delete(using='legacy_users')

多个数据库联用时数据导入导出

如果不是defalut(默认数据库)要在命令后边加 --database=数据库对应的settings.py中的名称 如: --database=db1 或 --database=db2

数据库同步(创建表)

1
2
3
4
python manage.py syncdb #同步默认的数据库,和原来的没有区别

#同步数据库 db1 (注意:不是数据库名是db1,是settings.py中的那个db1,不过你可以使这两个名称相同,容易使用)
python manage.py syncdb --database=db1

数据导出

1
2
3
python manage.py dumpdata app1 --database=db1 > app1_fixture.json
python manage.py dumpdata app2 --database=db2 > app2_fixture.json
python manage.py dumpdata auth > auth_fixture.json

数据库导入

1
2
python manage.py loaddata app1_fixture.json --database=db1
python manage.py loaddata app2_fixture.json --database=db2

Django 缓存系统 (调优)

缓存系统工作原理:

对于给定的网址,尝试从缓存中找到网址,如果页面在缓存中,直接返回缓存的页面,如果缓存中没有,一系列操作(比如查数据库)后,保存生成的页面内容到缓存系统以供下一次使用,然后返回生成的页面内容

1
2
3
4
5
{
'default': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
}
}

也就是默认利用本地的内存来当缓存,速度很快。当然可能出来内存不够用的情况,其它的一些内建可用的 Backends 有

1
2
3
4
5
6
'django.core.cache.backends.db.DatabaseCache'
'django.core.cache.backends.dummy.DummyCache'
'django.core.cache.backends.filebased.FileBasedCache'
'django.core.cache.backends.locmem.LocMemCache'
'django.core.cache.backends.memcached.MemcachedCache'
'django.core.cache.backends.memcached.PyLibMCCache'

利用文件系统来缓存:

1
2
3
4
5
6
7
8
9
10
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
'LOCATION': '/var/tmp/django_cache',
'TIMEOUT': 600,
'OPTIONS': {
'MAX_ENTRIES': 1000
}
}
}

利用数据库来缓存,利用命令创建相应的表:python manage.py createcachetable cache_table_name

1
2
3
4
5
6
7
8
9
10
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
'LOCATION': 'cache_table_name',
'TIMEOUT': 600,
'OPTIONS': {
'MAX_ENTRIES': 2000
}
}
}

一般流程

1
2
3
4
5
from django.shortcuts import render
def index(request):
# 读取数据库等 并渲染到网页
# 数据库获取的结果保存到 queryset 中
return render(request, 'index.html', {'queryset':queryset})

使用chache之后

1
2
3
4
5
6
7
from django.shortcuts import render
from django.views.decorators.cache import cache_page

@cache_page(60 * 15) # 秒数,这里指缓存 15 分钟,不直接写900是为了提高可读性
def index(request):
# 读取数据库等 并渲染到网页
return render(request, 'index.html', {'queryset':queryset})

Memcached 是目前 Django 可用的最快的缓存,另外,Django 还可以共享缓存。

Django 生成静态页面 (调优)

如果网站的流量过大,每次访问时都动态生成,执行SQL语句,消耗大量服务器资源,这时候可以考虑生成静态页面。把静态页面存储到本地,下次就直接访问静态页面就好了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from django.shortcuts import render
from django.template.loader import render_to_string
import os


def my_view(request):
context = {'some_key': 'some_value'}

static_html = '/path/to/static.html'
#匹配静态文件
if not os.path.exists(static_html):
content = render_to_string('template.html', context)
with open(static_html, 'w') as static_file:
static_file.write(content)

return render(request, static_html)

当用户访问时,如果判断没有静态页面就自动生成静态页面,然后返回静态文件,当文件存在的时候就不再次生成。

但是一般情况下都不需要生成静态页面,因为Django 有缓存功能,使用 Django Cache(缓存)就相当于把生成生成静态页面,而且还有自动更新的功能,比如30分钟刷新一下页面内容。

Django安全

1
Django表单用在模板中的时候我们会加一句 {% csrf_token %}

Django国际化

开启国际化的支持,需要在settings.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
MIDDLEWARE_CLASSES = (
...
'django.middleware.locale.LocaleMiddleware',
)


LANGUAGE_CODE = 'en'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True

LANGUAGES = (
('en', ('English')),
('zh-cn', ('中文简体')),
('zh-tw', ('中文繁體')),
)

#翻译文件所在目录,需要手工创建
LOCALE_PATHS = (
os.path.join(BASE_DIR, 'locale'),
)

TEMPLATE_CONTEXT_PROCESSORS = (
...
"django.core.context_processors.i18n",
)

生成需要翻译的文件:

1
2
django-admin.py makemessages -l zh-cn
django-admin.py makemessages -l zh-tw
手工翻译 locale 中的文本后,我们需要编译一下,这样翻译才会生效

1
django-admin.py compilemessages

Django session

Django完全支持也匿名会话,简单说就是使用跨网页之间可以进行通讯,比如显示用户名,用户是否已经发表评论。session框架让你存储和获取访问者的数据信息,这些信息保存在服务器上(默认是数据库中),以 cookies 的方式发送和获取一个包含 session ID的值,并不是用cookies传递数据本身。

启用session

编辑settings.py中的一些配置

MIDDLEWARE_CLASSES 确保其中包含以下内容

1
'django.contrib.sessions.middleware.SessionMiddleware',

INSTALLED_APPS 是包含

1
'django.contrib.sessions',

这些是默认启用的。如果你不用的话,也可以关掉这个以节省一点服务器的开销。

提示:您也可以配置使用比如 cache 来存储 session

在视图中使用 session

request.session 可以在视图中任何地方使用,它类似于python中的字典 session 默认有效时间为两周,可以在 settings.py 中修改默认值。

1
2
3
4
5
6
# 创建或修改 session
request.session[key] = value
# 获取 session
request.session.get(key,default=None)
# 删除 session
del request.session[key] # 不存在时报错

一个不让用户评价两次的例子

HttpResponse

1
2
3
4
5
6
7
8
from django.http import 
def post_comment(request, new_comment):
if request.session.get('has_commented', False):
return HttpResponse("You've already commented.")
c = comments.Comment(comment=new_comment)
c.save()
request.session['has_commented'] = True
return HttpResponse('Thanks for your comment!')

一个简化登录的验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def login(request):
m = Member.objects.get(username=request.POST['username'])
if m.password == request.POST['password']:
request.session['member_id'] = m.id
return HttpResponse("You're logged in.")
else:
return HttpResponse("Your username and password didn't match.")


def logout(request):
try:
del request.session['member_id']
except KeyError:
pass
return HttpResponse("You're logged out.")

Django传递数据给JS

有时候我们想把一个 list 或者 dict 传递给 javascript,处理后显示到网页上,比如要用 js 进行可视化的数据。

错误的使用方法

  • view.py
1
2
3
4
5
6
from __future__ import unicode_literals
from django.shortcuts import render

def home(request):
List = ['自强学堂', '渲染Json到模板']
return render(request, 'home.html', {'List': List})
  • html
1
2
3
4
<script type="text/javascript">
var List = {{ List }};
alert(List);
</script>

如果直接这么做,传递到 js 的时候,网页的内容会被转义,得到的格式会报错。

访问时会得到 Uncaught SyntaxError: Unexpected token ILLEGAL

需要注意两点:

  1. views.py中返回的函数中的值要用 json.dumps()处理
  2. 在网页上要加一个 safe 过滤器。

正确的使用方法

  • view.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# -*- coding: utf-8 -*-

from __future__ import unicode_literals

import json
from django.shortcuts import render

def home(request):
List = ['自强学堂', '渲染Json到模板']
Dict = {'site': '自强学堂', 'author': '涂伟忠'}
return render(request, 'home.html', {
'List': json.dumps(List),
'Dict': json.dumps(Dict)
})
  • html
1
2
3
4
5
6
//列表
var List = {{ List|safe }};
alert(List);
//字典
var Dict = {{ Dict|safe }};
alert(Dict);

Django遍历数组和字典的几种方法

遍历数组的三种方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var List = {{ List|safe }};

//下面的代码把List的每一部分放到头部和尾部
$('#list').prepend(List[0]);
$('#list').append(List[1]);

console.log('--- 遍历 List 方法 1 ---')
for(i in List){
console.log(List[i]);// i为索引
}

console.log('--- 遍历 List 方法 2 ---')
for (var i = List.length - 1; i >= 0; i--) {
// 鼠标右键,审核元素,选择 console 可以看到输入的值。
console.log(List[i]);
};

console.log('--- 同时遍历索引和内容,使用 jQuery.each() 方法 ---')
$.each(List, function(index, item){
console.log(index+' '+item);
});

遍历字典的方法和字典的取值

1
2
3
4
5
6
7
8
9
10
// 字典
var Dict = {{ Dict|safe }};
console.log("--- 两种字典的取值方式 ---")
console.log(Dict['site']);
console.log(Dict.author);

console.log("--- 遍历字典 ---");
for(i in Dict) {
console.log(i + Dict[i]);//注意,此处 i 为键值
}

Django使用Ajax

一个简单的例子,获取单个数值。

jQuery的get方法

1
2
3
4
$.get('API',{'参数名1':参数值1,'参数名2':参数值2},function(结果值){
$('接受返回数据的元素').html(结果值);
$('接受返回数据的元素').val(结果值);
})
  • view.py
1
2
3
4
5
6
7
8
9
10
11
12
# coding:utf-8
from __future__ import unicode_literals
import json
from django.http import HttpResponse
from django.shortcuts import render

def Add(request):
a = request.GET['a']
b = request.GET['b']
a = int(a)
b = int(b)
return HttpResponse(str(a+b))
  • html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{% extends 'Base.html' %} {% block content %}
<p>输入两个数字</p>
<form action="/add/" method="get">
a: <input type="text" id="a" name="a"><br>
b: <input type="text" id="b" name="b"><br>
<p>result: <span id="result"></span></p>
<button type="button" id="sum">计算</button>
</form>
<script>
$(document).ready(function(){
$('#sum').click(function(){
var a = $('#a').val();
var b = $('#b').val();
//使用很重要的jQuery的get方法
$.get('/add/',{'a':a,'b':b},function(ret){
$('#result').html(ret)
})
});
});
</script>
<img src="/static/images/time.jpg" alt="" / > {% endblock %}

较复杂的例子,传递列表和字典。

传递list

传递一个数组或字典到网页,由JS处理,再显示出来。关键是利用jQuery的 getJson 方法,核心代码如下:

1
2
3
4
5
$.getJSON('API',function(结果值){
//返回值 ret 在这里是一个字典
$('#dict_result').append(ret.字典中的键名+'<br>');
//也可以用ret['字典中的键名']
});

需要注意的是API可以这样来写,用 urls.py 中的 name 来获取是一个更好的方法!

1
{% url 'ajax-list' %}
  • view.py
1
2
3
4
5
6
7
8
9
10
11
12
13
from django.http import HttpResponse,JsonResponse

def ajax_list(request):
a = range(100)
# return HttpResponse(json.dumps(a),content_type="application/json")
#Django1.6以后的写法
return JsonResponse(a,safe=False)

def ajax_dict(request):
name_dict = {'twz':'Love python and Django','zqxt':'I am teaching Django'}
#return HttpResponse(json.dumps(name_dict),content_type="application/json")
#Django1.6以后的写法
return JsonResponse(name_dict,safe=False)
  • 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
25
26
27
28
29
<button id='list'>Ajax加载字典</button>
<p id="list_result"></p>

<button id='dict'>Ajax加载列表</button>
<p id="dict_result"></p>

<script>
$(document).ready(function(){
// 列表 list
$('#list').click(function(){
// $.getJSON('/ajax_list/',function(ret){
$.getJSON('{% url 'ajax-list' %}',function(ret){
//返回值 ret 在这里是一个列表
for (var i = ret.length - 1; i >= 0; i--) {
// 把 ret 的每一项显示在网页上
$('#list_result').append(' ' + ret[i])
};
});
});
//字典dict
$('#dict').click(function(){
$.getJSON('/ajax_dict/',function(ret){
//返回值 ret 在这里是一个字典
$('#dict_result').append(ret.zqxt+'<br>');
//也可以用ret['twz']
});
});
});
</script>

传递dict

  • view.py
1
2
3
4
5
6
7
8
9
10
11
def ajax_dict(request):
name_dict = {'twz':'Love python and Django','zqxt':'I am teaching Django'}
person_info_dict = [
{"name":"xiaoming", "age":20},
{"name":"tuweizhong", "age":24},
{"name":"xiaoli", "age":33}
]

#return HttpResponse(json.dumps(name_dict),content_type="application/json")
#Django1.6以后的写法
return JsonResponse(person_info_dict,safe=False)
  • html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<button id='dict'>Ajax加载字典</button>
<p id="dict_result"></p>

<script>
$(document).ready(function(){
//字典dict
$('#dict').click(function(){
$.getJSON('{% url 'ajax-dict' %}',function(ret){
$.each(ret,function(i,item){
$('#list_result').append(i+' '+item.name+' '+item.age+'</br>');
});
});
});
});
</script>

更复杂的例子,Ajax加载图片。

图片是放置在与APP目录同层次的common_static文件夹的pics文件夹之中的。

  • 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
25
26
27
28
29
30
<p>请输入</p>
<form action="/add/" method="get">
color:
<input type="text" id="color" name="color" value="red">
<br> number:
<input type="text" id="number" name="number" value="1">
<br>
<p>result: <span id='result'></span></p>
<button type="button" id='sum'>提交</button>
<img src="" alt="">
</form>
<script src="http://apps.bdimg.com/libs/jquery/1.11.1/jquery.min.js"></script>
<script>
$(document).ready(function() {
$("#sum").click(function() {
var color = $("#color").val();
var number = $("#number").val();

$.get("{% url 'get-pic' %}", {
'color': color,
'number': number
}, function(ret) {
$('#result').html('') //清空前面的结果
$.each(ret, function(index, item) {
$('#result').append('<img src="/static/pics/' + item + '">');
})
})
});
});
</script>
  • view.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
def get_pic(request):
color = request.GET.get('color')
number = request.GET.get('number')
name = '{}_{}'.format(color, number)

print "!"*100
# 过滤出符合要求的图片,假设是以输入的开头的都返回
result_list = filter(lambda x: x.startswith(name), PICS)

print 'result_list', result_list

return HttpResponse(
json.dumps(result_list),
content_type='application/json')

注意filterformat的使用

  • urls.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
from django.conf.urls import patterns, include, url

from django.contrib import admin
admin.autodiscover()

urlpatterns = patterns('',
# Examples:
url(r'^$', 'tools.views.index', name='home'),
url(r'^get_pic/$', 'tools.views.get_pic', name='get-pic'),
# url(r'^blog/', include('blog.urls')),

url(r'^admin/', include(admin.site.urls)),
)

Django Ajax CSRF认证

CSRF通过伪装来自受信任用户的请求来利用受信任的网站

Django 中自带了 防止CSRF攻击的功能,但是一些新手不知道如何使用,给自己编程带来了麻烦。常常会出现下面django csrf token missing or incorrect的错误。

GET 请求不需要 CSRF 认证,POST 请求需要正确认证才能得到正确的返回结果。一般在POST表单中加入

1
{% csrf_token %}

完整代码如下

1
2
3
4
<form method="POST" action="/post-url/">
{% csrf_token %}
<input name='zqxt' value="自强学堂学习Django技术">
</form>

使用Ajax调用的时候,就要麻烦一些。需要注意以下几点:

  • 在视图中使用 render (而不要使用 render_to_response)
  • 使用 jQuery 的 ajax 或者 post 之前 加入这个 js 代码:
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
jQuery(document).ajaxSend(function(event, xhr, settings) {
function getCookie(name) {
var cookieValue = null;
if (document.cookie && document.cookie != '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = jQuery.trim(cookies[i]);
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) == (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
function sameOrigin(url) {
// url could be relative or scheme relative or absolute
var host = document.location.host; // host + port
var protocol = document.location.protocol;
var sr_origin = '//' + host;
var origin = protocol + sr_origin;
// Allow absolute or scheme relative URLs to same origin
return (url == origin || url.slice(0, origin.length + 1) == origin + '/') ||
(url == sr_origin || url.slice(0, sr_origin.length + 1) == sr_origin + '/') ||
// or any other URL that isn't scheme relative or absolute i.e relative.
!(/^(\/\/|http:|https:).*/.test(url));
}
function safeMethod(method) {
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}

if (!safeMethod(settings.type) && sameOrigin(settings.url)) {
xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));
}
});

更为优雅简洁的代码(不能写在 *.js 中,要直接写在模板文件中):

  • csrf.js 放在 static 文件夹里面
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
/*====== django ajax ======*/
jQuery(document).ajaxSend(function(event, xhr, settings) {
function getCookie(name) {
var cookieValue = null;
if (document.cookie && document.cookie != '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = jQuery.trim(cookies[i]);
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) == (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
function sameOrigin(url) {
// url could be relative or scheme relative or absolute
var host = document.location.host; // host + port
var protocol = document.location.protocol;
var sr_origin = '//' + host;
var origin = protocol + sr_origin;
// Allow absolute or scheme relative URLs to same origin
return (url == origin || url.slice(0, origin.length + 1) == origin + '/') ||
(url == sr_origin || url.slice(0, sr_origin.length + 1) == sr_origin + '/') ||
// or any other URL that isn't scheme relative or absolute i.e relative.
!(/^(\/\/|http:|https:).*/.test(url));
}
function safeMethod(method) {
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}

if (!safeMethod(settings.type) && sameOrigin(settings.url)) {
xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));
}
});
/*======= django ajax end ======*/
  • html

```` csrfmiddlewaretoken:

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
代码集合
```html
<script src="/static/jquery.min.js"></script>
<script>
/*
自强学堂 学习更多IT技术
http://www.ziqiangxuetang.com
*/
$('document').ready(function(){
$.ajaxSetup({
data: {
csrfmiddlewaretoken: '{{ csrf_token }}'
},
});
})


function select_drug() {
var drug = document.forms[0].drug;
var drugs = "";
var i;
for (i = 0; i < drug.length; i++) {
if (drug[i].checked) {
drugs = drugs + drug[i].value + " ";
}
}

$.post('{% url "exam2014" %}', {
'drugs': drugs
}, function(ret) {
$('#result').html(ret);
})
}
</script>

Django Sitemap 站点地图

Django 中自带了 sitemap框架,用来生成 xml 文件 sitemap 很重要,可以用来通知搜索引擎页面的地址,页面的重要性,帮助站点得到比较好的收录

自强学堂参考链接

单元测试技术

Django中有完善的单元测试,我们可以对开发的每一个功能进行单元测试,这样只要运行一个命令 python manage.py test,就可以测试功能是否正常。

Python中的单元测试

关键格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import unittest

#目标函数
def 测试函数(参数值1,参数值2,....):
函数体

#编写测试类
class 测试类名(unittest.TestCase):
def 测试函数名(self):
self.assertEqual(测试函数(参数值1,参数值2,....),目标对比参数1)

def 测试函数名1(self):
self.assertEqual(测试函数(参数值1,参数值2,....),目标对比参数2)

def 测试函数名2(self):
self.assertEqual(测试函数(参数值1,参数值2,....),目标对比参数3)

#执行测试单元
if __name__ == '__main__':
unittest.main()

实例:测试一个除法功能

  • 编写测试实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import unittest

def division_funtion(x, y):
return x / y

class TestDivision(unittest.TestCase):
def test_int(self):
self.assertEqual(division_funtion(9, 3), 3)

def test_int2(self):
self.assertEqual(division_funtion(9, 4), 2.25)

def test_float(self):
self.assertEqual(division_funtion(4.2, 3), 1.4)

if __name__ == '__main__':
unittest.main()
  • 运行测试效果

有两个错误9/4=2!=2.25 以及4.2/3=1.4000000000000001 != 1.4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
C:\Users\Tony\Desktop\VueTest>python UniteTest.py
.FF
======================================================================
FAIL: test_int1 (__main__.TestDivision)
----------------------------------------------------------------------
Traceback (most recent call last):
File "UniteTest.py", line 13, in test_int1
self.assertEqual(division_function(9,4),2.25)
AssertionError: 2 != 2.25

======================================================================
FAIL: test_int2 (__main__.TestDivision)
----------------------------------------------------------------------
Traceback (most recent call last):
File "UniteTest.py", line 16, in test_int2
self.assertEqual(division_function(4.2,3),1.4)
AssertionError: 1.4000000000000001 != 1.4

----------------------------------------------------------------------
Ran 3 tests in 0.002s

FAILED (failures=2)
  • 进行相应的修改

设置浮点运算,保留到小数点后六位。

1
2
def division_funtion(x, y):
return round(float(x) / y, 6)
  • 输出结果

完全没有错误了

1
2
3
4
5
6
C:\Users\Tony\Desktop\VueTest>python UniteTest.py
...
-------------------------------------------------
Ran 3 tests in 0.001s

OK

Django中的单元测试

简单测试例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from django.test import TestCase
from myapp.models import Animal

class AnimalTestCase(TestCase):
def setUp(self):
Animal.objects.create(name="lion", sound="roar")
Animal.objects.create(name="cat", sound="meow")

def test_animals_can_speak(self):
"""Animals that can speak are correctly identified"""
lion = Animal.objects.get(name="lion")
cat = Animal.objects.get(name="cat")
self.assertEqual(lion.speak(), 'The lion says "roar"')
self.assertEqual(cat.speak(), 'The cat says "meow"')

Django上下文渲染器

简介

有时候我们想把一个变量在多个模板之间共用,这时候就可以用 Django 上下文渲染器。

  • setting.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'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',
],
},
},
]

这里的 context_processors 中放了一系列的渲染器,上下文渲染器其实就是函数返回字典,这些值可以用在模板中。 request函数就是在返回一个字典,每一个模板中都可以使用这个字典中提供的 request 变量。

比如 在template 中 获取当前访问的用户的用户名:

1
User Name: {{ request.user.username }}

动手写个上下文渲染器

  • 1、新建一个项目,基于 Django 1.9,我们新建了 zqxt 项目和 blog 这个应用。把 blog 这个app 加入到 settings.py 中。
  • 2、我们在 zqxt/zqxt/ 这个目录下(与settings.py 在一起)新建一个 context_processor.py
1
2
3
4
5
6
# -*- coding: utf-8 -*-
from django.conf import settings as original_settings
def settings(request):
return {'settings': original_settings}
def ip_address(request):
return {'ip_address': request.META['REMOTE_ADDR']}
  • 3、我们把新建的两个 上下文渲染器 加入到 settings.py 中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'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',

'zqxt.context_processor.settings',
'zqxt.context_processor.ip_address',
],
},
},
]
  • 4、修改 blog/views.py
1
2
3
4
5
6
7
from django.shortcuts import render

def index(reuqest):
return render(reuqest, 'blog/index.html')

def columns(request):
return render(request, 'blog/columns.html')
  • 5、新建两个模板文件,放在 zqxt/blog/template/blog/ 中

index.html

1
2
3
4
5
<h1>Blog Home Page</h1>

DEBUG: {{ settings.DEBUG }}

ip: {{ ip_address }}
columns.html

1
2
3
4
5
<h1>Blog Columns</h1>

DEBUG: {{ settings.DEBUG }}

ip: {{ ip_address }}
  • 6、修改 zqxt/urls.py
1
2
3
4
5
6
7
8
9
from django.conf.urls import include, url
from django.contrib import admin
from blog import views as blog_views

urlpatterns = [
url(r'^blog_home/$', blog_views.index),
url(r'^blog_columns/$', blog_views.columns),
url(r'^admin/', include(admin.site.urls)),
]
  • 7、打开开发服务器并访问这两个网址就能够查看到相同的数据了

Django 通用视图

通用视图就是在view中创造类,利用类本身的方法和继承自父类的方法来完成相关的功能。

Base Views

django.views.generic.base.View

这个类是通用类的基类,其它类都是继承自这个类,一般不会用到这个类,感觉用函数更简单些。 在urls.py中使用类视图的时候都是调用它的 .as_view()函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# views.py
from django.http import HttpResponse
from django.views.generic import View

class MyView(View):

def get(self, request, *args, **kwargs):
return HttpResponse('Hello, World!')

# urls.py
from django.conf.urls import patterns, url

from myapp.views import MyView

urlpatterns = patterns('',
url(r'^mine/$', MyView.as_view(), name='my-view'),
)

django.views.generic.base.TemplateView

get_context_data() 函数中,可以传一些 额外内容 到 模板

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
# views.py

from django.views.generic.base import TemplateView

from articles.models import Article

class HomePageView(TemplateView):

template_name = "home.html"

def get_context_data(self, **kwargs):
context = super(HomePageView, self).get_context_data(**kwargs)
context['latest_articles'] = Article.objects.all()[:5]
return context


# urls.py

from django.conf.urls import patterns, url

from myapp.views import HomePageView

urlpatterns = patterns('',
url(r'^$', HomePageView.as_view(), name='home'),
)

django.views.generic.base.RedirectView

用来进行跳转, 默认是永久重定向(301),可以直接在urls.py中使用,非常方便:

1
2
3
4
5
6
7
from django.conf.urls import patterns, url
from django.views.generic.base import RedirectView

urlpatterns = patterns('',
url(r'^go-to-django/$', RedirectView.as_view(url='http://djangoproject.com'), name='go-to-django'),
url(r'^go-to-ziqiangxuetang/$', RedirectView.as_view(url='http://www.ziqiangxuetang.com',permant=False), name='go-to-zqxt'),
)

另外的用法,可以用来记录文章的点击次数

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
# views.py
from django.shortcuts import get_object_or_404
from django.views.generic.base import RedirectView

from articles.models import Article

class ArticleCounterRedirectView(RedirectView):

url = ' # 要跳转的网址,
# url 可以不给,用 pattern_name 和 get_redirect_url() 函数 来解析到要跳转的网址

permanent = False #是否为永久重定向, 默认为 True
query_string = True # 是否传递GET的参数到跳转网址,True时会传递,默认为 False
pattern_name = 'article-detail' # 用来跳转的 URL, 看下面的 get_redirect_url() 函数


# 如果url没有设定,此函数就会尝试用pattern_name和从网址中捕捉的参数来获取对应网址
# 即 reverse(pattern_name, args) 得到相应的网址,
# 在这个例子中是一个文章的点击数链接,点击后文章浏览次数加1,再跳转到真正的文章页面
def get_redirect_url(self, *args, **kwargs):
If url is not set, get_redirect_url() tries to reverse the pattern_name using what was captured in the URL (both named and unnamed groups are used).
article = get_object_or_404(Article, pk=kwargs['pk'])
article.update_counter() # 更新文章点击数,在models.py中实现
return super(ArticleCounterRedirectView, self).get_redirect_url(*args, **kwargs)


# urls.py
from django.conf.urls import patterns, url
from django.views.generic.base import RedirectView

from article.views import ArticleCounterRedirectView, ArticleDetail

urlpatterns = patterns('',

url(r'^counter/(?P<pk>\d+)/$', ArticleCounterRedirectView.as_view(), name='article-counter'),
url(r'^details/(?P<pk>\d+)/$', ArticleDetail.as_view(), name='article-detail'),
)

Generic Display View

参考链接

django.views.generic.detail.DetailView

DetailView有以下方法

1.dispatch()
根据发送给view的请求类型分配post()方法或者get()方法,一般在默认情况下,带head的请求都是分配给get()方法处理的,但是如果你想向view发送带head的请求,而且在view处理请求的不是get()方法,可以重写get()方法。
2.http_method_not_allowed()
如果发送给view的方法是django不支持的,那么就会执行这个函数,它会返回django支持的http请求方法
3.get_template_names()
返回一个模板名字的列表,第一个找到的要渲染的目标模会被渲染
4.get_slug_field()
返回一个slug field的名字。然后slug会根据这个名字去查阅slug filed的值。
5.get_queryset()
返回一个QuerySet,包含了要展示在页面上的数据。
6.get_object(queryset=None)
返回单个对象,用于这个view将要展示的数据,如果返回的queryset是有效的,就用这个queryset,否则调用get_queryset()方法,去寻找一个pk_url_kwarg主键参数,如果有这个主键参数,就以此为基础去寻找相关的值,否则就用slug field方法去找相关的值
7.get_context_object_name(obj)
返回一个context variable name,包含页面要操作的数据。当 context_object_name没有被设置的时候,会自动使用model的小写作为 context_object_name,例如Article,article。
8.get_context_data(**kwargs)
返回一个上下文数据(context data),作为列表展示的对象。
9.get()
10.render_to_response()
返回一个self.response_class对象,如果获得任何一个keyword参数,那么keyword参数就会被传递给self.response_class的构造函数。
自动生成当前时间,使用DetailView将额外的数据传输到页面。

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
# views.py
from django.views.generic.detail import DetailView
from django.utils import timezone

from articles.models import Article

class ArticleDetailView(DetailView):

model = Article # 要显示详情内容的类

template_name = 'article_detail.html'
# 模板名称,默认为 应用名/类名_detail.html(即 app/modelname_detail.html)

# 在 get_context_data() 函数中可以用于传递一些额外的内容到网页
def get_context_data(self, **kwargs):
context = super(ArticleDetailView, self).get_context_data(**kwargs)
context['now'] = timezone.now()
return context


# urls.py
from django.conf.urls import url

from article.views import ArticleDetailView

urlpatterns = [
url(r'^(?P<slug>[-_\w]+)/$', ArticleDetailView.as_view(), name='article-detail'),
]

article_detail.html文件新建在app下的templates文件夹里面

1
2
3
4
5
6
7
<h1>标题:{{ object.title }}</h1>
<p>内容:{{ object.content }}</p>
<p>发表人: {{ object.reporter }}</p>
<p>发表于: {{ object.pub_date|date }}</p>


<p>日期: {{ now|date }}</p>

django.views.generic.list.ListView

ListView有以下几种方法: 1. dispatch()
2. http_method_not_allowed()
3. get_template_names()
4. get_queryset()
5. get_context_object_name()
6. get_context_data()
7. get()
8. render_to_response()

view.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
# views.py
from django.views.generic.list import ListView
from django.utils import timezone

from articles.models import Article

class ArticleListView(ListView):

model = Article

def get_context_data(self, **kwargs):
context = super(ArticleListView, self).get_context_data(**kwargs)
context['now'] = timezone.now()
return context

# urls.py:

from django.conf.urls import url

from article.views import ArticleListView

urlpatterns = [
url(r'^$', ArticleListView.as_view(), name='article-list'),
]

html文件

1
2
3
4
5
6
7
8
<h1>文章列表</h1>
<ul>
{% for article in object_list %}
<li>{{ article.pub_date|date }} - {{ article.headline }}</li>
{% empty %}
<li>抱歉,目前还没有文章。</li>
{% endfor %}
</ul>

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!