## Tasking
在我們不了解Django的時候,要對這樣一個任務進行Tasking,有點困難。不過,我們還是可以簡單地看看是應該如何去做:
* 生成APP。對于大部分主流的Web框架來說,它們都可以手動地生成一些腳手架,如Ruby語言中的Ruby On Rails、Node.js中的Express等等。
* 創建對應的Model,即其在數據庫中存儲的模型與我們在代碼中要使用的模型。
* 創建程序對應的View,用于處理數據。
* 創建程序的Template,用于顯示數據。
* 編寫測試來保證功能。
對于其他應用來說也是差不多的。
## 創建BlogpostAPP
### 生成APP
現在我們可以開始創建我們的APP,使用下面的代碼來創建:
$ django-admin startapp blogpost
會在blogpost目錄下,生成下面的文件:
~~~
.
├── __init__.py
├── admin.py
├── apps.py
├── migrations
│?? └── __init__.py
├── models.py
├── tests.py
└── views.py
~~~
### 創建Model
現在,我們需要來創建博客的Model即可。對于一篇基本的博客來說,它會包含下在面的幾部分內容:
* 標題
* 作者
* 鏈接(中文更需要一個好的鏈接)
* 內容
* 發布日期
我們就可以按照上面的內容來創建我們的Blogpost model:
~~~
from django.db import models
from django.db.models import permalink
class Blogpost(models.Model):
title = models.CharField(max_length=100, unique=True)
author = models.CharField(max_length=100, unique=True)
slug = models.SlugField(max_length=100, unique=True)
body = models.TextField()
posted = models.DateField(db_index=True, auto_now_add=True)
def __unicode__(self):
return '%s' % self.title
@permalink
def get_absolute_url(self):
return ('view_blog_post', None, { 'slug': self.slug })
~~~
上面的`get_absolute_url`方法就是用于返回博客的鏈接。之所以使用手動而不是自動生成,是因為自動生成不靠譜,而且不利
然后在Admin注冊這個Model
~~~
from django.contrib import admin
from blogpost.models import Blogpost
class BlogpostAdmin(admin.ModelAdmin):
exclude = ['posted']
prepopulated_fields = {'slug': ('title',)}
admin.site.register(Blogpost, BlogpostAdmin)
~~~
接著進入后臺,我們就可以看到BLOGPOST的一欄里,就可以對其進行相關的操作。

Django后臺界面
點擊Blogpost的Add后,我們就會進入如下的添加博客界面:

Django添加博客
實際上,這樣做的意義是將刪除(Delete)、修改(Update)、添加(Create)這些內容將給用戶后臺來做,當然它也不需要在View/Template層來做。在我們的Template層中,我們只需要關心如何來顯示這些數據。
現在,我們可以執行一次新的代碼提交——因為現在的代碼可以正常工作。這樣出現問題時,我們就可以即時的返回上一版本的代碼。
~~~
git add .
git commit -m "create blogpost model"
~~~
然后再進行下一步地操作。
### 配置URL
現在,我們就可以在我們的`urls.py`里添加相應的route來訪問頁面,代碼如下所示:
~~~
from django.conf import settings
from django.conf.urls import patterns, include, url
from django.conf.urls.static import static
from django.contrib import admin
apiRouter = routers.DefaultRouter()
apiRouter.register(r'blogpost', BlogpostSet)
urlpatterns = patterns('',
(r'^$', 'blogpost.views.index'),
url(r'^blog/(?P<slug>[^\.]+).html', 'blogpost.views.view_post', name='view_blog_post'),
url(r'^admin/', include(admin.site.urls))
) + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
~~~
在上面的代碼里,我們創建了兩個route:
* 指向首頁,其view是index
* 指向博客詳情頁,其view是view_post
指向博客詳情頁的URL正規`r'^blog/(?P<slug>[^\.]+).html`,會將形如blog/hello-world.html中的hello-world提取出來作為參數傳給view_post方法。
接著,我們就可以創建兩個view。
## 創建View
### 創建博客列表頁
對于我們的首頁來說,我們可以簡單的只顯示五篇博客,所以我們所需要做的就是從我們的Blogpost對象中,取出前五個結果即可。代碼如下所示:
~~~
from django.shortcuts import render, render_to_response, get_object_or_404
from blogpost.models import Blogpost
def index(request):
return render_to_response('index.html', {
'posts': Blogpost.objects.all()[:5]
})
~~~
Django的render_to_response方法可以根據一個給定的上下文字典渲染一個給定的目標,并返回渲染后的HttpResponse。即將相應的值,如這里的Blogpost.objects.all()[:5],填入相應的index.html中,再返回最后的結果。
因此,在我們的index.html中,我們就可以拿到前五篇博客。我們只需要遍歷出posts,拿出每個post相應的值,就可以完成列表頁。
~~~
{% extends 'base.html' %}
{% block title %}Welcome to my blog{% endblock %}
{% block content %}
<h1>Posts</h1>
{% for post in posts %}
<h2><a href="{{ post.get_absolute_url }}">{{ post.title }}</a></h2>
<p>{{post.posted}} - By {{post.author}}</p>
<p>{{post.body}}</p>
{% endfor %}
{% endblock %}
~~~
在上面的模板里,我們還取出了博客的鏈接用于跳轉到詳情頁。
### 創建博客詳情頁
依據上面拿到的slug,我們就可以創建對應的詳情頁的view,代碼如下所示:
~~~
def view_post(request, slug):
return render_to_response('blogpost_detail.html', {
'post': get_object_or_404(Blogpost, slug=slug)
})
~~~
這里的`get_object_or_404`將會根據slug來獲取相應的博客,如果取不出相應的博客就會返回404。因此,我們的詳情頁和上面的列表頁也是類似的。
~~~
{% extends 'base.html' %}
{% block head_title %}{{ post.title }}{% endblock %}
{% block title %}{{ post.title }}{% endblock %}
{% block content %}
<h2>{{ post.title }}</a></h2>
<p>{{post.posted}} - By {{post.author}}</p>
<p>{{post.body}}</p>
{% endblock %}
~~~
隨后,我們就可以再提交一次代碼了。
## 測試
TDD雖然是一個非常好的實踐,但是那是對于那些已經習慣寫測試的人來說。如果你寫測試的經歷非常小,那么我們就可以從寫測試開始。
在這里我們使用的是Django這個第三方框架來完成我們的工作,所以我們并不對這個框架的功能進行測試。雖然有些時候正是因為這些第三方框架的問題而導致的Bug,但是我們僅僅只是使用一些基礎的功能。這些基礎的功能也已經在他們的框架中測試過了。
### 測試首頁
先來做一個簡單的測試,即測試我們訪問首頁的時候,調用的函數是上面的index函數
~~~
from django.core.urlresolvers import resolve
from django.http import HttpRequest
from django.test import TestCase
from blogpost.views import index, view_post
class HomePageTest(TestCase):
def test_root_url_resolves_to_home_page_view(self):
found = resolve('/')
self.assertEqual(found.func, index)
~~~
但是這樣的測試看上去沒有多大意義,不過它可以保證我們的route可以和我們的URL對應上。在編寫完測試后,我們就可以命令提示行中運行:
~~~
python manage.py test
~~~
來查看測試的結果:
~~~
Creating test database for alias 'default'...
.
----------------------------------------------------------------------
Ran 1 test in 0.031s
OK
Destroying test database for alias 'default'...
(growth-django)
~~~
運行通過,現在我們可以進行下一個測試了——我們可以測試頁面的標題是不是我們想要的結果:
~~~
def test_home_page_returns_correct_html(self):
request = HttpRequest()
response = index(request)
self.assertIn(b'<title>Welcome to my blog</title>', response.content)
~~~
這里我們需要去請求相應的頁面來獲取頁面的標題,并用assertIn方法來斷言返回的首頁的html中含有`<title>Welcome to my blog</title>`。
### 測試詳情頁
同樣的我們也可以用測試是否調用某個函數的方法,來看博客的詳情頁的route是否正確?
~~~
class BlogpostTest(TestCase):
def test_blogpost_url_resolves_to_blog_post_view(self):
found = resolve('/blog/this_is_a_test.html')
self.assertEqual(found.func, view_post)
~~~
與上面測試首頁不一樣的是,在我們的Blogpost測試中,我們需要創建數據,以確保這個流程是沒有問題的。因此我們需要用`Blogpost.objects.create`方法來創建一個數據,然后訪問相應的頁面來看是否正確。
~~~
def test_blogpost_create_with_view(self):
Blogpost.objects.create(title='hello', author='admin', slug='this_is_a_test', body='This is a blog',
posted=datetime.now)
response = self.client.get('/blog/this_is_a_test.html')
self.assertIn(b'This is a blog', response.content)
~~~
或許你會疑惑這個數據會不會被注入到數據庫中,請看運行測試時返回的結果的第一句:
~~~
Creating test database for alias 'default'...
~~~
Django將會創建一個數據庫用于測試。
同理,我們也可以為首頁添加一個相似的測試:
~~~
def test_blogpost_create_with_show_in_homepage(self):
Blogpost.objects.create(title='hello', author='admin', slug='this_is_a_test', body='This is a blog',
posted=datetime.now)
response = self.client.get('/')
self.assertIn(b'This is a blog', response.content)
~~~
我們用同樣的方法創建了一篇博客,然后在首頁測試返回的內容中是否含有`This is a blog`。