Django 學(xué)習(xí)小組:博客開發(fā)實(shí)戰(zhàn)第三周教程——文章列表分頁和代碼語法高亮

本教程內(nèi)容已過時(shí),更新版教程請(qǐng)?jiān)L問: Django 博客開發(fā)入門教程叫挟。

摘要:前兩期教程我們實(shí)現(xiàn)了博客的 Model 部分蝶押,以及 Blog 的首頁視圖 IndexView,詳情頁面 DetailView匣掸,以及分類頁面 CategoryView。
相關(guān)教程

第一周Django 學(xué)習(xí)小組:博客開發(fā)實(shí)戰(zhàn)第一周教程 —— 編寫博客的 Model 與首頁面
第二周Django 學(xué)習(xí)小組:博客開發(fā)實(shí)戰(zhàn)第二周教程 —— 博客詳情頁面和分類頁面
第四周Django 學(xué)習(xí)小組:博客開發(fā)實(shí)戰(zhàn)第四周——標(biāo)簽云與文章歸檔
第五周Django 學(xué)習(xí)小組:博客開發(fā)實(shí)戰(zhàn)第五周——基于類的通用視圖詳解(一)
第六周Django 學(xué)習(xí)小組:博客開發(fā)實(shí)戰(zhàn)第六周教程 —— 實(shí)現(xiàn)評(píng)論功能

本周我們將繼續(xù)完善我們的個(gè)人博客氮双,來實(shí)現(xiàn)分頁和代碼高亮的功能。

提示:在閱讀教程的過程中霎匈,如有任何問題請(qǐng)?jiān)L問我們項(xiàng)目的 GithHub 或評(píng)論留言以獲取幫助戴差,本教程的相關(guān)代碼已全部上傳在 GitHub 的 blog-tutorial 分支 上。如果你對(duì)我們的教程或者項(xiàng)目有任何改進(jìn)建議铛嘱,請(qǐng)您隨時(shí)告知我們暖释。更多交流請(qǐng)加入我們的郵件列表 django_study@groups.163.com 和關(guān)注我們?cè)?Github 上的項(xiàng)目袭厂。

本文首發(fā)于編程派微信公眾號(hào):編程派(微信號(hào):codingpy)是一個(gè)專注Python編程的公眾號(hào),每天更新有關(guān)Python的國(guó)外教程和優(yōu)質(zhì)書籍等精選干貨球匕,歡迎關(guān)注纹磺。


實(shí)現(xiàn)文章展示列表的分頁功能

我們的數(shù)據(jù)庫(kù)中會(huì)有越來越多的文章,把它們?nèi)坑靡粋€(gè)列表顯示在首頁好像不太合適亮曹,如果顯示一定數(shù)量的文章橄杨,比如8篇,這就需要用到分頁功能照卦。
Django提供了一些類來幫助你管理分頁的數(shù)據(jù) -- 也就是說式矫,數(shù)據(jù)被分在不同頁面中,并帶有“上一頁/下一頁”標(biāo)簽役耕。這些類位于django/core/paginator.py中采转。

文章過多,為了提高用戶體驗(yàn)瞬痘,一次只展示部分文章故慈,為用戶提供一個(gè)分頁功能,就像下面這樣:

分頁效果圖

比較完善的分頁效果框全,應(yīng)該是這樣的:

  • 用戶在哪一頁惯悠,則當(dāng)前頁號(hào)高亮以提示用戶所在位置,比如上圖顯示用戶正處在第二頁竣况。
  • 當(dāng)用戶所處的位置還有上一頁時(shí)克婶,顯示上一頁按鈕;當(dāng)還有下一頁時(shí)丹泉,顯示下一頁按鈕情萤,否則不顯示。
  • 當(dāng)分頁較多時(shí)摹恨,總是顯示當(dāng)前頁及其前幾頁和后幾頁的頁碼(教程中使用的是兩頁)筋岛,其他頁碼用省略號(hào)代替。
  • 總是顯示第一頁和最后一頁的頁碼晒哄。

根據(jù)上面的需求睁宰,我們開始編寫相應(yīng)代碼。

關(guān)于分頁需要使用到的的 API 寝凌,Django 官方文檔對(duì)此有十分詳細(xì)的介紹柒傻,它還給出了一個(gè)完整示例,讀懂它的代碼后仿照它即可實(shí)現(xiàn)基本的分頁功能较木。請(qǐng)參考官方文檔對(duì)于分頁的示例红符,如果你不習(xí)慣英文的話,也可以參照網(wǎng)友的翻譯版本Django 中文文檔:分頁。下面就根據(jù)官方的示例來實(shí)現(xiàn)我們的需求预侯。

盡管可以把分頁邏輯直接寫在視圖內(nèi)致开,但是為了通用性,我們使用一點(diǎn)點(diǎn) Django 更加高級(jí)的技巧——模板標(biāo)簽(TemplateTags)萎馅。分頁功能的實(shí)現(xiàn)有很多第三方 APP 可以直接使用双戳,但是為了學(xué)習(xí) Django 的知識(shí),所以我們自己實(shí)現(xiàn)一個(gè)糜芳。這些第三方 APP 基本都是使用的模板標(biāo)簽飒货,因此這可能是一種比較好的實(shí)踐。

為了使用模板標(biāo)簽耍目,Django 要求我們先建立一個(gè) templatetags 文件夾膏斤,并在里面加上 __init__.py文件以指示 python 這是一個(gè)模塊(python 把含有該問價(jià)的文件夾當(dāng)做一個(gè)模塊,具體請(qǐng)參考任何一個(gè)關(guān)于 python 模塊的教程)邪驮。并且 templatetags 文件夾和你的 model.py莫辨,views.py 文件是同級(jí)的,也就是說你的目錄結(jié)構(gòu)看起來應(yīng)該是這樣:

polls/
    __init__.py
    models.py
    templatetags/
        __init__.py
        poll_extras.py
    views.py

(這個(gè)目錄結(jié)構(gòu)引自官方文檔毅访,關(guān)于詳細(xì)的模板標(biāo)簽的介紹請(qǐng)參考官方文檔:custom template tags沮榜,不一定全部讀懂,但還是推薦花幾十分鐘掃一遍明白其大致說了什么)喻粹。

在 templatetags 目錄下建立一個(gè) paginate_tags .py 文件蟆融,準(zhǔn)備工作做完,結(jié)合 Django 的模板系統(tǒng)守呜,我們來看看該如何編寫我們的程序型酥。

首先來回顧一下 Django 的模板系統(tǒng)是如何工作的,回想一下視圖函數(shù)的工作流程查乒,視圖函數(shù)接收一個(gè) Http 請(qǐng)求弥喉,經(jīng)過一系列處理,通常情況下其會(huì)渲染某個(gè)模板文件玛迄,把模板文件中的一些用 {{ }} 包裹的變量替換成從該視圖函數(shù)中相應(yīng)變量的值由境。事實(shí)上在此過程中 Django 悄悄幫我們做了一些事情,它把視圖函數(shù)中的變量的值封裝在了一個(gè) Context (一般翻譯成上下文)對(duì)象中蓖议,只要模板文件中的變量在 Context 中有對(duì)應(yīng)的值虏杰,它就會(huì)被相應(yīng)的值替換。因此勒虾,我們的程序可以這樣做:首先把取到的文章列表(官方術(shù)語是一個(gè) queryset)分頁纺阔,用戶請(qǐng)求第幾頁,我們就把第幾頁的文章列表傳遞給模板文件从撼;另外還要根據(jù)上面的需求傳遞頁碼值給模板文件州弟,這樣只要把模板文件中的變量替換成我們傳遞過去的值钧栖,那么就達(dá)到本文開篇處那樣的分頁顯示效果了低零。

開始編寫我們的代碼了婆翔,慣例依然是先看代碼,然后我們?cè)僦鹦薪忉專?/p>

paginate_tags.py

from django import template
from django.core.paginator import Paginator, PageNotAnInteger, EmptyPage

register = template.Library()


@register.simple_tag(takes_context=True)
def paginate(context, object_list, page_count):
    left = 3
    right = 3

    paginator = Paginator(object_list, page_count)
    page = context['request'].GET.get('page')

    try:
        object_list = paginator.page(page)
        context['current_page'] = int(page)
        pages = get_left(context['current_page'], left, paginator.num_pages) + get_right(context['current_page'], right,
                                                                                         paginator.num_pages)
    except PageNotAnInteger:
        object_list = paginator.page(1)
        context['current_page'] = 1
        pages = get_right(context['current_page'], right, paginator.num_pages)
    except EmptyPage:
        object_list = paginator.page(paginator.num_pages)
        context['current_page'] = paginator.num_pages
        pages = get_left(context['current_page'], left, paginator.num_pages)

    context['article_list'] = object_list
    context['pages'] = pages
    context['last_page'] = paginator.num_pages
    context['first_page'] = 1
    try:
        context['pages_first'] = pages[0]
        context['pages_last'] = pages[-1] + 1
    except IndexError:
        context['pages_first'] = 1
        context['pages_last'] = 2

    return ''  # 必須加這個(gè)掏婶,否則首頁會(huì)顯示個(gè)None


def get_left(current_page, left, num_pages):
    if current_page == 1:
        return []
    elif current_page == num_pages:
        l = [i - 1 for i in range(current_page, current_page - left, -1) if i - 1 > 1]
        l.sort()
        return l
    l = [i for i in range(current_page, current_page - left, -1) if i > 1]
    l.sort()
    return l


def get_right(current_page, right, num_pages):
    if current_page == num_pages:
        return []
    return [i + 1 for i in range(current_page, current_page + right - 1) if i < num_pages - 1]

首先讓我們來看看整個(gè)分頁程序的執(zhí)行過程啃奴,模板標(biāo)簽本質(zhì)上來說就是一個(gè) python 函數(shù)而已,只是該函數(shù)可以被用在 Django 的模板系統(tǒng)里面雄妥。函數(shù)就是接受參數(shù)最蕾,返回一個(gè)值。例如我們這里定義的 def paginate(context, object_list, page_count): 分頁函數(shù)老厌,它接收了這么一些參數(shù)瘟则,經(jīng)過各種處理,最終返回了 None 枝秤。

逐行解釋:

paginate.py

from django import template
from django.core.paginator import Paginator, PageNotAnInteger, EmptyPage
# 這是分頁功能涉及的一些類和異常醋拧,官方文檔對(duì)此有詳細(xì)介紹。當(dāng)然從命名也可以直接看出它們的用途:Paginator(分頁)淀弹,PageNotAnInteger(頁碼不是一個(gè)整數(shù)異常)丹壕,EmptyPage(空的頁碼號(hào)異常)

register = template.Library()
# 這是定義模板標(biāo)簽要用到的

@register.simple_tag(takes_context=True) 
# 這個(gè)裝飾器表明這個(gè)函數(shù)是一個(gè)模板標(biāo)簽,takes_context = True 表示接收上下文對(duì)象薇溃,就是前面所說的封裝了各種變量的 Context 對(duì)象菌赖。
def paginate(context, object_list, page_count): 
    # context是Context 對(duì)象,object_list是你要分頁的對(duì)象沐序,page_count表示每頁的數(shù)量
    
    left = 3 # 當(dāng)前頁碼左邊顯示幾個(gè)頁碼號(hào) -1琉用,比如3就顯示2個(gè)
    right = 3 # 當(dāng)前頁碼右邊顯示幾個(gè)頁碼號(hào) -1

    paginator = Paginator(object_list, page_count) # 通過object_list分頁對(duì)象
    page = context['request'].GET.get('page') # 從 Http 請(qǐng)求中獲取用戶請(qǐng)求的頁碼號(hào)

    try:
        object_list = paginator.page(page) # 根據(jù)頁碼號(hào)獲取第幾頁的數(shù)據(jù)
        context['current_page'] = int(page) # 把當(dāng)前頁封裝進(jìn)context(上下文)中
        pages = get_left(context['current_page'], left, paginator.num_pages) + get_right(context['current_page'], right, paginator.num_pages)
        # 調(diào)用了兩個(gè)輔助函數(shù),根據(jù)當(dāng)前頁得到了左右的頁碼號(hào)策幼,比如設(shè)置成獲取左右兩邊2個(gè)頁碼號(hào)邑时,那么假如當(dāng)前頁是5,則 pages = [3,4,5,6,7],當(dāng)然一些細(xì)節(jié)需要處理垄惧,比如如果當(dāng)前頁是2刁愿,那么獲取的是pages = [1,2,3,4]
      
    except PageNotAnInteger:
        # 異常處理,如果用戶傳遞的page值不是整數(shù)到逊,則把第一頁的值返回給他
        object_list = paginator.page(1)
        context['current_page'] = 1 # 當(dāng)前頁是1
        pages = get_right(context['current_page'], right, paginator.num_pages)
    except EmptyPage:
        # 如果用戶傳遞的 page 值是一個(gè)空值铣口,那么把最后一頁的值返回給他
        object_list = paginator.page(paginator.num_pages)
        context['current_page'] = paginator.num_pages # 當(dāng)前頁是最后一頁,num_pages的值是總分頁數(shù)
        pages = get_left(context['current_page'], left, paginator.num_pages)

    context['article_list'] = object_list # 把獲取到的分頁的數(shù)據(jù)封裝到上下文中
    context['pages'] = pages # 把頁碼號(hào)列表封裝進(jìn)去
    context['last_page'] = paginator.num_pages # 最后一頁的頁碼號(hào)
    context['first_page'] = 1 # 第一頁的頁碼號(hào)為1
    try:
        # 獲取 pages 列表第一個(gè)值和最后一個(gè)值觉壶,主要用于在是否該插入省略號(hào)的判斷脑题,在模板文件中將會(huì)體會(huì)到它的用處。注意這里可能產(chǎn)生異常铜靶,因?yàn)閜ages可能是一個(gè)空列表叔遂,比如本身只有一個(gè)分頁他炊,那么pages就為空,因?yàn)槲覀冇肋h(yuǎn)不會(huì)獲取頁碼為1的頁碼號(hào)(至少有1頁已艰,1的頁碼號(hào)已經(jīng)固定寫在模板文件中)
        context['pages_first'] = pages[0]
        context['pages_last'] = pages[-1] + 1
        # +1的原因是為了方便判斷痊末,在模板文件中將會(huì)體會(huì)到其作用。
        
    except IndexError:
        context['pages_first'] = 1 # 發(fā)生異常說明只有1頁
        context['pages_last'] = 2 # 1 + 1 后的值 

    return ''  # 必須加這個(gè)哩掺,否則首頁會(huì)顯示個(gè)None


def get_left(current_page, left, num_pages):
    """
    輔助函數(shù)凿叠,獲取當(dāng)前頁碼的值得左邊兩個(gè)頁碼值,要注意一些細(xì)節(jié)嚼吞,比如不夠兩個(gè)那么最左取到2盒件,為了方便處理,包含當(dāng)前頁碼值舱禽,比如當(dāng)前頁碼值為5炒刁,那么pages = [3,4,5]
    """
    if current_page == 1:
        return []
    elif current_page == num_pages:
        l = [i - 1 for i in range(current_page, current_page - left, -1) if i - 1 > 1]
        l.sort()
        return l
    l = [i for i in range(current_page, current_page - left, -1) if i > 1]
    l.sort()
    return l


def get_right(current_page, right, num_pages):
    """
    輔助函數(shù),獲取當(dāng)前頁碼的值得右邊兩個(gè)頁碼值誊稚,要注意一些細(xì)節(jié)翔始,比如不夠兩個(gè)那么最右取到最大頁碼值。不包含當(dāng)前頁碼值片吊。比如當(dāng)前頁碼值為5绽昏,那么pages = [6,7]
    """
    if current_page == num_pages:
        return []
    return [i + 1 for i in range(current_page, current_page + right - 1) if i < num_pages - 1]

把需要變量值都添加到上下文了,看看我們的模板文件該怎么寫:

templates/blog/pagination.html

<div id="pagenavi" class="noselect">
    {% if article_list.has_previous %} # 判斷是否還有上一頁俏脊,有的話要顯示一個(gè)上一頁按鈕
        <a class="previous-page" href="?page={{ article_list.previous_page_number }}">
            <span class="icon-previous"></span>上一頁
        </a>
    {% endif %}

    # 頁碼號(hào)為1永遠(yuǎn)顯示
    {% if first_page == current_page %} # 當(dāng)前頁就是第一頁
        <span class="first-page current">1</span>
    {% else %} # 否則的話全谤,第一頁是可以點(diǎn)擊的,點(diǎn)擊后通過?page=1的形式把頁碼號(hào)傳遞給視圖函數(shù)
        <a href="?page=1" class="first-page">1</a>
    {% endif %}

    {% if pages_first > 2 %} # 2以前的頁碼號(hào)要被顯示成省略號(hào)了
        <span>...</span>
    {% endif %}

    {% for page in pages %} # 通過for循環(huán)把pages中的值顯示出來
        {% if page == current_page %} # 是否當(dāng)前頁爷贫,按鈕會(huì)顯示不同的樣式
            <span class="current">{{ page }}</span>
        {% else %}
            <a href="?page={{ page }}">{{ page }}</a>
        {% endif %}
    {% endfor %}
    
    # pages最后一個(gè)值+1的值小于最大頁碼號(hào)认然,說明有頁碼號(hào)需要被省略號(hào)替換
    {% if pages_last < last_page %}
        <span>...</span>
    {% endif %}
    
    # 永遠(yuǎn)顯示最后一頁的頁碼號(hào),如果只有一頁則前面已經(jīng)顯示了1就不用再顯示了
    {% if last_page != 1 %}
        {% if last_page == current_page %}
            <span class="current">{{ last_page }}</span>
        {% else %}
            <a href="?page={{ last_page }}">{{ last_page }}</a>
        {% endif %}
    {% endif %}
    
    # 還有下一頁漫萄,則顯示一個(gè)下一頁按鈕
    {% if article_list.has_next %}
        <a class="next-page" href="?page={{ article_list.next_page_number }}">
            下一頁<span class="icon-next"></span>
        </a>
    {% endif %}
</div>

至此代碼部分編寫完了卷员,看看如何使用這個(gè)模板標(biāo)簽吧,比如我們要在首頁對(duì)文章列表進(jìn)行分頁:

templates/blog/index.html

{% load paginate_tags %} # 首先必須通過load模板標(biāo)簽載入分頁標(biāo)簽
{% paginate article_list 7 %} 把文章列表傳給paginate函數(shù)腾务,每頁分7個(gè)毕骡,context上下文則自動(dòng)被傳入,無需顯示指定

{% for article in article_list %}
    display the article information
{% endfor %}

{% include 'blog/pagination.html' %}
# 這里用到一個(gè) include 技巧岩瘦,把pagination的模板代碼寫在單獨(dú)的pagination.html文件中未巫,這樣哪里需要用到哪里就 include 進(jìn)來就行,提高代碼的復(fù)用性启昧。

至此叙凡,整個(gè)分頁功能就完成了,看看效果:

文章分頁演示

支持 fetch code 與代碼高亮

fetch code

我們的博客文章是支持 markdown 語法標(biāo)記的(使用的是 markdown2 第三方 app)密末,markdown 比較常用的兩個(gè)特性是 fetch code 和語法高亮握爷。由于我們目前沒有對(duì)博客文章的 markdown 標(biāo)記做任何拓展跛璧,因此要標(biāo)記一段代碼,我們必須在每行代碼前縮進(jìn) 4 個(gè)空格新啼,這很不方便追城。而 fetch code 可以讓我們?cè)趯懳恼聲r(shí)只按照下面的輸入就可以標(biāo)記一段代碼,相比每行縮進(jìn)四個(gè)空格要方便很多:

[?```](實(shí)際中去掉中括號(hào))
def test_function():
    print('fectch code like this!')
?[?```]

下面來拓展它师抄,很簡(jiǎn)單漓柑,把用 markdown 標(biāo)記的語句拓展一下教硫,在 Views.py 中找到 IndexView叨吮,其中有一句代碼的作用是來 markdown 我們的博客文章的:

for article in article_list:
    article.body = markdown2.markdown(article.body, )

將 markdown 函數(shù)拓展一下,傳入如下參數(shù)即可:

for article in article_list:
    article.body = markdown2.markdown(article.body, extras=['fenced-code-blocks'], )

這樣瞬矩,每次要輸入一段代碼時(shí)茶鉴,按照上面的語法輸入就可以了,比如我輸入下面的代碼段:

[?```] # 實(shí)際中去掉中括號(hào)景用!注意這個(gè)符號(hào)是半角下波浪符號(hào)涵叮,即數(shù)字1左邊的那個(gè)鍵對(duì)應(yīng)的符號(hào),
class ArticleDetailView(DetailView):
    model = Article
    template_name = "blog/detail.html"
    context_object_name = "article"
    pk_url_kwarg = 'article_id'

    def get_object(self, queryset=None):
        obj = super(ArticleDetailView, self).get_object()
        obj.body = markdown2.markdown(obj.body, extras=['fenced-code-blocks'], )
        return obj
[?```]

來看看效果:

fech code

此外別忘了把其他做了 markdown 標(biāo)記的地方也做相應(yīng)拓展伞插,目前我們一共有三處:IndexView割粮,DetailView,CategoryView媚污。

代碼高亮

現(xiàn)在輸入代碼方便了舀瓢,但是美中不足的是代碼只有一種顏色,我們想要代碼高亮耗美,需要使用到 Pygments 包京髓。先安裝它:pip install pygments,安裝好后別忘了添加到 settings.py 中:

settings.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'blog',
    'markdown2',
    'pygments', # 添加進(jìn)來
]

pygments 的工作原理是把代碼切分成一個(gè)個(gè)單詞商架,然后為這些單詞添加 css 樣式堰怨,不同的詞應(yīng)用不同的樣式,這樣就實(shí)現(xiàn)了代碼顏色的區(qū)分蛇摸,即高亮了語法备图,因此我們要引入一些 css 樣式文件。在我們的 GitHub 的 blog-tutorial 分支 的 DjangoBlog/blog/static/blog/css 目錄下有相應(yīng)的文件赶袄,拷貝下來添加到你的項(xiàng)目相同目錄下就可以了揽涮。之后再模板中引入樣式文件:

templates/base.html

<head>
    <meta charset="UTF-8">
    <title>Myblog</title>
    ...
    <link rel="stylesheet" href="{% static 'blog/css/pygments/github.css' %}">
    引入上面的樣式文件,當(dāng)然里面有很多樣式文件,喜歡哪個(gè)引哪個(gè)弃鸦,比如我引的是github風(fēng)格的語法高亮
    ...
</head>

再次輸入代碼塊看看:

?[?```] # 實(shí)際中去掉中括號(hào)python # 注:這里一定要指定相應(yīng)語言绞吁,否則無法高亮代碼
class ArticleDetailView(DetailView):
    model = Article
    template_name = "blog/detail.html"
    context_object_name = "article"
    pk_url_kwarg = 'article_id'

    def get_object(self, queryset=None):
        obj = super(ArticleDetailView, self).get_object()
        obj.body = markdown2.markdown(obj.body, extras=['fenced-code-blocks'], )
        return obj
[?```] # 實(shí)際中去掉中括號(hào)

看看效果:

代碼高亮

這里比較麻煩的是必須指定代碼對(duì)應(yīng)的語言,有人說 pygments 可以自動(dòng)識(shí)別語言的唬格,但是我目前的測(cè)試來看似乎沒有效果家破。目前沒有找到設(shè)置方法颜说,如有知道的朋友請(qǐng)告知。

整個(gè)完整的 Blog 項(xiàng)目代碼請(qǐng)?jiān)L問我們的 GitHub 組織倉(cāng)庫(kù)的 blog-tutorial 分支獲取汰聋。

聲明:本教程只是演示如何實(shí)現(xiàn)分頁和 markdown 語法高亮功能门粪,在細(xì)節(jié)上處理上還有很多需要斟酌的地方,如果您有更好的實(shí)現(xiàn)方式或者實(shí)踐經(jīng)驗(yàn)烹困,懇請(qǐng)傳授我們玄妈。如果您對(duì)本教程有任何不清晰的地方或者其他意見和建議,請(qǐng)及時(shí)通過郵件列表或者 GitHub Issue 或者評(píng)論留言反饋給我們髓梅。您的反饋和建議是我們持續(xù)改善本教程的最佳方式拟蜻。

接下來做什么?

個(gè)人博客功能逐步完善枯饿,接下來的教程我們將繼續(xù)實(shí)現(xiàn)個(gè)人博客常帶的功能:標(biāo)簽云文章歸檔酝锅,敬請(qǐng)期待下一期教程。如果你還有其他想實(shí)現(xiàn)的功能奢方,也請(qǐng)告訴我們搔扁,我們會(huì)在教程中陸續(xù)實(shí)現(xiàn)。

Django學(xué)習(xí)小組簡(jiǎn)介

django學(xué)習(xí)小組是一個(gè)促進(jìn) django 新手互相學(xué)習(xí)蟋字、互相幫助的組織稿蹲。

小組在一邊學(xué)習(xí) django 的同時(shí)將一起完成幾個(gè)項(xiàng)目,包括:

  • 一個(gè)簡(jiǎn)單的 django 博客鹊奖,用于發(fā)布小組每周的學(xué)習(xí)和開發(fā)文檔苛聘;
  • django中國(guó)社區(qū),為國(guó)內(nèi)的 django 開發(fā)者們提供一個(gè)長(zhǎng)期維護(hù)的 django 社區(qū)嫉入;

上面所說的這個(gè)社區(qū)類似于 segmentfault 和 stackoverflow 焰盗,但更加專注(只專注于 django 開發(fā)的問題)。

目前小組正在完成第一個(gè)項(xiàng)目咒林,本文即是該項(xiàng)目第三周的相關(guān)文檔熬拒。

更多的信息請(qǐng)關(guān)注我們的 github 組織,本教程項(xiàng)目的相關(guān)源代碼也已上傳到 GitHub 的 blog-tutorial 分支 上垫竞,請(qǐng)點(diǎn)擊鏈接獲取澎粟。

同時(shí),你也可以加入我們的郵件列表 django_study@groups.163.com 欢瞪,隨時(shí)關(guān)注我們的動(dòng)態(tài)活烙。我們會(huì)將每周的詳細(xì)開發(fā)文檔和代碼通過郵件列表發(fā)出。

如有任何建議遣鼓,歡迎提 Issue啸盏,歡迎 fork,pr骑祟,當(dāng)然也別忘了 star 哦回懦!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末气笙,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子怯晕,更是在濱河造成了極大的恐慌潜圃,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,496評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件舟茶,死亡現(xiàn)場(chǎng)離奇詭異谭期,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)吧凉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門隧出,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人客燕,你說我怎么就攤上這事鸳劳。” “怎么了也搓?”我有些...
    開封第一講書人閱讀 162,632評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)涵紊。 經(jīng)常有香客問我傍妒,道長(zhǎng),這世上最難降的妖魔是什么摸柄? 我笑而不...
    開封第一講書人閱讀 58,180評(píng)論 1 292
  • 正文 為了忘掉前任颤练,我火速辦了婚禮,結(jié)果婚禮上驱负,老公的妹妹穿的比我還像新娘嗦玖。我一直安慰自己,他們只是感情好跃脊,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評(píng)論 6 388
  • 文/花漫 我一把揭開白布宇挫。 她就那樣靜靜地躺著,像睡著了一般酪术。 火紅的嫁衣襯著肌膚如雪器瘪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,165評(píng)論 1 299
  • 那天绘雁,我揣著相機(jī)與錄音橡疼,去河邊找鬼。 笑死庐舟,一個(gè)胖子當(dāng)著我的面吹牛欣除,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播挪略,決...
    沈念sama閱讀 40,052評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼历帚,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼废酷!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起抹缕,我...
    開封第一講書人閱讀 38,910評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤澈蟆,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后卓研,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體趴俘,經(jīng)...
    沈念sama閱讀 45,324評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評(píng)論 2 332
  • 正文 我和宋清朗相戀三年奏赘,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了寥闪。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,711評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡磨淌,死狀恐怖疲憋,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情梁只,我是刑警寧澤缚柳,帶...
    沈念sama閱讀 35,424評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站搪锣,受9級(jí)特大地震影響秋忙,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜构舟,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評(píng)論 3 326
  • 文/蒙蒙 一灰追、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧狗超,春花似錦弹澎、人聲如沸芽隆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽撑蚌。三九已至麦撵,卻和暖如春刽肠,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背免胃。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工音五, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人羔沙。 一個(gè)月前我還...
    沈念sama閱讀 47,722評(píng)論 2 368
  • 正文 我出身青樓躺涝,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子坚嗜,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容