準備階段
首先熟悉專案的根本流程,下面是對專案的每個模塊流程階段具體分析
| 模塊 | 功能 |
|---|---|
| 注冊 | 圖形驗證、短信驗證 |
| 登陸 | 狀態保存、cookie、session |
| 個人中心 | 圖片上傳、更新資料 |
| 發布博客 | 資料入庫 |
| 博客首頁 | 資料分頁 |
| 博客詳情 | 博客詳情資料展示、評論工程 |
總體流程介紹完畢開始做準備作業需要資料庫來存盤說用到的資料,根據資料字典創建物件的表格
檔案所使用的靜態資源和一些配置以提供,方便大家使用
創建py專案
使用pycharm軟體創建一個django專案


專案創建完之后 可以運行 啟動專案
可能會出現的坑

該問題就是把‘/’識別為除號了,兩個str無法進行除號,代碼實際意思是將兩個str進行拼接,進入settings.py進行如下修改:
后面有具體組態檔
- ‘DIRS‘: [BASE_DIR / ‘templates‘] TypeError: unsupported operand type(s) for /: ‘str‘ and ‘str‘
更改地方

2.可能會有此問題query = query.decode(errors=‘replace‘)

專案運行成功顯示界面

準備階段
配置資料庫MySQL
打開本地資料庫–> 用戶自行選擇,可以創建一個新的用戶也可以使用root
新建資料庫
create database blog charset=utf8;
創建新用戶或者使用root用戶
create user diangen identified by '123456';
授權新用戶的權限
grant all on blog.* to 'diangen';
授權結束后重繪特權
flush privileges;
資料庫創建完畢
創建一個django專案找到組態檔里面的settings.py 連接資料庫

DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql', # 資料庫引擎
'HOST': '127.0.0.1', # 資料庫主機
'PORT': 3306, # 資料庫埠
'USER': 'itheima', # 資料庫用戶名
'PASSWORD': '123456', # 資料庫用戶密碼
'NAME': 'blog' # 資料庫名字
},
}
可能出現的錯誤
Error loading MySQLdb module: No module named ‘MySQLdb’.
出現錯誤的原因:
Django中操作MySQL資料庫需要驅動程式MySQLdb
目前專案虛擬環境中沒有驅動程式MySQLdb
配置并使用資料庫

配置Redis資料庫
檢查并安裝django-redis拓展包
pip install django-redis
settings.py檔案夾中添加
CACHES = {
"default": { # 默認
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6379/0",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
}
},
"session": { # session
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6379/1",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
}
},
}
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
SESSION_CACHE_ALIAS = "session"
default:
默認的Redis配置項,采用0號Redis庫,
session:
狀態保持的Redis配置項,采用1號Redis庫,
SESSION_ENGINE
修改session存盤機制使用Redis保存,
SESSION_CACHE_ALIAS:
使用名為"session"的Redis配置項存盤session資料, 配置完成后:運行程式,測驗結果
電腦安裝redis并進入redis檔案目錄
在命令列中運行
redis-server

redis運行成功
如果運行報錯 看看是不是redis的問題
例如
問題原因是Redis 快照關閉了導致不能存盤,可以通過關閉stop-writes-on-bgsave-error配置來解決,
(1)Windows系統中找到了redis.windows.conf檔案,可以看到如下
# By default Redis will stop accepting writes if RDB snapshots are enabled
# (at least one save point) and the latest background save failed.
# This will make the user aware (in a hard way) that data is not persisting
# on disk properly, otherwise chances are that no one will notice and some
# disaster will happen.
#
# If the background saving process will start working again Redis will
# automatically allow writes again.
#
# However if you have setup your proper monitoring of the Redis server
# and persistence, you may want to disable this feature so that Redis will
# continue to work as usual even if there are problems with disk,
# permissions, and so forth.
stop-writes-on-bgsave-error yes
默認該設定是打開的,可以直接在此處修改為no
stop-writes-on-bgsave-error no
配置日志
settings.py檔案夾中添加(后期報錯方便在日志查看)
LOGGING = {
'version': 1,
'disable_existing_loggers': False, # 是否禁用已經存在的日志器
'formatters': { # 日志資訊顯示的格式
'verbose': {
'format': '%(levelname)s %(asctime)s %(module)s %(lineno)d %(message)s'
},
'simple': {
'format': '%(levelname)s %(module)s %(lineno)d %(message)s'
},
},
'filters': { # 對日志進行過濾
'require_debug_true': { # django在debug模式下才輸出日志
'()': 'django.utils.log.RequireDebugTrue',
},
},
'handlers': { # 日志處理方法
'console': { # 向終端中輸出日志
'level': 'INFO',
'filters': ['require_debug_true'],
'class': 'logging.StreamHandler',
'formatter': 'simple'
},
'file': { # 向檔案中輸出日志
'level': 'INFO',
'class': 'logging.handlers.RotatingFileHandler',
'filename': os.path.join(BASE_DIR, 'logs/blog.log'), # 日志檔案的位置
'maxBytes': 300 * 1024 * 1024,
'backupCount': 10,
'formatter': 'verbose'
},
},
'loggers': { # 日志器
'django': { # 定義了一個名為django的日志器
'handlers': ['console', 'file'], # 可以同時向終端與檔案中輸出日志
'propagate': True, # 是否繼續傳遞日志資訊
'level': 'INFO', # 日志器接收的最低日志級別
},
}
}
創建檔案夾

不同的應用程式所定義的日志等級可能會有所差別,分的詳細點的會包含以下幾個等級:
FATAL/CRITICAL = 重大的,危險的
ERROR = 錯誤
WARNING = 警告
INFO = 資訊
DEBUG = 除錯
NOTSET = 沒有設定
urls.py檔案中
創建日志記錄器
import logging
logger = logging.getLogger('django')
輸出日志
logger.debug('測驗logging模塊debug')
logger.info('測驗logging模塊info')
logger.error('測驗logging模塊error')
配置靜態資源
專案檔案夾下創建目錄static檔案
settings.py 檔案中添加
指定靜態檔案加載路徑
STATIC_URL = '/static/'
配置靜態檔案加載路徑
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')]

配置完成后:運行程式,測驗結果,


專案啟動成功
下面開始我們的具體的模塊與應用
創建用戶模塊應用—注冊
創建應用users
python manage.py startapp users

創建完之后目錄結構會出現一個檔案夾

在工程的setting.py中 注冊用戶模塊應用

定義用戶注冊視圖
創建一個子目錄 把注冊的html放進去 并更改配置
將static檔案夾下在register.html拖拽到templates檔案中
設定模板路徑
在users.views.py檔案中定義視圖
from django.views import View
class RegisterView(View):
"""用戶注冊"""
def get(self, request):
"""
提供注冊界面
:param request: 請求物件
:return: 注冊界面
"""
return render(request, 'register.html')
定義用戶注冊路由
在users子應用中創建urls.py檔案,并定義子路由
from django.urls import path
from users.views import RegisterView
urlpatterns = [
# 引數1:路由
# 引數2:視圖函式
# 引數3:路由名,方便通過reverse來獲取路由
path('register/',RegisterView.as_view(),name='register'),
]
在工程的urls.py總路由中添加子應用路由引導 dblog中的urls
from django.contrib import admin
from django.urls import path,include
urlpatterns = [
path('admin/', admin.site.urls),
# include 引數1要設定為元組(urlconf_module, app_name)
# namespace 設定命名空間
path('', include(('users.urls', 'users'), namespace='users')),
]
需要把static中的界面拖拽到templates檔案夾中 因為我們配置的路徑是template存放的界面

運行測驗程式,
以上步驟是為了完成 第一個注冊見面并且配置路由
首先配置user里面的視頻viwes 和路由,然后在設定工程的urls.py總路由中添加子應用路由引導

修改靜態檔案加載方式
是由于靜態資源加載是相對路徑,因此我們需要修改靜態資源的加載方式
使用static標簽來加載靜態檔案,要使用static標簽,首先需要{% load static %}
# 以下代碼是html的header處修改
{% load staticfiles %}
<!-- 引入bootstrap的css檔案 -->
<link rel="stylesheet" href="{% static 'bootstrap/css/bootstrap.min.css' %}">
<!-- 引入vuejs -->
<script type="text/javascript" src="{% static 'js/vue-2.5.16.js' %}"></script>
<script type="text/javascript" src="{% static 'js/axios-0.18.0.min.js' %}"></script>
# 以下代碼是html的footer處修改
<!-- 引入js -->
<script type="text/javascript" src="{% static 'js/host.js' %}"></script>
<script type="text/javascript" src="{% static 'js/common.js' %}"></script>
<script type="text/javascript" src="{% static 'js/register.js' %}"></script>
# 運行測驗程式,沒有問題
定義用戶模型類
自定義用戶模型類
思考:為什么要自定義用戶模型類?
觀察注冊界面會發現,個人博客注冊頁面中必須有手機號,而且在登錄頁面中也使用手機號進行認證,此外個人中心頁面中有個人頭像和個人簡介欄位,
說白了就是用戶需要自己增加欄位to
如何自定義用戶模型類?
繼承自AbstractUser(可通過閱讀Django默認用戶模型類的原始碼得知) ,
新增手機號欄位,頭像欄位和簡介欄位這個基類僅有少部分由于Django機制,而設定的函式和常量,
如果連這個都不想繼承的話,直接用自定義,這將有可能導致其余的Django組件無法正常作業,
當然無論是繼承還是自定義,都必須在settings中設定AUTH_USER_MODEL
用戶資訊
from django.db import models
# Create your models here.
from django.contrib.auth.models import AbstractUser
# 用戶資訊
class User(AbstractUser):
# 電話號碼欄位
# unique 為唯一性欄位
mobile = models.CharField(max_length=20, unique=True,blank=True)
# 頭像
# upload_to為保存到回應的子目錄中
avatar = models.ImageField(upload_to='avatar/%Y%m%d/', blank=True)
# 個人簡介
user_desc = models.TextField(max_length=500, blank=True)
# 修改認證的欄位
USERNAME_FIELD = 'mobile'
#創建超級管理員的需要必須輸入的欄位
REQUIRED_FIELDS = ['username','email']
# 內部類 class Meta 用于給 model 定義元資料
class Meta:
db_table='tb_user' #修改默認的表名
verbose_name='用戶資訊' # Admin后臺顯示
verbose_name_plural=verbose_name # Admin后臺顯示
def __str__(self):
return self.mobile
指定本專案用戶模型類
AUTH_USER_MODEL = 'users.User'

遷移用戶模型類

執行遷移檔案 python manage.py migrate

運行測驗程式
圖形驗證碼介面設計和定義
準備captcha包(該包用于生成圖形驗證碼)
將生成圖片驗證碼的庫復制到新建的libs包中,
安裝Python處理圖片的庫
圖形驗證碼后端介面設計
1.請求方式
| 選項 | 方案 |
|---|---|
| 請求方法 | GET |
| 請求地址 | imagecode?uuid=xxxxx-xxxx-xxxxxx |
2.請求引數:路徑引數
引數名 型別 是否必傳 說明
uuid string 是 唯一編號
3.回應結果:image/jpeg
圖形驗證碼后端實作
1.圖形驗證碼視圖
from django.http import HttpResponseBadRequest,HttpResponse
from libs.captcha.captcha import captcha
from django_redis import get_redis_connection
class ImageCodeView(View):
def get(self,request):
#獲取前端傳遞過來的引數
uuid=request.GET.get('uuid')
#判斷引數是否為None
if uuid is None:
return HttpResponseBadRequest('請求引數錯誤')
# 獲取驗證碼內容和驗證碼圖片二進制資料
text, image = captcha.generate_captcha()
# 將圖片驗內容保存到redis中,并設定過期時間
redis_conn = get_redis_connection('default')
redis_conn.setex('img:%s' % uuid, 300, text)
# 回傳回應,將生成的圖片以content_type為image/jpeg的形式回傳給請求
return HttpResponse(image, content_type='image/jpeg')
2.總路由
from django.contrib import admin
from django.urls import path,include
urlpatterns = [
path('admin/', admin.site.urls),
# include 引數1要設定為元組(urlconf_module, app_name)
# namespace 設定命名空間
path('', include(('users.urls', 'users'), namespace='users')),
]
3.子路由
from django.urls import path
from users.views import ImageCodeView
urlpatterns = [
# 引數1:路由
# 引數2:視圖函式
# 引數3:路由名,方便通過reverse來獲取路由
path('imagecode/', ImageCodeView.as_view(),name='imagecode'),
]
修改模板中圖片驗證碼HTML代碼
1.html中的原代碼如下
<img src="{% static 'img/image_code.png' %}" @click="generate_image_code" alt="" style="width: 110px;height: 40px;">
2.修改如下
<img :src="image_code_url" @click="generate_image_code" alt="" style="width: 110px;height: 40px;">
路由配置成功在注冊界面可以正常顯示驗證碼并切換
短信驗證碼
短信驗證碼 使用的是容聯云官網 注冊 獲取


1.集成短信SDK到庫中
CCPRestSDK.py:由容聯云通訊開發者撰寫的官方SDK檔案,包括發送模板短信的方法
ccp_sms.py:呼叫發送模板短信的方法

短信驗證碼后端邏輯實作
from django.http import JsonResponse
from utils.response_code import RETCODE
from random import randint
from libs.yuntongxun.sms import CCP
import logging
logger=logging.getLogger('django')
class SmsCodeView(View):
def get(self,request):
# 接收引數
image_code_client = request.GET.get('image_code')
uuid = request.GET.get('uuid')
mobile=request.GET.get('mobile')
# 校驗引數
if not all([image_code_client, uuid,mobile]):
return JsonResponse({'code': RETCODE.NECESSARYPARAMERR, 'errmsg': '缺少必傳引數'})
# 創建連接到redis的物件
redis_conn = get_redis_connection('default')
# 提取圖形驗證碼
image_code_server = redis_conn.get('img:%s' % uuid)
if image_code_server is None:
# 圖形驗證碼過期或者不存在
return JsonResponse({'code': RETCODE.IMAGECODEERR, 'errmsg': '圖形驗證碼失效'})
# 洗掉圖形驗證碼,避免惡意測驗圖形驗證碼
try:
redis_conn.delete('img:%s' % uuid)
except Exception as e:
logger.error(e)
# 對比圖形驗證碼
image_code_server = image_code_server.decode() # bytes轉字串
if image_code_client.lower() != image_code_server.lower(): # 轉小寫后比較
return JsonResponse({'code': RETCODE.IMAGECODEERR, 'errmsg': '輸入圖形驗證碼有誤'})
# 生成短信驗證碼:生成6位數驗證碼
sms_code = '%06d' % randint(0, 999999)
#將驗證碼輸出在控制臺,以方便除錯
logger.info(sms_code)
# 保存短信驗證碼到redis中,并設定有效期
redis_conn.setex('sms:%s' % mobile, 300, sms_code)
# 發送短信驗證碼
CCP().send_template_sms(mobile, [sms_code, 5],1)
# 回應結果
return JsonResponse({'code': RETCODE.OK, 'errmsg': '發送短信成功'})
當點擊驗證碼的時候 pycharm控制臺中也會發送一個

添加response_code檔案
在工程中新建utils包,將response_code檔案復制到utils中

用戶注冊實作
from users.models import User
from django.db import DatabaseError
#注冊視圖
class RegisterView(View):
def get(self,request):
return render(request,'register.html')
def post(self,request):
"""
1.接收資料
2.驗證資料
2.1 引數是否齊全
2.2 手機號的格式是否正確
2.3 密碼是否符合格式
2.4 密碼和確認密碼要一致
2.5 短信驗證碼是否和redis中的一致
3.保存注冊資訊
4.回傳回應跳轉到指定頁面
:param request:
:return:
"""
# 1.接收資料
mobile=request.POST.get('mobile')
password=request.POST.get('password')
password2=request.POST.get('password2')
smscode=request.POST.get('sms_code')
# 2.驗證資料
# 2.1 引數是否齊全
if not all([mobile,password,password2,smscode]):
return HttpResponseBadRequest('缺少必要的引數')
# 2.2 手機號的格式是否正確
if not re.match(r'^1[3-9]\d{9}$',mobile):
return HttpResponseBadRequest('手機號不符合規則')
# 2.3 密碼是否符合格式
if not re.match(r'^[0-9A-Za-z]{8,20}$',password):
return HttpResponseBadRequest('請輸入8-20位密碼,密碼是數字,字母')
# 2.4 密碼和確認密碼要一致
if password != password2:
return HttpResponseBadRequest('兩次密碼不一致')
# 2.5 短信驗證碼是否和redis中的一致
redis_conn = get_redis_connection('default')
redis_sms_code=redis_conn.get('sms:%s'%mobile)
if redis_sms_code is None:
return HttpResponseBadRequest('短信驗證碼已過期')
if smscode != redis_sms_code.decode():
return HttpResponseBadRequest('短信驗證碼不一致')
# 3.保存注冊資訊
# create_user 可以使用系統的方法來對密碼進行加密
try:
user=User.objects.create_user(username=mobile,
mobile=mobile,
password=password)
except DatabaseError as e:
logger.error(e)
return HttpResponseBadRequest('注冊失敗')
from django.contrib.auth import login
login(request,user)
# 4.回傳回應跳轉到指定頁面
# 暫時回傳一個注冊成功的資訊,后期再實作跳轉到指定頁面
# redirect 是進行重定向
# reverse 是可以通過 namespace:name 來獲取到視圖所對應的路由
response = redirect(reverse('home:index'))
# return HttpResponse('注冊成功,重定向到首頁')
#設定cookie資訊,以方便首頁中 用戶資訊展示的判斷和用戶資訊的展示
response.set_cookie('is_login',True)
response.set_cookie('username',user.username,max_age=7*24*3600)
return response
在HTML表單中添加csrf_token


首頁展示
1.創建首頁應用:home
python manage.py startapp home
2.定義首頁視圖:IndexView—查詢分類資料并展示
2.1.請求方式
| 選項 | 方案 |
|---|---|
| 請求方法 | GET |
| 請求地址 | /?cat_id=xxx&page_num=xxx&page_size=xxx |
2.2.請求引數
| 引數名 | 型別 | 是否必傳 | 說明 |
|---|---|---|---|
| cat_id | string | 否 | 分類id |
| page_num | string | 否 | 文章分頁頁碼 |
| page_size | string | 否 | 文章每頁條目數 |
2.3.回應結果:HTML
| 欄位 | 說明 |
|---|---|
| 失敗 | 回應錯誤提示 |
| 成功 | 展示資料 |
3.查詢分類文章資料并通過context傳遞給HTML
from django.urls import reverse
from django.views import View
# Create your views here.
class IndexView(View):
"""首頁廣告"""
def get(self, request):
"""提供首頁廣告界面"""
return render(request, 'index.html')
4.配置首頁路由
在home子應用中創建urls.py檔案,并定義子路由
from django.urls import path
from home.views import IndexView
urlpatterns = [
path('', IndexView.as_view(),name='index'),
]
在工程的urls.py總路由中添加子應用路由引導
from django.urls import path, include
urlpatterns = [
path('', include(('home.urls','home'),namespace='home')),
]
5.重定注冊界面的跳轉到首頁
# 回應注冊結果
return redirect(reverse('home:index'))
用戶登陸
- 登錄頁面展示
1.在users.views.py檔案中定義視圖
from django.views import View
class LoginView(View):
def get(self,request):
return render(request,'login.html')
2.在users.urls.py檔案中定義路由
from users.views import LoginView
urlpatterns = [
# 引數1:路由
# 引數2:視圖函式
# 引數3:路由名,方便通過reverse來獲取路由
path('login/', LoginView.as_view(),name='login'),
]
3.修改login.html中的資源加載方式
<!-- Header部分 -->
{% load staticfiles %}
<!-- 引入bootstrap的css檔案 -->
<link rel="stylesheet" href="{% static 'bootstrap/css/bootstrap.min.css' %}">
<!-- 引入vuejs -->
<script type="text/javascript" src="{% static 'js/vue-2.5.16.js' %}"></script>
<script type="text/javascript" src="{% static 'js/axios-0.18.0.min.js' %}"></script>
<!-- Footer部分 -->
<script type="text/javascript" src="{% static 'js/host.js' %}"></script>
<script type="text/javascript" src="{% static 'js/common.js' %}"></script>
<script type="text/javascript" src="{% static 'js/login.js' %}"></script>
<!-- 點擊注冊部分 -->
<small class="form-text text-muted ml-1">還沒有賬號?<a href="{% url 'users:register' %}" style="color: cornflowerblue; ">注冊新賬號</a>
登錄介面實作
from django.contrib.auth import login
from django.contrib.auth import authenticate
class LoginView(View):
def post(self,request):
# 接受引數
mobile = request.POST.get('mobile')
password = request.POST.get('password')
remember = request.POST.get('remember')
# 校驗引數
# 判斷引數是否齊全
if not all([mobile, password]):
return HttpResponseBadRequest('缺少必傳引數')
# 判斷手機號是否正確
if not re.match(r'^1[3-9]\d{9}$', mobile):
return HttpResponseBadRequest('請輸入正確的手機號')
# 判斷密碼是否是8-20個數字
if not re.match(r'^[0-9A-Za-z]{8,20}$', password):
return HttpResponseBadRequest('密碼最少8位,最長20位')
# 認證登錄用戶
# 認證欄位已經在User模型中的USERNAME_FIELD = 'mobile'修改
user=authenticate(mobile=mobile, password=password)
if user is None:
return HttpResponseBadRequest('用戶名或密碼錯誤')
# 實作狀態保持
login(request, user)
# 回應登錄結果
response = redirect(reverse('home:index'))
# 設定狀態保持的周期
if remember != 'on':
# 沒有記住用戶:瀏覽器會話結束就過期
request.session.set_expiry(0)
# 設定cookie
response.set_cookie('is_login', True)
response.set_cookie('username', user.username, max_age=30 * 24 * 3600)
else:
# 記住用戶:None表示兩周后過期
request.session.set_expiry(None)
# 設定cookie
response.set_cookie('is_login', True, max_age=14*24 * 3600)
response.set_cookie('username', user.username, max_age=30 * 24 * 3600)
#回傳回應
return response
注冊登陸設定成功之后現在開始對首頁進行設定用戶名顯示到首頁

首頁用戶名展示
用戶名寫入到cookie
Vue渲染首頁用戶名
1.index.html
<!-- 如果用戶已經登錄,則顯示用戶名下拉框 -->
<li class="nav-item dropdown" v-if="is_login">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" @click="show_menu_click">[[username]]</a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown" style="display: block" v-show="show_menu">
<a class="dropdown-item" href="../static/write_blog.html">寫文章</a>
<a class="dropdown-item" href='../static/center.html'>個人資訊</a>
<a class="dropdown-item" href='#'>退出登錄</a>
</div>
</li>
<!-- 如果用戶未登錄,則顯示登錄按鈕 -->
<li class="nav-item" v-else>
<a class="nav-link" href="login.html">登錄</a>
</li>
2.index.js
mounted(){
//獲取用戶名資訊
this.username=getCookie('username');
//獲取是否登錄資訊
this.is_login=getCookie('is_login');
},

退出登錄
logout()方法介紹
退出登錄:
對session操作的,也就是操作redis,所以回傳的要么是空,要么成功,不會出現例外
logout()方法:
只需要傳入一個request物件就行,就會把當前用戶的session清除
logout()位置:
django.contrib.auth.init.py檔案中
from django.contrib.auth import logout
class LogoutView(View):
def get(self,request):
# 1.session資料清除
logout(request)
# 2.洗掉部分cookie資料
response=redirect(reverse('home:index'))
response.delete_cookie('is_login')
#3.跳轉到首頁
return response
提示:
由于首頁中登錄狀態是從cookie中讀取的,
所以退出登錄時,需要將cookie中登錄狀態清除,
實作退出登錄
<div class="dropdown-menu" aria-labelledby="navbarDropdown" style="display: block" v-show="show_menu">
<a class="dropdown-item" href="../static/write_blog.html">寫文章</a>
<a class="dropdown-item" href='../static/center.html'>個人資訊</a>
<a class="dropdown-item" href='{% url 'users:logout' %}'>退出登錄</a>
</div>
路由配置 users.urls.py
urlpatterns = [
# 引數1:路由
# 引數2:視圖函式
# 引數3:路由名,方便通過reverse來獲取路由
path('register/',RegisterView.as_view(),name='register'),
path('imagecode/', ImageCodeView.as_view(),name='imagecode'),
# 短信發送
path('smscode/', SmsCodeView.as_view(), name='smscode'),
# 登錄路由
path('login/', LoginView.as_view(), name='login'),
# 退出登錄
path('logout/', LogoutView.as_view(), name='logout'),
]
忘記密碼
1.在users.views.py檔案中定義視圖
from django.views import View
class ForgetPasswordView(View):
def get(self, request):
return render(request, 'forget_password.html')
2.在users.urls.py檔案中定義路由
from users.views import ForgetPasswordView
path('forgetpassword/', ForgetPasswordView.as_view(),name='forgetpassword'),
3.修改forget_password.html中的資源加載方式
<!-- Header部分 -->
{% load staticfiles %}
<!-- 引入bootstrap的css檔案 -->
<link rel="stylesheet" href="{% static 'bootstrap/css/bootstrap.min.css' %}">
<!-- 引入vuejs -->
<script type="text/javascript" src="{% static 'js/vue-2.5.16.js' %}"></script>
<script type="text/javascript" src="{% static 'js/axios-0.18.0.min.js' %}"></script>
...
<!-- Footer部分 -->
<script type="text/javascript" src="{% static 'js/host.js' %}"></script>
<script type="text/javascript" src="{% static 'js/common.js' %}"></script>
<script type="text/javascript" src="{% static 'js/forget_password.js' %}"></script>
...
<!-- 圖片驗證碼部分 -->
<img :src="image_code_url" @click="generate_image_code" alt="" style="width: 110px;height: 40px;">
4.修改login.html中的忘記密碼的跳轉連接
<small class="form-text text-muted ml-1"><a class="secondaryAction layui-text" href="{% url 'users:forgetpassword' %}">忘記密碼?</a>
忘記密碼介面實作
| 引數名 | 型別 | 是否必傳 | 說明 |
|---|---|---|---|
| mobile | string | 是 | 用戶名 |
| password | string | 是 | 密碼 |
| password2 | string | 是 | 確認密碼 |
| sms_code | string | 是 | 短信驗證碼 |
代碼
class ForgetPasswordView(View):
def post(self, request):
# 接收引數
mobile = request.POST.get('mobile')
password = request.POST.get('password')
password2 = request.POST.get('password2')
smscode = request.POST.get('sms_code')
# 判斷引數是否齊全
if not all([mobile, password, password2, smscode]):
return HttpResponseBadRequest('缺少必傳引數')
# 判斷手機號是否合法
if not re.match(r'^1[3-9]\d{9}$', mobile):
return HttpResponseBadRequest('請輸入正確的手機號碼')
# 判斷密碼是否是8-20個數字
if not re.match(r'^[0-9A-Za-z]{8,20}$', password):
return HttpResponseBadRequest('請輸入8-20位的密碼')
# 判斷兩次密碼是否一致
if password != password2:
return HttpResponseBadRequest('兩次輸入的密碼不一致')
# 驗證短信驗證碼
redis_conn = get_redis_connection('default')
sms_code_server = redis_conn.get('sms:%s' % mobile)
if sms_code_server is None:
return HttpResponseBadRequest('短信驗證碼已過期')
if smscode != sms_code_server.decode():
return HttpResponseBadRequest('短信驗證碼錯誤')
# 根據手機號查詢資料
try:
user = User.objects.get(mobile=mobile)
except User.DoesNotExist:
# 如果該手機號不存在,則注冊個新用戶
try:
User.objects.create_user(username=mobile, mobile=mobile, password=password)
except Exception:
return HttpResponseBadRequest('修改失敗,請稍后再試')
else:
# 修改用戶密碼
user.set_password(password)
user.save()
# 跳轉到登錄頁面
response = redirect(reverse('users:login'))
return response
路由配置 urts.py
# 忘記密碼
path('forgetpassword/', ForgetPasswordView.as_view(),name='forgetpassword'),

用戶中心展示
- 頁面展示
1.在users.views.py檔案中定義視圖
from django.views import View
class UserCenterView(View):
def get(self,request):
return render(request,'center.html')
2.在users.urls.py檔案中定義路由
from users.views import UserCenterView
urlpatterns = [
# 引數1:路由
# 引數2:視圖函式
# 引數3:路由名,方便通過reverse來獲取路由
path('center/', UserCenterView.as_view(),name='center'),
]
3.修改center.html中的資源加載方式
<!-- Header部分 -->
{% load staticfiles %}
<!-- 引入bootstrap的css檔案 -->
<link rel="stylesheet" href="{% static 'bootstrap/css/bootstrap.min.css' %}">
<!-- 引入vuejs -->
<script type="text/javascript" src="{% static 'js/vue-2.5.16.js' %}"></script>
<script type="text/javascript" src="{% static 'js/axios-0.18.0.min.js' %}"></script>
...
<!-- Footer部分 -->
<script type="text/javascript" src="{% static 'js/host.js' %}"></script>
<script type="text/javascript" src="{% static 'js/common.js' %}"></script>
<script type="text/javascript" src="{% static 'js/center.js' %}"></script>
...
<!-- 頁面跳轉部分 -->
<a class="dropdown-item" href='{% url 'users:center' %}'>個人資訊</a>
<a class="dropdown-item" href='{% url 'users:logout' %}'>退出登錄</a>
4.修改index.html中的的跳轉連接
<a class="dropdown-item" href='{% url 'users:center' %}'>個人資訊</a>

判斷用戶是否登錄
根據是否登錄的結果,決定用戶是否可以訪問用戶中心,
Django用戶認證系統提供了方法
request.user.is_authenticated()來判斷用戶是否登錄,如果通過登錄驗證則回傳True,反之,回傳False,
LoginRequiredMixin封裝了判斷用戶是否登錄的操作,
1.用戶中心使用LoginRequiredMixin
from django.views import View
from django.contrib.auth.mixins import LoginRequiredMixin
class UserCenterView(LoginRequiredMixin,View):
def get(self,request):
return render(request,'center.html')
2.設定未登錄用戶跳轉的路由
#在工程的settings.py檔案中,添加以下配置,
LOGIN_URL = '/login/'
3.根據登錄的next引數設定登錄跳轉路由
實作狀態保持
login(request, user)
回應登錄結果
next = request.GET.get('next')
if next:
response= redirect(next)
else:
response = redirect(reverse('home:index'))
1.獲取用戶資訊,模板渲染資料users.views.py 中添加功能
from django.contrib.auth.mixins import LoginRequiredMixin
class UserCenterView(LoginRequiredMixin,View):
def get(self,request):
# 獲取用戶資訊
user = request.user
#組織模板渲染資料
context = {
'username': user.username,
'mobile': user.mobile,
'avatar': user.avatar.url if user.avatar else None,
'user_desc': user.user_desc
}
return render(request,'center.html',context=context)
2.修改center.html中的資料顯示
<form method="post" enctype="multipart/form-data">
<!-- username -->
<div class="form-group col-md-4">
<label for="username">用戶名</label>
<input type="text" class="form-control" id="username" name="username" value="{{ username }}" >
</div>
{% if avatar %}
<br> <div class="col-md-4">頭像</div>
<img src="{{ avatar }}" style="max-width: 20%; border-radius: 15%;" class="col-md-4"><br>
{% else %}
<br><h5 class="col-md-4">暫無頭像</h5><br>
{% endif %}
<!-- avatar -->
<div class="form-group col-md-4">
<label for="avatar">上傳頭像</label>
<input type="file" class="form-control-file" name="avatar" id="avatar">
</div>
<!-- phone -->
<div class="form-group col-md-4">
<label for="phone">電話</label>
<input type="text" class="form-control" disabled="disabled" id="phone" name="phone" value="{{ mobile }}">
</div>
<!-- desc -->
<div class="form-group col-md-4">
<label for="desc">簡介</label>
<!-- 文本區域 -->
<textarea type="text" class="form-control" id="desc" name="desc" rows="12" >{{ user_desc }}</textarea>
</div>
<!-- 提交按鈕 -->
<button type="submit" class="btn btn-primary" style="margin-left: 12px" >修改</button>
</form>
用戶中心修改
- 用戶中心介面設計
1.請求方式
| 選項 | 方案 |
|---|---|
| 請求方法 | POST |
| 請求地址 | /center/ |
2.請求引數:表單
| 引數名 | 型別 | 是否必傳 | 說明 |
|---|---|---|---|
| username string | 否 | 用戶名 | |
| avatar | file | 否 | 頭像 |
| desc | string | 否 | 個人簡介 |
3.回應結果:HTML
| 欄位 | 說明 |
|---|---|
| 修改失敗 | 回應錯誤提示 |
| 修改成功 | 重繪展示 |
- 用戶中心修改介面實作
from django.contrib.auth.mixins import LoginRequiredMixin
class UserCenterView(LoginRequiredMixin,View):
def post(self,request):
# 接收資料
user = request.user
avatar = request.FILES.get('avatar')
username = request.POST.get('username',user.username)
user_desc = request.POST.get('desc',user.user_desc)
# 修改資料庫資料
try:
user.username=username
user.user_desc=user_desc
if avatar:
user.avatar=avatar
user.save()
except Exception as e:
logger.error(e)
return HttpResponseBadRequest('更新失敗,請稍后再試')
# 回傳回應,重繪頁面
response = redirect(reverse('users:center'))
#更新cookie資訊
response.set_cookie('username',user.username,max_age=30*24*3600)
return response
- 用戶中心頭像的上傳和展示
1.在settings.py檔案中設定圖片上傳的路徑并新建檔案夾media
MEDIA_ROOT = os.path.join(BASE_DIR, 'media/')
在settings.py檔案中設定
圖片的統一路由
MEDIA_URL = '/media/'
設定路由匹配規則,在工程的urls.py檔案中設定
from django.contrib import admin
from django.urls import path,include
urlpatterns = [
path('admin/', admin.site.urls),
# include 引數1要設定為元組(urlconf_module, app_name)
# namespace 設定命名空間
path('', include(('users.urls', 'users'), namespace='users')),
path('', include(('home.urls','home'),namespace='home')),
]
#以下代碼為設定圖片訪問路由規則
from django.conf import settings
from django.conf.urls.static import static
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

寫博客頁面展示
- 頁面展示
1.在users.views.py檔案中定義視圖
from django.views import View
class WriteBlogView(LoginRequiredMixin,View):
def get(self,request):
return render(request,'write_blog.html')
2.在users.urls.py檔案中定義路由
from users.views import WriteBlogView
urlpatterns = [
# 引數1:路由
# 引數2:視圖函式
# 引數3:路由名,方便通過reverse來獲取路由
path('writeblog/', WriteBlogView.as_view(),name='writeblog'),
]
3.修改center.html中的資源加載方式
<!-- Header部分 -->
{% load staticfiles %}
<!-- 引入bootstrap的css檔案 -->
<link rel="stylesheet" href="{% static 'bootstrap/css/bootstrap.min.css' %}">
<!-- 引入vuejs -->
<script type="text/javascript" src="{% static 'js/vue-2.5.16.js' %}"></script>
<script type="text/javascript" src="{% static 'js/axios-0.18.0.min.js' %}"></script>
<!-- Footer部分 -->
<!--ckeditor-->
<script type="text/javascript" src="{% static 'ckeditor/ckeditor-init.js' %}" data-ckeditor-basepath="{% static 'ckeditor/ckeditor/' %}" id="ckeditor-init-script"></script>
<script type="text/javascript" src="{% static 'ckeditor/ckeditor/ckeditor.js' %}"></script>
<!-- 引入js -->
<script type="text/javascript" src="{% static 'js/host.js' %}"></script>
<script type="text/javascript" src="{% static 'js/common.js' %}"></script>
<script type="text/javascript" src="{% static 'js/write_blog.js' %}"></script>
<!-- 頁面跳轉部分 -->
<a class="dropdown-item" href="{% url 'users:writeblog' %}">寫文章</a>
<a class="dropdown-item" href='{% url 'users:center'%}'>個人資訊</a>
<a class="dropdown-item" href='{% url 'users:center' %}'>退出登錄</a>
4.修改index.html中的的跳轉連接
<a class="dropdown-item" href="{% url 'users:writeblog' %}">寫文章</a>

文章分類模型
- 定義模型類
在home子應用的models.py模型中定義文章分類模型
from django.db import models
from django.utils import timezone
class ArticleCategory(models.Model):
"""
文章分類
"""
# 欄目標題
title = models.CharField(max_length=100, blank=True)
# 創建時間
created = models.DateTimeField(default=timezone.now)
def __str__(self):
return self.title
class Meta:
db_table='tb_category'
verbose_name = '類別管理'
verbose_name_plural = verbose_name
- 遷移模型類
1.創建遷移檔案
python manage.py makemigrations
2.執行遷移檔案
python manage.py migrate
文章分類后臺管理
當剛創建的時候,首頁是沒有資料,我們需要從后臺管理界面進行文章分類,從而展示到主界面
網站的管理員負責查看、添加、修改、洗掉資料
Django能夠根據定義的模型類自動地生成管理模塊
登陸站點:http://127.0.0.1:8000/admin
在setting中設定中文資訊
LANGUAGE_CODE = 'zh-Hans' #原配置資訊為'en-us'
TIME_ZONE = 'Asia/Shanghai'#原配置資訊為'UTC'
創建管理員
1.我們需要在User模型中設定 REQUIRED_FIELDS
在 users.modle.py中設定配置資訊
#創建超級管理員的需要必須輸入的欄位
REQUIRED_FIELDS = ['username','email']

2.在終端創建超級管理員
創建管理員的命令 :
python manage.py createsuperuser

然后重新登陸進入 站點服務
注冊模型類
在應用的admin.py檔案中注冊模型類
需要匯入模型模塊 :from home.models import ArticleCategory

模型注冊完之后我們即可在站點管理進行對分類操作

模型類展示我們輸入的內容是因為我們在模型中實作了__str_方法_
home.model.py
class ArticleCategory(models.Model):
"""
文章分類
"""
# 欄目標題
title = models.CharField(max_length=100, blank=True)
# 創建時間
created = models.DateTimeField(default=timezone.now)
def __str__(self):
return self.title
寫博客頁面展示分類
- 查詢資料并展示
1.查詢分類文章資料并通過context傳遞給HTML
from home.models import ArticleCategory
class WriteBlogView(LoginRequiredMixin,View):
def get(self,request):
# 獲取博客分類資訊
categories = ArticleCategory.objects.all()
context = {
'categories': categories
}
return render(request,'write_blog.html',context=context)
2.在write_blog.html檔案中使用模板語言展示資料
<!-- 文章欄目 -->
<div class="form-group">
<label for="column">欄目</label>
<select class="form-control col-3" id="category" name="category">
{% for category in categories %}
<option value="{{ category.id }}">{{ category.title }}</option>
{% endfor %}
</select>
</div>

文章模型
在home子應用的models.py模型中定義文章模型
from users.models import User
class Article(models.Model):
"""
文章
"""
# 定義文章作者, author 通過 models.ForeignKey 外鍵與內建的 User 模型關聯在一起
# 引數 on_delete 用于指定資料洗掉的方式,避免兩個關聯表的資料不一致,
author = models.ForeignKey(User, on_delete=models.CASCADE)
# 文章標題圖
avatar = models.ImageField(upload_to='article/%Y%m%d/', blank=True)
# 文章欄目的 “一對多” 外鍵
category = models.ForeignKey(
ArticleCategory,
null=True,
blank=True,
on_delete=models.CASCADE,
related_name='article'
)
# 文章標簽
tags = models.CharField(max_length=20,blank=True)
# 文章標題,
title = models.CharField(max_length=100,null=False,blank=False)
# 概要
sumary = models.CharField(max_length=200,null=False,blank=False)
# 文章正文,
content = models.TextField()
# 瀏覽量
total_views = models.PositiveIntegerField(default=0)
# 文章評論數
comments_count = models.PositiveIntegerField(default=0)
# 文章創建時間,
# 引數 default=timezone.now 指定其在創建資料時將默認寫入當前的時間
created = models.DateTimeField(default=timezone.now)
# 文章更新時間,
# 引數 auto_now=True 指定每次資料更新時自動寫入當前時間
updated = models.DateTimeField(auto_now=True)
# 內部類 class Meta 用于給 model 定義元資料
class Meta:
# ordering 指定模型回傳的資料的排列順序
# '-created' 表明資料應該以倒序排列
ordering = ('-created',)
db_table='tb_article'
verbose_name='文章管理'
verbose_name_plural=verbose_name
# 函式 __str__ 定義當呼叫物件的 str() 方法時的回傳值內容
# 它最常見的就是在Django管理后臺中做為物件的顯示值,因此應該總是為 __str__ 回傳一個友好易讀的字串
def __str__(self):
# 將文章標題回傳
return self.title
遷移模型類
1.創建遷移檔案
python manage.py makemigrations
2.執行遷移檔案
python manage.py migrate
博客保存
博客保存介面設計
1.請求方式
| 選項 | 方案 |
|---|---|
| 請求方法 | POST |
| 請求地址 | /writeblog/ |
2.請求引數:表單
| 引數名 | 型別 | 是否必傳 | 說明 |
|---|---|---|---|
| title string | 是 | 標題 | |
| avatar file | 是 | 標題圖 | |
| category | string | 是 | 欄目分類 |
| tags | string | 是 | 標簽 |
| sumary | string | 是 | 文章摘要 |
| content | string | 是 | 文章內容 |
3.回應結果:HTML
| 欄位 | 說明 |
|---|---|
| 提交失敗 | 回應錯誤提示 |
| 提交成功 | 跳轉到詳情頁面 |
- 用戶中心修改介面實作
from home.models import ArticleCategory,Article
class WriteBlogView(LoginRequiredMixin,View):
def post(self,request):
#接收資料
avatar=request.FILES.get('avatar')
title=request.POST.get('title')
category_id=request.POST.get('category')
tags=request.POST.get('tags')
sumary=request.POST.get('sumary')
content=request.POST.get('content')
user=request.user
#驗證資料是否齊全
if not all([avatar,title,category_id,sumary,content]):
return HttpResponseBadRequest('引數不全')
#判斷文章分類id資料是否正確
try:
article_category=ArticleCategory.objects.get(id=category_id)
except ArticleCategory.DoesNotExist:
return HttpResponseBadRequest('沒有此分類資訊')
#保存到資料庫
try:
article=Article.objects.create(
author=user,
avatar=avatar,
category=article_category,
tags=tags,
title=title,
sumary=sumary,
content=content
)
except Exception as e:
logger.error(e)
return HttpResponseBadRequest('發布失敗,請稍后再試')
#回傳回應,跳轉到文章詳情頁面
#暫時先跳轉到首頁
return redirect(reverse('home:index'))

首頁分類資料展示
2.定義首頁視圖:IndexView—查詢分類資料并展示
2.1.請求方式
| 選項 | 方案 |
|---|---|
| 請求方法 | GET |
| 請求地址 | /?cat_id=xxx&page_num=xxx&page_size=xxx |
2.2.請求引數
| 引數名 | 型別 | 是否必傳 | 說明 |
|---|---|---|---|
| cat_id | string | 否 | 分類id |
| page_num | string | 否 | 文章分頁頁碼 |
| page_size | string | 否 | 文章每頁條目數 |
2.3.回應結果:HTML
| 欄位 | 說明 |
|---|---|
| 失敗 | 回應錯誤提示 |
| 成功 | 展示資料 |
1.查詢分類文章資料并通過context傳遞給HTML
home.views.py
from home.models import ArticleCategory
from django.http import HttpResponseNotFound
class IndexView(View):
"""首頁廣告"""
def get(self, request):
"""提供首頁廣告界面"""
#?cat_id=xxx&page_num=xxx&page_size=xxx
cat_id=request.GET.get('cat_id',1)
#判斷分類id
try:
category = ArticleCategory.objects.get(id=cat_id)
except ArticleCategory.DoesNotExist:
return HttpResponseNotFound('沒有此分類')
# 獲取博客分類資訊
categories = ArticleCategory.objects.all()
context = {
'categories':categories,
'category':category
}
return render(request, 'index.html',context=context)
2.在index.html檔案中使用模板語言展示分類資料
<ul class="nav navbar-nav">
{% for cat in categories %}
{% if cat.id == category.id %}
<li class="nav-item active">
<a class="nav-link mr-2" href="/?cat_id={{ cat.id }}">{{ cat.title }}</a>
</li>
{% else %}
<li class="nav-item">
<a class="nav-link mr-2" href="/?cat_id={{ cat.id }}">{{ cat.title }}</a>
</li>
{% endif %}
{% endfor %}
</ul>
查詢分頁資料并展示
from home.models import ArticleCategory,Article
from django.http import HttpResponseNotFound
from django.core.paginator import Paginator,EmptyPage
class IndexView(View):
"""首頁廣告"""
def get(self, request):
"""提供首頁廣告界面"""
#?cat_id=xxx&page_num=xxx&page_size=xxx
cat_id=request.GET.get('cat_id',1)
page_num = request.GET.get('page_num', 1)
page_size = request.GET.get('page_size', 10)
#判斷分類id
try:
category = ArticleCategory.objects.get(id=cat_id)
except ArticleCategory.DoesNotExist:
return HttpResponseNotFound('沒有此分類')
# 獲取博客分類資訊
categories = ArticleCategory.objects.all()
#分頁資料
articles = Article.objects.filter(
category=category
)
# 創建分頁器:每頁N條記錄
paginator = Paginator(articles, page_size)
# 獲取每頁商品資料
try:
page_articles = paginator.page(page_num)
except EmptyPage:
# 如果沒有分頁資料,默認給用戶404
return HttpResponseNotFound('empty page')
# 獲取串列頁總頁數
total_page = paginator.num_pages
context = {
'categories':categories,
'category':category,
'articles': page_articles,
'page_size': page_size,
'total_page': total_page,
'page_num': page_num,
}
return render(request, 'index.html',context=context)
2.在index.html檔案中使用模板語言展示分類資料
<div class="container">
<!-- 串列回圈 -->
{% for article in articles %}
<div class="row mt-2">
<!-- 文章內容 -->
<!-- 標題圖 -->
<div class="col-3">
<img src="{{ article.avatar.url }}" alt="avatar" style="max-width:100%; border-radius: 20px">
</div>
<div class="col">
<!-- 欄目 -->
<a role="button" class="btn btn-sm mb-2 btn-warning">{{ article.category.title }}</a>
<!-- 標簽 -->
<span>
<a class="badge badge-secondary">{{ article.tags }}</a>
</span>
<!-- 標題 -->
<h4>
<b><a href="./detail.html" style="color: black;">{{ article.title }}</a></b>
</h4>
<!-- 摘要 -->
<div>
<p style="color: gray;">
{{ article.sumary }}
</p>
</div>
<!-- 注腳 -->
<p>
<!-- 查看、評論、時間 -->
<span><i class="fas fa-eye" style="color: lightskyblue;"></i>{{ article.total_views }} </span>
<span><i class="fas fa-comments" style="color: yellowgreen;"></i>{{ article.comments_count }} </span>
<span><i class="fas fa-clock" style="color: pink;"></i>{{ article.created | date }}</span>
</p>
</div>
<hr style="width: 100%;"/>
</div>
{% endfor %}
<!-- 頁碼導航 -->
<div class="pagenation" style="text-align: center">
<div id="pagination" class="page"></div>
</div>
</div>
3.修改底部js分頁代碼
<script type="text/javascript">
$(function () {
$('#pagination').pagination({
currentPage: {{ page_num }},
totalPage: {{ total_page }},
callback:function (current) {
location.href = '/?cat_id={{ category.id }}&page_size={{ page_size }}&page_num='+current;
}
})
});
</script>
博客詳情
詳情頁面展示
- 頁面展示
1.在home.views.py檔案中定義視圖
from django.views import View
class DetailView(View):
def get(self,request):
return render(request,'detail.html')
2.在home.urls.py檔案中定義路由
from users.views import DetailView
urlpatterns = [
# 引數1:路由
# 引數2:視圖函式
# 引數3:路由名,方便通過reverse來獲取路由
path('detail/', DetailView.as_view(),name='detail'),
]
3.修改detail.html中的資源加載方式
<!-- Header部分 -->
{% load staticfiles %}
<!-- 引入bootstrap的css檔案 -->
<link rel="stylesheet" href="{% static 'bootstrap/css/bootstrap.min.css' %}">
<!--詳情頁面匯入-->
<script src="{% static 'ckeditor/ckeditor/plugins/prism/lib/prism/prism_patched.min.js' %}"></script>
<link rel="stylesheet" href="{% static 'prism/prism.css' %}">
<!--匯入css-->
<link rel="stylesheet" href="{% static 'common/common.css' %}">
<link rel="stylesheet" href="{% static 'common/jquery.pagination.css' %}">
<!-- 引入vuejs -->
<script type="text/javascript" src="{% static 'js/vue-2.5.16.js' %}"></script>
<script type="text/javascript" src="{% static 'js/axios-0.18.0.min.js' %}"></script>
<script type="text/javascript" src="{% static 'js/jquery-1.12.4.min.js' %}"></script>
...
<!-- Footer部分 -->
<!--ckeditor-->
<script type="text/javascript" src="{% static 'ckeditor/ckeditor-init.js' %}" data-ckeditor-basepath="{% static 'ckeditor/ckeditor/' %}" id="ckeditor-init-script"></script>
<script type="text/javascript" src="{% static 'ckeditor/ckeditor/ckeditor.js' %}"></script>
<!-- 引入js -->
<script type="text/javascript" src="{% static 'js/host.js' %}"></script>
<script type="text/javascript" src="{% static 'js/common.js' %}"></script>
<script type="text/javascript" src="{% static 'js/detail.js' %}"></script>
<script type="text/javascript" src="{% static 'js/jquery.pagination.min.js' %}"></script>
...
<!-- 頁面跳轉部分 -->
<a class="dropdown-item" href="{% url 'users:writeblog' %}">寫文章</a>
<a class="dropdown-item" href='{% url 'users:center'%}'>個人資訊</a>
<a class="dropdown-item" href='{% url 'users:center' %}'>退出登錄</a>
- 查詢分類資料并展示
1.查詢文章資料并通過context傳遞給HTML
class DetailView(View):
def get(self,request):
# detail/?id=xxx&page_num=xxx&page_size=xxx
#獲取檔案id
id=request.GET.get('id')
# 獲取博客分類資訊
categories = ArticleCategory.objects.all()
try:
article=Article.objects.get(id=id)
except Article.DoesNotExist:
return render(request,'404.html')
context = {
'categories':categories,
'category':article.category,
'article':article,
}
return render(request,'detail.html',context=context)
2.在detail.html檔案中使用模板語言展示文章資料
#分類資料展示
<div>
<ul class="nav navbar-nav">
{% for cat in categories %}
{% if cat.id == category.id %}
<li class="nav-item active">
<a class="nav-link mr-2" href="/?cat_id={{ cat.id }}">{{ cat.title }}</a>
</li>
{% else %}
<li class="nav-item">
<a class="nav-link mr-2" href="/?cat_id={{ cat.id }}">{{ cat.title }}</a>
</li>
{% endif %}
{% endfor %}
</ul>
</div>
#詳情資料展示
<!-- 標題及作者 -->
<h1 class="mt-4 mb-4">{{ article.title }}</h1>
<div class="alert alert-success"><div>作者:<span>{{ article.author.username }}</span></div><div>瀏覽:{{ article.total_views }}</div></div>
<!-- 文章正文 -->
<div class="col-12" style="word-break: break-all;word-wrap: break-word;">
{{ article.content|safe }}
</div>
<br>

- 修改首頁跳轉到詳情頁面的鏈接
<!-- 標題 -->
<h4>
<b><a href="{% url 'home:detail' %}?id={{ article.id }}" style="color: black;">{{ article.title }}</a></b>
</h4>
推薦文章資料展示
- 添加文章瀏覽量資料
1.每次請求文章詳情時給瀏覽量+1
try:
article=Article.objects.get(id=id)
except Article.DoesNotExist:
return render(request,'404.html')
else:
article.total_views+=1
article.save()
- 查詢推薦文章并展示
1.查詢推薦文章資料并通過context傳遞給HTML
class DetailView(View):
def get(self,request):
# detail/?id=xxx&page_num=xxx&page_size=xxx
#獲取檔案id
id=request.GET.get('id')
# 獲取博客分類資訊
categories = ArticleCategory.objects.all()
try:
article=Article.objects.get(id=id)
except Article.DoesNotExist:
return render(request,'404.html')
else:
article.total_views+=1
article.save()
# 獲取熱點資料
hot_articles = Article.objects.order_by('-total_views')[:9]
context = {
'categories':categories,
'category':article.category,
'article':article,
'hot_articles':hot_articles
}
return render(request,'detail.html',context=context)
2.在detail.html檔案中使用模板語言展示推薦資料
<div class="sidebar__inner">
<h4><strong>推薦</strong></h4>
<hr>
{% for hot_article in hot_articles %}
<a href="{% url 'home:detail' %}?id={{ hot_article.id }}" style="color: black">{{ hot_article.title }}</a><br>
{% endfor %}
</div>
</div>
評論模型
- 定義模型類
在home子應用的models.py模型中定義評論模型
class Comment(models.Model):
#評論內容
content=models.TextField()
#評論的文章
article=models.ForeignKey(Article,
on_delete=models.SET_NULL,
null=True)
#發表評論的用戶
user=models.ForeignKey('users.User',
on_delete=models.SET_NULL,
null=True)
#評論發布時間
created=models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.article.title
class Meta:
db_table='tb_comment'
verbose_name = '評論管理'
verbose_name_plural = verbose_name
- 遷移模型類
1.創建遷移檔案
python manage.py makemigrations
2.執行遷移檔案
python manage.py migrate
發表評論
- 發表評論介面設計
1.請求方式
| 選項 | 方案 |
|---|---|
| 請求方法 | POST |
| 請求地址 | /detail/ |
2.請求引數:表單
| 引數名 | 型別 | 是否必傳 | 說明 |
|---|---|---|---|
| user_id | string | 是 | 發表評論的用戶id |
| article_id | string | 是 | 評論的文字id |
| content | string | 是 | 評論內容 |
3.回應結果:HTML
| 欄位 | 說明 |
|---|---|
| 提交失敗 | 回應錯誤提示 |
| 提交成功 | 重繪頁面展示 |
- 發表評論介面實作
1.發表評論實作
from home.models import Comment,Article
class DetailView(View):
def post(self,request):
#獲取用戶資訊
user=request.user
#判斷用戶是否登錄
if user and user.is_authenticated:
#接收資料
id=request.POST.get('id')
content=request.POST.get('content')
#判斷文章是否存在
try:
article = Article.objects.get(id=id)
except Article.DoesNotExist:
return HttpResponseNotFound('沒有此文章')
#保存到資料
Comment.objects.create(
content=content,
article=article,
user=user
)
#修改文章評論數量
article.comments_count+=1
article.save()
#拼接跳轉路由
path=reverse('home:detail')+'?id={}'.format(article.id)
return redirect(path)
else:
#沒有登錄則跳轉到登錄頁面
return redirect(reverse('users:login'))
2.detail.html修改
<form method="POST">
{% csrf_token %}
<input type="hidden" name="id" value="{{ article.id }}">
<div class="form-group"><label for="body"><strong>我也要發言:</strong></label>
<div>
<div class="django-ckeditor-widget" data-field-id="id_body" style="display: inline-block;">
<textarea cols="40" id="id_body" name="content" rows="10" required data-processed="0" :data-config="data_config" data-external-plugin-resources="[]" data-id="id_body" data-type="ckeditortype">
</textarea>
</div>
</div>
</div>
<!-- 提交按鈕 -->
<button type="submit" class="btn btn-primary ">發送</button>
</form>
詳情評論資料展示
- 查詢評論資料并展示
1.查詢評論資料并通過context傳遞給HTML
home.views.py
from home.models import Comment
from django.shortcuts import redirect,reverse
class DetailView(View):
def get(self,request):
# detail/?id=xxx&page_num=xxx&page_size=xxx
#獲取檔案id
id=request.GET.get('id')
page_num=request.GET.get('page_num',1)
page_size=request.GET.get('page_size',5)
# 獲取博客分類資訊
categories = ArticleCategory.objects.all()
try:
article=Article.objects.get(id=id)
except Article.DoesNotExist:
return render(request,'404.html')
else:
article.total_views+=1
article.save()
# 獲取熱點資料
hot_articles = Article.objects.order_by('-total_views')[:9]
# 獲取當前文章的評論資料
comments = Comment.objects.filter(
article=article
).order_by('-created')
#獲取評論總數
total_count = comments.count()
# 創建分頁器:每頁N條記錄
paginator = Paginator(comments, page_size)
# 獲取每頁商品資料
try:
page_comments = paginator.page(page_num)
except EmptyPage:
# 如果page_num不正確,默認給用戶404
return HttpResponseNotFound('empty page')
# 獲取串列頁總頁數
total_page = paginator.num_pages
context = {
'categories':categories,
'category':article.category,
'article':article,
'hot_articles':hot_articles,
'total_count': total_count,
'comments': page_comments,
'page_size': page_size,
'total_page': total_page,
'page_num': page_num,
}
return render(request,'detail.html',context=context)

2.在index.html檔案中使用模板語言展示分類資料
<!-- 顯示評論 -->
<h4>共有{{ total_count }}條評論</h4>
<div class="row">
{% for comment in comments %}
<div class="col-12" >
<hr><p><strong style="color: pink"></strong></p>
<div>
<div><span><strong>{{ comment.user.username }}</strong></span> <span style="color: gray">{{ comment.created | date:'Y:m:d H:i:s' }}</span></div>
<br>
<p>{{ comment.content|safe }}</p>
</div>
</div>
{% endfor %}
<div class="pagenation" style="text-align: center">
<div id="pagination" class="page"></div>
</div>
</div>
3.修改底部js分頁代碼
<script type="text/javascript">
$(function () {
$('#pagination').pagination({
currentPage: {{ page_num }},
totalPage: {{ total_page }},
callback:function (current) {
location.href = '/detail/?id={{ article.id }}&page_size={{ page_size }}&page_num='+current;
}
})
});
</script>
現在就是完成基本使用功能一個完整的**登錄 注冊 退出 發表文章 評論文章 **
后期待完善
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/279307.html
標籤:其他





