Python Django 學習紀錄(八) 專題練習:新聞公告系統
接下來依照書上的專題範例,實作並將重點記錄下來
以下是此專題練習會依序完成的步驟:
- 繪製系統流程圖
- 建置一個名為 newsadm 的專案
- 建置資料庫結構、圖示
- 建置網頁基礎模板
- 首頁及詳細頁面處理函式及模板
- 登入登出頁面處理函式及模板
- 管理頁面處理函式及模板
- 新增新聞頁面處理函式及模板
- 編輯新聞頁面處理函式及模板
- 刪除新聞頁面處理函式及模板
一、新聞公告系統流程圖:
以下是接下來將做的新聞公告系統的流程圖:
二、建置一個名為 newsadm 的專案
- 建立一個名為 newsadm 的專案
- 建立名為 newsadmapp 的 App
- 建立 templates 目錄、static 目錄
- 建立makemigrations 資料檔,並利用 migrate 將模型與資料庫同步
- 最後完成<setting.py>的設定
再來請完成<setting.py>的設定,由於前面的學習紀錄已經重複做過很多次,這裡就不再說明
接著在<urls.py> 定義下列的 URL:
先將還不會用到的路徑註解掉,之後要使用時再陸續打開,以免之後運行伺服器會產生找不到路徑中函數的問題:
from django.contrib import admin from django.urls import path,re_path from newsadmapp import views urlpatterns = [ path('admin/', admin.site.urls), path('',views.index), path('index/',views.index), re_path('index/(\d+)/$',views.index), #參數為1時顯示上一頁,2為下一頁,3由詳細頁面返回首頁 re_path('detail/(\d+)/$',views.detail), #參數表示顯示「id=參數」的資料 path('login/',views.login), path('logout/',views.logout), path('adminmain/',views.adminmain), re_path('adminmain/(\d+)/$',views.adminmain), path('newsadd/',views.newsadd), re_path('newsedit/(\d+)/$',views.newsedit), re_path('newsedit/(\d+)/(\d+)/$',views.newsedit), re_path('newsdelete/(\d+)/$',views.newsdelete), re_path('newsdelete/(\d+)/(\d+)/$',views.newsdelete), ]
三、建置資料庫結構、圖示
1.資料庫結構
資料庫結構定義在 <models.py> 中,裡面有資料表 NewsUnit
NewsUnit 資料表儲存新聞資料:catego、nickname、title、message、pubtime
分別對應至:新聞類別、發布者暱稱、內容、發布時間
「auto_now=True」參數取得系統目前時間作為欄位值
from django.db import models class NewsUnit(models.Model): catego = models.CharField(max_length=10, null=False) #類別關聯 nickname = models.CharField(max_length=20, null=False) #暱稱 title = models.CharField(max_length=50, null=False) #標題 message = models.TextField(null=False) #內容 pubtime = models.DateTimeField(auto_now=True) #發布時間 enabled = models.BooleanField(default=False) #是否顯示 press = models.IntegerField(default=0) #點擊次數 def __str__(self): return self.title
enabled 欄位是布林值,設為True會顯示,False會隱藏。預設為False,須等管理者審核通過才會更改為True
press 欄位記錄使用者點及次數,使用者點選該則新聞會將此欄位值加1
在設置好資料庫後,請關閉伺服器
進行 更新makemigrations 資料檔,並利用 migrate 將模型與資料庫同步
再重新啟動伺服器
2.圖示
請在 staitic 目錄裡面 創建 images 資料夾
並在裡面放入圖片,圖片可以到網路上尋找,但請對應的這裡的檔案名稱
圖片用途 | 檔案名稱 |
新增新聞 | append.png |
刪除新聞 | cross.png |
日期 | date.png |
網頁logo | DWonline.png |
首頁圖片 | home.png |
錯誤 | icon_warning.png |
登入 | Lock.png |
登出 | LockOff.png |
下一頁 | nextpage.png |
編輯新聞 | note_edit.png |
上一頁 | prevpage.png |
使用者小圖 | user.png |
3.CSS
CSS部分,我尚未學習,所以這邊暫不做說明
可以利用網路資源導入基礎CSS
四、建置網頁基礎模板
新聞公告系統的基礎模板為網頁上方標題及下方版權的部分
由於 <index.html> 和 <detail.html>網頁結構類似,為這兩個網頁建立模板檔 <base.html>
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>新聞系統</title> {% load staticfiles %} <link href="{% static "css/style.css" %}" rel="stylesheet" type="text/css" > </head> <body> <div id="warp"> <div id="header"> {% block login %}{% endblock %} <!-- 登入圖示 --> <div class="logo"> <img src="{% static "images/DWonline.png" %}" width="180" height="40" alt="news"> </div> </div> <div class="headings"> <h2>新聞公告系統</h2> </div> {% block content %}{% endblock %} <!-- 內容 --> <div id="footer">© Copyright 2017 ivanpotato 新聞系統</div> </div> </body> </html>
接下來則有四個網頁結構相同,分別是 <adminmain.html>、<newsadd.html>、<newsedit.html>、<newsdelete.html>
分別是登入、新增、編輯、刪除四個頁面,標題皆為「最新消息-管理介面」,且都具有登出圖示
因此建立另一個基礎模板 <baseadmin.html>
<!DOCTYPE html> <html> <head> <title>最新消息-管理介面</title> {% load staticfiles %} <link href="{% static "css/style.css" %}" rel="stylesheet" type="text/css"> </head> <body> <div id="warp"> <div id="header"> <div class="toplogin"> {% block append %}{% endblock %} <!-- 讓<adminmain.html>在右上方加入新增資料的圖示 --> <a href="/logout/" /> <img src="{% static "images/Lockoff.png" %}" alt="登出管理介面" width="32" height="32" /></a> </div> <div class="logo"> <img src="{% static "images/DWonline.png" %}" width="180" height="40" alt="news" /> </div> </div> <div class="headings"> <h2>最新消息-管理介面</h2> </div> {% block content %}{% endblock %} <div id="footer">© Copyright 2018 ivanpotato 新聞系統</div> </div> </body> </html>
五、首頁及詳細頁面處理函式及模板
※上圖為首頁範例圖
1.首頁<後台>
在首頁按下上一頁、下一頁鈕,或是在詳細頁面按下 首頁 鈕,都會執行<views.py>的 index 函式、還有載入<index.html>網頁
from django.shortcuts import render from newsadmapp import models import math #導入math套件,將會使用math.ceil方法 page1 = 1 #目前頁面,設定開始時顯示地1頁新聞列表 def index(request, pageindex=None): global page1 #設為全域變數,重複開啟本頁面時可保留 page1 的值 pagesize = 8 #每頁顯示的資料筆數 newsall = models.NewsUnit.objects.all().order_by('-id') #讀取新聞資料表,依時間遞減排序 datasize = len(newsall) #新聞筆數 totpage = math.ceil(datasize / pagesize) #總頁數,math.ceil將以無條件進位方式整除 if pageindex == None: #無參數 page1 = 1 newsunits = models.NewsUnit.objects.filter(enabled=True).order_by('-id')[:pagesize] #取前8筆 elif pageindex =='1': #上一頁 start = (page1-2)*pagesize #該頁的第1筆資料 if start >=0: #有前頁資料就顯示 newsunits = models.NewsUnit.objects.filter(enabled=True).order_by('-id')[start:(start+pagesize)] page1 -= 1 elif pageindex == '2': #下一頁 start = page1*pagesize if start < datasize: #有下頁資料就顯示 newsunits = models.NewsUnit.objects.filter(enabled=True).order_by('-id')[start:start+pagesize] page1 += 1 elif pageindex == '3': #由詳細頁面返回首頁 start = (page1-1)*pagesize #取得原有頁面第1筆資料 newsunits = models.NewsUnit.objects.filter(enabled=True).order_by('-id')[start:start+pagesize] currentpage = page1 #將目前頁面以區域變數傳回html return render(request, "index.html",locals())
2.首頁<前台>
{% extends "base.html" %} <!-- 繼承<base.html>模版 --> {% load staticfiles %} <!-- 載入<static>目錄的靜態檔案 --> {% block login %} <!-- 登入管理介面 --> <div class="toplogin"> <a href="/login/" title="登入管理介面"> <img src="{% static "images/Lock.png" %}" alt="登入管理介面" width="32" height="32" /> </a> </div> {% endblock %} {% block content %} <div class="contentbox"> <table width="100%"> <tr> <th align="left">分類</th> <th align="left">標題</th> <th align="left">時間</th> <th align="left">點閱</th> </tr> {% for unit in newsunits %} <tr class="alt"> <td><div class="typeblock">【{{ unit.catego }}】</div></td> <td><a href="/detail/{{unit.id}}/">{{ unit.title }}</a></td> <td>{{ unit.pubtime }}</td> <td>{{ unit.press }}</td> </tr> {% empty %} <div class="status warning"> <p><img src="{% static "images/icon_warning.png" %}" alt="Warning" /><span>注意</span>"目前新聞資料庫中沒有任何資料</p> </div> {% endfor %} </table> <div class="topfunction"> {% if currentpage > 1 %} <a href="/index/1/" title="上一頁"> <img src="{% static "images/prevpage.png" %}" alt="下一頁" width="64" height="24" /> </a> {% endif %} {% if currentpage < totpage %} <a href="/index/2/" title="下一頁"> <img src="{% static "images/nextpage.png" %}" alt="下一頁" width="64" height="24" /> </a> {% endif %} </div> </div> {% endblock %}
※上圖為詳細頁面範例圖
3.詳細頁面<後台>
在首頁點選新聞標題,就會執行<views.py>的 detail 函式、還有載入<detail.html>網頁
def detail(request, detailid=None): unit = models.NewsUnit.objects.get(id=detailid) #根據參數取出資料 category = unit.catego title = unit.title pubtime = unit.pubtime nickname = unit.nickname message = unit.message unit.press += 1 #點擊次數加1 unit.save() #儲存資料 return render(request,"detail.html",locals())
4.詳細頁面<前台>
{% extends "base.html" %} {% load staticfiles %} {% block content %} <div class="contentbox"> <table width="100%"> <tr> <th align="left"><span class="typeblock">{{category}} </span> {{title}}</th> </tr> <tr> <td> <span class="newsinfo"> <img src="{% static "images/date.png" %}" alt="" width="16" height="16" /> {{pubtime}} <img src="{% static "images/user.png" %}" alt="" width="16" height="16" /> {{nickname}} <p> {{message}}</p> </span> </td> </tr> </table> <div class="topfunction"> <a href="/index/3/" title="返回首頁"> <img src="{% static "images/home.png" %}" alt="返回首頁" width="86" height="32" /></a> </div> </div> {% endblock %}
六、登入登出頁面處理函式及模板
1.登入登出頁面<後台>
使用者在首頁點選登入圖示<Lock.png>就會執行<view.py>中的 login 函式並載入 <login.html> 網頁
def login(request): messages = '' #初始時清除訊息 if request.method =='POST': #如果是以POST方式才處理 name = request.POST['username'].strip() #取得輸入帳號 password = request.POST['password'] #取得輸入密碼 user1 = authenticate(username=name, password=password) #驗證 if user1 is not None: #驗證通過 if user1.is_active: #帳號有效 auth.login(request, user1) #登入 return redirect('/adminmain/') #開啟管理頁面 else: #帳號無效 messages = '帳號尚未啟用!' else: #驗證未通過 messages = '登入失敗!' return render(request, "login.html", locals()) def logout(request): auth.logout(request) return redirect('/index/')
2.登入登出頁面<前台>
<!DOCTYPE html> <html> <head> <meta charset='utf-8'> <title>管理登入介面</title> {% load staticfiles %} <link href="{% static "css/style.css" %}" rel="stylesheet" type="text/css" /> </head> <body> <form action="." method="POST" id="loginform"> {% csrf_token %} <div class="fields"> <p class="sep"> <label class="small" for="username">管理者帳號</label> <input type="text" name="username" id="username" required /> <!-- required代表此欄位必須要輸入才可送出 --> </p> <p class="sep"> <label class="small" for="password">管理者密碼</label> <input type="password" name="password" id="password" required /> </p> <div class="action"> <input type="submit" class="butDef" value="登入系統" /> <input type="button" name="button1" value="退回首頁" onclick="window.location='/index/'" /> </div> <span style="color:red">{{messages}}</span> </div> </form> </body> </html>
七、管理頁面處理函式及模板
1.管理頁面<後台>
使用者在登入頁面通過驗證就會執行<view.py>中的 adminmain 函式並載入 <adminmain.html> 網頁
adminmain 函式與 index 函式幾乎相同,只有要回傳的網頁要改為 return 給 "adminmain.html"
def adminmain(request, pageindex=None): global page1 #設為全域變數,重複開啟本頁面時可保留 page1 的值 pagesize = 8 #每頁顯示的資料筆數 newsall = models.NewsUnit.objects.all().order_by('-id') #讀取新聞資料表,依時間遞減排序 datasize = len(newsall) #新聞筆數 totpage = math.ceil(datasize / pagesize) #總頁數,math.ceil將以無條件進位方式整除 if pageindex == None: #無參數 page1 = 1 newsunits = models.NewsUnit.objects.filter(enabled=True).order_by('-id')[:pagesize] #取前8筆 elif pageindex =='1': #上一頁 start = (page1-2)*pagesize #該頁的第1筆資料 if start >=0: #有前頁資料就顯示 newsunits = models.NewsUnit.objects.filter(enabled=True).order_by('-id')[start:(start+pagesize)] page1 -= 1 elif pageindex == '2': #下一頁 start = page1*pagesize if start < datasize: #有下頁資料就顯示 newsunits = models.NewsUnit.objects.filter(enabled=True).order_by('-id')[start:start+pagesize] elif pageindex == '3': #由詳細頁面返回首頁 start = (page1-1)*pagesize #取得原有頁面第1筆資料 newsunits = models.NewsUnit.objects.filter(enabled=True).order_by('-id')[start:start+pagesize] currentpage = page1 #將目前頁面以區域變數傳回html return render(request, "adminmain.html",locals())
2.管理頁面<前台>
比對 <index.html> 來說,<adminmain.html>則是在右上方多了新增新聞圖示、每則新聞最後方加入編輯圖示、刪除圖示。
還有要將點擊 標題 的連結從詳細頁面改為導向編輯頁面
這邊要注意的是,這裡繼承的是<baseadmin.html>模版,而不是<base.html>模版。
{% extends "baseadmin.html" %} <!-- 繼承<base.html>模版 --> {% load staticfiles %} <!-- 載入<static>目錄的靜態檔案 --> {% block append %} <a href="/newsadd/" title="新增新聞"> <img src="{% static "images/append.png" %}" alt="新增新聞" width="32" height="32" /> </a> {% endblock %} {% block content %} <div class="contentbox"> <table width="100%"> <tr> <th align="left">分類</th> <th align="left">標題</th> <th align="left">時間</th> <th align="left">編輯</th> </tr> {% for unit in newsunits %} <tr class="alt"> <td><div class="typeblock">【{{ unit.catego }}】</div></td> <td><a href="/newsedit/{{unit.id}}/">{{ unit.title }}</a></td> <td>{{ unit.pubtime }}</td> <td> <a href="/newsedit/{{unit.id}}/" title="新聞編輯" > <img src="{% static "images/note_edit.png" %}" width="16" height="16" /> </a> <a href="/newsdelete/{{unit.id}}/" title="新聞刪除" > <img src="{% static "images/cross.png" %}" width="16" height="16" /> </a> </td> </tr> {% empty %} <div class="status warning"> <p><img src="{% static "images/icon_warning.png" %}" alt="Warning" /><span>注意</span>"目前新聞資料庫中沒有任何資料</p> </div> {% endfor %} </table> <div class="topfunction"> {% if currentpage > 1 %} <a href="/index/1/" title="上一頁"> <img src="{% static "images/prevpage.png" %}" alt="下一頁" width="64" height="24" /> </a> {% endif %} {% if currentpage < totpage %} <a href="/index/2/" title="下一頁"> <img src="{% static "images/nextpage.png" %}" alt="下一頁" width="64" height="24" /> </a> {% endif %} </div> </div> {% endblock %}
八、新增新聞頁面處理函式及模板
1.新增頁面<後台>
使用者在管理頁面點擊新增新聞圖示就會執行<view.py>中的 newsadd 函式並載入 <newsadd.html> 網頁
def newsadd(request): message = '' #清除訊息 category = request.POST.get('news_type','') #取得使用者輸入的類別 subject = request.POST.get('news_subject','') #取得使用者輸入的標題 editor = request.POST.get('news_editor','') #取得使用者輸入的發布者暱稱 content = request.POST.get('news_content','') #取得使用者輸入的內容 ok = request.POST.get('news_ok','') #取得使用者輸入的審核 if subject == '' or editor == '' or content =='': #若有欄位未填寫就顯示此訊息(類別為下拉選單、審核是單邊按鈕,皆已有預設值不用檢查) message = '每一個欄位都要填寫..' else: if ok =='yes': #根據ok的值設定enabled欄位(將審核欄位輸入值轉換為布林值) enabled = True else: enabled = False unit = models.NewsUnit.objects.create(catego=category,nickname=editor,title=subject,message=content,enabled=enabled,press=0) unit.save() return redirect('/adminmain/') return render(request,"newsadd.html",locals())
2.新增頁面<前台>
{% extends "baseadmin.html" %} {% load staticfiles %} {% block content %} <div class="contentbox"> <form action='.' id="form1" name="form1" method="POST"> {% csrf_token %} <!-- 以POST方式送出表單,所以要加入 CSRF 驗證 --> <table width="90%" align="center"> <tr> <th colspan="2" align="left">新增新聞</th><!-- 表頭單元橫跨2列的表格 --> </tr> <tr><!-- 建立4個類別的下拉是選單 --> <td width="20%" valign="baseline"><strong>分類</strong></td> <td valign="baseline"> <select name="news_type" id="news_type"> <option value="公告" selected="selected">公告</option> <option value="更新">更新</option> <option value="活動">活動</option> <option value="其他">其他</option> </select> </td> </tr> <tr> <td valign="baseline"><strong>標題</strong></td> <td valign="baseline"><input type="text" name="news_subject" id="news_subject" size="45" required /></td> </tr> <tr> <td valign="baseline"><strong>編輯者</strong></td> <td valign="baseline"><input type="text" name="news_editor" id="news_editor" required /></td> </tr> <tr> <td valign="baseline"><strong>內容</strong></td> <td valign="baseline"><textarea name="news_content" id="news_content" cols="60" rows="15" required></textarea></td> </tr> <tr> <td valign="baseline"><strong>審核</strong></td> <td valign="baseline"> <input type="radio" name="news_ok" id="news_ok2" value="yes" checked="checked" />已通過 <input type="radio" name="news_ok" id="news_ok" value="no" />審核中 </td> </tr> <tr> <td> </td> <td> <input type="submit" name="button" id="button" value="送出"> <input type="reset" name="button2" id="button2" value="重設"> <input type="button" name="button3" id="button3" value="回主頁面" onclick="window.location='/adminmain/'"> </td> </tr> </table> <span style="color: red">{{message}}</span> </form> </div> {% endblock %}
九、編輯新聞頁面處理函式及模板
1.編輯頁面<後台>
使用者在管理頁面點擊編輯新聞圖示就會執行<view.py>中的 newsedit 函式並載入 <newsedit.html> 網頁
def newsedit(request, newsid=None, edittype=None): #修改資料 unit = models.NewsUnit.objects.get(id=newsid) #讀取指定資料 categories = ["公告","更新","活動","其他"] #將4個項目串列儲存於categories區域變數,傳送給<newsedit.html>建立下拉式選單(包含雙引號) if edittype == None: #進入修改頁面,顯示原有資料(此為使用者點新聞標題及新聞圖示執行的程式) type = unit.catego subject = unit.nickname editor = unit.nickname content = unit.message ok= unit.enabled elif edittype =='1': #修改完畢,存檔 (此為使用者點送出按鈕執行的程式) category = request.POST.get('news_type','') #取得使用者輸入的類別 subject = request.POST.get('news_subject','') #取得使用者輸入的標題 editor = request.POST.get('news_editor','') #取得使用者輸入的發布者暱稱 content = request.POST.get('news_content','') #取得使用者輸入的內容 ok = request.POST.get('news_ok','') #取得使用者輸入的審核 if ok =='yes': #根據ok的值設定enabled欄位(將審核欄位輸入值轉換為布林值) enabled = True else: enabled = False unit.catego = category #將資料寫入資料庫 unit.nickname = editor unit.title = subject unit.message = content unit.enabled = enabled unit.save() return redirect('/adminmain/') return render(request,"newsedit.html",locals())
2.編輯頁面<後台>
{% extends "baseadmin.html" %} {% load staticfiles %} {% block content %} <div class="contentbox"> <form action="/newsedit/{{newsid}}/1/" id="form1" name="form1" method="POST"> {% csrf_token %} <table width="90%" align="center"> <tr> <th colspan="2" align="left">編輯新聞</th> </tr> <tr> <td width="20%" valign="baseline"><strong>分類</strong></td> <td valign="baseline"> <select name="news_type" id="news_type"> {% for category in categories %} {% if type == category %} <option value={{category}} selected="selected">{{category}}</option> {% else %} <option value={{category}} >{{category}}</option> {% endif %} {% endfor %} </select> </td> </tr> <tr> <td align="baseline"><strong>標題</strong></td> <td align="baseline"><input type="text" name="news_subject" id=news_subject size="45" value="{{subject}}" /></td> </tr> <tr> <td valign="baseline"><strong>編輯者</strong></td> <td valign="baseline"><input type="text" name="news_editor" id="news_editor" value="{{editor}}" /></td> </tr> <tr> <td valign="baseline"><strong>內容</strong></td> <td valign="baseline"><textarea name="news_content" id="news_content" cols="60" rows="15" >{{content}}</textarea></td> </tr> <tr> <td valign="baseline"><strong>審核</strong></td> <td valign="baseline"> {% if ok %} <input type="radio" name="news_ok" id="news_ok2" value="yes" checked="checked">已通過 <input type="radio" name="news_ok" id="news_ok" value="no" >審核中 {% else %} <input type="radio" name="news_ok" id="news_ok2" value="yes">已通過 <input type="radio" name="news_ok" id="news_ok" value="no" checked="checked">審核中 {% endif %} </td> </tr> <tr> <td> </td> <td> <input type="submit" name="button" id="button" value="送出"> <input type="reset" name="button2" id="button2" value="重設"> <input type="button" name="button3" id="button3" value="回主頁面" onclick="window.location='/adminmain/'"> </td> </tr> </table> </form> </div> {% endblock %}
十、刪除新聞頁面處理函式及模板
1.刪除頁面<後台>
使用者在管理頁面點擊刪除新聞圖示就會執行<view.py>中的 newsdelete 函式並載入 <newsdelete.html> 網頁
def newsdelete(request, newsid=None, deletetype=None): #刪除資料 unit = models.NewsUnit.objects.get(id=newsid) #根據newsid讀取指定資料 if deletetype == None: #進入刪除頁面,顯示原有資料(此為使用者點刪除新聞圖示執行的程式) type = str(unit.catego.strip()) subject = unit.title editor = unit.nickname content = unit.message date = unit.pubtime elif deletetype == '1': #按下刪除按鈕 unit.delete() return redirect('/adminmain/') return render(request,"newsdelete.html",locals())
2.刪除頁面<前台>
{% extends "baseadmin.html" %} {% load staticfiles %} {% block content %} <div class="contentbox"> <form action="/newsdelete/{{newsid}}/1/" name="form1" id="form1" method="POST"> {% csrf_token %} <table width="90%" align="center"> <tr> <th colspan="2" align="left">刪除新聞</th> </tr> <tr> <td width="20%"><strong>分類</strong></td> <td>{{type}}</td> </tr> <tr> <td><strong>標題</strong></td> <td>{{subject}}</td> </tr> <tr> <td><strong>編輯者</strong></td> <td>{{editor}}</td> </tr> <tr> <td><strong>內容</strong></td> <td>{{content}}</td> </tr> <tr> <td><strong>日期</strong></td> <td>{{date}}</td> </tr> <tr> <td><strong><font color="#FF0000">是否確定刪除</font></strong></td> <td> <input type="submit" name="button" id="button" value="確定"> <input type="button" name="button3" id="button3" value="回主頁面" onclick="window.location='/adminmain/'" /> </td> </tr> </table> </form> </div> {% endblock %}