用django來開發自己的博客

[TOC]

Django的MVC模式/MTV模式

Django緊緊地遵循MVC模式,可以稱得上是一種MVC框架。 以下是Django中M、V和C各自的含義:

  • M:數據存取部分,由django數據庫層處理;
  • V:選擇顯示哪些數據要顯示以及怎樣顯示的部分,由視圖和模板處理;
  • C:根據用戶輸入委派視圖的部分,由Django框架根據URLconf設置,對給定URL調用適當的Python函數。

由于C由框架自行處理,而Django里更關注的是模型(Model)、模板(Template)和視圖(Views),Django也被稱為MTV框架。在MTV開發模式中:

  • M:代表模型(Model),即數據存取層。該層處理與數據相關的所有事務:如何存取、如何驗證有效性、包含哪些行為以及數據之間的關系等;
  • T:代表模板(Template),即表現層。該層處理與表現相關的決定:如何在頁面或其他類型文檔中進行顯示。
  • V:代表視圖(View),即業務邏輯層。該層包含存取模型及調取恰當模板的相關邏輯。你可以把它看作模型與模板之間的橋梁。

(以上摘自《The Django Book》)

完整的開發過程

1. 創建開發環境

mkvirtualenv myblog  # 創建虛擬環境
pip install django  # 安裝最新版的django-1.10.6

2. 創建項目和app

django-admin.py startproject myblog
django-admin.py startapp blog  # 一個項目可以包含多個app

當前目錄結構如下圖所示


目錄結構1.png

目錄結構介紹

  • blog: app的目錄
  • migrations: 包含對模型定義與修改的遷移記錄;
  • admin.py: 對 Django 站點管理的定制;
  • apps.py: 包含對 App 的配置;
  • models.py: 應包含定義的模型;
  • tests.py: 包含單元測試;
  • views.py: 應包含各種視圖。
  • myblog: 整個項目的配置目錄
  • settings.py: 項目的配置文件
  • urls.py: 總的urls配置文件
  • **wsgi.py **: 部署服務器時用到的文件

新定義的app加到settings.py中的INSTALL_APPS中
編輯setting文件,如下圖


深度截圖20170304154138.png

3. 連接數據庫

暫時先用默認的sqlite3數據庫,不需要特別的配置。以后需要再改成別的數據庫。


4. 建立模型

  • 文章模型
class Article(models.Model):
    STATUS = (
        ('0', '發布'),
        ('1', '草稿')
    )
    title = models.CharField(max_length=64, unique=True, verbose_name='標題')
    abstract = models.TextField(verbose_name='摘要', max_length=54, blank=True, null=True, help_text="可選項,若為空格則摘取正文前54個字符")
    body = models.TextField(verbose_name='內容')
    # on_delete 當指向的表被刪除時,將該項設為空
    category = models.ForeignKey('Category', verbose_name='分類', null=True, on_delete=models.SET_NULL)
    tags = models.ManyToManyField('Tag', verbose_name='標簽', blank=True)
    url = models.CharField(max_length=255, verbose_name='鏈接', unique=True)
    status = models.CharField(default='0', max_length=1, choices=STATUS, verbose_name='文章狀態')
    created_time = models.DateTimeField(auto_now_add=True, verbose_name='創建時間')
    last_modified_time = models.DateTimeField(auto_now=True, verbose_name='最近修改時間')

    class Meta:
        # Meta 包含一系列選項,這里的ordering表示排序, - 表示逆序
        # 即當從數據庫中取出文章時,以文章最后修改時間逆向排序
        verbose_name = '文章(Article)'
        verbose_name_plural = verbose_name
        ordering = ['-last_modified_time']

    def __str__(self):
        return self.title
  • 分類模型
class Category(models.Model):
    name = models.CharField(max_length=20, verbose_name='類名')
    created_time = models.DateTimeField(auto_now_add=True, verbose_name='創建時間')
    last_modified_time = models.DateTimeField(auto_now=True, verbose_name='最近修改時間')

    class Meta:
        verbose_name = '分類(Category)'
        verbose_name_plural = verbose_name
        ordering = ['-created_time']

    def __str__(self):
        return self.name
  • 標簽模型
class Tag(models.Model):
    name = models.CharField(max_length=20, verbose_name='標簽名')
    created_time = models.DateTimeField(auto_now_add=True, verbose_name='創建時間')
    last_modified_time = models.DateTimeField(auto_now=True, verbose_name='最近修改時間')

    class Meta:
        verbose_name = '標簽(Tag)'
        verbose_name_plural = verbose_name
        ordering = ['-created_time']

    def __str__(self):
        return self.name
  • 評論模型
class Comment(models.Model):
    user_name = models.CharField(max_length=64, verbose_name='評論者名字')
    content = models.TextField(verbose_name='評論內容')
    created_time = models.DateTimeField(auto_now_add=True, verbose_name='評論時間')
    article = models.ForeignKey('Article', verbose_name='評論所屬文章', on_delete=models.CASCADE)

    def __str__(self):
        return self.content[:20]

模型只是利用 Django 提供的 ORM 完成對實際表結構的映射,因此在完成模型定義后,我們需要將其真正同步到實際的數據庫中去。新版本的 Django 中,該操作需要分成兩步,

  1. 生成 migrations:
    python manage.py makemigrations blog
    深度截圖20170304162344.png
  2. 進行數據庫同步
    python manage.py migrate
    深度截圖20170304162405.png

可以用python manage.py shell進入django shell對數據庫進行操作
如下圖所示

深度截圖20170304164013.png


5. 設置后臺管理

from django.contrib import admin
from .models import Article, Category, BlogComment, Tag

# Register your models here.

admin.site.register([Article, Category, BlogComment, Tag])

6. 設置分頁

Django提供了一些類來幫助你管理分頁的數據,這些類位于django/core/paginator.py中。
分頁需要使用到的的 API ,Django 官方文檔對此有十分詳細的介紹,請參考django文檔中分頁教程
盡管可以把分頁邏輯直接寫在視圖內,但是為了通用性,我們使用一點點 Django 更加高級的技巧——模板標簽(TemplateTags)。

模板標簽介紹

為了使用模板標簽,Django 要求我們先建立一個 templatetags 文件夾,并在里面加上 init.py文件以指示 python 這是一個模塊(python 把含有該問價的文件夾當做一個模塊,具體請參考任何一個關于 python 模塊的教程)。并且 templatetags 文件夾和你的 model.py,views.py 文件是同級的,也就是說你的目錄結構看起來應該是這樣:

app/
    __init__.py
    models.py
    templatetags/
        __init__.py
        app_extras.py
    views.py

分頁代碼

首先來回顧一下 Django 的模板系統是如何工作的,回想一下視圖函數的工作流程,視圖函數接收一個 Http 請求,經過一系列處理,通常情況下其會渲染某個模板文件,把模板文件中的一些用 {{ }} 包裹的變量替換成從該視圖函數中相應變量的值。事實上在此過程中 Django 悄悄幫我們做了一些事情,它把視圖函數中的變量的值封裝在了一個 Context (一般翻譯成上下文)對象中,只要模板文件中的變量在 Context 中有對應的值,它就會被相應的值替換。
因此,我們的程序可以這樣做:首先把取到的文章列表(官方術語是一個 queryset)分頁,用戶請求第幾頁,我們就把第幾頁的文章列表傳遞給模板文件;另外還要根據上面的需求傳遞頁碼值給模板文件,這樣只要把模板文件中的變量替換成我們傳遞過去的值,那么就達到本文開篇處那樣的分頁顯示效果了。
paginate.py

from django import template
from django.core.paginator import Paginator, PageNotAnInteger, EmptyPage
// 這是分頁功能涉及的一些類和異常,官方文檔對此有詳細介紹。當然從命名也可以直接看出它們的用途:Paginator(分頁),PageNotAnInteger(頁碼不是一個整數異常),EmptyPage(空的頁碼號異常)

register = template.Library()
// 這是定義模板標簽要用到的

@register.simple_tag(takes_context=True)
// 這個裝飾器表明這個函數是一個模板標簽,takes_context = True 表示接收上下文對象,就是前面所說的封裝了各種變量的 Context 對象。
def paginate(context, object_list, page_count):
    //context是Context 對象,object_list是你要分頁的對象,page_count表示每頁的數量
    left = 3 # 當前頁碼左邊顯示幾個頁碼號 -1,比如3就顯示2個
    right = 3 # 當前頁碼右邊顯示幾個頁碼號 -1
    paginator = Paginator(object_list, page_count) # 通過object_list分頁對象
    page = context['request'].GET.get('page') # 從 Http 請求中獲取用戶請求的頁碼號
    try:
        object_list = paginator.page(page) # 根據頁碼號獲取第幾頁的數據
        context['current_page'] = int(page) # 把當前頁封裝進context(上下文)中
        pages = get_left(context['current_page'], left, paginator.num_pages) + get_right(context['current_page'], right, paginator.num_pages)
        // 調用了兩個輔助函數,根據當前頁得到了左右的頁碼號,比如設置成獲取左右兩邊2個頁碼號,那么假如當前頁是5,則 pages = [3,4,5,6,7],當然一些細節需要處理,比如如果當前頁是2,那么獲取的是pages = [1,2,3,4]
    except PageNotAnInteger:
        // 異常處理,如果用戶傳遞的page值不是整數,則把第一頁的值返回給他
        object_list = paginator.page(1)
        context['current_page'] = 1 # 當前頁是1
        pages = get_right(context['current_page'], right, paginator.num_pages)
    except EmptyPage:
        // 如果用戶傳遞的 page 值是一個空值,那么把最后一頁的值返回給他
        object_list = paginator.page(paginator.num_pages)
        context['current_page'] = paginator.num_pages # 當前頁是最后一頁,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 # 第一頁的頁碼號為1
    try:
        // 獲取 pages 列表第一個值和最后一個值,主要用于在是否該插入省略號的判斷,在模板文件中將會體會到它的用處。注意這里可能產生異常,因為pages可能是一個空列表,比如本身只有一個分頁,那么pages就為空,因為我們永遠不會獲取頁碼為1的頁碼號(至少有1頁,1的頁碼號已經固定寫在模板文件中)
        context['pages_first'] = pages[0]
        context['pages_last'] = pages[-1] + 1
        // +1的原因是為了方便判斷,在模板文件中將會體會到其作用。
    except IndexError:
        context['pages_first'] = 1 # 發生異常說明只有1頁
        context['pages_last'] = 2 # 1 + 1 后的值
    return ''  # 必須加這個,否則首頁會顯示個None

def get_left(current_page, left, num_pages):
    """
    輔助函數,獲取當前頁碼的值得左邊兩個頁碼值,要注意一些細節,比如不夠兩個那么最左取到2,為了方便處理,包含當前頁碼值,比如當前頁碼值為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):
    """
    輔助函數,獲取當前頁碼的值得右邊兩個頁碼值,要注意一些細節,比如不夠兩個那么最右取到最大頁碼值。不包含當前頁碼值。比如當前頁碼值為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 %} # 判斷是否還有上一頁,有的話要顯示一個上一頁按鈕
        <a class="previous-page" href="?page={{ article_list.previous_page_number }}">
            <span class="icon-previous"></span>上一頁
        </a>
    {% endif %}

    # 頁碼號為1永遠顯示
    {% if first_page == current_page %} # 當前頁就是第一頁
        <span class="first-page current">1</span>
    {% else %} # 否則的話,第一頁是可以點擊的,點擊后通過?page=1的形式把頁碼號傳遞給視圖函數
        <a href="?page=1" class="first-page">1</a>
    {% endif %}

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

    {% for page in pages %} # 通過for循環把pages中的值顯示出來
        {% if page == current_page %} # 是否當前頁,按鈕會顯示不同的樣式
            <span class="current">{{ page }}</span>
        {% else %}
            <a href="?page={{ page }}">{{ page }}</a>
        {% endif %}
    {% endfor %}

    # pages最后一個值+1的值小于最大頁碼號,說明有頁碼號需要被省略號替換
    {% if pages_last < last_page %}
        <span>...</span>
    {% endif %}

    # 永遠顯示最后一頁的頁碼號,如果只有一頁則前面已經顯示了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 %}

    # 還有下一頁,則顯示一個下一頁按鈕
    {% if article_list.has_next %}
        <a class="next-page" href="?page={{ article_list.next_page_number }}">
            下一頁<span class="icon-next"></span>
        </a>
    {% endif %}
</div>

至此,整個分頁功能就完成了。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容