【Python+Django】一个博客网站的设计和代码实现

wuchangjian2021-11-16 12:50:02编程学习

本文最终实现一个博客网站,包含一个博客的主要用户,文章和评论管理功能,在此基础上扩展了简单的文章列表分页,文章排序和浏览量显示功能。

可作为Python Web,Django的练手项目,也可以作为计算机毕业设计参考项目。

全文超2W字,码字不易,希望大家能点赞支持鼓励下!


系统功能需求分析

一个典型的博客网站,主要包含如下几个功能模块:

1、用户管理:用户的注册和登录

2、文章管理:文章的新建,修改,查看以及文章列表展示

3、评论管理:评论的添加,修改和删除

系统设计及实现思路

系统设计包括三部分:数据库设计,功能函数视图设计,前端页面设计

1、数据库设计

按照前面的功能模块,我们需要自定义数据表如下:

  • 博客文章表:ArticlePost
  • 评论表:Comment

另外我们为了快速实现系统,用户管理功能实现我们直接基于Django自带的用户及认证模块。

2、页面及功能设计

为了实现我们前面的功能模块我们设计如下几个功能页面:

1、登录页面:

其中需要登录,校验,登录后同时需要存储用户信息在Session中,以备登录后的页面使用。

2、注册页面:

提供注册信息表单,提交注册通过后,系统生成一个新的用户。

3、首页(博文列表页):

展示所有的博文列表并实现列表的分页功能,点击阅读链接可以查看文章详情,另外我们增加浏览量显示功能用于进行简单的数据统计。

4、写文章页面:

撰写文章并发布文章

5、文章详情页面:

展示文章详情,并提供修改文章,删除文章功能按钮。

6、评论管理页面

添加评论,删除评论和显示评论

7、后台管理

为了快速实现系统我们将直接启用Django自带的Admin管理功能。

系统实现过程及源码

大致理清了我们需要实现的功能和页面模块之后,我们开始撸代码

1 开发环境搭建及技术选型

服务端:Python 3.8

Web框架:Django 3.2

数据库:MySQL mysql-8.0.13-winx64

开发工具IDE:Pycharm(社区版)

前端框架:Bootstrap 4

详细环境搭建工具安装请参考下文:

[Python+Django] Web图书管理系统毕业设计(一)之开发工具和技术篇23 赞同 · 0 评论文章正在上传…重新上传取消​

2、Django项目创建

1.在任意盘符文件夹下新建一个空白Pycharm项目文件夹比如:PycharmProjects

2.打开Pycharm,进入空白文件夹:PycharmProjects

3.通过Pycharm 的Windows 命令行输入界面输入创建Django项目的命令,创建一个新的项目: DjangoBlog

django-admin startproject DjangoBlog

3、 数据库创建和连接配置

Django 对各种数据库提供了很好的支持,包括:PostgreSQL、MySQL、SQLite、Oracle。

Django 为这些数据库提供了统一的调用API。

我们可以根据自己业务需求选择不同的数据库。

MySQL 是 Web 应用中最常用的数据库。

本文采用MySQL。

此步骤将数据库设置连接到自己的MySQL数据库,并完成数据库的创建.

Django只能操作到数据表级别,不能操作到数据库级别,所以需要手工创建一个数据库:djangoblog

Django数据库连接配置

Django使用MySQL需要mysql 驱动,如果你没安装 mysql 驱动,可以执行以下命令安装:

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 数据库:

在与 settings.py 同级目录下的 __init__.py 中引入模块和进行配置:

import pymysql 
pymysql.install_as_MySQLdb()

至此,我们创建了一个Django项目DjangoBlog用于我们后续的在线考试管理系统开发的程序编写。

同时为此项目创建了一个MySQL数据库:djangoblog用于我们程序开发过程中的数据存放和处理。

4、功能模块详细开发

一个典型的Django项目模块功能的开发包括如下几个步骤:

  • 创建app
  • 注册app
  • 定义模型
  • 定义视图函数
  • 配置访问路由URL
  • 静态资源准备及配置
  • 前端模板开发
  • 测试及运行

创建APP

在Django中的一个app代表一个功能模块,Django 规定,如果要使用模型,必须要创建一个 app。

本系统我们总共涉及三个功能模块用户,文章和评论管理,所以我们创建三个app分别管理文件。

  • 创建文章管理模块app
python manage.py startapp article
  • 创建用户管理模块app
python manage.py startapp userprofile
  • 创建评论管理模块app
python manage.py startapp comment

注册APP

在 DjangoBlog文件夹目录下的settings.py 中找到INSTALLED_APPS配置项,将新创建的3个app添加到项目的app列表,如下:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'article',     # 添加此项
    'userprofile',     # 添加此项
    'comment',     # 添加此项
]

定义模型

我们之前创建了一个空白的数据库djangoblog,这一步我们通过Django的模型来完成数据库表的创建.

在article/models.py 中新建模型ArticlePost 用来管理文章:

from django.db import models
# 导入内建的User模型。
from django.contrib.auth.models import User
# timezone 用于处理时间相关事务。
from django.utils import timezone
from 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 models
from django.contrib.auth.models import User
from 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对模型所做的更改传递到数据库中的方式。

注意,每当对数据库进行了更改(添加、修改、删除等)操作,都需要进行数据迁移。

Django的迁移代码是由模型文件自动生成的,它本质上只是个历史记录,Django可以用它来进行数据库的滚动更新,通过这种方式使其能够和当前的模型匹配。

在命令行中输入命令让 Django知道我们自定义模型有一些变更,并根据我们自定义app的模型生成创建数据表的脚本:

python manage.py makemigrations

最后通过命令创建app模型对应的数据库表:

python manage.py migrate

定义视图函数

Django 中视图的概念是「一类具有相同功能和模板的网页的集合」。

比如,在一个博客网站系统中,我们可能需要如下几个视图:

登录:输入用户和密码,根据校验结果进行登录处理。
写文章:编辑文章,提交文章
评论:添加评论,提交评论

这些需求都靠视图(View)来完成。

每一个视图表现为一个简单的Python函数,它需要要做的只有两件事:返回一个包含被请求页面内容的 HttpResponse对象,或者抛出一个异常,比如 Http404 。

视图函数中的request与网页发来的请求有关,里面包含get或post的内容、用户浏览器、系统等信息。

根据系统设计过程中需要的功能,我们分别在不同的app 中创建对应的View视图。

文章管理

在文章管理的app article中我们在文件article/views.py文件中创建如下几个视图函数:

article_list

  • 实现默认按发布日期排序
  • 实现可按浏览量(热度)排序
  • 利用Django 的paginator组件实现分页功能

article_create:

  • 实现文章的创建和提交功能

article_detail:

  • 在展示文章的标题和内容详情,同时在此视图中取出评论,并展示所有评论列表。

article_delete:

  • 实现文章删除功能,如果非作者无权限修改对应文章。

article_update:

  • 实现文章的修改功能,如果非作者无权限修改对应文章。

article/views.py完整代码如下:

from django.contrib.auth.decorators import login_required
from django.http import HttpResponse
from django.shortcuts import render,redirect
# 导入数据模型ArticlePost
from comment.models import Comment
from . import models
from .models import ArticlePost
# 引入User模型
from django.contrib.auth.models import User
# 引入分页模块
from django.core.paginator import Paginator

def 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)

评论管理:

在评论管理的app comment中我们在文件comment/views.py文件中创建如下视图函数,评论比较简单我们暂时只创建一个添加评论的就可以了:

post_comment:

  • 实现评论的创建和提交功能

comment/views.py完整代码如下:

from django.shortcuts import render, get_object_or_404, redirect
from django.contrib.auth.decorators import login_required
from django.http import HttpResponse
from article.models import ArticlePost
from . 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自带的用户和认证模块的一些内置组件。

Django在数据迁移的时候会自动创建一套用户及权限相关的表。

我们在开发一些用户权限控制不复杂的网站或者系统时,可以直接采用Django自带的用户认证模块功能。

用户注册,登录过程会涉及一些简单的表单,Django内部也集成了一个Form组件,利用Form组件我们可以在Django中定义好表单,减少在前端模板中实现表单的硬编码代码量。从而快速实现我们需要的功能。

要使用Form我们需要在对应app新建一个form.py 用来定义表单。

在我们之前为用户管理创建的应用userprofile的文件夹下新建文件form.py.

在此文件中我们定义用于用户登录和注册的两个表单。

关于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个视图函数,登录,登出以及注册。

其中登录和注册函数的实现将基于我们上文定义的Form表单。

user_login:

通过Django自带的login函数实现登录校验并将用户数据存储到session中。

user_logout:

用户登出

user_register:

实现用户注册,提交之后新建一个博客用户。

userprofile/views.py完整代码如下:

from django.shortcuts import render, redirect
from django.contrib.auth import authenticate, login,logout
from django.http import HttpResponse
from .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

有了视图后,我们需要将视图函数和Web网页链接对应起来。

url可以理解为访问网站时输入的网址链接,配置好url后Django才知道怎样定位app。

本文中我们有3个app,Django可以对URL进行分级管理,我们在每个app文件夹内的urls.py分别定义各自app相关的URL,然后在根目录DjangoBlog的urls.py中通过include指令来包含各个app中的URL。

在实际URL访问过程中,系统会根据配置的URL地址和include关系实现路由的逐级分发。

三个app中的URL配置如下:

article/urls.py

# 引入path
from django.conf.urls import url
from django.urls import path
# 引入views.py
from . 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

# 引入path
from django.urls import path
# 引入views.py
from . import views

# 正在部署的应用的名称
app_name = 'comment'

urlpatterns = [
    # # path函数将url映射到视图
    # 发表评论
    path('post-comment/<int:article_id>/', views.post_comment, name='post_comment'),
]

userprofile/urls.py

from django.urls import path
from . import views

app_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配置如下:

DjangoBlog/urls.py

from django.conf.urls import url
from django.contrib import admin
# 记得引入include
from django.urls import path, include
# 存放映射关系的列表
from article import views

urlpatterns = [
    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')),
]

至此,我们后台的功能已经实现完毕,接下来我们来实现前端模板部分。


静态资源准备及配置

本系统前后端不分离,前端框架选用当前比较受欢迎的Bootstrap4,为了快速开发,本系统所有的页面都使用原生Bootstrap进行开发,未采用第三方的模板和主题。

bootstrap.js依赖 jquery.js和popper.js 才能正常运行,需要一起准备。

在根目录下新建static文件夹,将准备好的静态文件放进去。

完成后Static文件夹结构如下图:

准备好静态资源后,我们还需要在Django中指定静态文件的存放位置,这样才能够在模板中正确引用它们。

在DjangoBlog/settings.py 中进行如下配置:

STATIC_URL = '/static/'
 
STATICFILES_DIRS = [
 os.path.join(BASE_DIR, 'static'), # 添加此项
]

模板创建

在创建模板之前,我们先在根目录下新建一个文件夹templates用于存放我们的所有的模板文件。

模板位置也同样需要进行配置指定模板的存放位置,在DjangoBlog/settings.py 中进行如下配置:

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',
 ],
 },
 },
]

接着我们在模板文件中新建三个文件:

base.html:是整个项目的模板基础,所有的网页都从它继承;

header.html:是网页顶部的导航栏;

footer.html:是网页底部的注脚。

分别编写三个静态HTML文件代码如下:

templates/base.html:

<!--    载入静态文件-->
{% 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 &copy; DjangoBlog 2021</p>
    </div>
</footer>

上述三个文件是网站页面的通用组件模块,基本上每个页面都不会变,所以我们把他们独立出来。

我们编写Django后续的页面模板时可以直接继承对应的通用模板组件。

接下来我们在三个通用组件的基础上分别编写各个视图对应的模板页面:

模板的整体目录如下:

限于篇幅,放一些关键代码供参考:

templates/article/create.html

<!-- 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">-->
<!--                    &laquo; 1-->
<!--                </a>-->
                <a href="?page=1&order={{ order }}" class="btn btn-success">
                    &laquo; 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 }} &raquo;-->
<!--                </a>-->
                <a href="?page={{ articles.paginator.num_pages }}&order={{ order }}"
                    class="btn btn-success">{{ articles.paginator.num_pages }} &raquo;</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

templates/userprofile/login.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 %}

运行服务器测试效果

在Windows命令行输入:

python manage.py runserver

运行服务器后在浏览器中输入我们之前配置的首页对应的URL地址:

http://127.0.0.1:8000/article/article-list/


结语:

本文仅仅实现了一个博客主要的三个功能,注册登录,文章和评论部分,一个博客还有很多的功能可以加入其中,比如富文本编辑,用户头像功能,文章栏目管理,第三方登录等等,大家可以自行完善相关功能。


最后,原创不易,希望大家能够点赞支持下!

发表评论    

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。