時(shí)間:2023-09-19 20:54:02 | 來源:網(wǎng)站運(yùn)營
時(shí)間:2023-09-19 20:54:02 來源:網(wǎng)站運(yùn)營
【Python+Django】一個(gè)博客網(wǎng)站的設(shè)計(jì)和代碼實(shí)現(xiàn):本文最終實(shí)現(xiàn)一個(gè)博客網(wǎng)站,包含一個(gè)博客的主要用戶,文章和評(píng)論管理功能,在此基礎(chǔ)上擴(kuò)展了簡單的文章列表分頁,文章排序和瀏覽量顯示功能。文末附源碼獲取方式
全文超2W字,碼字不易,希望大家能點(diǎn)贊支持鼓勵(lì)下!
django-admin startproject DjangoBlog
3、 數(shù)據(jù)庫創(chuàng)建和連接配置pip install pymysql
安裝好之后, 進(jìn)入DjangoBlog 項(xiàng)目下的DjangoBlog 文件夾,打開setting.py 文件,找到DATABASES配置項(xiàng),修改DATABSES配置項(xiàng)為如下內(nèi)容:DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', # 數(shù)據(jù)庫引擎 'NAME': 'djangoblog', # 數(shù)據(jù)庫名稱 'HOST': '127.0.0.1', # 數(shù)據(jù)庫地址,本機(jī) ip 地址 127.0.0.1 'PORT': 3306, # 端口 'USER': 'root', # 數(shù)據(jù)庫用戶名 'PASSWORD': '123456', # 數(shù)據(jù)庫密碼 }}
然后使用 pymysql 模塊連接 mysql 數(shù)據(jù)庫:import pymysql pymysql.install_as_MySQLdb()
至此,我們創(chuàng)建了一個(gè)Django項(xiàng)目DjangoBlog用于我們后續(xù)的在線考試管理系統(tǒng)開發(fā)的程序編寫。python manage.py startapp article
python manage.py startapp userprofile
python manage.py startapp comment
注冊APPINSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'article', # 添加此項(xiàng) 'userprofile', # 添加此項(xiàng) 'comment', # 添加此項(xiàng)]
定義模型from django.db import models# 導(dǎo)入內(nèi)建的User模型。from django.contrib.auth.models import User# timezone 用于處理時(shí)間相關(guān)事務(wù)。from django.utils import timezonefrom django.urls import reverse# 博客文章數(shù)據(jù)模型class ArticlePost(models.Model): # 文章作者。參數(shù) on_delete 用于指定數(shù)據(jù)刪除的方式 author = models.ForeignKey(User, on_delete=models.CASCADE) # 文章標(biāo)題。models.CharField 為字符串字段,用于保存較短的字符串,比如標(biāo)題 title = models.CharField(max_length=100) # 文章正文。保存大量文本使用 TextField body = models.TextField() # 文章創(chuàng)建時(shí)間。參數(shù) default=timezone.now 指定其在創(chuàng)建數(shù)據(jù)時(shí)將默認(rèn)寫入當(dāng)前的時(shí)間 created = models.DateTimeField(default=timezone.now) # 文章更新時(shí)間。參數(shù) auto_now=True 指定每次數(shù)據(jù)更新時(shí)自動(dòng)寫入當(dāng)前時(shí)間 updated = models.DateTimeField(auto_now=True) # 文章瀏覽量 total_views = models.PositiveIntegerField(default=0) # 內(nèi)部類 class Meta 用于給 model 定義元數(shù)據(jù) class Meta: # ordering 指定模型返回的數(shù)據(jù)的排列順序 # '-created' 表明數(shù)據(jù)應(yīng)該以倒序排列 ordering = ('-created',) # 函數(shù) __str__ 定義當(dāng)調(diào)用對(duì)象的 str() 方法時(shí)的返回值內(nèi)容 def __str__(self): # return self.title 將文章標(biāo)題返回 return self.title # 獲取文章地址 def get_absolute_url(self): return reverse('article:article_detail', args=[self.id])
在comment/models.py 中新建模型Comment用來管理評(píng)論:from django.db import modelsfrom django.contrib.auth.models import Userfrom article.models import ArticlePost# 博文的評(píng)論class Comment(models.Model): article = models.ForeignKey(ArticlePost,on_delete=models.CASCADE,related_name='comments') user = models.ForeignKey(User,on_delete=models.CASCADE,related_name='comments') body = models.TextField() created = models.DateTimeField(auto_now_add=True) class Meta: ordering = ('created',) def __str__(self): return self.body[:20]
編寫好了Model后,接下來就需要進(jìn)行數(shù)據(jù)遷移。遷移是Django對(duì)模型所做的更改傳遞到數(shù)據(jù)庫中的方式。python manage.py makemigrations
最后通過命令創(chuàng)建app模型對(duì)應(yīng)的數(shù)據(jù)庫表:python manage.py migrate
定義視圖函數(shù)登錄:輸入用戶和密碼,根據(jù)校驗(yàn)結(jié)果進(jìn)行登錄處理。這些需求都靠視圖(View)來完成。
寫文章:編輯文章,提交文章
評(píng)論:添加評(píng)論,提交評(píng)論
from django.contrib.auth.decorators import login_requiredfrom django.http import HttpResponsefrom django.shortcuts import render,redirect# 導(dǎo)入數(shù)據(jù)模型ArticlePostfrom comment.models import Commentfrom . import modelsfrom .models import ArticlePost# 引入U(xiǎn)ser模型from django.contrib.auth.models import User# 引入分頁模塊from django.core.paginator import Paginatordef article_list(request): # 根據(jù)GET請求中查詢條件 # 返回不同排序的對(duì)象數(shù)組 if request.GET.get('order') == 'total_views': article_list = ArticlePost.objects.all().order_by('-total_views') order = 'total_views' else: article_list = ArticlePost.objects.all() order = 'normal' paginator = Paginator(article_list, 4) page = request.GET.get('page') articles = paginator.get_page(page) # 修改此行 context = { 'articles': articles, 'order': order } return render(request, 'article/list.html', context)def article_create(request): if request.method == 'POST': new_article_title = request.POST.get('title') new_article_body = request.POST.get('body') new_article_author = User.objects.get(id=1) models.ArticlePost.objects.create(title=new_article_title, body=new_article_body,author=new_article_author) return redirect("article:article_list") # 如果用戶請求獲取數(shù)據(jù) else: return render(request, 'article/create.html')# 文章詳情def article_detail(request, id): # 取出相應(yīng)的文章 article = ArticlePost.objects.get(id=id) # 瀏覽量 +1 article.total_views += 1 article.save(update_fields=['total_views']) # 取出文章評(píng)論 comments = Comment.objects.filter(article=id) # 需要傳遞給模板的對(duì)象 # context = { 'article': article } context = { 'article': article, 'comments': comments } # 載入模板,并返回context對(duì)象 return render(request, 'article/detail.html', context)# 刪文章def article_delete(request, id): # 根據(jù) id 獲取需要?jiǎng)h除的文章 article = ArticlePost.objects.get(id=id) # 調(diào)用.delete()方法刪除文章 article.delete() # 完成刪除后返回文章列表 return redirect("article:article_list")# 更新文章# 提醒用戶登錄@login_required(login_url='/userprofile/login/')def article_update(request, id): # # 獲取需要修改的具體文章對(duì)象 article = ArticlePost.objects.get(id=id) # 過濾非作者的用戶 if request.user != article.author: return HttpResponse("抱歉,你無權(quán)修改這篇文章。") # 判斷用戶是否為 POST 提交表單數(shù)據(jù) if request.method == "POST": new_article_title = request.POST.get('title') new_article_body = request.POST.get('body') article.title = new_article_title article.body = new_article_body article.save() # 完成后返回到修改后的文章中。需傳入文章的 id 值 return redirect("article:article_detail", id=id) else: # 賦值上下文,將 article 文章對(duì)象也傳遞進(jìn)去,以便提取舊的內(nèi)容 context = {'article': article} return render(request, 'article/update.html', context)
評(píng)論管理:from django.shortcuts import render, get_object_or_404, redirectfrom django.contrib.auth.decorators import login_requiredfrom django.http import HttpResponsefrom article.models import ArticlePostfrom . import models# 文章評(píng)論@login_required(login_url='/userprofile/login/')def post_comment(request, article_id): article = get_object_or_404(ArticlePost, id=article_id) if request.method == 'POST': new_comment_body = request.POST.get('body') new_article_user = request.user models.Comment.objects.create(article=article, body=new_comment_body,user=new_article_user) return redirect(article) else: return HttpResponse("發(fā)表評(píng)論僅接受POST請求。")
用戶管理:關(guān)于Django的表單,后續(xù)有機(jī)會(huì)我專門開文章說明下具體用法userprofile/form.py
# 引入表單類from django import forms# 引入 User 模型from django.contrib.auth.models import User# 登錄表單,繼承了 forms.Form 類class UserLoginForm(forms.Form): username = forms.CharField() password = forms.CharField()# 注冊用戶表單class UserRegisterForm(forms.ModelForm): # 復(fù)寫 User 的密碼 password = forms.CharField() password2 = forms.CharField() class Meta: model = User fields = ('username', 'email') # 對(duì)兩次輸入的密碼是否一致進(jìn)行檢查 def clean_password2(self): data = self.cleaned_data if data.get('password') == data.get('password2'): return data.get('password') else: raise forms.ValidationError("密碼輸入不一致,請重試。")
在用戶管理模塊中我們需要實(shí)現(xiàn)3個(gè)視圖函數(shù),登錄,登出以及注冊。from django.shortcuts import render, redirectfrom django.contrib.auth import authenticate, login,logoutfrom django.http import HttpResponsefrom .forms import UserLoginForm,UserRegisterForm# Create your views here.def user_login(request): if request.method == 'POST': user_login_form = UserLoginForm(data=request.POST) if user_login_form.is_valid(): # .cleaned_data 清洗出合法數(shù)據(jù) data = user_login_form.cleaned_data # 檢驗(yàn)賬號(hào)、密碼是否正確匹配數(shù)據(jù)庫中的某個(gè)用戶 # 如果均匹配則返回這個(gè) user 對(duì)象 user = authenticate(username=data['username'], password=data['password']) if user: # 將用戶數(shù)據(jù)保存在 session 中,即實(shí)現(xiàn)了登錄動(dòng)作 login(request, user) return redirect("article:article_list") else: return HttpResponse("賬號(hào)或密碼輸入有誤。請重新輸入~") else: return HttpResponse("賬號(hào)或密碼輸入不合法") elif request.method == 'GET': user_login_form = UserLoginForm() context = { 'form': user_login_form } return render(request, 'userprofile/login.html', context) else: return HttpResponse("請使用GET或POST請求數(shù)據(jù)")def user_logout(request): logout(request) return redirect("article:article_list")# 用戶注冊def user_register(request): if request.method == 'POST': user_register_form = UserRegisterForm(data=request.POST) if user_register_form.is_valid(): new_user = user_register_form.save(commit=False) # 設(shè)置密碼 new_user.set_password(user_register_form.cleaned_data['password']) new_user.save() # 保存好數(shù)據(jù)后立即登錄并返回博客列表頁面 login(request, new_user) return redirect("article:article_list") else: return HttpResponse("注冊表單輸入有誤。請重新輸入~") elif request.method == 'GET': user_register_form = UserRegisterForm() context = { 'form': user_register_form } return render(request, 'userprofile/register.html', context) else: return HttpResponse("請使用GET或POST請求數(shù)據(jù)")
配置訪問路由URL# 引入pathfrom django.conf.urls import urlfrom django.urls import path# 引入views.pyfrom . import views# 正在部署的應(yīng)用的名稱app_name = 'article'urlpatterns = [ url(r'^$', views.article_list), # path函數(shù)將url映射到視圖 path('article-list/', views.article_list, name='article_list'), # 文章詳情 path('article-detail/<int:id>/', views.article_detail, name='article_detail'), # 寫文章 path('article-create/', views.article_create, name='article_create'), # 刪除文章 path('article-delete/<int:id>/', views.article_delete, name='article_delete'), # 更新文章 path('article-update/<int:id>/', views.article_update, name='article_update'),]
comment/urls.py# 引入pathfrom django.urls import path# 引入views.pyfrom . import views# 正在部署的應(yīng)用的名稱app_name = 'comment'urlpatterns = [ # # path函數(shù)將url映射到視圖 # 發(fā)表評(píng)論 path('post-comment/<int:article_id>/', views.post_comment, name='post_comment'),]
userprofile/urls.pyfrom django.urls import pathfrom . import viewsapp_name = 'userprofile'urlpatterns = [ # 用戶登錄 path('login/', views.user_login, name='login'), # 用戶退出 path('logout/', views.user_logout, name='logout'), # 用戶注冊 path('register/', views.user_register, name='register'),]
根目錄的RUL配置如下:from django.conf.urls import urlfrom django.contrib import admin# 記得引入includefrom django.urls import path, include# 存放映射關(guān)系的列表from article import viewsurlpatterns = [ path('admin/', admin.site.urls), url(r'^$', views.article_list), path('article/', include('article.urls', namespace='article')), path('userprofile/', include('userprofile.urls', namespace='userprofile')), path('comment/', include('comment.urls', namespace='comment')),]
STATIC_URL = '/static/' STATICFILES_DIRS = [ os.path.join(BASE_DIR, 'static'), # 添加此項(xiàng)]
模板創(chuàng)建TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(BASE_DIR, 'templates')], # 添加此項(xiàng) 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, },]
接著我們在模板文件中新建三個(gè)文件:<!-- 載入靜態(tài)文件-->{% load static %}<!DOCTYPE html><!-- 網(wǎng)站主語言 --><html lang="zh-cn"><head> <!-- 網(wǎng)站采用的字符編碼 --> <meta charset="utf-8"> <!-- 預(yù)留網(wǎng)站標(biāo)題的位置 --> <title>{% block title %}{% endblock %}</title> <!-- 引入bootstrap的css文件 --> <script src="https://cdn.staticfile.org/jquery/3.2.1/jquery.min.js"></script> <script src="https://cdn.staticfile.org/popper.js/1.15.0/umd/popper.min.js"></script> <!-- 引入layer.js --> <link rel="stylesheet" href="{% static 'bootstrap/css/bootstrap.min.css' %}"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.5.0/font/bootstrap-icons.css"></head><body><!-- 引入導(dǎo)航欄 -->{% include 'header.html' %}<!-- 預(yù)留具體頁面的位置 -->{% block content %}{% endblock content %}<!-- 引入注腳 -->{% include 'footer.html' %}<!-- bootstrap.js 依賴 jquery.js 和popper.js,因此在這里引入 --><script src="{% static 'jquery/jquery-3.6.0.js' %}"></script><!-- popper.js 采用 cdn 遠(yuǎn)程引入,意思是你不需要把它下載到本地。 在實(shí)際的開發(fā)中推薦靜態(tài)文件盡量都使用 cdn 的形式。 教程采用本地引入是為了讓讀者了解靜態(tài)文件本地部署的流程。--><script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1-lts/dist/umd/popper.min.js"></script><!-- 引入bootstrap的js文件 --><script src="{% static 'bootstrap/js/bootstrap.min.js' %}"></script></body></html>
templates/header.html:<!-- 定義導(dǎo)航欄 --><nav class="navbar navbar-expand-lg navbar-dark bg-primary"> <div class="container"> <!-- 導(dǎo)航欄商標(biāo) --> <a class="navbar-brand" href="#">我的博客</a> <!-- 導(dǎo)航入口 --> <div> <ul class="navbar-nav"> <li class="nav-item"> <a class="nav-link" href="{% url 'article:article_create' %}">創(chuàng)作</a> </li> <li class="nav-item"> <a class="nav-link" href="{% url 'article:article_list' %}">首頁</a> </li> <!-- Django的 if 模板語句 --> {% if user.is_authenticated %} <!-- 如果用戶已經(jīng)登錄,則顯示用戶名下拉框 --> <li class="nav-item dropdown"> <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> {{ user.username }} </a> <div class="dropdown-menu" aria-labelledby="navbarDropdown"> <a class="dropdown-item" href='{% url "userprofile:logout" %}'>退出登錄</a> </div> </li> <!-- 如果用戶未登錄,則顯示 “登錄” --> {% else %} <li class="nav-item"> <a class="nav-link" href="{% url 'userprofile:login' %}">登錄</a> </li> <!-- if 語句在這里結(jié)束 --> {% endif %} </ul> </div> </div></nav>
templates/footer.html:{% load static %}<!-- Footer --><div> <br><br><br></div><footer class="py-3 bg-dark fixed-bottom"> <div class="container"> <p class="m-0 text-center text-white">Copyright © DjangoBlog 2021</p> </div></footer>
上述三個(gè)文件是網(wǎng)站頁面的通用組件模塊,基本上每個(gè)頁面都不會(huì)變,所以我們把他們獨(dú)立出來。<!-- extends表明此頁面繼承自 base.html 文件 -->{% extends "base.html" %} {% load static %}<!-- 寫入 base.html 中定義的 title -->{% block title %} 寫文章 {% endblock title %}<!-- 寫入 base.html 中定義的 content -->{% block content %}<!-- 寫文章表單 --><div class="container"> <div class="row"> <div class="col-12"> <br> <!-- 提交文章的表單 --> <form method="post" action="."> <!-- Django中需要POST數(shù)據(jù)的地方都必須有csrf_token --> {% csrf_token %} <!-- 文章標(biāo)題 --> <div class="form-group"> <!-- 標(biāo)簽 --> <label for="title">文章標(biāo)題</label> <!-- 文本框 --> <input type="text" class="form-control" id="title" name="title"> </div> <!-- 文章正文 --> <div class="form-group"> <label for="body">文章正文</label> <!-- 文本區(qū)域 --> <textarea type="text" class="form-control" id="body" name="body" rows="12"></textarea> </div> <!-- 提交按鈕 --> <button type="submit" class="btn btn-primary">完成</button> </form> </div> </div></div>{% endblock content %}
templates/article/list.html<!-- extends表明此頁面繼承自 base.html 文件 -->{% extends "base.html" %}{% load static %}<!-- 寫入 base.html 中定義的 title -->{% block title %}首頁{% endblock title %}<!-- 寫入 base.html 中定義的 content -->{% block content %}<!-- 定義放置文章標(biāo)題的div容器 --><div class="container"> <nav aria-label="breadcrumb"> <ol class="breadcrumb"> <li class="breadcrumb-item"> <a href="{% url 'article:article_list' %}"> 最新 </a> </li> <li class="breadcrumb-item"> <a href="{% url 'article:article_list' %}?order=total_views"> 最熱 </a> </li> </ol> </nav> {% for article in articles %} <div class="row mt-2"> <!-- 文章內(nèi)容 --> <div class="col-sm-12"> <!-- 卡片容器 --> <div class="card h-100"> <!-- 標(biāo)題 --> <!-- 摘要 --> <div class="card-body"> <h4 class="card-title">{{ article.title }}</h4> <p class="card-text">{{ article.body|slice:'100' }}...</p> <a href="{% url 'article:article_detail' article.id %}" class="card-link">閱讀本文</a> <small class="col align-self-end" style="color: gray;"> <span class="bi bi-eye"> {{ article.total_views }} </span> </small> </div> </div> </div> </div> {% endfor %}</div><!-- 頁碼導(dǎo)航 --><div class="pagination row"> <div class="m-auto"> <span class="step-links"> <!-- 如果不是第一頁,則顯示上翻按鈕 --> {% if articles.has_previous %}<!-- <a href="?page=1" class="btn btn-success">--><!-- « 1--><!-- </a>--> <a href="?page=1&order={{ order }}" class="btn btn-success"> « 1 </a> <span>...</span> <a href="?page={{ articles.previous_page_number }}" class="btn btn-secondary" > {{ articles.previous_page_number }} </a> {% endif %} <!-- 當(dāng)前頁面 --> <span class="current btn btn-danger btn-lg"> {{ articles.number }} </span> <!-- 如果不是最末頁,則顯示下翻按鈕 --> {% if articles.has_next %}<!-- <a href="?page={{ articles.next_page_number }}"--><!-- class="btn btn-secondary"--><!-- >--><!-- {{ articles.next_page_number }}--><!-- </a>--> <a href="?page={{ articles.next_page_number }}&order={{ order }}" class="btn btn-secondary">{{ articles.next_page_number }}</a> <span>...</span><!-- <a href="?page={{ articles.paginator.num_pages }}"--><!-- class="btn btn-success"--><!-- >--><!-- {{ articles.paginator.num_pages }} »--><!-- </a>--> <a href="?page={{ articles.paginator.num_pages }}&order={{ order }}" class="btn btn-success">{{ articles.paginator.num_pages }} »</a> {% endif %} </span> </div></div>{% endblock content %}
templates/article/update.html{% extends "base.html" %} {% load static %}{% block title %} 更新文章 {% endblock title %}{% block content %}<div class="container"> <div class="row"> <div class="col-12"> <br> <form method="post" action="."> {% csrf_token %} <div class="form-group"> <label for="title">文章標(biāo)題</label> <!-- 在 value 屬性中指定文本框的初始值為舊的內(nèi)容,即 article 對(duì)象中的 title 字段 --> <input type="text" class="form-control" id="title" name="title" value="{{ article.title }}"> </div> <div class="form-group"> <label for="body">文章正文</label> <!-- 文本域不需要 value 屬性,直接在標(biāo)簽體中嵌入數(shù)據(jù)即可 --> <textarea type="text" class="form-control" id="body" name="body" rows="12">{{ article.body }}</textarea> </div> <button type="submit" class="btn btn-primary">完成</button> </form> </div> </div></div>{% endblock content %}
templates/article/detail.html{% extends "base.html" %} {% load static %}{% block title %} 登錄 {% endblock title %}{% block content %}<div class="container"> <div class="row justify-content-md-center"> <div class="col-6"> <br> <form method="post" action="."> {% csrf_token %} <!-- 賬號(hào) --> <div class="form-group"> <label for="username">賬號(hào)</label> <input type="text" class="form-control" id="username" name="username"> </div> <!-- 密碼 --> <div class="form-group"> <label for="password">密碼</label> <input type="password" class="form-control" id="password" name="password"> </div> <!-- 提交按鈕 --> <button type="submit" class="btn btn-primary">登錄</button> <div class="form-group"> <br> <h5>還沒有賬號(hào)?</h5> <h5>點(diǎn)擊<a href='{% url "userprofile:register" %}'>注冊賬號(hào)</a>加入我們吧!</h5> <br> </div> </form> </div> </div></div>{% endblock content %}
templates/userprofile/register.html{% extends "base.html" %} {% load static %}{% block title %} 注冊 {% endblock title %}{% block content %}<div class="container"> <div class="row justify-content-md-center"> <div class="col-md-6"> <br> <form method="post" action="."> {% csrf_token %} <!-- 賬號(hào) --> <div class="form-group"> <label for="username">昵稱</label> <input type="text" class="form-control" id="username" name="username" required> </div> <!-- 郵箱 --> <div class="form-group"> <label for="email">Email</label> <input type="text" class="form-control" id="email" name="email"> </div> <!-- 密碼 --> <div class="form-group"> <label for="password">設(shè)置密碼</label> <input type="password" class="form-control" id="password" name="password" required> </div> <!-- 確認(rèn)密碼 --> <div class="form-group"> <label for="password2">確認(rèn)密碼</label> <input type="password" class="form-control" id="password2" name="password2" required> </div> <!-- 提交按鈕 --> <button type="submit" class="btn btn-primary btn-block">提交</button> </form> </div> </div></div>{% endblock content %}
關(guān)鍵詞:設(shè)計(jì),實(shí)現(xiàn)
客戶&案例
營銷資訊
關(guān)于我們
微信公眾號(hào)
版權(quán)所有? 億企邦 1997-2025 保留一切法律許可權(quán)利。