时间:2023-09-19 20:54:02 | 来源:网站运营
时间:2023-09-19 20:54:02 来源:网站运营
【Python+Django】一个博客网站的设计和代码实现:本文最终实现一个博客网站,包含一个博客的主要用户,文章和评论管理功能,在此基础上扩展了简单的文章列表分页,文章排序和浏览量显示功能。文末附源码获取方式
全文超2W字,码字不易,希望大家能点赞支持鼓励下!
django-admin startproject DjangoBlog
3、 数据库创建和连接配置pip install pymysql
安装好之后, 进入DjangoBlog 项目下的DjangoBlog 文件夹,打开setting.py 文件,找到DATABASES配置项,修改DATABSES配置项为如下内容:DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', # 数据库引擎 'NAME': 'djangoblog', # 数据库名称 'HOST': '127.0.0.1', # 数据库地址,本机 ip 地址 127.0.0.1 'PORT': 3306, # 端口 'USER': 'root', # 数据库用户名 'PASSWORD': '123456', # 数据库密码 }}
然后使用 pymysql 模块连接 mysql 数据库:import pymysql pymysql.install_as_MySQLdb()
至此,我们创建了一个Django项目DjangoBlog用于我们后续的在线考试管理系统开发的程序编写。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', # 添加此项 'userprofile', # 添加此项 'comment', # 添加此项]
定义模型from django.db import models# 导入内建的User模型。from django.contrib.auth.models import User# timezone 用于处理时间相关事务。from django.utils import timezonefrom django.urls import reverse# 博客文章数据模型class ArticlePost(models.Model): # 文章作者。参数 on_delete 用于指定数据删除的方式 author = models.ForeignKey(User, on_delete=models.CASCADE) # 文章标题。models.CharField 为字符串字段,用于保存较短的字符串,比如标题 title = models.CharField(max_length=100) # 文章正文。保存大量文本使用 TextField body = models.TextField() # 文章创建时间。参数 default=timezone.now 指定其在创建数据时将默认写入当前的时间 created = models.DateTimeField(default=timezone.now) # 文章更新时间。参数 auto_now=True 指定每次数据更新时自动写入当前时间 updated = models.DateTimeField(auto_now=True) # 文章浏览量 total_views = models.PositiveIntegerField(default=0) # 内部类 class Meta 用于给 model 定义元数据 class Meta: # ordering 指定模型返回的数据的排列顺序 # '-created' 表明数据应该以倒序排列 ordering = ('-created',) # 函数 __str__ 定义当调用对象的 str() 方法时的返回值内容 def __str__(self): # return self.title 将文章标题返回 return self.title # 获取文章地址 def get_absolute_url(self): return reverse('article:article_detail', args=[self.id])
在comment/models.py 中新建模型Comment用来管理评论:from django.db import modelsfrom django.contrib.auth.models import Userfrom article.models import ArticlePost# 博文的评论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后,接下来就需要进行数据迁移。迁移是Django对模型所做的更改传递到数据库中的方式。python manage.py makemigrations
最后通过命令创建app模型对应的数据库表:python manage.py migrate
定义视图函数登录:输入用户和密码,根据校验结果进行登录处理。这些需求都靠视图(View)来完成。
写文章:编辑文章,提交文章
评论:添加评论,提交评论
from django.contrib.auth.decorators import login_requiredfrom django.http import HttpResponsefrom django.shortcuts import render,redirect# 导入数据模型ArticlePostfrom comment.models import Commentfrom . import modelsfrom .models import ArticlePost# 引入User模型from django.contrib.auth.models import User# 引入分页模块from django.core.paginator import Paginatordef article_list(request): # 根据GET请求中查询条件 # 返回不同排序的对象数组 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") # 如果用户请求获取数据 else: return render(request, 'article/create.html')# 文章详情def article_detail(request, id): # 取出相应的文章 article = ArticlePost.objects.get(id=id) # 浏览量 +1 article.total_views += 1 article.save(update_fields=['total_views']) # 取出文章评论 comments = Comment.objects.filter(article=id) # 需要传递给模板的对象 # context = { 'article': article } context = { 'article': article, 'comments': comments } # 载入模板,并返回context对象 return render(request, 'article/detail.html', context)# 删文章def article_delete(request, id): # 根据 id 获取需要删除的文章 article = ArticlePost.objects.get(id=id) # 调用.delete()方法删除文章 article.delete() # 完成删除后返回文章列表 return redirect("article:article_list")# 更新文章# 提醒用户登录@login_required(login_url='/userprofile/login/')def article_update(request, id): # # 获取需要修改的具体文章对象 article = ArticlePost.objects.get(id=id) # 过滤非作者的用户 if request.user != article.author: return HttpResponse("抱歉,你无权修改这篇文章。") # 判断用户是否为 POST 提交表单数据 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 文章对象也传递进去,以便提取旧的内容 context = {'article': article} return render(request, 'article/update.html', context)
评论管理: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# 文章评论@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("发表评论仅接受POST请求。")
用户管理:关于Django的表单,后续有机会我专门开文章说明下具体用法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): # 复写 User 的密码 password = forms.CharField() password2 = forms.CharField() class Meta: model = User fields = ('username', 'email') # 对两次输入的密码是否一致进行检查 def clean_password2(self): data = self.cleaned_data if data.get('password') == data.get('password2'): return data.get('password') else: raise forms.ValidationError("密码输入不一致,请重试。")
在用户管理模块中我们需要实现3个视图函数,登录,登出以及注册。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 清洗出合法数据 data = user_login_form.cleaned_data # 检验账号、密码是否正确匹配数据库中的某个用户 # 如果均匹配则返回这个 user 对象 user = authenticate(username=data['username'], password=data['password']) if user: # 将用户数据保存在 session 中,即实现了登录动作 login(request, user) return redirect("article:article_list") else: return HttpResponse("账号或密码输入有误。请重新输入~") else: return HttpResponse("账号或密码输入不合法") 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请求数据")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) # 设置密码 new_user.set_password(user_register_form.cleaned_data['password']) new_user.save() # 保存好数据后立即登录并返回博客列表页面 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请求数据")
配置访问路由URL# 引入pathfrom django.conf.urls import urlfrom django.urls import path# 引入views.pyfrom . import views# 正在部署的应用的名称app_name = 'article'urlpatterns = [ url(r'^$', views.article_list), # path函数将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# 正在部署的应用的名称app_name = 'comment'urlpatterns = [ # # path函数将url映射到视图 # 发表评论 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# 存放映射关系的列表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'), # 添加此项]
模板创建TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(BASE_DIR, 'templates')], # 添加此项 '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', ], }, },]
接着我们在模板文件中新建三个文件:<!-- 载入静态文件-->{% load static %}<!DOCTYPE html><!-- 网站主语言 --><html lang="zh-cn"><head> <!-- 网站采用的字符编码 --> <meta charset="utf-8"> <!-- 预留网站标题的位置 --> <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><!-- 引入导航栏 -->{% include 'header.html' %}<!-- 预留具体页面的位置 -->{% 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 远程引入,意思是你不需要把它下载到本地。 在实际的开发中推荐静态文件尽量都使用 cdn 的形式。 教程采用本地引入是为了让读者了解静态文件本地部署的流程。--><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:<!-- 定义导航栏 --><nav class="navbar navbar-expand-lg navbar-dark bg-primary"> <div class="container"> <!-- 导航栏商标 --> <a class="navbar-brand" href="#">我的博客</a> <!-- 导航入口 --> <div> <ul class="navbar-nav"> <li class="nav-item"> <a class="nav-link" href="{% url 'article:article_create' %}">创作</a> </li> <li class="nav-item"> <a class="nav-link" href="{% url 'article:article_list' %}">首页</a> </li> <!-- Django的 if 模板语句 --> {% if user.is_authenticated %} <!-- 如果用户已经登录,则显示用户名下拉框 --> <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 语句在这里结束 --> {% 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>
上述三个文件是网站页面的通用组件模块,基本上每个页面都不会变,所以我们把他们独立出来。<!-- 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数据的地方都必须有csrf_token --> {% csrf_token %} <!-- 文章标题 --> <div class="form-group"> <!-- 标签 --> <label for="title">文章标题</label> <!-- 文本框 --> <input type="text" class="form-control" id="title" name="title"> </div> <!-- 文章正文 --> <div class="form-group"> <label for="body">文章正文</label> <!-- 文本区域 --> <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 %}<!-- 定义放置文章标题的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"> <!-- 文章内容 --> <div class="col-sm-12"> <!-- 卡片容器 --> <div class="card h-100"> <!-- 标题 --> <!-- 摘要 --> <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><!-- 页码导航 --><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 %} <!-- 当前页面 --> <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">文章标题</label> <!-- 在 value 属性中指定文本框的初始值为旧的内容,即 article 对象中的 title 字段 --> <input type="text" class="form-control" id="title" name="title" value="{{ article.title }}"> </div> <div class="form-group"> <label for="body">文章正文</label> <!-- 文本域不需要 value 属性,直接在标签体中嵌入数据即可 --> <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 %} <!-- 账号 --> <div class="form-group"> <label for="username">账号</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>还没有账号?</h5> <h5>点击<a href='{% url "userprofile:register" %}'>注册账号</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 %} <!-- 账号 --> <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">设置密码</label> <input type="password" class="form-control" id="password" name="password" required> </div> <!-- 确认密码 --> <div class="form-group"> <label for="password2">确认密码</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 %}
关键词:设计,实现