说在前面
这里重点介绍Django后端, 前端我用的是bootstrap5前端组件库, 水平不够就不进行过多介绍了.
开发环境搭建
- 使用的python版本为Python3.8;
- 使用的django版本为Django3.0;
- 使用的编辑器为vscode.
项目需求分析
这里我要搭建的是一个普通的blog, 需要的是blog的基础功能, 包括:
- 导航栏
- 横幅幻灯片
- 文章列表
- 文章分类
- 文章标签
- 文章内容
- 文章浏览量
- 文章搜索
- 热门排行
其实也没多少东西, 包含的页面主要有:
- 博客首页
- 文章分类页
- 文章标签页
- 文章搜索页
- 文章内容页
数据库设计
文章表结构
表字段 |
字段类型 |
备注 |
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
| LANGUAGE_CODE = 'zh-hans' TIME_ZONE = 'Asia/Shanghai' USE_TZ = True
|
在INSTALL_APPS中添加应用:
1 2 3 4
| INSTALLED_APPS = [ 'blog.apps.BlogConfig', ... ]
|
配置数据库:
这里我用的是mysql数据库(默认是使用sqlite3), 需要安装 MySQLdb 才能使用.
pip install MySQLdb
并在blog/__init__.py
中添加 import MySQLdb.
1 2 3 4 5 6 7 8 9 10 11
| DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'myblog', 'USER': 'name', 'PASSWORD': 'password', 'HOST': '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
|
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: 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) 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')
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)
|
此时后台站点管理的界面是这样的:
添加文章界面是这样的:
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 = [ path('admin/', admin.site.urls), re_path(r'^media/(?P<path>.*)$', static.serve, {'document_root': settings.MEDIA_ROOT}), 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
| from django.urls import path
from . import views
app_name = 'blog' urlpatterns = [ path('', views.index, name='index'), path('articles/<int:id>/', views.article, name='articles), # 同分类文章列表的url, 并将category传给views path('categories/<str:category>/', views.category, name='categories'), # 同标签文章列表的url, 并将tag传给views path('tags/<str:tag>/', views.tag, name='tags'), # 搜索文章的url path('search/', views.search, name='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 = {'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') 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前端框架, 技术不够成熟, 只能做个简单演示.
将各个功能分成多个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(分页)界面.
各个页面的实现
最终各个页面的成果也出来了:
首页:
[{"url":"https://i.loli.net/2020/07/24/DbG9EdNuwcrBMfJ.png","alt":"首页大屏页面"},{"url":"https://i.loli.net/2020/07/25/w2RAtaMZoXxridg.png","alt":"首页小屏页面"}]
分类页面:
[{"url":"https://i.loli.net/2020/07/25/aQu1xNJfmwcl7tO.png","alt":"分类大屏页面"},{"url":"https://i.loli.net/2020/07/25/RpAsDeI8wB2FzS5.png","alt":"分类小屏页面"}]
标签页面:
[{"url":"https://i.loli.net/2020/07/25/j5qJkmdcY8AyvfR.png","alt":"标签大屏界面"},{"url":"https://i.loli.net/2020/07/25/auGJwUzNcjOvPlt.png","alt":"标签小屏界面"}]
文章页面:
[{"url":"https://i.loli.net/2020/07/25/osezSb28gXvkJRd.png","alt":"文章大屏界面"},{"url":"https://i.loli.net/2020/07/25/vFIx6cR2eGNoprZ.png","alt":"文章小屏界面"}]
搜索页面:
[{"url":"https://i.loli.net/2020/07/25/PDRTGqlAHxmCJ1M.png","alt":"搜索大屏界面.png"},{"url":"https://i.loli.net/2020/07/25/DyoxB8FMpLncYw1.png","alt":"搜索小屏界面.png"}]
最后再说两句
该博客我已上传到 github , 有需要可以自取 myblog.
关于很多细节方便我没过多说明, 参考文献中的三个文档都有详细说明, 可以在那里面找到.
参考文献
[1] Django3.0官方文档
[2] Django Django教程 Django视频 Django实战 Django开发 - 刘江的Django教程
[3] Django中文网,Django中国,Django中文社区,django教程,Django!