Django作为一款成熟的Python Web开发框架提供了丰富的内置功能,如ORM(对象关系映射)、Admin管理界面、URL分发、模板系统、表单处理等,使得开发者能够快速搭建Web应用,大幅提高了开发效率。以前写过一篇博文《Django+Vue快速实现博客网站》介绍了通过Djang+Vue快速实现博客网站,django+vue作为个人博客来说稍显复杂,部署起来也比较麻烦,Vue的单页面架构也不利于SEO,更简单的解决方案其实还是用django的模板系统快速构建web应用,对于个人博客来说部署和运维更加简单也利于SEO。下面介绍如何快速的通过django模板系统快速实现个人博客。
一、工程目录组织结构
二、模型及管理实现
模型及管理端的实现沿用《Django+Vue快速实现博客网站》文章中的实现,用Django搭建很快很简单。
模型很简单,根据博客要显示的内容包括有‘文章分类’、‘文章标签’、‘博客文章’、‘站点信息’、‘社交信息’、‘聚焦’,模型定义分别如下: 这里要说明的是因为博客文章内容准备用markdown编写,所以引入了mdeditor from mdeditor.fields import MDTextField
内容字段content=MDTextField(verbose_name='内容')
模型代码示例如下:
1、模型
from django.db import models
from common.basemodel import BaseModel
from mdeditor.fields import MDTextField
# Create your models here.
'''文章分类'''
class BlogCategory(BaseModel):
id = models.AutoField(primary_key=True)
title = models.CharField(max_length=50,verbose_name='分类名称',default='')
href = models.CharField(max_length=100,verbose_name='分类路径',default='')
def __str__(self):
return self.title
class Meta:
verbose_name = '文章分类'
verbose_name_plural = '文章分类'
'''文章标签'''
class Tag(BaseModel):
id=models.AutoField(primary_key=True)
tag=models.CharField(max_length=20, verbose_name='标签')
def __str__(self):
return self.tag
class Meta:
verbose_name='标签'
verbose_name_plural='标签'
'''博客文章'''
class BlogPost(BaseModel):
id = models.AutoField(primary_key=True)
title = models.CharField(max_length=200, verbose_name='文章标题', unique = True)
category = models.ForeignKey(BlogCategory, blank=True,null=True, verbose_name='文章分类', on_delete=models.DO_NOTHING)
isTop = models.BooleanField(default=False, verbose_name='是否置顶')
isHot = models.BooleanField(default=False, verbose_name='是否热门')
isShow = models.BooleanField(default=False, verbose_name='是否显示')
summary = models.TextField(max_length=500, verbose_name='内容摘要', default='')
content = MDTextField(verbose_name='内容')
viewsCount = models.IntegerField(default=0, verbose_name="查看数")
commentsCount = models.IntegerField(default=0, verbose_name="评论数")
tags = models.ManyToManyField(to=Tag, related_name="tag_post", blank=True, default=None, verbose_name="标签")
blogSource = models.CharField(max_length=200, blank=True, null=True, default='',verbose_name='文章来源')
pubTime = models.DateTimeField(blank=True, null=True, verbose_name='发布日期')
@property
def tag_list(self):
return ','.join([i.tag for i in self.tags.all()])
def __str__(self):
return self.title
class Meta:
verbose_name = '博客文章'
verbose_name_plural = '博客文章'
'''站点信息'''
class Site(BaseModel):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=50, verbose_name='站点名称', unique = True)
avatar = models.CharField(max_length=200, verbose_name='站点图标')
slogan = models.CharField(max_length=200, verbose_name='站点标语')
domain = models.CharField(max_length=200, verbose_name='站点域名')
notice = models.CharField(max_length=200, verbose_name='站点备注')
desc = models.CharField(max_length=200, verbose_name='站点描述')
def __str__(self):
return self.name
class Meta:
verbose_name = '站点信息'
verbose_name_plural = '站点信息'
'''社交信息'''
class Social(BaseModel):
id = models.AutoField(primary_key=True)
title = models.CharField(max_length=20, verbose_name='标题')
icon = models.CharField(max_length=200, verbose_name='图标')
color = models.CharField(max_length=20, verbose_name='颜色')
href = models.CharField(max_length=100, verbose_name='路径')
def __str__(self):
return self.title
class Meta:
verbose_name = '社交信息'
verbose_name_plural = '社交信息'
'''聚焦'''
class Focus(BaseModel):
id = models.AutoField(primary_key=True)
title = models.CharField(max_length=20, verbose_name='标题')
img = models.CharField(max_length=100, verbose_name='路径')
def __str__(self):
return self.title
class Meta:
verbose_name = '聚焦'
verbose_name_plural = '聚焦'
'''友链'''
class Friend(BaseModel):
id = models.AutoField(primary_key=True)
siteName = models.CharField(max_length=20, verbose_name='友链站点名称')
path = models.CharField(max_length=100, verbose_name='地址路径')
desc = models.CharField(max_length=200, verbose_name='描述')
def __str__(self):
return self.siteName
class Meta:
verbose_name = '友链'
verbose_name_plural = '友链'
2、admin管理
实际上只要把模型注册到admin就可以了
from django.contrib import admin
from blog.models import *
# Register your models here.
@admin.register(BlogCategory)
class BlogCategoryAdmin(admin.ModelAdmin):
admin.site.site_title="ishareblog后台"
admin.site.site_header="ishareblog后台"
admin.site.index_title="ishareblog管理"
list_display = ['id', 'title', 'href']
@admin.register(BlogPost)
class BlogPostAdmin(admin.ModelAdmin):
list_display = ['title','category','isTop','isHot']
search_fields = ('title',)
@admin.register(Site)
class SiteAdmin(admin.ModelAdmin):
list_display = ['name','slogan','domain','desc']
@admin.register(Social)
class SocialAdmin(admin.ModelAdmin):
list_display = ['title','href']
@admin.register(Focus)
class FoucusAdmin(admin.ModelAdmin):
list_display = ['title','img']
@admin.register(Friend)
class FoucusAdmin(admin.ModelAdmin):
list_display = ['siteName','path','desc']
@admin.register(Tag)
class TagAdmin(admin.ModelAdmin):
list_display = ['id','tag']
三、博客展现实现
博客前端展现用django的模板技术实现。在网上找了一个基于Bootstrap v4.3.1的小清新风格HTML博客模板,https://gitee.com/yinqi/Light-Year-Blog 这个博客模只有三个页面,首页,详细页和About页面,样式和js都不多,比较简单。将html模板放入到templates的blog目录,为了便于维护将一些公共部分抽到了base.html,index.html和post.html 通过{ % extends 'blog/base.html' % }
进行应用
1、视图实现
from django.http import HttpResponse, Http404
from django.template import loader
from django.core.paginator import Paginator
from blog.models import BlogPost, Tag, BlogCategory
from django.shortcuts import render
from django.db.models import Count
from django.db.models.functions import TruncYear
import markdown2
# 首页/列表页视图实现.
def index(request):
category_id = request.GET.get('category')
tag_id = int(request.GET.get('tag',0))
year = request.GET.get('year')
search = request.GET.get('search')
if category_id:
blogpost_list = BlogPost.objects.filter(category=category_id, isShow=True).order_by('-isTop', '-pubTime')
elif tag_id:
blogpost_list = BlogPost.objects.filter(tags__id=tag_id, isShow=True).order_by('-isTop', '-pubTime')
elif year:
blogpost_list = BlogPost.objects.filter(pubTime__year=year, isShow=True).order_by('-isTop', '-pubTime')
elif search:
blogpost_list = BlogPost.objects.filter(content__icontains=search, isShow=True).order_by('-isTop', '-pubTime')
else:
# 筛选出需要显示的博客文章
blogpost_list = BlogPost.objects.filter(isShow=True).order_by('-isTop', '-pubTime', '-update_time')
# 每页显示的数量
per_page = 10
# 创建分页器实例
paginator = Paginator(blogpost_list, per_page)
# 获取当前页码,如果没有提供,则默认为第一页
page_number = request.GET.get('page') or 1
# 获取当前页的数据
page_obj = paginator.get_page(page_number)
# 计算显示的页码范围
current_page = int(page_number)
pages_to_show = 11 # 当前页前后各5页加上当前页共11页
start_page = max(current_page - 5, 1)
end_page = min(start_page + pages_to_show - 1, paginator.num_pages)
template = loader.get_template("blog/index.html")
context = {
"page_obj": page_obj,
'start_page': start_page,
'end_page': end_page,
'hot_posts': get_hot_posts(),
'tags': get_all_tags(),
'post_grouped_by_year':get_post_groped_by_year(),
'categories': get_categories(),
'category_id': category_id,
'tag_id': tag_id,
'year': year,
'search': search,
}
return HttpResponse(template.render(context, request))
# 详情页视图实现.
def post_detail(request, id):
try:
post_obj = BlogPost.objects.get(id=id)
html_content = markdown2.markdown(post_obj.content,
extras=["code-color", "fenced-code-blocks", "cuddled-lists", "tables",
"with-toc", "highlightjs-lang"])
html_content = html_content.replace('< table >', '< table class="table table-bordered" >')
html_content = html_content.replace('< img src=', '< img style="max-width:100%;height:auto;" src= ')
context = {
"post_obj": post_obj, "html_content": html_content, "hot_posts": get_hot_posts(),"tags": get_all_tags(),"post_grouped_by_year":get_post_groped_by_year(),'categories': get_categories()}
except BlogPost.DoesNotExist:
raise Http404("Post does not exist")
return render(request, "blog/post.html", context)
def get_hot_posts():
# 获取点赞数最高的前5篇文章
hot_posts = BlogPost.objects.filter(isShow=True).order_by('-viewsCount', '-pubTime')[:5]
return hot_posts
def get_all_tags():
# 获取所有的标签
tags = Tag.objects.all() # 获取所有的标签
return tags
def get_post_groped_by_year():
# 将发布日期截断为年份,并计算每年的文章数量。
post_grouped_by_year = (
BlogPost.objects
.annotate(year=TruncYear('pubTime'))
.values('year') # 返回的字典包含'year'键
.annotate(publication_count=Count('id')) # 计算每年的文章数量
.order_by('-year') # 按年排序
)
return post_grouped_by_year
def get_categories():
# 获取所有分类
categories = BlogCategory.objects.all()
return categories
2、模板实现
静态文件如css、js等放到static的blog目录,html模板文件放到templates的blog目录
在setting.py文件中配置 STATIC_URL = 'static/'
,在html模板文件中通过{% raw %}{ % load static % }{% endraw %}
将静态文件的地址引用进来
将公共部分抽取出来形成base.html
{ % load static % }
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>XieJava的博客</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="" />
<meta name="keywords" content="" />
<meta name="author" content="xiejava" />
<link rel="shortcut icon" type="image/x-icon" href="favicon.ico" />
<link rel="stylesheet" type="text/css" href="{ % static 'blog/css/bootstrap.min.css' % }" />
<link rel="stylesheet" type="text/css" href="{ % static 'blog/css/materialdesignicons.min.css' % }" />
<link rel="stylesheet" type="text/css" href="{ % static 'blog/css/style.min.css' % }" />
</head>
<body>
<header class="lyear-header text-center" style="background-image:url(images/left-bg.jpg);">
<div class="lyear-header-container">
<div class="lyear-mask"></div>
<h1 class="lyear-blogger pt-lg-4 mb-0"><a href="{ % url 'index' % }">XieJava的博客</a></h1>
<nav class="navbar navbar-expand-lg">
<a class="navbar-toggler" data-toggle="collapse" data-target="#navigation" aria-controls="navigation" aria-expanded="false" aria-label="Toggle navigation">
<div class="lyear-hamburger">
<div class="hamburger-inner"></div>
</div>
</a>
<div id="navigation" class="collapse navbar-collapse flex-column">
<div class="profile-section pt-3 pt-lg-0">
<img class="profile-image mb-3 rounded-circle mx-auto" src="https://img9.doubanio.com/icon/ul70489051-4.jpg" width="120" height="120" alt="xiejava" >
<div class="lyear-sentence mb-3">
记录最好的自己<br>
写是为了更好的思考,坚持写作,力争更好的思考。
</div>
<hr>
</div>
<ul class="navbar-nav flex-column text-center">
<li class="nav-item active">
<a class="nav-link" href="{ % url 'index' % }">首页</a>
</li>
{ % for category in categories % }
<li class="nav-item">
<a class="nav-link" href="{ % url 'index' % }?category={ { category.id } }">{ { category.title } }</a>
</li>
{ % endfor % }
<li class="nav-item">
<a class="nav-link" href="{ % url 'index' % }">关于我</a>
</li>
</ul>
<div class="my-2 my-md-3">
<form class="lyear-search-form form-inline justify-content-center pt-3">
<input type="text" id="search" name="search" class="form-control mr-md-1" placeholder="搜索关键词" />
</form>
</div>
</div>
</nav>
</div>
</header>
<div class="lyear-wrapper">
<section class="mt-5 pb-5">
<div class="container">
<div class="row">
<!-- 文章列表 -->
<div class="col-xl-8">
<!-- 内容 -->
{ % block content % }
<!-- 默认内容 -->
{ % endblock % }
</div>
<!-- 内容 end -->
<!-- 侧边栏 -->
<div class="col-xl-4">
<div class="lyear-sidebar">
<!-- 热门文章 -->
<aside class="widget widget-hot-posts">
<div class="widget-title">热门文章</div>
<ul>
{ % for post in hot_posts % }
<li>
<a href="{ % url 'post_detail' id=post.id % }">{ { post.title } }</a> <span>{ { post.pubTime } }</span>
</li>
{ % endfor % }
</ul>
</aside>
<!-- 归档 -->
<aside class="widget">
<div class="widget-title">归档</div>
<ul>
{ % for post in post_grouped_by_year % }
<li><a href="{ % url 'index' % }?year={ { post.year|date:'Y' } }" >{ % if year == post.year|date:'Y' % }<b>{ { post.year|date:'Y' } } 年 </b>{ % else % }{ { post.year|date:'Y' } } 年{ % endif % }</a> ({ { post.publication_count } })</li>
{ % endfor % }
</ul>
</aside>
<!-- 标签 -->
<aside class="widget widget-tag-cloud">
<div class="widget-title">标签 </div>
<div class="tag-cloud">
{ % for tag in tags % }
<a href="{ % url 'index' % }?tag={ { tag.id } }" { % if tag_id == tag.id % }class="badge badge-primary"{ % else % }class="badge badge-light"{ % endif % }>{ { tag.tag } }</a>
{ % endfor % }
</div>
</aside>
</div>
</div>
<!-- 侧边栏 end -->
</div>
</div>
<!-- end container -->
</section>
</div>
<script type="text/javascript" src="{ % static 'blog/js/jquery.min.js' % }"></script>
<script type="text/javascript" src="{ % static 'blog/js/jquery.nicescroll.min.js' % }"></script>
<script type="text/javascript" src="{ % static 'blog/js/bootstrap.min.js' % }"></script>
<script type="text/javascript" src="{ % static 'blog/js/main.min.js' % }"></script>
</body>
</html>
博客首页/列表页
通过{ % extends 'blog/base.html' % }
将公共部门引入进来后index.html的内容就简洁了很多
index.html
{ % extends 'blog/base.html' % }
<!-- 内容 -->
{ % block content % }
{ % if page_obj.object_list.count > 0 % }
{ % for blogpost in page_obj.object_list % }
<article class="lyear-arc">
<div class="arc-header">
<h2 class="arc-title"><a href="article/{ { blogpost.id } }">{ { blogpost.title } }</a></h2>
<ul class="arc-meta">
<li><i class="mdi mdi-calendar"></i> { { blogpost.pubTime } }</li>
<li><i class="mdi mdi-tag-text-outline">
</i> { % for tag in blogpost.tags.all % }<a href="{ % url 'index' % }?tag={ { tag.id } }">{ { tag.tag } }</a> { % endfor % }</li>
<!--<li><i class="mdi mdi-comment-multiple-outline"></i> <a href="#">3 评论</a></li>-->
<li><i class="mdi mdi-heart-outline"></i> <a href="#">{ { blogpost.viewsCount } } 喜欢</a></li>
</ul>
</div>
<div class="arc-synopsis">
<p>{ { blogpost.summary } }</p>
</div>
</article>
{ % endfor % }
<!-- 分页 -->
<div class="row">
<div class="col-lg-12">
<ul class="pagination">
{ % if page_obj.has_previous % }
<li class="page-item"><a class="page-link" href="?page={ { page_obj.previous_page_number } }"><i class="mdi mdi-chevron-left"></i></a></li>
{ % endif % }
{ % for page_no in page_obj.paginator.page_range % }
{ % if page_no >= start_page and page_no <= end_page % }
{ % if page_no == page_obj.number % }
<li class="page-item active"><a class="page-link" href="#">{ { page_no } }</a></li>
{ % else % }
<li class="page-item"><a class="page-link" href="?page={ { page_no } }{ % if tag_id % }&tag={ { tag_id } }{ % endif % }{ % if year % }&year={ { year } }{ % endif % }{ % if search % }&search={ { search } }{ % endif % }">{ { page_no } }</a></li>
{ % endif % }
{ % endif % }
{ % endfor % }
{ % if page_obj.has_next % }
<li class="page-item"><a class="page-link" href="?page={ { page_obj.next_page_number } }{ % if tag_id % }&tag={ { tag_id } }{ % endif % }{ % if year % }&year={ { year } }{ % endif % }{ % if search % }&search={ { search } }{ % endif % }"><i class="mdi mdi-chevron-right"></i></a></li>
{ % endif % }
<p>总页数: { { page_obj.paginator.num_pages } }</p>
</ul>
</div>
</div>
{ % else % }
<p> 没有找到文章 </p>
{ % endif % }
<!-- 分页 end -->
{ % endblock % }
<!-- 内容 end -->
博客详情页post.html
{ % extends 'blog/base.html' % }
<!-- 文章阅读 -->
{ % block content % }
<article class="lyear-arc">
<div class="arc-header">
<h2 class="arc-title"><a href="#">{ { post_obj.title } }</a></h2>
<ul class="arc-meta">
<li><i class="mdi mdi-calendar"></i> { { post_obj.pubTime } }</li>
<li> { % for tag in post_obj.tags.all % }<a href="{ % url 'index' % }?tag={ { tag.id } }">{ { tag.tag } }</a>{ % endfor % }</li>
<!--<li><i class="mdi mdi-comment-multiple-outline"></i> <a href="#">3 评论</a></li>-->
<li><i class="mdi mdi-heart-outline"></i> <a href="#">{ { post_obj.viewsCount } } 喜欢</a></li>
</ul>
</div>
<div class="arc-preview">
<img src="images/blog/post-1.png" alt="" class="img-fluid rounded" />
</div>
<div class="lyear-arc-detail">
{ { html_content|safe } }
</div>
</article>
{ % endblock % }
<!-- 内容 end -->
四、部署及效果
在部署之前执行python manage.py collectstatic
将admin等其他模块用到的静态文件统一输出到static的目录。
通过 python manage.py runserver
启动应用就可以看到效果。
实际效果见 http://iblog.ishareread.com/
博客首页
博客详情页
五、源代码
所有源代码及说明见 https://gitee.com/xiejava/ishareblog