close

Python Django 學習紀錄:架設個人部落格(一)

構建、美化

 

在網路上尋到 杨仕航  的網站

有許多 Django 相關的技術文章,也有關於 Django 的教學視頻

這是他的 billbill空間 再敲一行代码 

在這裡寫下我從他的視頻學習到的技術

並將如何架設個人部落格的重點與過程,精簡的記錄下來

一.、構建個人部落格

 1.簡單構建: 

站功能模塊:(Django App)

  1. 部落格
    1. 文章
    2. 分類
    3. 標籤
  2. 評論
  3. 點讚
  4. 閱讀
  5. 用戶 - 第三方登陸

 

 2.建立虛擬環境: 

避免多個專案之間的python互相衝突

可以完整又方便的快速導出 python 套件列表

使用以下指令安裝: 

pip install virtualenv

可以使用下列操作指令:

創建虛擬機: 

virtualenv <虛擬環境名稱>

啟動虛擬機: 

Scripts/activate

退出虛擬機: 

deactivate

創建一個名為<mysiteenv>的虛擬機

完成後進入目錄並啟動虛擬機,使用以下指令安裝 Django: 

pip install Django

 3.初步創建部落格專案與應用: 

(1)創建專案與應用

創建一個名為<mysite>的專案: 

django-admin startproject mysite

切換進入mysite目錄: 

cd mysite

創建一個名為<blog>的應用: 

python manage.py startapp blog

 

這時候目錄會是這個樣子:

1.JPG

(2)建立應用模型

接下來創建部落格文章、部落格分類的資料庫模型: 

from django.db import models
from django.contrib.auth.models import User #導入內建的使用者模組
# Create your models here.

class BlogType(models.Model): #部落格文章類型
    type_name = models.CharField(max_length=15)

    def __str__(self):
        return self.type_name

class Blog(models.Model): #部落格文章(由於會用外鍵連接到部落格文章類型,所以此模型要放在後面)
    title = models.CharField(max_length=50)
    content = models.TextField()
    author = models.ForeignKey(User, on_delete=models.CASCADE) #使用外鍵連接到使用者模組,由於刪除時則會將關聯的對象一併刪除
    created_time = models.DateTimeField(auto_now_add = True)
    last_updated_time = models.DateTimeField(auto_now = True)
    blog_type = models.ForeignKey(BlogType, on_delete=models.CASCADE)  #使用外鍵連接到部落格文章類型

    def __str__(self):
        return "<Blog: %s>" % self.title

(3)創建超級使用者

先將生成的文件應用到資料庫: 

python manage.py migrate

創建超級使用者:

python manage.py createsuperuser

建立好後打開<settings.py>

在INSTALLED_APPS中加入 'blog' 這個剛剛創建的 App

這裡可以參照以前的方法順便將admin系統語系改為中文(zh-hant)

接著創建更改的文件,並將生成的文件應用到資料庫:  

python manage.py makamigrations
python manage.py migrate

(4)註冊資料庫模型至Django資料庫

為了方便查詢資料庫,先將資料庫模型註冊至Django的 admin系統:   

from django.contrib import admin
from .models import BlogType,Blog
# Register your models here.
@admin.register(BlogType)
class BlogTypeAdmin(admin.ModelAdmin):
    list_display = ('id', 'type_name')

@admin.register(Blog)
class BlogAdmin(admin.ModelAdmin):
    list_display = ('title','blog_type','author','created_time','last_updated_time')

(5)創建分類與文章進行測試

啟動虛擬機,在mysite目錄輸入以下指令:   

python manage.py runserver

使用瀏覽器開啟「 "http://127.0.0.1:8000/admin/」

登入剛才創建的超級使用者

並創建一個分類與一篇文章,方便之後能夠測試其他功能

二.、常用的模板標籤和過濾器

 1.常用模板標籤

循環:for

條件:if(邏輯判斷)、ifequal、ifnotequal

鏈接:url

模版嵌套:block、extends、include

注釋: {#  #}、<!--  -->

日期:date

字數截取:truncatechars、truncatechars_html、truncatewords、truncatewords_html

是否信任html:safe

長度:length 

2.建立視圖

打開<views.py>來建立初步的視圖:   

from django.shortcuts import render, render_to_response, get_object_or_404
from .models import Blog, BlogType

def blog_list(request):
    context={} #建立字典
    context['blogs'] = Blog.objects.all() #將資料放入字典
    return render(request, 'blog/blog_list.html', context) #將字典返回到'bloglist.html'

def blog_detail(request, blog_pk): #導入 blog_pk 變數
    context={} #建立字典
    context['blog'] = get_object_or_404(Blog, pk=blog_pk)  #獲取blog_pk
    return render(request, 'blog/blog_detail.html', context) #將字典返回到'blog_detail.html'

def blogs_with_type(request, blog_type_pk): #導入 blog_type_pk 變數
    context={}
    blog_type = get_object_or_404(BlogType, pk=blog_type_pk) #獲取編號
    context['blogs'] = Blog.objects.filter(blog_type=blog_type) #將編號使用過濾器過濾
    context['blog_type'] = blog_type #將過濾後的資料存入變數
    return render(request, 'blog/blogs_with_type.html', context)

3.建立模版<templates> 

在mysite專案底下建立全局模版<templates>目錄

並開啟<settings.py>設定路徑,在'DIRS':[ ]中添加以下內容:

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

首先要建立的是基礎模板<base.html>,來讓其他三個模版繼承

(1)建立<base.html>(基礎模版):

<!DOCTYPE html>
<html>
<head><meta charset="utf-8">
    <!-- 將標題區塊化 -->
    <title>{% block title %}{% endblock %}</title>
</head>
<body>
    <div>
        <!-- 連接回主頁 -->
        <a href="{% url 'home' %}">
            <h3>個人部落格</h3>
        </a>
    </div>
    <hr>
    {% block content %}{% endblock %}
</body>
</html>

在<templates>目錄底下建立<blog>目錄,專門用來存放blog應用的相關模版

建立三個模板,分別是:主頁面、文章詳細頁面、分類文章頁面

將要繼承<base.html>基礎模版

(2)建立<blog_list.html>(主頁面)文件:

<!-- 繼承模板 -->
{% extends "base.html" %}

<!-- 繼承標題區塊 -->
{% block title %}
    我的部落格
{% endblock %}

<!-- 繼承內容區塊 -->
{% block content %}
        <!-- 取得 blogs 篇數 -->
        <p>一共有 {{ blogs|length }} 篇文章</p>
        {% for blog in blogs %}
            <!-- 設定超連結,導向文章詳細頁面 -->
            <a href="{% url 'blog_detail' blog.pk %}">
                <h3>{{ blog.title }}</h3>
            </a>
            <!-- truncatechars:<int> (縮略長文) -->
            <p>{{ blog.content|truncatechars:30}}</p>
        <!-- 若無文章,則顯示以下訊息 -->
        {% empty %}
            <p>暫無文章,敬請期待</p>
        {% endfor %}
{% endblock %}

(3)建立<blog_detail.html>(文章詳細頁面)文件:

<!-- 繼承模板 -->
{% extends "base.html" %}

<!-- 繼承標題區塊 -->
{% block title %}
    {{ blog.title }}
{% endblock %}

<!-- 繼承內容區塊 -->
{% block content %}
    <h3>{{ blog.title }}</h3>
    <p>作者:{{ blog.author }}</p>
    <!-- 將日期改為純數字24時制顯示 -->
    <p>發表日期:{{ blog.created_time|date:"Y-m-d H:i:s" }}</p>
    <a href="{% url 'blogs_with_type' blog.blog_type.pk %}">
        <p>分類:{{ blog.blog_type }}</p>
    </a>
    <p>{{ blog.content }}</p>
{% endblock %}

(4)建立<blog_with_type>(分類頁面)文件: 

<!-- 繼承模板 -->
{% extends "base.html" %}

<!-- 繼承標題區塊 -->
{% block title %}
    <!-- 取得分類名稱 -->
    <title>{{ blog_type.type_name }}</title>
{% endblock %}

<!-- 繼承內容區塊 -->
{% block content %}
    <!-- 取得分類名稱 -->
    <h3>{{ blog_type.type_name }}</h3>
        <!-- 取得 blogs 篇數 -->
        <p>一共有 {{ blogs|length }} 篇文章</p>
        {% for blog in blogs %}
            <!-- 設定超連結,導向文章詳細頁面 -->
            <a href="{% url 'blog_detail' blog.pk %}">
                <h3>{{ blog.title }}</h3>
            </a>
            <!-- truncatechars:<int> (縮略長文) -->
            <p>{{ blog.content|truncatechars:30}}</p>
        <!-- 若無文章,則顯示以下訊息 -->
        {% empty %}
            <p>暫無文章,敬請期待</p>
        {% endfor %}
{% endblock %}

4.建立<urls.py> 

在blog裡面建立一個<urls.py>文件,用來設置blog這個應用的調度器:

from django.urls import path
from . import views

urlpatterns = [
    #連接文章標題至文章詳細頁面
    path('<int:blog_pk>', views.blog_detail, name = "blog_detail"),
    #連接文章份類至分類頁面
    path('type/<int:blog_type_pk>', views.blogs_with_type, name = "blogs_with_type"),
]

修改<mysite/urls.py>,設置部落格頁面與導入blog 應用中的調度器:

from django.contrib import admin
from django.urls import path, include
from blog.views import blog_list

urlpatterns = [
    path('', blog_list, name = 'home'),
    path('admin/', admin.site.urls),
    path('blog', include('blog.urls')), #導入應用中設置的路徑
]

三.、使用CSS美化頁面

1.頁面設計 

2.jpg

2.導覽列設計

LOGO網站名稱 + 導覽

┌---------------------------------------------------------------┐

 |   網站名稱  首頁 | 部落格                |

└----------------------------------------------┘

3.使用靜態文件  

打開<"settings.py">

並在最下方加入以下代碼來指定路徑:

STATICFILES_DIRS=[
    os.path.join(BASE_DIR,'static'),
]

接著在 mysite 根目錄創建一個名為 static 的目錄

然後再 static 目錄中創建一個名為 css 的目錄

4.添加首頁與導覽列

首先要新增一個首頁

我們將這個首頁的路徑設置在mysite專案的根目錄

將原先預設一開啟網站會導入部落格頁面的路徑更換至blog目錄的<"urls.py">

並新增首頁路徑至mysite的<"urls.py">:

(1)mysite的<"urls.py">:

from django.contrib import admin
from django.urls import path, include
from . import views

urlpatterns = [
    path('', views.home, name = 'home'), #新增的首頁路徑
    path('admin/', admin.site.urls),
    path('blog/', include('blog.urls')), #導入應用中設置的路徑
]

(2)blog的<"urls.py">:

from django.urls import path
from . import views

urlpatterns = [
    path('',views.blog_list, name='blog_list'), #將原先的部落格頁面路徑修改至此
    #連接文章標題至文章詳細頁面
    path('<int:blog_pk>', views.blog_detail, name = "blog_detail"),
    #連接文章份類至分類頁面
    path('type/<int:blog_type_pk>', views.blogs_with_type, name = "blogs_with_type"),
]

 

接著在mysite專案的根目錄添加一個<"views.py">文件

用來寫專案會用到的全局處理方法

(3)在mysite的<"views.py">文件寫入首頁的處理方法:

from django.shortcuts import render, render_to_response

def home(request):
    context ={}
    return render(request, 'home.html',context)

(4)在<"base.html">基礎模版頁面新增導覽列的連結、加載靜態文件與導入CSS:

這邊添加一個 block ,用來擴展其他頁面額外的 CSS文件

{% load staticfiles %} <!-- 導入 staticfiles -->
<!DOCTYPE html>
<html>
<head><meta charset="utf-8">
    <!-- 將標題區塊化 -->
    <title>{% block title %}{% endblock %}</title>
    <link rel="stylesheet" href="{% static 'css/base.css' %}"> <!-- 導入 css 文件 -->
    {% block header_extends %}{% endblock %}  <!-- 用於擴展額外 css -->

</head>
<body>
    <div class="nav">
        <!-- 連接回主頁 -->
        <a class="logo" href="{% url 'home' %}">
            <h3>個人部落格</h3>
        </a>
        <!-- 首頁連結 -->
        <a href="/">首頁</a>
        <!-- 部落格連結 -->
        <a href="{% url 'blog_list' %}">部落格</a>
    </div>

    {% block content %}{% endblock %}
</body>
</html>

 

(5)在 mysite 專案的 templates 目錄添加新的模版<"home.html">:

這裡要先加載靜態文件的方法

接著導入屬於 home 的 css文件

{% extends "base.html" %}
{% load staticfiles %} <!-- 加載靜態文件 -->
{% block titles %}
    我的網站 | 首頁
{% endblock %}

{% block header_extends %} <!-- 導入 css 文件 -->
<link rel="stylesheet" href="{% static 'css/home.css' %}">
{% endblock %} 

{% block content %}
    <!-- 加入 class 標籤讓 css 文件辨別 -->
    <h3 class="home-content">歡迎訪問我的網站!</h3>
{% endblock %}

5.編寫 CSS 文件 

這邊需要有一些關於 CSS 的基礎知識

在 mysite 目錄的 static 目錄中的 css 目錄 創建下列兩個檔案

(1)<"base.css">:

* {
    margin: 0;   /*預設邊界修改為0*/
    padding: 0;  
}
div.nav {
    background-color: #eee;
    border-bottom: 1px solid #ccc; /*底部邊線*/
    padding: 10px 5px;
}

div.nav a{
    text-decoration: none; /*移除下劃線*/
    color: #000; /*導覽列文字連結改為黑色*/
    padding: 5px 10px; /*邊距*/
}

div.nav a.logo {
    display: inline-block; /*縮到行內*/
    font-size: 120%;
}

(2)<"home.css">:

h3.home-content {
    font-size: 222%;
    text-align: center; /*置中*/
    position: absolute; /*改為絕對位置*/
    top: 50%;
    left: 50%;
    transform: translate(-50%,-50%);

}

 

四.、CSS框架協助前端佈局 

(1)如何選擇 CSS 框架

易用性、兼容性、大小、效果、功能

 

(2)部署 bootstrap

bootstrap 中文網站

選擇下載bootstrap

然後選擇下載用於生產環境的 bootstrap

在 mysite 專案中的 static 目錄中 創建一個名為 bootstrap 的資料夾

並將下載下來的檔案解壓縮在該資料夾目錄中

再來請下載最新的 jquery 檔案,放在static 目錄中

接著修改 base.html,讓所有網頁都能夠仔入bootstrap的內容

{% load staticfiles %} <!-- 導入 staticfiles -->
<!DOCTYPE html>
<html lang="zh-TW">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">  <!-- 對應IE瀏覽器 -->
    <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- 適應響應式設計 -->
    <!-- 將標題區塊化 -->
    <title>{% block title %}{% endblock %}</title>
    <link rel="stylesheet" href="{% static 'css/base.css' %}"> <!-- 導入 css 文件 -->
    <link rel="stylesheet" href="{% static 'bootstrap/css/bootstrap.min.css' %}">

    {% block header_extends %}{% endblock %}  <!-- 用於擴展額外 css -->

</head>
<body>
    <div class="nav">
        <!-- 連接回主頁 -->
        <a class="logo" href="{% url 'home' %}">
            <h3>個人部落格</h3>
        </a>
        <!-- 首頁連結 -->
        <a href="/">首頁</a>
        <!-- 部落格連結 -->
        <a href="{% url 'blog_list' %}">部落格</a>
    </div>

    {% block content %}{% endblock %}

    <!-- 導入bootstrap和jquery -->
    <script type="text/javascript" src="{% static 'bootstrap/js/bootstrap.min.js' %}"></script>
    <script type="text/javascript" src="{% static 'static/jquery-1.12.4.min.js' %}"></script>
</body>
</html>

(3)bootstrap 概要

1.布局容器

.container 用於固定寬度並支持響應式的容器

<div class="container">

.container-fluid 用於 100% 寬度 ,佔據全部 viewport 的容器

<div class="container-fluid">

2.柵格系統

這邊較為複雜 可以到 這裡 詳細了解

簡而言之,就是bootstrap 提供了一套行動裝置優先的流式柵格系統

系統會自動分為最多12列,將整個網頁劃分為許多個區塊

(4)使用 bootstrap 導覽列

接下來將之前使用css設計過的導覽列

改為使用 bootstrap 框架 的導覽列

將<base.html>中導覽列的部分修改如下:

將導航條改回bootstrap的響應式導航條

添加 "視窗較小自動改為下拉式選單" 的響應式功能與按鈕

將導航條隨著頁面一同捲動

 

添加 "active"狀態的block,例如進入部落格分頁的時候,導航條的部落格色調就會變深灰色(active狀態)

{% load staticfiles %} <!-- 導入 staticfiles -->
<!DOCTYPE html>
<html lang="zh-TW">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">  <!-- 對應IE瀏覽器 -->
    <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- 適應響應式設計 -->
    <!-- 將標題區塊化 -->
    <title>{% block title %}{% endblock %}</title>
    <link rel="stylesheet" href="{% static 'css/base.css' %}"> <!-- 導入 css 文件 -->
    <link rel="stylesheet" href="{% static 'bootstrap/css/bootstrap.min.css' %}">

    {% block header_extends %}{% endblock %}  <!-- 用於擴展額外 css -->

</head>
<body>
    <!-- bootstrap 響應式導航條 -->
    <div class="navbar navbar-default navbar-fixed-top" role="navigation">
        <!-- 選用寬度填滿頁面的容器 -->
        <div class="container-fluid">
            <!-- 導航條主標題 -->
            <div class="navbar-header">
                <a class="navbar-brand" href="{% url 'home' %}">個人部落格</a>
                <!-- 響應式下拉選單 -->
                <button class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar-collapse">
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
            </div>
            <!-- 導航條列表-->
            <div class="collapse navbar-collapse" id="navbar-collapse">
                <ul class="nav navbar-nav">
                    <!-- 選取的列表加入active狀態-->
                    <li class="{% block nav_home_active %}{% endblock %}">
                        <a href="{% url 'home' %}">首頁</a>
                    </li>
                    <li class="{% block nav_blog_active %}{% endblock %}">
                        <a href="{% url 'blog_list' %}">部落格</a>
                    </li>
                </ul>
            </div>
        </div>

    </div>



    {% block content %}{% endblock %}

    <!-- 導入jquery和bootstrap(jquery必須在前) -->
    <script type="text/javascript" src="{% static 'jquery-3.3.1.min.js' %}"></script>
    <script type="text/javascript" src="{% static 'bootstrap/js/bootstrap.min.js' %}"></script>
    
</body>
</html>

在<home.html>添加active塊

{% block nav_home_active %}active{% endblock %}

在<blog_detail.html>、<blog_list.html>、<blogs_with_type.html>添加active塊

{% block nav_blog_active %}active{% endblock %}

 

最後修改CSS樣式,將原本設定給導航條的樣式刪除,並加入避免導航條蓋住文章內容的代碼( !important 為優先採用)

<"base.css">:

* {
    margin: 0;   /*預設邊界修改為0*/
    padding: 0;  
}

body {
    margin-top: 80px!important;
}

 

五.、Bootstrap響應式布局

(1)柵格區塊布局

首先將部落格文章列表<blog_list.html>分成兩大區塊

左半邊佔 8 格,為文章列表與內容

右半邊佔 4 格,為文章分類

為了在該頁面使用文章分類的 models

所以在  blog 應用的<views.py>中的 blog_list 函式中要在字典中加入 BlogType 這個model 的資料:

def blog_list(request):
    context={} #建立字典
    context['blogs'] = Blog.objects.all() #將資料放入字典
    context['blog_types'] = BlogType.objects.all() #將資料放入字典
    return render(request, 'blog/blog_list.html', context) #將字典返回到'bloglist.html'

以下為Bootstrap布局後的 <blog_list.html>:

<!-- 繼承模板 -->
{% extends "base.html" %}

<!-- 繼承標題區塊 -->
{% block title %}
    我的部落格
{% endblock %}
{% block nav_blog_active %}active{% endblock %}
<!-- 繼承內容區塊 -->
{% block content %}

        <div class="container">
            <div class="row">
                <!-- 左區塊文章內容 -->
                <div class="col-md-8">
                    <!-- 取得 blogs 篇數 -->
                    <p>一共有 {{ blogs|length }} 篇文章</p>
                    {% for blog in blogs %}
                        <!-- 設定超連結,導向文章詳細頁面 -->
                        <a href="{% url 'blog_detail' blog.pk %}">
                            <h3>{{ blog.title }}</h3>
                        </a>
                        <!-- truncatechars:<int> (縮略長文) -->
                        <p>{{ blog.content|truncatechars:30}}</p>
                    <!-- 若無文章,則顯示以下訊息 -->
                    {% empty %}
                        <p>暫無文章,敬請期待</p>
                    {% endfor %}
                </div>
                <!-- 右區塊文章分類 -->
                <div class="col-md-4">
                    <h4>部落格分類</h4>
                    {% for blog_type in blog_types %}
                        <ul><a href="{% url 'blogs_with_type' blog_type.pk %}">{{ blog_type.type_name }}</a></ul>
                    {% empty %}
                        <ul>暫無分類,敬請期待</ul>
                    {% endfor %}
                    
                </div>
            </div>

        </div>



{% endblock %}

3.JPG

(2)應用分工

由於有許多關於 blog 應用的文件

包含< blog_list.html>、<blog_detail.html>、<blog_with_type.html>

還有之後會添加的CSS文件

所以我們將在blog應用中新增一個templates目錄,在其中創建一個名為blog的目錄,並將上面三個模板改放在blog目錄中

並且在blog應用中新增一個名為static的目錄,在其中創建一個名為css的目錄,並在裡面添加一個<blog.css>空白文件供之後修改樣式

(3)添加面板

由於版面還不夠美觀,所以接下來加入面板

可以參考 bootstrap 中文文檔

bootstrap框架後,可能還有些細微調整還是得由CSS修改

所以在此頁面導入在 blog/static/css 目錄的 <blog.css> 文件

接下來則在將兩個區塊分別加上面板

並且對於不同螢幕大小進行響應式設計 :

<!-- 繼承模板 -->
{% extends "base.html" %}
{% load staticfiles %} <!-- 加載靜態文件 -->
<!-- 繼承標題區塊 -->
{% block title %}
    我的部落格
{% endblock %}
{% block header_extends %}<link rel="stylesheet" href="{% static 'css/blog.css' %}">{% endblock %}  <!-- 用於擴展額外 css -->
{% block nav_blog_active %}active{% endblock %}
<!-- 繼承內容區塊 -->
{% block content %}

        <div class="container">
            <div class="row">
                <!-- 左區塊文章內容 -->
                <div class="col-xs-12 col-sm-8 col-md-9 col-lg-10">
                    <div class="panel panel-default">
                        <!-- 取得 blogs 篇數、加上面板標題的block 供其他模板使用 -->
                        <div class="panel-heading">{% block blog_list_title %}部落格文章列表 (一共有 {{ blogs|length }} 篇文章){% endblock %}</div>
                        <div class="panel-body">
                            {% for blog in blogs %}
                                <!-- 命名class,讓css文件修改此區塊樣式 -->
                                <div class="blog">
                                    <!-- 設定超連結,導向文章詳細頁面 -->
                                    <h3><a href="{% url 'blog_detail' blog.pk %}">{{ blog.title }}</a></h3>
                                    <p class="blog-info">
                                        <span class="glyphicon glyphicon-tag"></span>分類:<a href="{% url 'blogs_with_type' blog.blog_type.pk %}">{{ blog.blog_type }}</a>
                                        <span class="glyphicon glyphicon-time"></span>發表日期:{{ blog.created_time|date:"Y-m-d" }}
                                    </p>
                                    <!-- truncatechars:<int> (縮略長文) -->
                                    <p>{{ blog.content|truncatechars:120}}</p>
                                </div>
                            <!-- 若無文章,則顯示以下訊息 -->
                            {% empty %}
                                <div class="blog">
                                    <p>暫無文章,敬請期待</p>
                                </div>
                            {% endfor %}
                        </div>
                    </div>
                    
                </div>
                <!-- 右區塊文章分類 -->
                <div class="hidden-xs col-sm-4 col-md-3 col-lg-2">
                    <div class="panel panel-default">
                        <div class="panel-heading">部落格分類</div>
                        <div class="panel-body">
                            {% for blog_type in blog_types %}
                                <ul><a href="{% url 'blogs_with_type' blog_type.pk %}">{{ blog_type.type_name }}</a></ul>
                            {% empty %}
                                <ul>暫無分類,敬請期待</ul>
                            {% endfor %}
                        </div>
                    </div>
                </div>
            </div>

        </div>
{% endblock %}

做出一些CSS的微調與修改:

div.blog h3{
    margin-top: 0.5em;
}   

div.blog:not(:last-child) {   /*最後一行不執行*/
    margin-bottom: 2em;
    border-bottom: 1px solid#eee;
    padding-bottom: 1em;
}

div.blog p.blog-info {
    margin-bottom: 0;
}

4.JPG

(4)再度繼承模板

文章分類頁面使用的程式碼與文章列表頁面大同小異

這邊將直接繼承<blog_list.html>

只有在文章列表面板的標題做一點更動

所以在<blog_list.html>已經給了一個{% block blog_list_title %}{%endblock%}

<blog_with_type.html>將改為以下程式碼:

<!-- 繼承模板 -->
{% extends "blog/blog_list.html" %}
{% load staticfiles %} <!-- 加載靜態文件 -->
<!-- 繼承標題區塊 -->
{% block title %}
{{ blog_type.type_name }}
{% endblock %}

<!-- 繼承內容區塊 -->
{% block blog_list_title %}
    分類:{{ blog_type.type_name }} 
    (一共有 {{ blogs|length }} 篇文章)   
    <a href="{% url 'blog_list' %}">查看全部部落格文章</a>
{% endblock %}

(5)文章詳細頁面布局

在文章詳細頁面的部分也使用bootstrap柵格區塊的劃分

將所有內容改動到畫面正中

將文章資訊的部分改為<ul>與<li>標籤,

最後分別將文章資訊、文章內容加上class標籤,導入<blog.css>對這兩個class做出一些微調

以下為<blog_detail.html>程式碼:

<!-- 繼承模板 -->
{% extends "base.html" %}

<!-- 繼承標題區塊 -->
{% block title %}{{ blog.title }}{% endblock %}

{% block nav_blog_active %}active{% endblock %}

{% load staticfiles %} <!-- 加載靜態文件 -->
{% block header_extends %}
<link rel="stylesheet" href="{% static 'css/blog.css' %}">
{% endblock %}  <!-- 用於擴展額外 css -->
<!-- 繼承內容區塊 -->
{% block content %}
    <div class="container">
        <div class="row">
            <div class="col-xs-10 col-xs-offset-1">
                <h3>{{ blog.title }}</h3>
                <ul class="blog-info-description">
                    <li>作者:{{ blog.author }}</li>
                    <li>分類:<a href="{% url 'blogs_with_type' blog.blog_type.pk %}">{{ blog.blog_type }}</a></li>
                    <!-- 將日期改為純數字24時制顯示 -->
                    <li>發表日期:{{ blog.created_time|date:"Y-m-d H:i:s" }}</li>
                </ul>
                <div class="blog-content">{{ blog.content }}</div>
            </div>
        </div>
    </div>


{% endblock %}

以下為<blog.css>程式碼:

div.blog h3{
    margin-top: 0.5em;
}   

div.blog:not(:last-child) {   /*最後一行不執行*/
    margin-bottom: 2em;
    border-bottom: 1px solid#eee;
    padding-bottom: 1em;
}

div.blog p.blog-info {
    margin-bottom: 0;
}

ul.blog-info-description {
    list-style: none; /*將標籤符號去除*/
    margin-bottom: 1em;
}

ul.blog-info-description li {
    display: inline-block; /*修改為行內*/
    margin-right: 2em;
}
div.blog-content {
    text-indent: 2em; /*開頭縮排*/
}

5.JPG

六.、分頁和 shell 命令行模式

(1)分頁功能的用途

部落格文章數量較多的時候,全部加載速度會十分緩慢

而採用分頁加載能夠有效改善

 

(2)shell 命令行模式添加部落格文章

使用shell的方式,有助於熟悉對於django的操作

在專案虛擬環境輸入 python manage.py shell 進入 shell 命令行模式

接著導入會使用到的資料庫模組

就可以透過 shell 命令行模式來新增部落格文章了

以下為範例程式:

6.JPG

可以 runserver 來看看成果

7.JPG

緊接著使用 for 迴圈 一次新增多篇文章,供之後測試分頁使用

8.JPG

(3)分頁器實現分頁

1.分頁器介紹

使用 Django 本身提供的分頁器 Paginator

請在<views.py>的上方加入以下代碼導入:

from django.core.paginator import Paginator

分頁器的使用方式:

paginator = Paginator(object_list(資料庫列表), each_page_count(每頁資料數量))

頁面呼叫方式:

page1 = paginator.page(1) 

 

2.設定模型默認排序

使用分頁器的時候需要有排序的準則,或沒有設置的話系統會跳出提醒

所以打開 <models.py> 來對 Blog 資料表添加默認排序的設置

以下為 <models.py> 更新後的代碼:

from django.db import models
from django.contrib.auth.models import User #導入內建的使用者模組
# Create your models here.

class BlogType(models.Model): #部落格文章類型
    type_name = models.CharField(max_length=15)

    def __str__(self):
        return self.type_name

class Blog(models.Model): #部落格文章(由於會用外鍵連接到部落格文章類型,所以此模型要放在後面)
    title = models.CharField(max_length=50)
    content = models.TextField()
    author = models.ForeignKey(User, on_delete=models.CASCADE) #使用外鍵連接到使用者模組,刪除時會將關聯的對象一併刪除
    created_time = models.DateTimeField(auto_now_add = True)
    last_updated_time = models.DateTimeField(auto_now = True)
    blog_type = models.ForeignKey(BlogType, on_delete=models.CASCADE)  #使用外鍵連接到部落格文章類型

    def __str__(self):
        return "<Blog: %s>" % self.title

    class Meta:
        ordering = ['-created_time']

設定好後要進行makemigrations 和 migrate 並重新 runserver

9.JPG

(4)分頁的使用

前端:發送請求,請求打開具體分頁內容

後端:處理請求,返回具請分頁內容響應請求

這邊採用 GET 的方法取得分頁參數,而非之前採用Django傳送參數的方法

首先在<views.py>中,修改blog_list的內容,將分頁器加入其中: 

from django.shortcuts import render, render_to_response, get_object_or_404
from django.core.paginator import Paginator #導入分頁器
from .models import Blog, BlogType

def blog_list(request):

    blogs_all_list = Blog.objects.all()
    paginator = Paginator(blogs_all_list, 5) #每5篇進行分頁
    page_num = request.GET.get('page', 1) #獲取url的頁面參數 (GET請求)
    page_of_blogs = paginator.get_page(page_num) #get_page會自動識別頁碼,若無效則返回1,超出頁數則顯示最後一頁

    context={} #建立字典
    context['blogs'] = page_of_blogs.object_list
    context['page_of_blogs'] = page_of_blogs
    context['blog_types'] = BlogType.objects.all() #將資料放入字典
    return render(request, 'blog/blog_list.html', context) #將字典返回到'bloglist.html'

修改<blog_list.html>,在文章內容後加入分頁: 

                    <!-- 分頁 -->
                    <div>
                         <ul class="pagination">
                            <!-- 上一頁 -->
                            <li>
                                {% if page_of_blogs.has_previous %}
                                  <a href="?page={{ page_of_blogs.previous_page_number }}" aria-label="Previous">
                                    <span aria-hidden="true">&laquo;</span>
                                  </a>
                                {% else %}
                                    <span aria-hidden="true">&laquo;</span>
                                {% endif %}
                            </li>
                            <!-- 全部頁碼 -->
                            {% for page_num in page_of_blogs.paginator.page_range %}
                                <li><a href="?page={{ page_num }}">{{ page_num }}</a></li>
                            {% endfor %}
                            <!-- 下一頁 -->
                            <li>
                                {% if page_of_blogs.has_next %}
                                  <a href="?page={{ page_of_blogs.next_page_number }}" aria-label="Next">
                                    <span aria-hidden="true">&raquo;</span>
                                  </a>
                                {% else %}
                                    <span aria-hidden="true">&raquo;</span>
                                {% endif %}
                            </li>
                          </ul>
                    </div>

(5)settings 自定義參數

公用的全局設置可以放在settings中來統一管理

在文件開頭添加以下代碼: 

from django.conf import settings

而這邊我們在settings文件的最下方加入以下代碼,為每個分頁的文章篇數,供之後使用: 

#自定義參數
EACH_PAGE_BLOGS_NUMBER = 2

(6)分頁的優化顯示

友好的用戶體驗:凸顯當前頁碼、不要過多的頁碼選擇(影響頁面布局)

1.縮減頁碼與加入省略符號

這邊有些對於初學者可能會有些複雜,我將詳細的註解都打在程式碼之中

打開<views.py>,並在 blog_list 中加入關於分頁器的代碼:   

def blog_list(request):

    blogs_all_list = Blog.objects.all()
    paginator = Paginator(blogs_all_list, settings.EACH_PAGE_BLOGS_NUMBER) #根據settings的自定義參數進行分頁
    page_num = request.GET.get('page', 1) #獲取url的頁面參數 (GET請求)
    page_of_blogs = paginator.get_page(page_num) #get_page會自動識別頁碼,若無效則返回1,超出頁數則顯示最後一頁
    current_page_num = page_of_blogs.number #獲取當前頁碼
    #獲取當前頁碼前後各2頁範圍
        #list 建立串列、range 範圍內的所有值(不包含最後一項,所以尾項要+1)、max 取最大值、min 取最小值、paginator.num_pages 取得總頁數
        #串列(範圍(當前頁數-2,到當前頁數,再跟1相比,小於1則取1)
        #    +範圍(當前頁數,到當前頁數+2,再跟總頁數相比,若大於總頁數則取總頁數)尾項加上1)
    page_range = list(range(max(1, current_page_num - 2), current_page_num)) + \
                 list(range(current_page_num, min(paginator.num_pages, current_page_num + 2) + 1))
    #加上省略頁碼標記
    if page_range[0] -1 >= 2:  #若第0項減1小於等於2
        page_range.insert(0, '...') #則在串列插入省略符號
    if page_range[-1] + 2 <= paginator.num_pages: #若最後一項加2大於等於總頁數
        page_range.append('...') #則在串列最後加上省略符號
    #加上首頁與尾頁
    if page_range[0] != 1: #若串列第0項不為1
        page_range.insert(0, 1) #則在第0項插入1
    if page_range[-1] != paginator.num_pages: #若串列最後一項項不為總頁數
        page_range.append(paginator.num_pages) #則在串列最後加上總頁數



    context={} #建立字典
    context['blogs'] = page_of_blogs.object_list
    context['page_of_blogs'] = page_of_blogs
    context['blog_types'] = BlogType.objects.all() #將資料放入字典
    context['page_range'] = page_range #取得分頁器資訊
    return render(request, 'blog/blog_list.html', context) #將字典返回到'bloglist.html'

這裡先完成了後端,接著前端與凸顯代碼一起編寫

2.凸顯當前頁碼與 刪除省略符號的超連結

使用 if 條件判斷

在使用者到該分頁的時候,頁碼將呈現active狀態

而如果頁碼為省略符號,也使用 if 條件判斷 顯示為無超連結的頁碼

在 <blog_list.html> 中的<ul class="pagination"> 修改為以下代碼:   

                         <ul class="pagination">
                            <!-- 上一頁 -->
                            <li>
                                {% if page_of_blogs.has_previous %}
                                  <a href="?page={{ page_of_blogs.previous_page_number }}" aria-label="Previous">
                                    <span aria-hidden="true">&laquo;</span>
                                  </a>
                                {% else %}
                                    <span aria-hidden="true">&laquo;</span>
                                {% endif %}
                            </li>
                            <!-- 全部頁碼 -->
                            {% for page_num in page_range %}
                                <!-- 若為當前頁碼,則加入active狀態 -->
                                {% if page_num == page_of_blogs.number %}
                                    <li class="active"><span>{{ page_num }}</span></li>
                                {% else %}
                                    <!-- 若為省略號,則沒有超連結 -->
                                    {% if page_num == '...' %}
                                        <li ><span>{{ page_num }}</span></li>
                                    {% else %}
                                        <li ><a href="?page={{ page_num }}">{{ page_num }}</a></li>
                                    {% endif %}
                                {% endif %}
                            {% endfor %}
                            <!-- 下一頁 -->
                            <li>
                                {% if page_of_blogs.has_next %}
                                  <a href="?page={{ page_of_blogs.next_page_number }}" aria-label="Next">
                                    <span aria-hidden="true">&raquo;</span>
                                  </a>
                                {% else %}
                                    <span aria-hidden="true">&raquo;</span>
                                {% endif %}
                            </li>
                          </ul>

下方為目前完成部分的展示:

10.JPG

(7)分類列表的分頁整合

之前添加的分頁器只包含在部落格文章列表

在分類列表還無法生效,所以要打開<views.py> 修改 blogs_with_type 的代碼:   

def blogs_with_type(request, blog_type_pk): #導入 blog_type_pk 變數

    blog_type = get_object_or_404(BlogType, pk=blog_type_pk) #獲取編號
    blogs_all_list = Blog.objects.filter(blog_type=blog_type) #取得分類後的資料
    paginator = Paginator(blogs_all_list, settings.EACH_PAGE_BLOGS_NUMBER) #根據settings自定義參數進行分頁
    page_num = request.GET.get('page', 1) #獲取url的頁面參數 (GET請求)
    page_of_blogs = paginator.get_page(page_num) #get_page會自動識別頁碼,若無效則返回1,超出頁數則顯示最後一頁
    current_page_num = page_of_blogs.number #獲取當前頁碼
    #獲取當前頁碼前後各2頁範圍
        #list 建立串列、range 範圍內的所有值(不包含最後一項,所以尾項要+1)、max 取最大值、min 取最小值、paginator.num_pages 取得總頁數
        #串列(範圍(當前頁數-2,到當前頁數,再跟1相比,小於1則取1)
        #    +範圍(當前頁數,到當前頁數+2,再跟總頁數相比,若大於總頁數則取總頁數)尾項加上1)
    page_range = list(range(max(1, current_page_num - 2), current_page_num)) + \
                 list(range(current_page_num, min(paginator.num_pages, current_page_num + 2) + 1))
    #加上省略頁碼標記
    if page_range[0] -1 >= 2:  #若第0項減1小於等於2
        page_range.insert(0, '...') #則在串列插入省略符號
    if page_range[-1] + 2 <= paginator.num_pages: #若最後一項加2大於等於總頁數
        page_range.append('...') #則在串列最後加上省略符號
    #加上首頁與尾頁
    if page_range[0] != 1: #若串列第0項不為1
        page_range.insert(0, 1) #則在第0項插入1
    if page_range[-1] != paginator.num_pages: #若串列最後一項項不為總頁數
        page_range.append(paginator.num_pages) #則在串列最後加上總頁數



    context={} #建立字典
    context['blogs'] = page_of_blogs.object_list #取得當前頁碼的資料
    context['page_of_blogs'] = page_of_blogs
    context['page_range'] = page_range #取得分頁器資訊
    context['blog_type'] = blog_type #將過濾後的資料存入變數
    context['blog_types'] = BlogType.objects.all() #將資料放入字典(繼承blog_list需要用到相同的變數)
    return render(request, 'blog/blogs_with_type.html', context)

修改<blog_with_type.html>

由於文章數量會繼承<blog_list.html>分頁器下方的文章數量訊息,所以這邊可以將原先的訊息刪除:   

<!-- 繼承模板 -->
{% extends "blog/blog_list.html" %}
{% load staticfiles %} <!-- 加載靜態文件 -->
<!-- 繼承標題區塊 -->
{% block title %}
{{ blog_type.type_name }}
{% endblock %}

<!-- 繼承內容區塊 -->
{% block blog_list_title %}
    分類:{{ blog_type.type_name }} 
    <a href="{% url 'blog_list' %}">查看全部部落格文章</a>
{% endblock %}

 

下方為目前完成部分的展示:

11.JPG

六.、上下篇文章按月分類

(1)filter 篩選條件(等於)

大於:__gt (greater than)

大於等於:__gte

小於:__lt (less than)

小於等於:__lte

包含:__contains  (加icontain忽略大小寫)

開頭是:__startswith

結尾是:__endswith

其中之一:__in

範圍:__range

(2)exclude 排除條件(不等於)

為filter的條件取反


(3)條件中的雙下畫線

字段查詢類型

外鍵拓展(以部落格分類為例)

日期拓展(以月分分類為例)

支持鏈式查詢:可以一直鏈接下去

 

(4)上一篇和下一篇部落格文章

在文章詳細頁面<blog_detail.html> 添加 上一篇、下一篇的文章標題與連結

在<views.py>中的 blog_detail 函數中修改以下代碼:   

def blog_detail(request, blog_pk): #導入 blog_pk 變數
    context={} #建立字典
    blog = get_object_or_404(Blog, pk=blog_pk)  #獲取blog_pk
    
    #資料庫文章日期已經為由新到舊排列(新文章日期大於舊文章日期)
    #獲取前一篇文章,取得大於該文章的日期中的最後一篇
    context['previous_blog'] = Blog.objects.filter(created_time__gt=blog.created_time).last()
    #獲取後一篇文章,取得小於該文章的日期中的第一篇
    context['next_blog'] = Blog.objects.filter(created_time__lt=blog.created_time).first()
    context['blog'] = blog
    return render(request, 'blog/blog_detail.html', context) #將字典返回到'blog_detail.html'

接著在<blog_detail.html>下方添加上一篇、下一篇的文章標題與連結

若已經無文章,則顯示"沒有了",代碼如下˙:   

<!-- 繼承模板 -->
{% extends "base.html" %}

<!-- 繼承標題區塊 -->
{% block title %}{{ blog.title }}{% endblock %}

{% block nav_blog_active %}active{% endblock %}

{% load staticfiles %} <!-- 加載靜態文件 -->
{% block header_extends %}
<link rel="stylesheet" href="{% static 'css/blog.css' %}">
{% endblock %}  <!-- 用於擴展額外 css -->
<!-- 繼承內容區塊 -->
{% block content %}
    <div class="container">
        <div class="row">
            <div class="col-xs-10 col-xs-offset-1">
                <h3>{{ blog.title }}</h3>
                <ul class="blog-info-description">
                    <li>作者:{{ blog.author }}</li>
                    <li>分類:<a href="{% url 'blogs_with_type' blog.blog_type.pk %}">{{ blog.blog_type }}</a></li>
                    <!-- 將日期改為純數字24時制顯示 -->
                    <li>發表日期:{{ blog.created_time|date:"Y-m-d H:i:s" }}</li>
                </ul>
                <div class="blog-content">{{ blog.content }}</div>
                <!-- 上一篇、下一篇文章標題連結 -->
                <div class="blog_more">
                    <p>上一篇:
                        {% if previous_blog %}
                            <a href="{% url 'blog_detail' previous_blog.pk %}">{{ previous_blog.title }}</a></p>
                        {% else %}
                            沒有了
                        {% endif %}
                    <p>下一篇:{% if next_blog %}
                            <a href="{% url 'blog_detail' next_blog.pk %}">{{ next_blog.title }}</a></p>
                        {% else %}
                            沒有了
                        {% endif %}
                    </p>
                </div>
            </div>
        </div>
    </div>


{% endblock %}

(5)按月分類

1.url路徑設定

首先在blog專案的<urls.py>添加新的路徑:   

from django.urls import path
from . import views

urlpatterns = [
    path('',views.blog_list, name='blog_list'), #將原先的部落格頁面路徑修改至此
    #連接文章標題至文章詳細頁面
    path('<int:blog_pk>', views.blog_detail, name = "blog_detail"),
    #連接文章份類至分類頁面
    path('type/<int:blog_type_pk>', views.blogs_with_type, name = "blogs_with_type"),
    path('date/<int:year>/<int:month>', views.blogs_with_date, name = "blogs_with_date"),

]

2.後端編寫

接著在blog目錄中的<views.py>中,新建一個名為blogs_with_date 的函數

由於與blogs_with_type 函數大部分內容相同,這邊只在有修改的部分加上註解:   

def blogs_with_date(request, year, month): 

    blogs_all_list = Blog.objects.filter(created_time__year = year, created_time__month = month) #篩選取得日期歸檔後的資料
    
    paginator = Paginator(blogs_all_list, settings.EACH_PAGE_BLOGS_NUMBER) 
    page_num = request.GET.get('page', 1) 
    page_of_blogs = paginator.get_page(page_num)
    current_page_num = page_of_blogs.number 
    page_range = list(range(max(1, current_page_num - 2), current_page_num)) + \
                 list(range(current_page_num, min(paginator.num_pages, current_page_num + 2) + 1))
    if page_range[0] -1 >= 2: 
        page_range.insert(0, '...') 
    if page_range[-1] + 2 <= paginator.num_pages: 
        page_range.append('...') 
    if page_range[0] != 1: 
        page_range.insert(0, 1) 
    if page_range[-1] != paginator.num_pages: 
        page_range.append(paginator.num_pages) 



    context={} 
    #供面板標題使用(傳送到<blogs_with_date.html>模板頁面)
    context['blogs_with_date'] = '%s年%s月' % (year, month) 
    context['blog_dates'] = Blog.objects.dates('created_time','month',order='DESC')#降序排序日期

    context['blogs'] = page_of_blogs.object_list 
    context['page_of_blogs'] = page_of_blogs
    context['page_range'] = page_range 
    context['blog_types'] = BlogType.objects.all() 
    return render(request, 'blog/blogs_with_date.html', context) #傳送到<blogs_with_date.html>模板頁面

在blog_list 函數的字典也要加上日期的變數:   

context['blog_dates'] = Blog.objects.dates('created_time','month',order='DESC')#降序排序日期

3.前端文章列表添加"日期歸檔"面板、創建日期歸檔模板

打開<blog_list.html>在分類面板下面新增日期歸檔面板:   

                    <div class="panel panel-default">
                        <div class="panel-heading">日期歸檔</div>
                        <div class="panel-body">
                            <ul class="blog_date">
                                {% for blog_date in blog_dates %}
                                    <li>
                                        <a href="{% url 'blogs_with_date' blog_date.year blog_date.month %}">{{ blog_date|date:"Y年m月" }}</a>
                                    </li>
                                {% endfor %}
                            </ul>
                        </div>
                    </div>

在blog目錄創建一個名為<blog_with_date.html>的模板

內容與<blog_with_type.html>大部分相同,只有面板標題需要做更改:   

<!-- 繼承模板 -->
{% extends "blog/blog_list.html" %}
{% load staticfiles %} <!-- 加載靜態文件 -->
<!-- 繼承標題區塊 -->
{% block title %}
{{ blog_type.type_name }}
{% endblock %}

<!-- 繼承內容區塊 -->
{% block blog_list_title %}
    日期歸檔:{{ blogs_with_date }} 
    <a href="{% url 'blog_list' %}">查看全部部落格文章</a>
{% endblock %}

 

(6)重複程式碼精簡:

這邊將blog目錄的<views.py>中,多個函數共同使用的相同代碼獨立出來

新定義一個名為 get_blog_list_common_data 的函數,並將會共同使用的代碼寫入其中

最後返回字典,供其他函數使用:   

def get_blog_list_common_data(request, blogs_all_list):

    paginator = Paginator(blogs_all_list, settings.EACH_PAGE_BLOGS_NUMBER) #根據settings自定義參數進行分頁
    page_num = request.GET.get('page', 1) #獲取url的頁面參數 (GET請求)
    page_of_blogs = paginator.get_page(page_num) #get_page會自動識別頁碼,若無效則返回1,超出頁數則顯示最後一頁
    current_page_num = page_of_blogs.number #獲取當前頁碼
    #獲取當前頁碼前後各2頁範圍
        #list 建立串列、range 範圍內的所有值(不包含最後一項,所以尾項要+1)、max 取最大值、min 取最小值、paginator.num_pages 取得總頁數
        #串列(範圍(當前頁數-2,到當前頁數,再跟1相比,小於1則取1)
        #    +範圍(當前頁數,到當前頁數+2,再跟總頁數相比,若大於總頁數則取總頁數)尾項加上1)
    page_range = list(range(max(1, current_page_num - 2), current_page_num)) + \
                 list(range(current_page_num, min(paginator.num_pages, current_page_num + 2) + 1))
    #加上省略頁碼標記
    if page_range[0] -1 >= 2:  #若第0項減1小於等於2
        page_range.insert(0, '...') #則在串列插入省略符號
    if page_range[-1] + 2 <= paginator.num_pages: #若最後一項加2大於等於總頁數
        page_range.append('...') #則在串列最後加上省略符號
    #加上首頁與尾頁
    if page_range[0] != 1: #若串列第0項不為1
        page_range.insert(0, 1) #則在第0項插入1
    if page_range[-1] != paginator.num_pages: #若串列最後一項項不為總頁數
        page_range.append(paginator.num_pages) #則在串列最後加上總頁數

    context={} #建立字典
    context['blogs'] = page_of_blogs.object_list
    context['page_of_blogs'] = page_of_blogs
    context['blog_types'] = BlogType.objects.all() #將資料放入字典
    context['blog_dates'] = Blog.objects.dates('created_time','month',order='DESC')
    context['page_range'] = page_range #取得分頁器資訊

    return context

而其他三個函數修改如下:   

def blog_list(request):

    blogs_all_list = Blog.objects.all()

    context= get_blog_list_common_data(request, blogs_all_list)

    return render(request, 'blog/blog_list.html', context) #將字典返回到'bloglist.html'

def blogs_with_type(request, blog_type_pk): #導入 blog_type_pk 變數

    blog_type = get_object_or_404(BlogType, pk=blog_type_pk) #獲取編號
    blogs_all_list = Blog.objects.filter(blog_type=blog_type) #取得分類後的資料

    context = get_blog_list_common_data(request, blogs_all_list)
    context['blog_type'] = blog_type #將過濾後的資料存入變數

    return render(request, 'blog/blogs_with_type.html', context)

def blogs_with_date(request, year, month): 

    blogs_all_list = Blog.objects.filter(created_time__year = year, created_time__month = month) #篩選取得日期歸檔後的資料    

    context = get_blog_list_common_data(request, blogs_all_list)
    #供面板標題使用(傳送到<blogs_with_date.html>模板頁面)
    context['blogs_with_date'] = '%s年%s月' % (year, month) 

    return render(request, 'blog/blogs_with_date.html', context) #傳送到<blogs_with_date.html>模板頁面

 

七.、文章統計

在文章分類與日期歸檔的地方,添加統計文章數量的部分

最後執行結果如圖:

12.JPG

 1.文章分類統計: 

使用 annotate 註釋,拓展查詢字段

在 <views.py> get_blog_list_common_data 函數中,修改查詢分類的字典,將文章數量也加入字典中

首先導入計算資料庫數量的模組:   

from django.db.models import Count

接著建立一個名為 blog_type_count 的變數,來放入查詢文章與數量的資料:   

    #獲取分類文章與數量
    blog_type_count = BlogType.objects.annotate(blog_count=Count('blog')) #統計BlogType關聯項的Blog模組的小寫
    context['blog_types'] = blog_type_count

修改<blog_list.html>頁面:   

                <div class="hidden-xs col-sm-4 col-md-3 col-lg-2">
                    <div class="panel panel-default">
                        <div class="panel-heading">部落格分類</div>
                        <div class="panel-body">
                            <ul class="blog-types">
                            {% for blog_type in blog_types %}
                                <li><a href="{% url 'blogs_with_type' blog_type.pk %}">{{ blog_type.type_name }}({{ blog_type.blog_count }})</a></li>
                            {% empty %}
                                <li>暫無分類,敬請期待</li>
                            {% endfor %}
                            </ul>
                        </div>
                    </div>

 2.日期歸檔統計: 

使用字典的方式來查詢,鍵為日期歸檔的資料,值為篇數

在 <views.py> get_blog_list_common_data 函數中,修改查詢日期歸檔的字典,將文章數量也加入字典中:   

    #獲取日期歸檔與數量
    blog_dates = Blog.objects.dates('created_time','month',order='DESC') #日期歸檔資料
    blog_dates_dict={} #建立字典(鍵(日期歸檔的資料):值(篇數))
    for blog_date in blog_dates:
        blog_count = Blog.objects.filter(created_time__year=blog_date.year,
                                         created_time__month=blog_date.month).count()
        blog_dates_dict[blog_date] = blog_count #迴圈儲存"鍵 = [值]"

修改<blog_list.html>頁面:   

                    <div class="panel panel-default">
                        <div class="panel-heading">日期歸檔</div>
                        <div class="panel-body">
                            <ul class="blog_date">
                                {% for blog_date, blog_count in blog_dates.items %}
                                    <li>
                                        <a href="{% url 'blogs_with_date' blog_date.year blog_date.month %}">{{ blog_date|date:"Y年m月" }}({{ blog_count }})</a>
                                    </li>
                                {% endfor %}
                            </ul>
                        </div>
                    </div>

八.、部落格後台富文本編輯與上傳圖片

 1.使用django-ckeditor: 

django有許多的編輯器,選擇 django-ckeditor 的標準如下:

  • 具有基本的富文本編輯功能
  • 可以上傳圖片
  • 可以查看原碼
  • 有持續更新   

django-ckeditor 的相關文件點此

 2.安裝django-ckeditor: 

(1)安裝

在虛擬環境中的 mysite 專案安裝套件,指令如下:   

pip install django-ckeditor

(2)註冊應用

在 settings 的應用中,添加以下代碼:   

'ckeditor',

(3)配置 model

導入套件,並將文章內文的字段改為RichTextField:   

from django.db import models
from django.contrib.auth.models import User #導入內建的使用者模組
from ckeditor.fields import RichTextField #導入富文本套件
# Create your models here.

class BlogType(models.Model): #部落格文章類型
    type_name = models.CharField(max_length=15)

    def __str__(self):
        return self.type_name

class Blog(models.Model): #部落格文章(由於會用外鍵連接到部落格文章類型,所以此模型要放在後面)
    title = models.CharField(max_length=50)
    content = RichTextField() #改為RichTextField
    author = models.ForeignKey(User, on_delete=models.CASCADE) #使用外鍵連接到使用者模組,刪除時會將關聯的對象一併刪除
    created_time = models.DateTimeField(auto_now_add = True)
    last_updated_time = models.DateTimeField(auto_now = True)
    blog_type = models.ForeignKey(BlogType, on_delete=models.CASCADE)  #使用外鍵連接到部落格文章類型

    def __str__(self):
        return "<Blog: %s>" % self.title

    class Meta:
        ordering = ['-created_time']

 3.添加上傳圖片的功能: 

(1)安裝

在虛擬環境中的 mysite 專案安裝套件,指令如下:   

pip install pillow

(2)註冊應用

在 settings 的應用中,添加以下代碼:   

'ckeditor_uploader',

(3)配置settings

在 settings 的最下方自定義參數前,添加以下代碼: :   

# media
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR,'media')

#配置ckeditor
CKEDITOR_UPLOADER_PATH = 'upload/'

(4)配置url

開發的時候使用下列方法添加url,部屬的時候需要用其他的方法:   

from django.contrib import admin
from django.urls import path, include
from . import views
from django.conf import settings #導入settings
from django.conf.urls.static import static #導入static

urlpatterns = [
    path('', views.home, name = 'home'), #新增的首頁路徑
    path('admin/', admin.site.urls),
    path('blog/', include('blog.urls')), #導入應用中設置的路徑
    path('ckeditor',include('ckeditor_uploader.urls')), #引入路徑
]

urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) #導入路徑

(5)配置model把字段改成RichTextUploadingField

將<models.py>的代碼修改如下:   

from django.db import models
from django.contrib.auth.models import User #導入內建的使用者模組
from ckeditor_uploader.fields import RichTextUploadingField #改為從uploader導入 RichTextUploadingField
# Create your models here.

class BlogType(models.Model): #部落格文章類型
    type_name = models.CharField(max_length=15)

    def __str__(self):
        return self.type_name

class Blog(models.Model): #部落格文章(由於會用外鍵連接到部落格文章類型,所以此模型要放在後面)
    title = models.CharField(max_length=50)
    content = RichTextUploadingField() #修改此處
    author = models.ForeignKey(User, on_delete=models.CASCADE) #使用外鍵連接到使用者模組,刪除時會將關聯的對象一併刪除
    created_time = models.DateTimeField(auto_now_add = True)
    last_updated_time = models.DateTimeField(auto_now = True)
    blog_type = models.ForeignKey(BlogType, on_delete=models.CASCADE)  #使用外鍵連接到部落格文章類型

    def __str__(self):
        return "<Blog: %s>" % self.title

    class Meta:
        ordering = ['-created_time']

13.JPG

※要點擊上傳到伺服器,才能取得網址

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 ivankao 的頭像
    ivankao

    IvanKao的部落格

    ivankao 發表在 痞客邦 留言(1) 人氣()