如何使用Django3架設簡單的部落格 — — Django教學 EP.1
什麼是Django?
Django 是一個可以快速寫出網站的python web framework ,Instagram 在創立之初就是使用Django作為他們的框架,而現在國外非常多大型的網站都是使用 Django來做後端開發。
他們的slogan
The Web framework for perfectionists with deadlines
意思是說,是一個可以給完美主義者快速架出網站的工具
他有很多預設的優良功能,包含admin後台、安全性設定、ORM、router、MVC架構等等,這些都只需要我們打少量的程式碼就能輕鬆地做出來。
那接下來就開始我們今天的教學吧!
(過程中如果有程式找不到在哪裡看都可以上完整版github repo)
前置準備
Disclaimer: 本篇教學專注在Django上,假設你大概知道怎麼用python,電腦上應該是有裝好python的狀態
本篇屬於比較進階一點的教學,不是在教程式的基礎,也不是在教觀念,而是讓懂一點點python的人可以架設屬於自己的部落格。
首先先創建一個project資料夾,並且進入該資料夾
我們先進到 terminal
- 在 Mac上用 Command+Space 搜尋 terminal
- 在 Windows 上 開啟 cmd (並確保有安裝好 python 並在環境變數中的PATH變數加入Python的目錄)
接下來我們要創建專案資料夾:
利用 mkdir (make directory 創建目錄)指令 建立一個新的資料夾 django-blog-demo,也可以直接在電腦上新增一個資料夾
mkdir django-blog-demo
cd django-blog-demo
pyenv + pipenv
pyenv是用來管理python版本的工具,可以讓一台電腦上有多個不同版本的python,這在做一個專案中是非常重要的,因為我們不希望我們的python版本不清不楚,造成之後維護,或是檔案共享的困難
pipenv則是 python pip 這個 package manger 比較新的虛擬環境工具, 很多人可能用過 virtualenv, conda, 或是 venv,但我個人比較喜歡 pipenv, 因為 pipenv 可以產生 Pipfile.lock 跟 Pipfile 的檔案。
如果寫過nodejs的人可能就知道,這就很像npm 的 package.json 跟 packge-lock.json 或 yarn-lock.json,可以確保以後重新安裝、或要做部署(發佈到AWS或Digital Ocean等虛擬機)之後(可能放到github上面再git pull下來)可以快速安裝正確版本的套件,避免相容性的問題。
回到剛剛的 “django-blog-demo”
此時我們要在這邊檢查我們 python 的版本。
我們這邊要使用 python 3.7的版本,先使用 pyenv install –list 檢查可安裝的版本, 後面加上 ‘| grep 3.7’ 用來過濾出只含有 ‘3.7’ 這個字串的那幾行
pyenv install --list | grep 3.7
這時候我看到有一個 3.7.6 感覺不錯,就來裝一下(通常一樣是3.7會建議裝後面數字大一點的版本,會功能上比較穩定,也會有很多安全性的修復,如果你的專案之後要放在網路上,這就非常重要)
pyenv install 3.7.6
這邊安裝通常會比較久一點,我們就讓他跑一下
pyenv versions
這時可以看到我們本機上可以切換的python版本,裡面就包含了剛才的 3.7.6
接下來確保我們在剛剛的 ‘django-blog-demo’中,可以使用 pwd 這個指令檢查現在的位子
pwd
此時顯示出來,後面應該要是django-demo-blog或是你設定的資料夾名稱。沒問題後我們就可以開始設定環境
pyenv local 3.7.6
這個指令是用來設定現在這個資料夾裡面的python版本,我們把它設成3.7.6
接下來要來安裝django(到這個目錄裡)
有別於一般習慣的 pip install, 我們要用 pipenv install,這樣他只會安裝到我們這個目錄中,並且可以做好版本管理。(類似nodejs的 npm install)
我們先確定這個版本的 python裡面要有pipenv
先在這個 3.7.6的版本中安裝 pipenv
pip install pipenv
我們這時候執行的 pip 是我們 python 3.7.6 的 pip (相當於 python -m pip)
pipenv install --python $(which python) django==3.0.2
這邊我們使用到下面幾個參數
- –python 設定要使用的 python 版本 (虛擬環境中的python版本)(因為有時候直接pipenv install 可能沒有抓到 pyenv 設定的版本 所以我為了以防萬一,通常會加上這段)
- $(which python) 的意思就是 把 ‘which python’ 的 output 結果 取代這個 $() ,得到的結果就會是 pyenv 設定的 python 的目錄 (確保用剛剛設定的python版本執行)
- django==3.0.2 這邊我們就是要裝 django,並且確定使用 3.0.2 的版本
在安裝package的時候我們都會希望比較重要的package 版本能夠標明清楚,避免之後一樣的程式跑到別的電腦或是AWS之類的上面跑不出來(相容性問題)
接下來我們就可以進入虛擬環境囉!
pipenv shell
這時候我們可以看一下裡面django的版本
django-admin --version
3.0.2
我們可以看到剛剛的安裝好的 3.0.2,這樣就安裝成功囉!
創立Django Project
接下來我們要來新增一個 django 專案
django-admin startproject blog .
- blog 是專案名稱
- 後面加一個 ‘.’ 是告訴 django 我要在現在這個位子創建專案,而不要把我的東西另外放進新的資料夾
這時候我們可以用 tree 指令 看一下資料夾裡面多了什麼東西
(沒有tree指令可以去google一下)
tree
目前我們的目錄長這樣
.
├── Pipfile
├── Pipfile.lock
├── blog
│ ├── init.py
│ ├── asgi.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── db.sqlite3
└── manage.py
- 剛剛的 Pipfile, Pipfile.lock是我們版本管理用的東西,可以不用管他
- manage.py 這個檔案就是 我們用來控制 django 各種命令的地方
- blog這個資料夾就是我們的project基本設定資料,和很多router等等存放的地方
- db.sqlite3 是我們的簡易 sqlite database
- asgi.py 是 django 3 新的東西,以前會有一個 wsgi.py,是用來以後我們專案放到網路上讓他可以持續運行的東西
- 當然還是有wsgi.py
- urls.py 是用來設定我們的網址(例如: /about, /blogs, /contact 等等)
- settings.py 就是我們的設定檔
- __init.py__ 是 django 自己會去調配的初始化設定檔,基本上我們不用理他
這時候我們就可以開始試試看跑我們的server
python manage.py runserver
#或是
./manage.py runserver
這時候打開我們的瀏覽器,進到網址 http://127.0.0.1:8000 或是 localhost:8000會看到類似這樣的畫面
這樣就是python server 開始跑囉!
設定
接下來使用你最愛用的文字編輯器 VSCode, Sublime, vim 等等打開你的專案資料夾就可以開始設定基本的內容!
首先我們可能會希望把語言改成中文,並調整時區
找到 blog/settings.py,並更新下面兩個變數
LANGUAGE_CODE = 'zh-hant'
TIME_ZONE = 'Asia/Taipei'
接下來的設定我們不會動太多,因為現在只是一個最基礎的教學,之後會再寫文章講到如何使用別的database還有其他設定
Migration
接下來我們就快要進入部落格的環節了
首先,注意到從剛剛到現在terminal裡面一直都有一個紅紅的東西
這是告訴我們,我們的database 並沒有做好 migration。白話一點就是說,我們還沒把這個django網站相關的資料寫進我們的database。
在新增部落格之前,當我們一創建好專案,django就有很多資料庫的東西要記錄,例如cookies, sessions, timestamp, 使用者帳號等等,所以他要對我們的database先做一些基本操作 (他會使用ORM做類似 INSERT INTO sessions … 之類的SQL指令,我們不用擔心這塊)
所以這邊我們要先做migration
先Ctrl+C 關閉server
./manage.py migrate
趁這個時候我們也來創建一下 super user 帳號
./manage.py createsuperuser
這邊電子信箱不用打,而我密碼為了方便,設得很簡單,他會跳出提醒但是可以直接按y跳過
接下來回去runserver
./manage.py runserver
這時候就不會再跳出剛才的提醒了
此時我們可以去看一下django的後台:
進到 localhost:8000/admin,會看到這樣的畫面
這時我們就可以用剛剛的帳密登入,登入後看到這個主畫面,就是一個很好用的後台了。
那接下來我們就要回去新增部落格功能,讓後台可以管理新增部落格,之後再呈現到前端上面。
終於要來開始新增部落格的功能了
一樣CTRL+C 關閉 server,之後建立一個叫做 post 的 App
./manage.py startapp post
此時django會幫我們創一個post資料夾,看一下裡面有什麼東西
tree post
post
├── init.py
├── admin.py
├── apps.py
├── migrations
│ └── init.py
├── models.py
├── tests.py
└── views.py
- 外面的init.py 一樣是一些設定檔(基本上是空的) 先暫時不用理他
- admin.py 是用來設定部落格跟我們後台之間的 內容呈現 (表單樣式、哪些格子可以編輯等等)
- apps.py 就是註冊我們這個 post app在用的 class (之後用來在別的地方import)
- models.py 是用來設定我們的模型 (MVC架構中的M)(這可以決定我們的一篇部落格要有什麼東西,例如:標題、內容、圖片…等)
- migrations/ 目錄裡面放的是 makemigration 之後產生的ORM指令
- tests.py 是用來做 unit-testing 的 目前我們先不用理她
- views.py 是來做 MVC架構中的 View,可能也包含一些 controller的功能(之後會再解釋)
接下來我們要去 blog/settings 註冊我們這個app,讓他可以讀取到
到settings.py 找到 INSTALLED_APPS 這個 list, 加入 ‘post’
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# 加入 post
'post'
]
這樣我們的app就註冊完成囉(後台的部分還沒)
那我們接著就可以開始來創建我們部落格的模型了
用text editor 進到 models.py
這邊我們建立一個簡單的範本,裡面只有標題跟內容
post/models.py
from django.db import models
class Post(models.Model):
title = models.CharField('標題', max_length=20)
content = models.CharField('內容', max_length=200)
- 這邊是繼承 models.Model這個模板
- 然後我們設定title是一個Character Field,用來存放文字,後面 ‘標題’ 是這個title的別稱(會顯示在後台)
- max_length 是用來設定最多幾個字
- content的話一樣的道理
接下來,我們要把這樣的資料結構創建schema到資料庫中
回到我們的console
./manage.py makemigrations post
./manage.py makemigrations post
./manage.py migrate
- 這邊 makemigrations 就會創建一些ORM 的 SQL 指令讓等一下的 migrate執行(用來把資料結構input到資料庫中)
- migrate就會執行這個動作
- 如此一來我們的資料庫的結構就和我們的model同步了
admin註冊
接著我們要做的就是去 admins.py 這邊 把我們的 app 表單註冊到後台
post/admin.py
from django.contrib import admin
from .models import Post
class PostAdmin(admin.ModelAdmin):
list_display = ('id', 'title', 'content')
search_fields = ('title', 'content')
admin.site.register(Post, PostAdmin)
- 這邊第一行,是他預設的admin,目的是把django admin這個 module import 近來
- 第二行是從當前目錄下的 models.py 引入 剛剛做好的 Post 模型
- 接下來我們創建一個 PostAdmin class 繼承 ModelAdmin這個模板
- list_display是在清單目錄下要呈現的內容
- 包含預設的id(SQL會自己創建)、標題、內容
- search_fields 是為了要可以使用搜尋功能,可以直接加入這行,讓在admin中以後如果很多篇文章,可以根據標題或內容直接搜尋
接下來回到我們的瀏覽器,到localhost:8000/admin
可以看到已經有Post了,但是這邊我們很想把他用成中文,所以我們要再回到models.py裡面做一些設定
from django.db import models
class Post(models.Model):
class Meta:
verbose_name = '文章'
verbose_name_plural = '文章'
title = models.CharField('標題', max_length=20, )
content = models.CharField('內容', max_length=200)
def __str__(self):
return self.title
這邊我們用這個 Meta class 用來對 Post模組做一些設定
verbose_name跟verbose_name_plural 決定這個 post 要被以什麼名稱口頭上稱呼他(admin用的)我們中文中都統一設定成 ‘文章’
而 __str__ 則是用來設定到時候每篇文章要用誰來稱呼他(預設會用 id,我們想要每篇文章用標題稱呼他)
這時回到我們的admin,可以看到 Posts被叫成 ‘文章了’,而上面的 POST 我們需要動到他模板的template,就先暫時不管他。
我們現在可以去新增幾篇文章試試看
現在可以看到我們現在列表中有我們剛剛新增的內容了,而這些內容都會被儲存在我們的 db.sqlite3這個檔案中(其實有安全性的問題,所以大型專案不建議使用 sqlite)
把內容推播出去
接下來我們要把內容推播出去,利用views.py決定我們要呈現的內容(可能有點含糊,跟著做就對了)
這裡我們要用最簡單,寫起來最快的 Class-Based Views (CBV),有別於一些網路上其他的文章,都是使用functional views 這邊我們會用比較新的功能來快速產生views
from django.views.generic import ListView, DetailView
from .models import Post
class PostListView(ListView):
model = Post
template_name = 'post_list.html'
class PostDetailView(DetailView):
model = Post
template_name = 'post_detail.html'
- 這邊先 import 我們要繼承的 快速view產生模板 ListView和 DetailView
- 接著和 admin.py 的時候一樣 加入我們的model當 reference
- 接下來我要有一個列表View(用來呈現文章列表)
- 再一個 Detail View (用來呈現單一內容)
- 再來裡面的 model就是 用我們的模型做reference
- template_name 則是告訴 django我們等一下要用到的 html 模板檔案
在前端呈現這些文章
接下來就是我們的呈現環節了,要產生真正的網頁,並且把這些內容呈現出來
這時候我們就需要設定所謂的 template(模板)
並且設定 routes(路徑)在 urls.py 中
先創立模板
回到根目錄(django-blog-demo) 創立一個 templates 資料夾
在裡面建立一個base.html
接下來我們要用到 Bootstrap 這個CSS framework 來快速做出我們的模板
點擊上面的連結進到Bootstrap,之後點get started 找到這個 starter template 然後把他複製到我們的 base.html
接下來我們先把剛剛可愛的django已經安裝成功頁面換掉
接著再創建兩個檔案分別為 post_detail.html和post_list.html,
裡面目前都放這樣的內容
{% extends 'base.html' %}
意思是從 base.html這個檔案去延伸
目前我們的目錄結構長這樣:
.
├── Pipfile
├── Pipfile.lock
├── blog
│ ├── init.py
│ ├── asgi.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── db.sqlite3
├── manage.py
├── post
│ ├── init.py
│ ├── admin.py
│ ├── apps.py
│ ├── migrations
│ │ ├── 0001_initial.py
│ │ └── init.py
│ ├── models.py
│ ├── tests.py
│ └── views.py
└── templates
├── base.html
├── post_detail.html
└── post_list.html
更新: 這時我們要去 blog/settings.py 把templats/ 這個資料夾加到template engine的讀取清單中
TEMPLATES = [
{
‘BACKEND’: ‘django.template.backends.django.DjangoTemplates’,
# 加入templates目錄
‘DIRS’: [os.path.join(BASE_DIR, ‘templates’)],
#…
接著我們要去 blog/urls.py 讓這兩個頁面會呈現出來
blog/urls.py
from django.contrib import admin
from django.urls import path
from post.views import PostListView, PostDetailView
urlpatterns = [
path('admin/', admin.site.urls),
path('', PostListView.as_view()),
path('<pk>', PostDetailView.as_view())
]
這邊就是要讓 localhost:8000 呈現 PostListView,而 localhost:8000/1 呈現第一篇文章,依此類推
這個 <pk> 代表 primary key 就是用來表示文章編號的代名詞而已
接下來我們重新啟動server就可以看到不一樣囉
現在打開 localhost:8000, localhost:8000/1, localhost:8000/2 都會看到這樣的頁面,這就表示模板讀取成功囉!
調整模板
接著就要開始在模板裡面呈現東西了!
django 使用的是 jinja template 裡面有很多不同的邏輯功能,可以讓我們呈現網站非常快速,詳情可以上django 官網
先回到 base.html
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no"
/>
<!-- Bootstrap CSS -->
<link
rel="stylesheet"
href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css"
integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z"
crossorigin="anonymous"
/>
<title>Django | 簡易部落格</title>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<div class="container">
<a class="navbar-brand" href="/">簡易Django部落格</a>
</div>
</nav>
{% block body %}{% endblock %}
<!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script
src="https://code.jquery.com/jquery-3.5.1.slim.min.js"
integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrXaRkfj"
crossorigin="anonymous"
></script>
<script
src="https://cdn.jsdelivr.net/npm/[email protected]/dist/umd/popper.min.js"
integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN"
crossorigin="anonymous"
></script>
<script
src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"
integrity="sha384-B4gt1jrGC7Jh4AgTPSdUtOBvfO8shuf57BaghqFfPlYxofvL8/KUEfYiJOMMV+rV"
crossorigin="anonymous"
></script>
</body>
</html>
調整title,並把 body 中的 hello world 換成 {% block body %}{% endblock %}
並且在上面加一個簡單的bootstrap 導覽列 (navbar)
這就是我們等一下要去 post_detail跟post_list中取代的部分,而外面的部分等一下在裡面就不用重打。
這時再回去看我們的網站就有基本的樣式囉
部落格列表
回到post_list.html
{% extends 'base.html' %}
{% block body %}
<div class="container mt-5">
<h1>文章列表</h1>
<div class="row">
{% for post in object_list %}
<div class="col-md-6">
<div class="card my-3">
<div class="card-body">
<h5 class="card-title">{{ post.title }}</h5>
<p class="card-text text-muted">
{{ post.content }}
</p>
<a href="/{{ post.id }}" class="card-link">閱讀更多</a>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
{% endblock %}
- extends 代表繼承 base.html (剛剛的導覽列那些)
- 接下來這些div就是bootstrap的一些classes,因為本篇不是bootstrap的文章,就不多做介紹了,之後以會再做bootstrap的簡易教學
- {%block body%} 的意思就是編輯剛剛我們 base.html裡面那個區塊(在裡面加東西)。後面記得要加 {%endblock%}
- {%for%}{%endfor%} 就是回圈 在裡面可以去從一個陣列單獨的一個一個東西抓出來。
- 像這邊我們的例子就是要從 object_list 這裡抓東西 (這就是我們的文章列表)
- 注意這邊 object_list 是固定的字眼,如果想要把它換成 posts之類的,要到views裡面設定 (可以查一下)
- 這有點像 javascript 的 map 或是 forEach 那種概念
- 之後我們就是呈現一張卡片,在標題的地方呈現 {{ post.title }} 他就會換成文章標題
- content一樣的道理
- 而在 <a href=”/{{ post.id}}”> 這邊我們是要讓網址變成 href=”/1″ href=”/2″ 這樣子,讓我們連到 等一下的detail頁面
如果以上不懂沒有關係,這是跟html和bootstrap比較有關,可以先複製貼上看一下效果
接下來點進去 閱讀更多就會到我們的detail頁面,我們可以開始做我們的detail囉!
文章detail頁面
這邊我們用得很簡單,以下看 post_detail.html
{% extends 'base.html' %}
{% block body %}
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="/">文章列表</a></li>
<li class="breadcrumb-item active" aria-current="page">
{{ post.title }}
</li>
</ol>
</nav>
<div class="container mt-2">
<h1>{{ post.title }}</h1>
<hr />
<p>{{ post.content }}</p>
</div>
{% endblock %}
就只是簡單呈現 title跟 content,並且在上面用一個breadcrumb目錄來方便導覽。
基本上這樣就大功告成囉!
ps. terminal 裡面有一個 這樣的 error 會產生的原因是因為我們偷懶,現在post urls.py目錄是直接設在根目錄下面 localhost:8000/ ,所以當他請求 favicon.ico 的時候原本是要去抓網站logo 變成跑去抓一篇 id 為 favicon.ico 的文章。
不過這在我們現在的project中並不構成影響,可以不用理會!
最後,如果有不懂的東西歡迎Facebook, medium 留言提問
完整github repo: https://github.com/knhn1004/django-blog-demo
之後會再出如何部署成一個真正的網站等等教學,也會有如何用django寫 restful API 搭配 jquery 或 react 等框架作為前端 的教學
這只是一篇最基礎的教學,用文章可能不太清楚,之後打算會有影片series放在youtube上,年底可能也會有一些線上課程上線,盡請期待!
謝謝大家!
作者: 周家鋐 — — 浚鋐網路科技有限公司 創辦人
By Chiahong Chou on .
Exported from Medium on August 19, 2021.