说在前面

这里重点介绍Django后端, 前端我用的是bootstrap5前端组件库, 水平不够就不进行过多介绍了.

开发环境搭建

  • 使用的python版本为Python3.8;
  • 使用的django版本为Django3.0;
  • 使用的编辑器为vscode.

项目需求分析

这里我要搭建的是一个普通的blog, 需要的是blog的基础功能, 包括:

  • 导航栏
  • 横幅幻灯片
  • 文章列表
  • 文章分类
  • 文章标签
  • 文章内容
  • 文章浏览量
  • 文章搜索
  • 热门排行

其实也没多少东西, 包含的页面主要有:

  1. 博客首页
  2. 文章分类页
  3. 文章标签页
  4. 文章搜索页
  5. 文章内容页

数据库设计

文章表结构

表字段 字段类型 备注
id int 系统自动生成的主键
title CharField(max_length=128) 文章标题
category ForeignKey 多对一, 关联文章分类表
tags ManyToManyField 多对多, 关联标签列表
cover ImageField 文章封面
views PositiveIntegerField 文章浏览数
abstract TextField(max_length=250) 文章摘要
content TextField 文章内容
created_time DateTimeField 文章创建时间
modified_time DateTimeField 文章修改时间

幻灯片表结构

表字段 字段类型 备注
id int 系统自动生成的主键
text_info CharField(max_length=100) 图片文本信息
img ImageField 幻灯片图片
link_url URLField 图片链接的URL
index int 图片播放位序
is_active BooleanField 是否播放

文章分类表

表字段 字段类型 备注
id int 系统自动生成的主键
name CharField(max_length=30) 类别名
index index 类别次序

文章标签表

表字段 字段类型 备注
id int 系统自动生成的主键
name CharField(max_length=30) 标签名

项目创建以及配置

创建项目

打开命令行, cd 到一个你想放置你代码的目录, 然后运行以下命令:
django-admin startproject myblog
这行代码将会在当前目录下创建一个 myblog 目录.
进入 myblog 目录,输入命令
python manage.py runserver
即可在 https://127.0.0.1:8000/ 中看到欢迎界面
创建应用时使用命令
python manage.py startapp blog
这将会创建一个名为blog的目录, 即为应用.

其它常用命令还有:
python manage.py makemigrations
Django 会检测你对模型文件的修改, 并且把修改的部分储存为一次迁移.
再使用命令
python manage.py migrate
进行迁移.

python manage.py shell
使用该命令可以在shell界面对django项目进行简单调试.

myblog/settings.py配置

设置语言和时区:

1
2
3
4
#settings
LANGUAGE_CODE = 'zh-hans' # 修改语言为中文
TIME_ZONE = 'Asia/Shanghai' # 修改时区为上海
USE_TZ = True # 建议设置为True, 这样后端全局都会使用UTC时间, 但是在模板中渲染时会自动转换为当前时区

在INSTALL_APPS中添加应用:

1
2
3
4
INSTALLED_APPS = [
'blog.apps.BlogConfig',#注册APP应用
...
]

配置数据库:

这里我用的是mysql数据库(默认是使用sqlite3), 需要安装 MySQLdb 才能使用.
pip install MySQLdb
并在blog/__init__.py中添加 import MySQLdb.

1
2
3
4
5
6
7
8
9
10
11
# 修改成mysql如下
DATABASES = {
    'default': {
        'ENGINE''django.db.backends.mysql'
'NAME''myblog',  #  你的数据库名称
        'USER''name',  #  你的数据库用户名
        'PASSWORD''password',  #  你的数据库密码
        'HOST''localhost',  #  你的数据库主机,留空默认为localhost
        'PORT''3306',  #  你的数据库端口
    }
}

注意要提前建好schema.

设置静态文件夹目录的路径:

1
2
3
4
5
6
7
8
9
# 设置静态文件目录
STATIC_URL = '/static/'
STATICFILES_DIRS = (
os.path.join(BASE_DIR, "static"),
)

# 设置媒体文件上传目录
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media') #

Django ORM创建

Django是通过Model操作数据库, Model准确且唯一的描述了数据. 它包含你储存的数据的重要字段和行为. 一般来说, 每一个模型都映射一张数据库表.

  • 每个模型都是一个 Python 的类, 这些类继承 django.db.models.Model.
  • 模型类的每个属性都相当于一个数据库的字段.
  • 利用这些, Django 提供了一个自动生成访问数据库的 API.

根据我们数据库的设计, 可以很快写出这个Model(文章内容我是使用富文本保存的, 但是django并没有集成富文本, 这里使用的是django-ckeditor富文本编辑器, 可以使用pip install django-cheditor自行安装, 注意在INSTALL_APPS中注册ckeditor, 具体使用方法可以查看 Django搭建个人博客:使用django-ckeditor富文本编辑器):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# myblog/blog/models.py

from django.db import models
from ckeditor.fields import RichTextField


class Category(models.Model):
"""分类表"""

name = models.CharField(max_length=100, unique=True)
index = models.IntegerField(default=999)

def __str__(self):
return self.name

class Meta:
# 排序方式按照index排序
ordering = ["index"]
verbose_name = 'category'
verbose_name_plural = 'categorys'


class Tag(models.Model):
"""标签表"""

name = models.CharField(max_length=100)

def __str__(self):
return self.name

class Meta:
verbose_name = 'tag'
verbose_name_plural = 'tags'


class Article(models.Model):
"""文章表"""

title = models.CharField(max_length=128)
# 使用ckeditor的富文本保存.
content = RichTextField(blank=True, null=True)
abstract = models.TextField(max_length=500, blank=True, null=True)
cover = models.ImageField(upload_to='article_cover/%Y/%m/%d/',
verbose_name='article_cover', blank=True, null=True)
category = models.ForeignKey(
Category, on_delete=models.DO_NOTHING, blank=True, null=True)
tags = models.ManyToManyField(Tag, blank=True, null=True)
views = models.PositiveIntegerField(default=0)
created_time = models.DateTimeField(auto_now_add=True)
modified_time = models.DateTimeField(auto_now=True)

class Meta:
ordering = ["-created_time"]
verbose_name = 'article'
verbose_name_plural = 'articles'

def __str__(self):
return self.title


class Banner(models.Model):
"""幻灯片表"""

text_info = models.CharField(max_length=50, default='')
img = models.ImageField(upload_to='banner/')
link_url = models.URLField(max_length=100)
is_active = models.BooleanField(default=False)

def __str__(self):
return self.text_info

class Meta:
verbose_name = 'banner'
verbose_name_plural = 'banners'

各个参数我就不一一赘述了, 有需要的可以在官方文档查询.

ORM创建后, 要执行命令
python manage.py makemigrations
python manage.py migrate
进行检测和迁移.

Admin管理界面设计

先要创建一个管理员账号python manage.py createsuperuser按照指示注册账号密码.
启动服务器后, 打开浏览器, 转到你本地域名的 “/admin/“ 目录, — 比如 “http://127.0.0.1:8000/admin/“ .你应该会看见管理员登录界面.

需要在你的应用的admin.py文件中注册.
打开blog/admin.py, 键入以下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
from django.contrib import admin
from .models import Article, Banner, Category, Tag


class TagAdmin(admin.ModelAdmin):
fields = [
'name'
]


class CategoryAdmin(admin.ModelAdmin):
fields = [
'name',
'index'
]


class ArticleAdmin(admin.ModelAdmin):
# 编辑时分为多个字段集
fieldsets = [
('文章标题', {'fields': ['title']}),
('封面', {'fields': ['cover']}),
('文章内容', {'fields': ['abstract', 'content']}),
('阅读量', {'fields': ['views'], 'classes': ['collapse']}),
]

# 文章列表里显示想要显示的字段
list_display = ('id', 'title', 'category', 'views', 'created_time')

# 满50条数据就自动分页
list_per_page = 50

# 后台数据列表排序方式
ordering = ('-created_time',)

# 设置哪些字段可以点击进入编辑界面
list_display_links = ('id', 'title')

# 设置后台查询时用的字段
search_fields = ['title']

# 用来过滤字段的过滤器
list_filter = ['created_time', 'modified_time']


class ArticleInline(admin.StackedInline):
model = Article
extra = 2


class BannerAdmin(admin.ModelAdmin):
# 编辑时的排列顺序
fields = [
'text_info',
'img',
'link_url',
'is_active',
'index'
]


admin.site.register(Article, ArticleAdmin)
admin.site.register(Category, CategoryAdmin)
admin.site.register(Tag, TagAdmin)
admin.site.register(Banner, BannerAdmin)

此时后台站点管理的界面是这样的:

django01

添加文章界面是这样的:

django02

URL设计

先打开myblog/urls.py, 键入以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from django.contrib import admin
from django.urls import path, re_path, include
from django.conf import settings
from django.views import static

urlpatterns = [
# 后台站点管理的url
path('admin/', admin.site.urls),
# 获取储存图片的url
re_path(r'^media/(?P<path>.*)$', static.serve,
{'document_root': settings.MEDIA_ROOT}),
# blog应用的url
path('', include('blog.urls'))
]

再在blog应用文件夹中创建urls.py, 键入以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# blog/urls.py
from django.urls import path

from . import views

app_name = 'blog'
urlpatterns = [
# 首页的url
path('', views.index, name='index'),
# 文章内容的url, 并将文章id传给views
path('article/<int:id>/', views.article),
# 同分类文章列表的url, 并将category传给views
path('categories/<str:category>/', views.category),
# 同标签文章列表的url, 并将tag传给views
path('tags/<str:tag>/', views.tag),
# 搜索文章的url
path('search/', views.search),
]

Views视图函数

打开blog/views.py, 键入以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
from django.shortcuts import render
from django.core.paginator import Paginator
from .models import Article, Banner, Category, Tag


# 获取全部页面都有的一些参数, 包括所有分类, 标签和热门文章
def getGlobalParameter():
categories = Category.objects.all()
tags = Tag.objects.all()
hot_articles = Article.objects.order_by('-views')[:5]
result = {'categories': categories, 'category_link': '/categories/',
'tags': tags, 'tag_link': '/tags/',
'hot_articles': hot_articles, 'article_link': '/articles/'}
return result


# 获取给定文章的基本信息
def getArticlesInfo(articles, has_content=False):
articles_info = []
# 所有文章的除内容外的信息
for article in articles:
info = {'id': article.id, 'title': article.title, 'cover': article.cover,
'abstract': article.abstract, 'views': article.views,
'category': article.category, 'tags': article.tags.all(),
'created_time': article.created_time,
'link': '/articles/{}'.format(article.id)
}
if has_content:
info.update({'content': article.content})
articles_info.append(info)

return articles_info


# 获取给定文章的分页信息和当前页面的文章
def getPageInfoAndArticles(articles, current_page=1, pages_num=5):
paginator = Paginator(articles, pages_num)
if current_page < 0 or current_page > paginator.num_pages:
current_page = 1
current_articles = paginator.page(current_page)
page_info = {'pages': paginator.page_range, 'current': current_page,
'has_next': current_articles.has_next(), 'has_previous': current_articles.has_previous}
return page_info, current_articles


# 首页
def index(request):
# 获取当前请求的页面
try:
current_page = int(request.GET.get(
"page")) if request.GET.get("page") else 1
except:
current_page = 1
# 获取页面信息和当前页面的文章
page_info, articles = getPageInfoAndArticles(
Article.objects.all(), current_page=current_page, pages_num=5)
# 获取幻灯片
banners = Banner.objects.filter(is_active=True)
# 将所有内容传递给模板
content = {'page_info': page_info,
'banners': banners, 'articles': getArticlesInfo(articles)}
content.update(getGlobalParameter())
return render(request, 'blog/index.html', content)


# 文章内容页
def article(request, id):
article = Article.objects.get(id=id)
# 更新该文章阅读数
article.views += 1
article.save()
# 获取包括内容在内的文章信息, 并将文章信息加入content传递给模板
content = {'article': getArticlesInfo([article], has_content=True)[0]}
content.update(getGlobalParameter())
return render(request, 'blog/article.html', content)


def category(request, category):
# 获取当前请求的页面
try:
current_page = int(request.GET.get(
"page")) if request.GET.get("page") else 1
except:
current_page = 1
# 获取页面信息和当前页面的同类文章
page_info, articles = getPageInfoAndArticles(
Article.objects.filter(category__name=category), current_page=current_page, pages_num=5)
content = {'page_info': page_info, 'articles': getArticlesInfo(articles)}
content.update(getGlobalParameter())
return render(request, 'blog/category.html', content)


def tag(request, tag):
# 获取当前请求的页面
try:
current_page = int(request.GET.get(
"page")) if request.GET.get("page") else 1
except:
current_page = 1
# 获取页面信息和当前页面的同类文章
page_info, articles = getPageInfoAndArticles(
Article.objects.filter(tags__name=tag), current_page=current_page, pages_num=5)
content = {'page_info': page_info, 'articles': getArticlesInfo(articles)}
content.update(getGlobalParameter())
return render(request, 'blog/tag.html', content)


def search(request):
q = request.GET.get('q') # 获取搜索的关键词q
try:
current_page = int(request.GET.get(
"page")) if request.GET.get("page") else 1
except:
current_page = 1
content = {}
if q != None:
# 获取到搜索关键词通过标题进行匹配
page_info, articles = getPageInfoAndArticles(
Article.objects.filter(title__icontains=q), current_page=current_page, pages_num=5)
content = {'q': q,
'page_info': page_info,
'articles': getArticlesInfo(articles)}
content.update(getGlobalParameter())
return render(request, 'blog/search.html', content)

模板搭建

前端模板我是使用的bootstrap前端框架, 技术不够成熟, 只能做个简单演示.

django03

将各个功能分成多个HTML, 最终将其组合起来, 这样有利于修改.
最终首页HTML的实现就非常简单了:

1
2
3
4
5
6
7
8
9
10
11
12
13
{% extends "blog\includes\base.html" %}

{% block banner %}
{% include "blog\includes\banner.html" %}
{% endblock banner %}

{% block article_list %}
{% include "blog\includes\article_list.html" %}
{% endblock article_list%}

{% block pagination %}
{% include "blog\includes\pagination.html" %}
{% endblock pagination %}

其中所有页面都有的界面, 如分类, 热门文章, 标签, 页脚, 都放在了base.html, 其他所有页面都继承自base.html, 并在其界面基础上进行修改, 比如搜索界面, 就加入了搜索框, 并重写了pagination(分页)界面.

各个页面的实现

最终各个页面的成果也出来了:

首页:

分类页面:

标签页面:

文章页面:

搜索页面:

最后再说两句

该博客我已上传到 github , 有需要可以自取 myblog.
关于很多细节方便我没过多说明, 参考文献中的三个文档都有详细说明, 可以在那里面找到.

参考文献

[1] Django3.0官方文档
[2] Django Django教程 Django视频 Django实战 Django开发 - 刘江的Django教程
[3] Django中文网,Django中国,Django中文社区,django教程,Django!