close

Python Django 專題練習:遊戲資訊系統(註冊、登入登出、使用者加入最愛功能)

 

由於其他許多事情的耽誤,將近一個月沒有複習Django相關的程式,所以寫一個遊戲資訊彙總的系統

並且可以登入登出、收藏文章

以下是此專題練習會依序完成的步驟: 

  1. 繪製系統流程圖
  2. 建置一個名為 game 的專案
  3. 建置資料庫結構、圖示
  4. 建置網頁基礎模板
  5. 登入登出系統與頁面處理函式及模板
  6. 註冊系統
  7. 首頁及詳細頁面處理函式及模板
  8. 我的最愛列表處理函式及模板

 

一、遊戲資訊系統流程圖:

以下是接下來將做的遊戲資訊系統的流程圖:

遊戲資訊系統.jpg

 

二、建置一個名為 game 的專案

 1.建置專案: 

  1. 建立一個名為 game 的專案
  2. 建立名為 gameapp 的 App 
  3. 建立 templates 目錄、static 目錄
  4. 建立makemigrations 資料檔,並利用 migrate 將模型與資料庫同步
  5. 最後完成<setting.py>的設定

123.JPG

 

2.url.py配置: 

from django.contrib import admin
from django.urls import path,re_path

from gameapp import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('crawler/',views.crawler),
    path('index/',views.index),
    re_path('detail/(\d+)/$',views.detail)
]

3.Bootstrap與JQuery配置: 

(1)Bootstrap

為了快速達成響應式網頁的架設,這裡採用bootstrap框架

之前沒有說明過如何整合Bootstrap與Django,其實十分簡單

首先到Bootstrap的官方網站,下載bootstrap

將解壓縮後的bootstrap-3.3.7-dist目錄整個丟到game專案裡的static目錄即可

(2)JQuery

Bootstrap依賴JQuery,所以這裡也要同時引入

下載好JQuery後,在static目錄下創建js目錄,並將js檔案放入其中

三、運用爬蟲建置資料庫

因為想練習爬蟲,又希望能有自己的資料庫來做練習

我這邊先使用jupyter notebook做爬蟲的練習與測試,之後再一併整合進Django中

1.巴哈姆特-少女前線看板圖片 

由於主圖片變數是要傳給每個頁面,所以這邊設定為全域變數

但由於全域變數不能藉由render傳遞給前台(templates)

所以之後會在各函式中轉換為區域變數

#主圖片爬蟲
def titleimg():
    global timg1
    url = 'https://forum.gamer.com.tw/A.php?bsn=31406'   #選擇網址
    user_agent = 'Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN; rv:1.9.2.15) Gecko/20110303 Firefox/3.6.15' #偽裝使用者
    headers = {'User-Agent':user_agent}
    data_res = urllib.request.Request(url=url,headers=headers)
    data = urllib.request.urlopen(data_res)
    data = data.read().decode('utf-8')  
    sp = BeautifulSoup(data, "html.parser")

    titleimg = sp.find("div",{"class":"FM-abox1"}).findAll("img", src = re.compile('/welcome/'))

    for timg in titleimg:
        timg1 = timg['src']

 

2.巴哈姆特-少女前線看板文章列表 

import requests
from bs4 import BeautifulSoup
import urllib
import re
 
url = 'https://forum.gamer.com.tw/B.php?bsn=31406'   #選擇網址
user_agent = 'Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN; rv:1.9.2.15) Gecko/20110303 Firefox/3.6.15' #偽裝使用者
headers = {'User-Agent':user_agent}
data_res = urllib.request.Request(url=url,headers=headers)
data = urllib.request.urlopen(data_res)
data = data.read().decode('utf-8')  
sp = BeautifulSoup(data, "html.parser")

title = sp.findAll('td',{"class":"b-list__main"})
titles = []
for titlee in title:
    titles.append(titlee.text.strip('\n'))

links = []
ll = 'https://forum.gamer.com.tw/'
link = sp.find("table",{"class":"b-list"}).findAll("a", href = re.compile('C.php?'))
for linkk in link:
    page = re.compile(r'^((?!page).)*$')  ##不匹配page
    last = re.compile(r'^((?!last).)*$')  ##不匹配last
    m = page.match(linkk['href'])  ##設定變數m來排除page
    if m != None:  ##若不為None (None會跳出例外)
        n = last.match(m.group(0)) ## 設定變數n來排除last
        if n != None: ##若不為None (None會跳出例外)
            links.append(ll+n.group(0))
            
for t,l in zip(titles,links):
    print(t,l)
    content(l)  ##此為獲取文章作者、內容的自訂函式(在下方)
※這邊遇到的問題
爬網址的時候,會多爬取到網頁上顯示文章內頁數的地方(每個文章後面會有最新頁數連結)
導致一個文章的網址會重複爬到多次,因此採用正則表達來排除網址內包含page和last的內容
124.JPG

3.巴哈姆特-少女前線看板文章作者、內容 

def content(aa):
 
    url = aa   #選擇網址
    user_agent = 'Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN; rv:1.9.2.15) Gecko/20110303 Firefox/3.6.15' #偽裝使用者
    headers = {'User-Agent':user_agent}
    data_res = urllib.request.Request(url=url,headers=headers)
    data = urllib.request.urlopen(data_res)
    data = data.read().decode('utf-8')  
    sp = BeautifulSoup(data, "html.parser")

    authors = sp.find('div',{"class":"c-post__header__author"}).findAll("a",{"class":"username"})
    for author in authors:
        print(author.text)  ##打印作者

    contents = sp.find('div',{"class":"c-article__content"})
    print(contents.text)   ##打印內容

4.建置model與資料庫

(1)建立資料模型

開啟 gameapp專案下的 <models.py> 檔,該檔預設已經import model套件。 

from django.db import models
 
class gamee(models.Model):
    cAuthor = models.CharField(max_length=50, blank=True) #建立字串型別的欄位,最大長度為20字元,欄位不可空白
    cContent = models.CharField(max_length=9999,blank=True, default='')
    cTitle = models.CharField(max_length=100,blank=True, default='')#blank=True 欄位可空白
    cLink = models.CharField(max_length=100,blank=True, default='')
 
    #每一筆資料在管理介面顯示的內容以下列程式定義
    def __str__(self):
        return self.cTitle #表示顯示cTitle欄位

資料庫模型建立完成後,必須將建立資料表的架構和版本記錄下來,以利以後的追蹤。

如果專案伺服器是啟動中的,請按 CTRL+C 關閉伺服器,回到game 目錄,並以「python manage.py  makemigrations」更新migrations目錄。

接著以「 python manage.py migrate」進行migrate,migrate會根據migrations紀錄將模型同步到資料庫。

(2)admin 後台管理

請開啟 gameapp 目錄底下的 <admin.py> 並寫入以下程序: 

from django.contrib import admin

from gameapp.models import gamee
 
#下面會以register的方式,將建立的資料模型向admin註冊
class gameAdmin(admin.ModelAdmin):
    list_display=('id','cTitle','cAuthor','cContent','cLink')
admin.site.register(gamee,gameAdmin)

記得要儲存文件,接下來先關閉server,要建立管理者帳號密碼來登入admin

這邊帳號先以admin,信箱設為admin@admin.com,密碼設為aa000000(不能設為全數字)

125.JPG

5.爬蟲並存入資料庫

我希望的是在遊戲資訊系土網站主頁按下"獲取最新資訊"連結的時候,進行爬蟲並更新網站的資訊內容

也就是按下連結的時候會執行下面所寫的crawler這個函式 

其中要特別注意的是,要將網頁原始碼存入資料庫需要進行轉譯

否則會被SQL自帶的防護系統阻擋

轉譯的方法可以參考這篇

def crawler(request):  #爬蟲程式
    global titles,links,at,ct,t,l
    url = 'https://forum.gamer.com.tw/B.php?bsn=31406'   #選擇網址
    user_agent = 'Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN; rv:1.9.2.15) Gecko/20110303 Firefox/3.6.15' #偽裝使用者
    headers = {'User-Agent':user_agent}
    data_res = urllib.request.Request(url=url,headers=headers)
    data = urllib.request.urlopen(data_res)
    data = data.read().decode('utf-8')  
    sp = BeautifulSoup(data, "html.parser")

    title = sp.findAll('td',{"class":"b-list__main"})
    titles = []
    for titlee in title:
        titles.append(titlee.text.strip('\n'))

    links = []
    ll = 'https://forum.gamer.com.tw/'
    link = sp.find("table",{"class":"b-list"}).findAll("a", href = re.compile('C.php?'))
    for linkk in link:
        page = re.compile(r'^((?!page).)*$')  ##不匹配page
        last = re.compile(r'^((?!last).)*$')  ##不匹配last
        m = page.match(linkk['href'])  ##設定變數m來排除page
        if m != None:  ##若不為None (None會跳出例外)
            n = last.match(m.group(0)) ## 設定變數n來排除last
            if n != None: ##若不為None (None會跳出例外)
                links.append(ll+n.group(0))

    for t,l in zip(titles,links):
        print(t,l)
        content(l) #使用爬蟲出來的網址進行文章內容的爬蟲
        sql()  #將爬出的內容進行與資料庫的連接
    return redirect('/index/')



def content(aa):
    global titles,links,at,ct,t,l
    url = aa   #選擇網址
    user_agent = 'Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN; rv:1.9.2.15) Gecko/20110303 Firefox/3.6.15' #偽裝使用者
    headers = {'User-Agent':user_agent}
    data_res = urllib.request.Request(url=url,headers=headers)
    data = urllib.request.urlopen(data_res)
    data = data.read().decode('utf-8')  
    sp = BeautifulSoup(data, "html.parser")

    authors = sp.find('div',{"class":"c-post__header__author"}).findAll("a",{"class":"username"})
    for author in authors:
        at = author.text
        print(at)

    contents = sp.find('div',{"class":"c-article__content"})
    ct = html.escape(str(contents))#html編碼轉譯
    print(ct)

def sql():
    global titles,links,at,ct,t,l

    cAuthor = at    
    cContent = ct
    cTitle = t
    cLink = l
    try:
        if gamee.objects.get(cTitle=t):
            print('已有重複資料')
            
    except:
        unit = gamee.objects.create(cAuthor=cAuthor, cContent=cContent, cTitle=cTitle, cLink=cLink) 
        unit.save()                      #寫入資料庫
        print('成功儲存一筆資料')

 

這邊先做一個簡單的展示,之後會再做修改

127.JPG

四、網頁基礎模版

1.主頁、詳細頁面,會共同使用的模板

這邊命名為"base.html"

首先載入static目錄的內容

接著導引bootstrap的路徑

在一開始放了一張置中的響應式圖片(由主圖片爬蟲爬出,轉換為區域變數timg2後傳遞) 

然後是bootstrap的導覽列(這邊直接修改bootstrap的官方程式碼)

根據使用者是否登入會呈現出不同列表

底下是版權聲明

最後則是載入JQuery

 

 

{% load staticfiles %}
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    {% block title %}{% endblock %}
    <link href="{% static 'bootstrap-3.3.7-dist/css/bootstrap.min.css' %}" rel="stylesheet">
    
</head>
<body>
    <div id="headings" align="center">
        <img src="{{timg2}}" class="img-responsive" />
    </div>
    <nav class="navbar navbar-default">
      <div class="container-fluid">
        <!-- Brand and toggle get grouped for better mobile display -->
        <div class="navbar-header">
          <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#my-nav" aria-expanded="false">
            <span class="sr-only">切換導航條</span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
          </button>
          <a class="navbar-brand" href="#">少女前線資料系統</a>
        </div>

        <!-- Collect the nav links, forms, and other content for toggling -->
        <div class="collapse navbar-collapse" id="my-nav">
          <ul class="nav navbar-nav">
            <li class="active"><a href="/index/">主頁</a></li>
          </ul>
          {% if request.session.is_login %} <!-- 若登入則出現此列 -->
            <ul class="nav navbar-nav ">
              <li><a href="/lovepage/">我的最愛列表</a></li>
            </ul>
          {% endif %}
          <ul class="nav navbar-nav navbar-right">
            <li><a href="/crawler/">獲取最新資料</a></li>
            {% if request.session.is_login %}
                <li><a href="#">{{ request.session.user_name }},歡迎您登入!</a></li>
                <li><a href="/logout/">登出</a></li>
            {% else %}
                <li><a href="/login/">登入</a></li>
                <li><a href="/register/">註冊</a></li>
            {% endif %}
          </ul>
        </div><!-- /.navbar-collapse -->
      </div><!-- /.container-fluid -->
    </nav>
    {% block content %}{% endblock %}
    <div id="footer">&copy; Copyright 2017 ivanpotato 遊戲資訊系統</div>


    <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
    <script src="{% static 'js/jquery-3.3.1.js' %}"></script>

    <!-- Include all compiled plugins (below), or include individual files as needed -->
    <script src="{% static 'bootstrap-3.3.7-dist/js/bootstrap.min.js' %}"></script>
</body>
</html>



 

130.JPG

五、登入登出系統與頁面處理函式及模板

1.建立使用者資訊資料庫

為了要讓使用者註冊與登入登出,需要保存各種用戶的資料

因此先建立一個使用者資訊的資料庫

這次將要保存用戶的用戶名稱、密碼、信箱、性別、創建時間

開啟 gameapp專案下的 <models.py> 檔,先前已經建立過gamee資料表

在下面建立一個名為User的資料表: 

class User(models.Model):
    gender = (
        ('male','男'),
        ('female','女'),
    )
    name = models.CharField(max_length=128, unique = True) #唯一,不可有相同姓名
    password = models.CharField(max_length=256)
    email = models.EmailField(unique=True)
    sex = models.CharField(max_length=32,choices=gender,default="男")
    ctime = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.name

資料庫模型建立完成後,必須將建立資料表的架構和版本記錄下來,以利以後的追蹤。

如果專案伺服器是啟動中的,請按 CTRL+C 關閉伺服器,回到game 目錄,並以「python manage.py  makemigrations」更新migrations目錄。

接著以「 python manage.py migrate」進行migrate,migrate會根據migrations紀錄將模型同步到資料庫。

接著使用Django的後台管理系統,請開啟 gamesapp 目錄底下的 <admin.py> 並寫入以下程序:  

class UserAdmin(admin.ModelAdmin):
    list_display=('id','name','password','email','sex','ctime')
admin.site.register(User,UserAdmin)

2.urls.py路徑設置

預計會使用到首頁、登入頁面、登出頁面、註冊頁面(將在下一節提到)

from django.contrib import admin
from django.urls import path,re_path
from gameapp import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('index/',views.index),
    path('login/',views.login),
    path('logout/',views.logiout),
    path('register/',views.register),

]

3.建置登入表單

Django雖然有auth套件可以快速的做出登入驗證,但這裡我選擇自行建立表單模型 

同時也方便之後可以自動生成前台表單

 form 表單建置 參考資料

首先必須在 gameapp 目錄下新增一個 <form.py> 檔案,然後建立繼承forms.Form的類別,格式與Mode的格式相似

由於搭配Bootstrap使用,所以分別添加了屬性

from django import forms

class UserForm(forms.Form):
    username = forms.CharField(label="用戶名", max_length=128, widget=forms.TextInput(attrs={'class': 'form-control'}))
    password = forms.CharField(label="密碼", max_length=256, widget=forms.PasswordInput(attrs={'class': 'form-control'}))

3.登入系統前台頁面

132.JPG

這邊使用許多 Bootstrap語法,快速建立出一個登入表單

{{message}}是登入錯誤時的提醒訊息

表單則是能藉由form自動生成

 

{% extends "base.html" %}
{% load staticfiles %}
{% block title %}<title>登入頁面</title>{% endblock %}
{% block content %}
<div class="container">
    <div class="col-md-4 col-md-offset-4">
        
        <form class="form-login" action="/login/" method="POST">
            {% if message %}
            <div class="alert alert-warning">{{message}}</div>
            {% endif %}
            {% csrf_token %}
            <h1>歡迎登入!</h1>
            <div class="form-group">
                {{ login_form.username.label_tag }} <!-- 由forms.py自動生成 -->
                {{ login_form.username }}
            </div>
            <div class="form-group">
                {{ login_form.password.label_tag }}
                {{ login_form.password }}
            </div>
            <button type="reset" class="btn btn-default pull-left">重設</button>
            <button type="submit" class="btn btn-primary pull-right">確定登入</button>

        </form>
    </div>
</div>
{% endblock %}

 

4.登入系統後台頁面(views.py)

註解都打在程式碼裡面了

需要注意的地方就是這裡做了密碼加密

註冊的時候會進行加密

登入的時候需要進行解密

from django.shortcuts import render,redirect
from gameapp.models import gamee,User
from . import models
from django.http import HttpResponse
from django import forms
from gameapp.forms import UserForm,RegisterForm
import hashlib


def hash_code(s, salt='ivan'): #密碼加密
    h = hashlib.sha256()
    s = s + salt
    h.update(s.encode())
    return h.hexdigest()

def login(request):

    if request.session.get('is_login',None): #檢查session確定是否登入,不允許重複登入
        return redirect("/index/")  #若已登入則導向主頁
    if request.method == 'POST':    #接收POST訊息,若無則讓返回空表單
        login_form = UserForm(request.POST)   #導入表單模型
        if login_form.is_valid(): #驗證表單
            username = login_form.cleaned_data['username']  #從表單的cleaned_data中獲得具體值
            password = login_form.cleaned_data['password'] 
            try:
                user = models.User.objects.get(name=username)
                if user.password == hash_code(password): #密文處理
                    #使用session寫入登入者資料
                    request.session['is_login'] = True
                    request.session['user_id'] = user.id
                    request.session['user_name'] = user.name
                    message = "登入成功"
                    return redirect('/index/')
                else:
                    message = "密碼不正確"
            except:
                message = "該用戶不存在"
    login_form = UserForm(request.POST) #返回空表單
    return render(request,"login.html",locals())

def logout(request):
    if not request.session.get('is_login',None): #如果原本未登入,就不需要登出
        return redirect('/index/')
    request.session.flush() #一次性將session內容全部清除
    return redirect('/index/') 

 

六、註冊系統處理函式及模板

1.建立註冊頁面表單模型

註冊頁面同樣也需要一個表單,因此在<forms.py>中建立一個 RegisterForm類別

class RegisterForm(forms.Form):
    gender = (
        ('male','男'),
        ('female','女'),
    )
    username = forms.CharField(label="用戶名", max_length=128, widget=forms.TextInput(attrs={'class': 'form-control'}))
    password1 = forms.CharField(label="密碼", max_length=256, widget=forms.PasswordInput(attrs={'class': 'form-control'}))
    password2 = forms.CharField(label="確認密碼", max_length=256, widget=forms.PasswordInput(attrs={'class': 'form-control'}))
    email = forms.EmailField(label = "信箱", max_length=256, widget=forms.EmailInput(attrs={'class':'form-control'}))
    sex = forms.ChoiceField(label = "性別",choices=gender)

2.註冊系統前台頁面

133.JPG

這邊跟登入系統大同小異

使用許多 Bootstrap語法,快速建立出一個註冊表單

{{message}}是輸入錯誤時的提醒訊息

表單則是能藉由form自動生成

 

{% extends "base.html" %}
{% load staticfiles %}
{% block title %}<title>註冊頁面</title>{% endblock %}
{% block content %}
<div class="container">
    <div class="col-md-4 col-md-offset-4">
        <form class="form-register" action="/register/" method="POST">
            {% if message %}
                <div class="alert alert-warning">{{ message }}</div>
            {% endif %}
            {% csrf_token %}
            <h2>歡迎註冊!</h2>
            <div class="form-group">
                {{ register_form.username.label_tag }}
                {{ register_form.username }}
            </div>
            <div class="form-group">
                {{ register_form.password1.label_tag }}
                {{ register_form.password1 }}
            </div>
            <div class="form-group">
                {{ register_form.password2.label_tag }}
                {{ register_form.password2 }}
            </div>
            <div class="form-group">
                {{ register_form.email.label_tag }}
                {{ register_form.email }}
            </div>
            <div class="form-group">
                {{ register_form.sex.label_tag }}
                {{ register_form.sex }}
            </div>

            <button type="reset" class="btn btn-default pull-left">重設</button>
            <button type="submit" class="btn btn-primary pull-right">送出</button>
        </form>


    </div>

</div>


{% endblock %}

 

3.註冊系統後台頁面(views.py)

註解都打在程式碼裡面了

需要注意的地方就是這裡做了密碼加密

註冊的時候會進行加密

登入的時候需要進行解密

def register(request):
    if request.method == 'POST':
        register_form = RegisterForm(request.POST)
        message = '請檢察填寫的內容!'
        if register_form.is_valid(): #驗證數據,提取表單內容
            username = register_form.cleaned_data['username'] 
            password1 = register_form.cleaned_data['password1']
            password2 = register_form.cleaned_data['password2']
            email = register_form.cleaned_data['email']
            sex = register_form.cleaned_data['sex']
            if password1 != password2: #若兩次密碼不同
                message = "兩次輸入的密碼不同!"
                return render(request, 'register.html', locals())
            else:
                same_name_user = models.User.objects.filter(name=username) #比對資料庫是否有相同用戶名
                if same_name_user:
                    message = "該用戶名稱已存在!"
                    return render(request, 'register.html', locals())
                same_email_user = models.User.objects.filter(email=email)  #比對資料庫是否有相同信箱
                if same_email_user:
                    message = "信箱已被使用!"
                    return render(request, 'register.html', locals())
                #若上面條件皆通過,則創建新的用戶
                new_user = models.User()
                new_user.name = username
                new_user.password = hash_code(password1)
                new_user.email = email
                new_user.sex = sex
                new_user.save()
                return redirect('/login/') #自動跳轉到登入頁面
    register_form = RegisterForm(request.POST)
    return render(request, 'register.html', locals())

七、首頁及詳細頁面處理函式及模板

 

1.首頁(前台)

136.JPG

將資料庫的檔案以for迴圈的方式顯示,傳遞id參數至detail詳細頁面

{% extends "base.html" %}
{% load staticfiles %}
{% block content %}
    <div class="topfunction">
        <a href="/crawler/" title="獲取最新資訊">獲取最新資訊</a>
    </div>
    <table border="3" cellpadding="2" cellspacing="2">
        <th>標題</th>
        <th>作者</th>
        <th>連結網址</th>
        {% for un in unit %}
        <tr>
            <td><a href="/detail/{{un.id}}">{{un.cTitle}}</a></td>
            <td>{{un.cAuthor}}</td>
            <td><a href="{{un.cLink}}">巴哈姆特討論串</a></td>
        </tr>
        {% endfor %}
    </table>
{% endblock %}

2.首頁(後台)

反序查找資訊,最新資訊顯示在最前面

def index(request):
    unit = gamee.objects.all().order_by( '-id' ) 
    return render(request,"index.html",locals())

 

 

3.詳細頁面(前台)

 

將查找到的資料,以表格的方式顯示

點選加入我的最愛,會將該文章加入我的最愛列表(這部分將在之後提到)

並加入回主頁、回頂部的連結

這邊    <meta name="referrer" content="never">讓瀏覽器不帶refer訊息,繞過顯示爬蟲圖片網址的refer檢測  

138.JPG

{% extends "base.html" %}
{% load staticfiles %}
{% block title %}
<title>遊戲資訊文章頁面</title>
<!-- 讓瀏覽器不帶refer訊息,繞過顯示爬蟲圖片網址的refer的檢測 -->
<meta name="referrer" content="never"> 
{% endblock %}
{% block content %}
<div align="center">
    <a href="/index/" class="btn btn-info" role="button">返回主頁</a>
    <!-- 判斷是否登入,若有登入則顯示此按鈕 -->
    {% if request.session.is_login %} 
        <a href="/adtlovelist/add/{{unit.id}}" class="btn btn-danger" role="button">加入最愛</a>
    {% endif %}
</div>
<div> </div>
<div class="table-responsive">
    <table border="3" cellpadding="2" cellspacing="2" class="table">

        <tr>
            <td>標題:{{cTitle}}</td>
        </tr>
        <tr>
            <td>作者:{{cAuthor}}</td>
        </tr>
        <tr>
            <td>內文:{% autoescape off %}{{cContent}}{% endautoescape %}</td>
        </tr>
    </table>
</div>

<div align="center">
    <a href="/index/" class="btn btn-info" role="button">返回主頁</a>
    <a href="." class="btn btn-danger" role="button">回頂部</a>
</div>

{% endblock %}

4.詳細頁面(後台)

根據傳入的參數,查找資料

def detail(request, detailid=None):
    timg2 = timg1
    unit = gamee.objects.get(id=detailid)
    cTitle = unit.cTitle
    cAuthor = unit.cAuthor
    cContent = html.unescape(unit.cContent).replace("data-src","src") 

    #cContent反轉譯,且由於巴哈有延遲載入,因此src屬性名稱不同需替換
    return render(request,"detail.html",locals())

八、加入、刪除,我的最愛文章列表處理函式及模板

1.修改使用者資料表

在使用者資訊中添加一個 名為 clove 的欄位,用來儲存 我的最愛列表 的串列

由於需要串列的型態存入、以串列的型態取出來使用

Django 沒有內建的 ListField ,所以需要自定義來使用,以下為代碼:

from django.db import models
import ast
 #自定義field

class ListField(models.TextField):

    def __init__(self, *args, **kwargs):
        super(ListField, self).__init__(*args, **kwargs)

    def from_db_value(self, value, expression, connection, context):
        if not value:
            value = []

        if isinstance(value, list):
            return value

        return ast.literal_eval(value)

    def get_prep_value(self, value):
        if value is None:
            return value

        return str(value)

    def value_to_string(self, obj):
        value = self._get_val_from_obj(obj)
        return self.get_db_prep_value(value)

修改 User 資料表,增加 clove 欄位,並儲存成 ListField 型態:

class User(models.Model):
    gender = (
        ('male','男'),
        ('female','女'),
    )
    name = models.CharField(max_length=128, unique = True) #唯一,不可有相同姓名
    password = models.CharField(max_length=256)
    email = models.EmailField(unique=True)
    sex = models.CharField(max_length=32,choices=gender,default="男")
    ctime = models.DateTimeField(auto_now_add=True)
    clove = ListField(blank=True)

    def __str__(self):
        return self.name

資料庫模型建立完成後,必須將建立資料表的架構和版本記錄下來,以利以後的追蹤。

如果專案伺服器是啟動中的,請按 CTRL+C 關閉伺服器,回到game 目錄,並以「python manage.py  makemigrations」更新migrations目錄。

接著以「 python manage.py migrate」進行migrate,migrate會根據migrations紀錄將模型同步到資料庫。

接著使用Django的後台管理系統,請開啟 gamesapp 目錄底下的 <admin.py> ,在UserAdmin 中添加 'clove')   

2.添加urls.py 路徑

總共新增了兩個路徑:我的最愛頁面、加入、刪除我的最愛 

根據傳入的字串決定執行加入最愛或刪除最愛的程式

from django.contrib import admin
from django.urls import path,re_path

from gameapp import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('crawler/',views.crawler),
    path('index/',views.index),
    path('login/',views.login),
    path('logout/',views.logout),
    path('register/',views.register),
    path('lovepage/',views.lovepage), #我的最愛頁面
    re_path('detail/(\d+)/$',views.detail),
    re_path('adtlovelist/(\w+)/(\d+)/$',views.adtlovelist)  #加入、刪除我的最愛
]

3.我的最愛列表 前台頁面

這邊跟主頁設計得大同小異,表格新增了刪除我的最愛文章的選項欄位

刪除的id是使用forloop計數器的方式傳遞串列的項數

139.JPG

{% extends "base.html" %}
{% load staticfiles %}
{% block title %}<title>我的最愛列表</title>{% endblock %}
{% block content %}

<div class="table-responsive">
    <table border="3" cellpadding="2" cellspacing="2" class="table">
        <th>標題</th>
        <th>作者</th>
        <th>連結網址</th>
        <th>刪除</th>
        <!-- 從我的最愛列表串列迴圈 -->
        {% for un in ut.clove %}
        <tr>
            <td><a href="/detail/{{un.4}}">{{un.1}}</a></td>
            <td>{{un.0}}</td>
            <td><a href="{{un.3}}">巴哈姆特討論串</a></td>
            <!-- 使用forloop計數器 選取要刪除的串列項數 -->
            <td><a href="/adtlovelist/delete/{{forloop.counter0}}" class="btn btn-danger" role="button">刪除</a></td>
        </tr>
        {% endfor %}
    </table>
</div>
{% endblock %}

4.我的最愛列表 頁面(後台)

這邊比較複雜些,詳細的註解都寫在裡面了

#我的最愛列表
def lovepage(request):
    ut = models.User.objects.get(id=request.session['user_id']) #提取使用者資料表
    #將會在前台使用 ut.clove 我的最愛串列

    return render(request,"lovepage.html",locals())

#加入、刪除我的最愛文章
def adtlovelist (request, ctype=None, loveid = None): # ctype決定加入或是刪除,loveid則是傳入的文章id
    userinfo = models.User.objects.get(id=request.session['user_id']) #讀取使用者id
    lovelist = userinfo.clove #將我的最愛列表暫時存入lovelist串列
    redetail = '/detail/' + loveid #取得當前詳細頁網址
    if ctype == 'add': #加入最愛文章
        love = gamee.objects.get(id=loveid) #讀取遊戲資訊列表
        flag = True #設定檢查旗標
        for unit in userinfo.clove: #檢查是否已有相同標題
            if love.cTitle == unit[1]: #如果遊戲資訊標題==使用者我的最愛標題 就不存入
                flag = False
                break
        if flag: #如果文章不存在
            temlist = [] #暫時串列
            temlist.append(love.cAuthor)
            temlist.append(love.cTitle)
            temlist.append(love.cLink)
            temlist.append(love.id)
            lovelist.append(temlist) #將暫時串列加入我的最愛串列

            userinfo.clove = lovelist #將我的最愛串列寫入資料庫
            userinfo.save() #儲存資料庫
            
        return redirect(redetail) #跳轉回詳細頁面網址

    elif ctype == 'delete':  #刪除我的最愛文章
        del lovelist[int(loveid)] #從我的最愛中移除該文章,用計數器的方式取得list的項數
        userinfo.clove = lovelist #將刪除後的我的最愛串列寫入資料庫
        userinfo.save() #儲存資料庫

        return redirect('/lovepage/') #跳轉到我的最愛列表頁面
 
arrow
arrow
    全站熱搜

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