前言
前两天在Django群里看到国光大佬说用Django重写了自己博客后台,顺手点进他的Blog看了下,瞬间喜欢上了他博客的风格,经过一番研究,顺利给自己也部署了一个Blog,使用Hexo框架的Matery主题,全部xml配置,写文章直接到指定目录用Typora写.md文档,然后两行命令生成静态页面和发布,总之简单易用,非常Nice……
搞完自己博客突然发现压根就没有地方用到后台管理系统,所以今天克隆国光大佬改造的后台源码来学习,一方面深入理解Django,另一方面希望可以从这入门前端,把页面做得好看点。
集中贴上用到的项目Git项目网址:
博客主题:https://github.com/blinkfox/hexo-theme-matery
Django-admin优化simpleui项目:https://github.com/newpanjing/simpleui
国光大佬重写Matery的项目地址:https://github.com/sqlsec/Django-Hexo-Matery
一、环境准备
首先进入要存放项目的地方,克隆国光大佬的源码下来:
git clone https://github.com/sqlsec/Django-Hexo-Matery.git
打开目录看看,是一个Django项目的结构
安装python虚拟环境:
mkvirtualenv py-django-matery
打开项目源码发现里面没有python依赖包清单,那就先安装自己已知的,然后允许项目按报错一个个补充吧,最终安装了如下包:
#django框架
pip install django
#django优化的admin框架
pip install django-simpleui
#django列表分页的依赖包
pip install django-pure-pagination
#目前最快纯Python MarkDown解析器
pip install mistune
#django富文本编辑器
pip install django-mdeditor
#django导入导出插件
pip install django-import_export
#mysqlclient数据库驱动
pip install mysqlclient
打开setting.py,看到使用的是Mysql,我已安装有,只需要修改对应连接账号密码,并创建好数据库做数据迁移:
# 数据库配置
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'django-blog', #换成自己的数据库名
'USER': 'root',
'PASSWORD': 'P@ssw0rd', #改成自己的密码
'HOST': '127.0.0.1'
}
}
数据迁移,如果提示No changes detected,那么先删掉migrations文件夹下已有的迁移文件:
#生成迁移文件
python manage.py makemigrations
#数据迁移生成数据库表
python manage.py migrate
创建超级用户:
python manage.py createsuperuser
一切就绪,运行Django项目调试服务器,看看能否正常运行网站:
python manage.py runserver 0.0.0.0:8999
打开浏览器访问:http://127.0.0.1:8999/admin
登陆进去:
查看博客首页:
二、内容分析
Modles & Admin后台
后台Modles共有6个,具体如下:
Tags:文章标签
包含如下内容:
序号 | 字段名 | 中文名 | 说明 |
---|---|---|---|
1 | name | 标签名称 | 标签名称 |
2 | get_items | 文章数 | 自定义字段,用来统计包含本标签的文章数 |
Modle代码:
class Tag(models.Model):
"""
文章标签
"""
name = models.CharField(max_length=30, verbose_name='标签名称')
# 统计文章数 并放入后台
def get_items(self):
return len(self.article_set.all())
get_items.short_description = '文章数'
class Meta:
verbose_name = '标签'
verbose_name_plural = verbose_name
def __str__(self):
return self.name
admin界面如下:
admin代码:
# 标签
@admin.register(Tag)
class TagAdmin(admin.ModelAdmin):
list_display = ('name', 'get_items')
search_fields = ('name', )
readonly_fields = ('get_items',)
list_per_page = 20
Category:文章分类
包含如下内容:
序号 | 字段名 | 中文名 | 说明 |
---|---|---|---|
1 | name | 分类名称 | CharField |
2 | index | 分类排序 | 首页菜单显示顺序 |
3 | active | 是否添加到菜单 | 是否添加到菜单 |
4 | icon | 菜单图标 | 存储菜单图标url |
5 | get_items | 文章数 | 自定义字段,用来统计包含本分类的文章数 |
6 | icon_data | 图标预览 | 自定义图标显示标签字段 |
Modle代码:
class Category(models.Model):
"""
文章分类
"""
name = models.CharField(max_length=30, verbose_name='分类名称')
index = models.IntegerField(default=99, verbose_name='分类排序')
active = models.BooleanField(default=True, verbose_name='是否添加到菜单')
icon = models.CharField(max_length=30, default='fa-home',verbose_name='菜单图标')
# 统计文章数 并放入后台
def get_items(self):
return len(self.article_set.all())
def icon_data(self):
return format_html(
'<i class="{}"></i>',
self.icon,
)
get_items.short_description = '文章数'
icon_data.short_description = '图标预览'
class Meta:
verbose_name = '分类'
verbose_name_plural = verbose_name
def __str__(self):
return self.name
admin界面如下:
admin代码:
# 分类
@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
list_display = ('name', 'index', 'active', 'get_items', 'icon', 'icon_data')
search_fields = ('name', )
list_editable = ('active', 'index', 'icon')
readonly_fields = ('get_items',)
Article:文章
包含如下内容:
序号 | 字段名 | 中文名 | 说明 |
---|---|---|---|
1 | title | 文章标题 | |
2 | desc | 文章描述 | |
3 | cover | 文章封面 | |
4 | content | 文章内容 | 富文本字段,Admin是Markdown编辑框 |
5 | click_count | 点击次数 | 下面定义了自增函数 |
6 | is_recommend | 是否推荐 | |
7 | add_time | 发布时间 | |
8 | update_time | 更新时间 | |
9 | category | 文章分类 | 外键 |
10 | tag | 文章标签 | 多对多字段 |
11 | cover_data | 文章封面 | 返回图片html标签,定义了图片大小 |
12 | cover_admin | 文章封面2 | 返回图片html标签,大小不一样 |
13 | viewed | 阅读数自增函数 | 自定义自增函数 |
Model代码:
class Article(models.Model):
"""
文章
"""
title = models.CharField(max_length=50, verbose_name='文章标题')
desc = models.TextField(max_length=100, verbose_name='文章描述')
cover = models.CharField(max_length=200, default='https://image.3001.net/images/20200304/15832956271308.jpg', verbose_name='文章封面')
content = MDTextField(verbose_name='文章内容')
click_count = models.IntegerField(default=0, verbose_name='点击次数')
is_recommend = models.BooleanField(default=False, verbose_name='是否推荐')
add_time = models.DateTimeField(default=datetime.now, verbose_name='发布时间')
update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间')
category = models.ForeignKey(Category, blank=True, null=True, verbose_name='文章分类', on_delete=models.CASCADE)
tag = models.ManyToManyField(Tag, verbose_name='文章标签')
def cover_data(self):
return format_html(
'<img src="{}" width="156px" height="98px"/>',
self.cover,
)
def cover_admin(self):
return format_html(
'<img src="{}" width="440px" height="275px"/>',
self.cover,
)
def viewed(self):
"""
增加阅读数
"""
self.click_count += 1
self.save(update_fields=['click_count'])
cover_data.short_description = '文章封面'
cover_admin.short_description = '文章封面'
class Meta:
verbose_name = '文章'
verbose_name_plural = verbose_name
def __str__(self):
return self.title
admin界面如下:
admin代码:
# 文章
@admin.register(Article)
class ArticleAdmin(ImportExportModelAdmin):
list_display = ('title', 'category', 'cover_data', 'is_recommend', 'add_time', 'update_time')
search_fields = ('title', 'desc', 'content')
list_filter = ('category', 'tag', 'add_time')
list_editable = ('category', 'is_recommend')
readonly_fields = ('cover_admin', )
list_per_page = 15
fieldsets = (
('编辑文章', {
'fields': ('title', 'content')
}),
('其他设置', {
'classes': ('collapse', ),
'fields': ('cover', 'cover_admin', 'desc', 'is_recommend', 'click_count', 'tag', 'category', 'add_time'),
}),
)
formfield_overrides = {
models.CharField: {'widget': TextInput(attrs={'size': '59'})},
models.TextField: {'widget': Textarea(attrs={'rows': 4, 'cols': 59})},
}
Comment:文章评论
包含如下内容:
序号 | 字段名 | 中文名 | 说明 |
---|---|---|---|
1 | content | 评论内容 | |
2 | username | 用户名 | 文本录入的评论人名称 |
3 | add_time | 发布时间 | |
4 | article | 文章 | 关联的文章 |
5 | pid | 父级评论 | 关联的父级评论 |
Model代码:
class Comment(models.Model):
"""
文章评论
"""
content = models.TextField(verbose_name='评论内容')
username = models.CharField(max_length=30, blank=True, null=True, verbose_name='用户名')
add_time = models.DateTimeField(auto_now_add=True, verbose_name='发布时间')
article = models.ForeignKey(Article, blank=True, null=True, verbose_name='文章', on_delete=models.CASCADE)
pid = models.ForeignKey('self', blank=True, null=True, verbose_name='父级评论', on_delete=models.CASCADE)
class Meta:
verbose_name = '评论'
verbose_name_plural = verbose_name
def __str__(self):
return self.content[:20]
admin界面如下:
admin代码:
# 评论
@admin.register(Comment)
class CommentAdmin(ImportExportModelAdmin):
list_display = ('content', 'username', 'article')
search_fields = ('content', 'username', 'article')
list_display = ('add_time', )
目前评论功能并未完成,暂无法使用!
Links:友情链接
包含如下内容:
序号 | 字段名 | 中文名 | 说明 |
---|---|---|---|
1 | title | 标题 | |
2 | url | 地址 | |
3 | desc | 描述 | |
4 | image | 头像地址 | |
5 | avatar_data | 头像预览 | 返回图片HTML标签 |
Model代码:
class Links(models.Model):
"""
友情链接
"""
title = models.CharField(max_length=50, verbose_name='标题')
url = models.URLField(verbose_name='地址')
desc = models.TextField(verbose_name='描述', max_length=250)
image = models.URLField(default='https://image.3001.net/images/20190330/1553875722169.jpg', verbose_name='头像')
def avatar_data(self):
return format_html(
'<img src="{}" width="50px" height="50px" style="border-radius: 50%;" />',
self.image,
)
def avatar_admin(self):
return format_html(
'<img src="{}" width="250px" height="250px"/>',
self.image,
)
avatar_data.short_description = '头像'
avatar_admin.short_description = '头像预览'
class Meta:
verbose_name = '友链'
verbose_name_plural = verbose_name
def __str__(self):
return self.url
admin界面如下:
admin代码:
# 友链
@admin.register(Links)
class LinksAdmin(ImportExportModelAdmin):
list_display = ('title', 'url', 'avatar_data', 'desc')
search_fields = ('title', 'url', 'desc')
readonly_fields = ('avatar_admin', )
list_editable = ('url',)
fieldsets = (
(None, {
'fields': ('title', 'url', 'desc', 'avatar_admin', 'image', )
}),
)
formfield_overrides = {
models.CharField: {'widget': TextInput(attrs={'size': '59'})},
models.TextField: {'widget': Textarea(attrs={'rows': 4, 'cols': 59})},
}
Site:站点配置
包含如下内容:
序号 | 字段名 | 中文名 | 说明 |
---|---|---|---|
1 | desc | 网站描述 | |
2 | keywords | 网站关键词 | |
3 | title | 网站标题 | |
4 | index_title | 首页标题 | |
5 | type_chinese | 座右铭汉语 | 打字机对应的汉语 |
6 | type_english | 座右铭英语 | 打字机对应的英语 |
7 | icp_number | 备案号 | |
8 | icp_url | 备案链接 | |
9 | site_mail | 我的邮箱 | 个人信息邮箱 |
10 | site_qq | 我的QQ | 个人信息QQ |
11 | site_avatar | 我的头像 |
Model代码:
class Site(models.Model):
"""
站点配置
"""
desc = models.CharField(max_length=50, verbose_name='网站描述')
keywords = models.CharField(max_length=50, verbose_name='网站关键词')
title = models.CharField(max_length=50, verbose_name='网站标题')
index_title = models.CharField(max_length=50, verbose_name='首页标题')
type_chinese = models.CharField(max_length=50, verbose_name='座右铭汉语')
type_english = models.CharField(max_length=80, verbose_name='座右铭英语')
icp_number = models.CharField(max_length=20, verbose_name='备案号')
icp_url = models.CharField(max_length=50, verbose_name='备案链接')
site_mail = models.CharField(max_length=50, verbose_name='我的邮箱')
site_qq = models.CharField(max_length=50, verbose_name='我的QQ')
site_avatar = models.CharField(max_length=200, default='https://image.3001.net/images/20171226/15142933784705.png', verbose_name='我的头像')
class Meta:
verbose_name = '网站设置'
verbose_name_plural = verbose_name
def __str__(self):
return self.title
站点配置通常基本固定,不会随意调整,因此没放到Admin里面,另外我看到setting里面也对这些配置做了定义,后面学习Templates再确认到底用的哪一个。
Views & URLS & Templates
接下来,我们从展示的Blog界面,来了解对应的URL、模板、视图函数。
响应逻辑
用户通过浏览器访问URL,服务器接收到请求后由urls.py里面的路由配置确定使用哪个Views视图函数,Views视图函数里面确定了返回哪些数据和哪个Templates模板,用户浏览器接收到数据和模板后组合渲染成页面展示出来。
首先我们访问 http://12.0.0.1:8999 不加其他路径,查看URLS,可以看到对应的Views视图类为Index :
Index类内容如下:
class Index(View):
"""
首页展示
"""
def get(self, request):
#获取所有文章,按新增时间倒序
all_articles = Article.objects.all().order_by('-add_time')
#获取所有推荐的文章
top_articles = Article.objects.filter(is_recommend=1)
# 首页分页功能
try:
page = request.GET.get('page', 1)
except PageNotAnInteger:
page = 1
p = Paginator(all_articles, 9, request=request)
articles = p.page(page)
return render(request, 'index.html', {
'all_articles': articles,
'top_articles': top_articles,
})
index视图调用index.html模板,同时提供‘all_articlels’和‘top_articles’两部分数据给模板。模板内容如下:
{% extends 'base.html' %}
{% block banner %}
{% ifequal all_articles.number 1 %}
<div id="page" class="carousel carousel-slider center index-cover" data-indicators="true" style="margin-top: -64px;">
<div class="carousel-item red white-text bg-cover about-cover">
<div class="container">
{% include 'banner.html' %}
<!-- 开始阅读 和 社交链接 -->
<div class="cover-btns">
<a href="#indexCard" class="waves-effect waves-light btn">
<i class="fas fa-angle-double-down"></i>开始阅读
</a>
</div>
<div class="cover-social-link">
<a href="mailto:{{ SITE_MAIL }}" class="tooltipped" target="_blank" data-tooltip="邮件联系我" data-position="top" data-delay="50">
<i class="fas fa-envelope-open"></i>
</a>
<a href="/article/" class="tooltipped" target="_blank" data-tooltip="文章更新动态" data-position="top" data-delay="50">
<i class="far fa-calendar-alt"></i>
</a>
</div>
</div>
</div>
</div>
{% else %}
<div class="bg-cover pd-header about-cover">
{% include 'banner.html' %}
</div>
{% endifequal %}
{% endblock %}
{% block contents %}
{% ifequal all_articles.number 1 %}
<div id="indexCard" class="index-card">
<div class="container ">
<div class="card">
<div class="card-content">
<div class="dream">
<div class="title center-align">
<i class="far fa-lightbulb"></i> 宁静致远
</div>
<div class="row">
<div class="col l8 offset-l2 m10 offset-m1 s10 offset-s1 center-align text">
__画船听雨:你看得到我打在屏幕上的字,看不到我落在键盘上的泪。 Kali Linux:The quieter you become, the more you are able to hear. 冰心:成功的花,人们只惊羡她现时的明艳!然而当初她的芽儿,浸透了奋斗的泪泉,洒遍了牺牲的血雨。
</div>
</div>
</div>
<div id="recommend-sections" class="recommend">
<div class="title"><i class="far fa-thumbs-up"></i> 推荐文章</div>
<div class="row">
{% for i in top_articles %}
<div class="col s12 m6">
<div class="post-card" style="background-image: url('{{ i.cover }}')">
<div class="post-body">
<div class="post-categories">
<a href="{% url 'article_category' i.category_id %}" class="category">{{ i.category }}</a>
</div>
<a href="{% url 'detail' i.id %}">
<h3 class="post-title">{{ i.title }}</h3>
</a>
<p class="post-description">
{{ i.desc }}
</p>
<a href="{% url 'detail' i.id %}" class="read-more btn waves-effect waves-light" style="background: linear-gradient(to right, #FF5E3A 0%, #FF2A68 100%)" target="_blank">
<i class="icon far fa-eye fa-fw"></i>阅读更多
</a>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
</div>
</div>
</div>
{% endifequal %}
<main class="content">
<article id="articles" class="container articles">
<div class="row article-row">
{% for article in all_articles.object_list %}
<div class="article col s12 m6 l4" data-aos="zoom-in">
<div class="card">
<a href="{% url 'detail' pk=article.id %}">
<div class="card-image">
<img src="/2020/05/17/dui-guo-guang-da-lao-django-chong-xie-hexo-bo-ke-xiang-mu-de-xue-xi/{{ article.cover }}" class="responsive-img" alt="{{ article.title }}">
<span class="card-title">{{ article.title }}</span>
</div>
</a>
<div class="card-content article-content">
<div class="summary block-with-text">
{{ article.desc }}
</div>
<div class="publish-info">
<span class="publish-date">
<i class="far fa-clock fa-fw icon-date"></i>{{ article.add_time | date:"Y-m-d"}}
</span>
<span class="publish-author">
<i class="fas fa-bookmark fa-fw icon-category"></i>
<a href="{% url 'article_category' article.category_id %}" class="post-category">
{{ article.category }}
</a>
</span>
</div>
</div>
<div class="card-action article-tags">
{% for tag in article.tag.all %}
<a href="{% url 'article_tag' tag.id %}">
<span class="chip tag-color">{{ tag }}</span>
</a>
{% endfor %}
</div>
</div>
</div>
{% endfor %}
</div>
</article>
</main>
{% endblock %}
{% block pagination %}
<div class="container paging">
<div class="row">
<div class="col s6 m4 l4">
{% if all_articles.has_previous %}
<a href="?{{ all_articles.previous_page_number.querystring }}" class="left btn-floating btn-large waves-effect waves-light left-color">
{% else %}
<a class="left btn-floating btn-large disabled">
{% endif %}
<i class="fas fa-angle-left"></i>
</a>
</a></div>
<div class="page-info col m4 l4 hide-on-small-only">
<div class="center-align b-text-gray">{{ all_articles.number }} / {{ all_articles.pages | last }}</div>
</div>
<div class="col s6 m4 l4">
{% if all_articles.has_next %}
<a href="?{{ all_articles.next_page_number.querystring }}" class="right btn-floating btn-large waves-effect waves-light right-color">
{% else%}
<a class="right btn-floating btn-large disabled">
{% endif %}
<i class="fas fa-angle-right"></i>
</a>
</a></div>
</div>
</div>
{% endblock %}
可以看到首页模板首先继承了base.html模板,打开base.html可以看到这里面写好了CSS/JS引用、页眉logo、菜单(包括分类文章的菜单)、页脚等公共内容,后续页面模板都可以继承它,然后只关注核心内容:
<!DOCTYPE HTML>
<html lang="zh-CN">
{% load static %}
<head>
<meta charset="utf-8">
<meta name="keywords" content="{{ SITE_KEY}}">
<meta name="description" content="{{ SITE_DESC }}">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<meta name="renderer" content="webkit|ie-stand|ie-comp">
<meta name="mobile-web-app-capable" content="yes">
<meta name="format-detection" content="telephone=no">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<title>{% block title %}{% endblock %}{{ SITE_NAME }}
<link rel="icon" type="image/png" href="https://image.3001.net/images/20191031/15724874583730.png">
<link rel="stylesheet" type="text/css" href="{% static 'css/all.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'css/materialize.min.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'css/aos.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'css/animate.min.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'css/lightgallery.min.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'css/matery.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'css/my.css' %}">
{% block css %}{% endblock %}
{% block js %}{% endblock %}
<script src="{% static 'js/jquery-2.2.0.min.js' %}"></script>
<link rel="stylesheet" href="/css/prism-tomorrow.css" type="text/css">
<body>
<header class="navbar-fixed">
<nav id="headNav" class="bg-color nav-transparent">
<div id="navContainer" class="nav-wrapper head-container">
<div class="brand-logo">
<a href="/" class="waves-effect waves-light">
<img src="https://image.3001.net/images/20191031/15724874583730.png" class="logo-img" alt="LOGO">
<span class="logo-span">国光</span>
</a>
</div>
<a href="#" data-target="mobile-nav" class="sidenav-trigger button-collapse"><i class="fas fa-bars"></i></a>
<ul class="right nav-menu">
<li class="hide-on-med-and-down nav-item">
<a href="/" class="waves-effect waves-light">
<i class="fas fa-home" style="zoom: 0.6;"></i>
<span>首页</span>
</a>
</li>
{% for category in active_categories %}
<li class="hide-on-med-and-down nav-item">
<a href="{% url 'article_category' category.id %}" class="waves-effect waves-light">
<i class="{{ category.icon }}" style="zoom: 0.6;"></i>
<span>{{ category.name }}</span>
</a>
</li>
{% endfor %}
<li class="hide-on-med-and-down nav-item">
<a href="http://ctf.njupt.edu.cn/members" target="_blank" rel="noopener" class="waves-effect waves-light">
<i class="fab fa-xing" style="zoom: 0.6;"></i>
<span>X1cT34m</span>
</a>
</li>
<li class="hide-on-med-and-down nav-item">
<a href="{% url 'friends' %}" class="waves-effect waves-light">
<i class="fab fa-ravelry" style="zoom: 0.6;"></i>
<span>友情链接</span>
</a>
</li>
<li class="hide-on-med-and-down nav-item">
<a href="/about/" class="waves-effect waves-light">
<i class="fab fa-gg" style="zoom: 0.6;"></i><span>关于</span>
</a>
</li>
<li>
<a href="#searchModal" class="modal-trigger waves-effect waves-light">
<i id="searchIcon" class="fas fa-search" title="搜索" style="zoom: 0.85;"></i>
</a>
</li>
</ul>
<!-- 手机菜单栏 -->
<div id="mobile-nav" class="side-nav sidenav">
<div class="mobile-head bg-color">
<img src="https://image.3001.net/images/20191031/15724874583730.png" class="logo-img circle responsive-img">
<div class="logo-name">国光</div>
<div class="logo-desc">
白帽子国光的信息安全学习记录博客
</div>
</div>
<ul class="menu-list mobile-menu-list">
<li class="m-nav-item">
<a href="/" class="waves-effect waves-light">
<i class="fa-fw fas fa-home"></i>
首页
</a>
</li>
{% for category in active_categories %}
<li class="m-nav-item">
<a href="{% url 'article_category' category.id %}" class="waves-effect waves-light">
<i class="fa-fw {{ category.icon }}"></i>
{{ category.name }}
</a>
</li>
{% endfor %}
<li class="m-nav-item">
<a href="http://ctf.njupt.edu.cn/members" target="_blank" rel="noopener" class="waves-effect waves-light">
<i class="fa-fw fab fa-xing"></i>
X1cT34m
</a>
</li>
<li class="m-nav-item">
<a href="{% url 'friends' %}" class="waves-effect waves-light">
<i class="fa-fw fab fa-ravelry"></i>
友情链接
</a>
</li>
<li class="m-nav-item">
<a href="/about/" class="waves-effect waves-light">
<i class="fa-fw fab fa-gg"></i>
关于
</a>
</li>
</ul>
</div>
</div>
</nav>
</header>
{% block custom_style %}
<style>
.carousel-control {
width: 45px;
height: 45px;
line-height: 55px;
border-radius: 45px;
background: transparent;
cursor: pointer;
z-index: 100;
}
#prev-cover {
position: absolute;
top: 48%;
left: 8px;
}
#next-cover {
position: absolute;
top: 48%;
right: 8px;;
}
#prev-cover i {
margin-right: 3px;
}
#next-cover i {
margin-left: 3px;
}
.carousel-control:hover {
background-color:rgba(0, 0, 0, .4);
}
.carousel-control i {
color: #fff;
font-size: 2.4rem;
}
html, body {
height:100%;
}
#page {
height:100%;
min-height: 100%;
width:100%;
}
</style>
{% endblock %}
{% block banner %}
{% endblock %}
<script>
$(function () {
let coverSlider = $('.carousel');
coverSlider.carousel({
duration: Number('120'),
fullWidth: true,
indicators: 'false' === 'true'
});
let carouselIntervalId;
let restartPlay = function () {
};
});
</script>
{% block contents %}
{% endblock %}
<!-- 翻页按钮 -->
{% block pagination %}
{% endblock %}
<!-- 页脚 -->
<footer class="page-footer bg-color">
<div class="container row center-align">
<div class="col s12 m8 l8 copy-right">
Copyright ©
<span id="year">年份</span>
<a href="https://www.sqlsec.com" target="_blank">国光</a>
| Powered by <a href="https://www.djangoproject.com/" target="_blank" rel="nofollow">Django</a>
| Theme <a href="https://github.com/blinkfox/hexo-theme-matery" target="_blank" rel="nofollow">Matery</a>
<br>
<span id="icp"><img src="{% static 'medias/icp.png' %}" style="vertical-align: text-bottom;" />
<a href="{{ SITE_ICP_URL }}" target="_blank" rel="nofollow">{{ SITE_ICP }}</a>
</span>
</div>
<div class="col s12 m4 l4 social-link social-statis">
<a href="mailto:admin@sqlsec.com" class="tooltipped" target="_blank" data-tooltip="邮件联系我" data-position="top" data-delay="50">
<i class="fas fa-envelope-open"></i>
</a>
<a href="/avs/" class="tooltipped" target="_blank" data-tooltip="文章更新动态" data-position="top" data-delay="50">
<i class="far fa-calendar-alt"></i>
</a>
</div>
</div>
</footer>
<!-- 滚动进度条 -->
<div class="progress-bar"></div>
<!-- 搜索遮罩框 -->
<div id="searchModal" class="modal">
<div class="modal-content">
<div class="search-header">
<span class="title"><i class="fas fa-search"></i> 搜索</span>
<input type="search" id="searchInput" name="s" placeholder="请输入搜索的关键字"
class="search-input">
</div>
<div id="searchResult"></div>
</div>
</div>
<script src="{% static 'js/search.js' %}"></script>
<script type="text/javascript">
$(function () {
searchFunc("/" + "search.xml", 'searchInput', 'searchResult');
});
</script>
<!-- 回到顶部按钮 -->
<div id="backTop" class="top-scroll">
<a class="btn-floating btn-large waves-effect waves-light" href="#!">
<i class="fas fa-arrow-up"></i>
</a>
</div>
<script src="{% static '/js/materialize.min.js' %}"></script>
<script src="{% static '/js/masonry.pkgd.min.js' %}"></script>
<script src="{% static '/js/aos.js' %}"></script>
<script src="{% static '/js/scrollProgress.min.js' %}"></script>
<script src="{% static '/js/lightgallery-all.min.js' %}"></script>
<script src="{% static '/js/matery.js' %}"></script>
<script src="{% static '/js/clicklove.js' %}" async="async"></script>
<script src="{% static '/js/instantpage.js' %}" type="module"></script>
<script src="{% static 'js/prism.js' %}"></script>
</body>
</html>
</body>
URLS注解
本项目没有建立APP子路由,所有路由配置都在项目目录下的urls.py里面:
from django.contrib import admin
from django.urls import path, re_path, include
from django.conf.urls.static import static
from django.conf import settings
from django.views import static
from blog.views import Index, Friends, Detail, Archive, CategoryList, CategoryView, TagList, TagView, About
urlpatterns = [
path('admin/', admin.site.urls),
# 首页
path('', Index.as_view(), name='index'),
# 友情链接
path('friends/', Friends.as_view(), name='friends'),
# 后台 markdown 编辑器配置,富文本字段会调用这个路由
path('mdeditor/', include('mdeditor.urls')),
# 文章详情
re_path(r'article/av(?P<pk>\d+)', Detail.as_view(), name='detail'),
# 文章归档
path('article/', Archive.as_view(), name='archive'),
# 分类统计
path(r'category/', CategoryList.as_view(), name='category'),
# 文章分类
re_path(r'category/cg(?P<pk>\d+)', CategoryView.as_view(), name='article_category'),
# 标签统计
path(r'tag/', TagList.as_view(), name='tag'),
# 文章标签
re_path(r'tag/tg(?P<pk>\d+)', TagView.as_view(), name='article_tag'),
# 关于本站
path('about/', About.as_view(),name='about'),
# 静态文件
re_path(r'^static/(?P<path>.*)$', static.serve, {'document_root': settings.STATIC_ROOT }, name='static')
]
# 设置后台名称,也可以写在任意一个admin.py里面
admin.site.site_header = '国光博客后台'
admin.site.site_title = 'Django Blog 后台'
#DEBUG模式时设定静态文件目录
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
视图逻辑
由于模板HTML内容太多,不好逐个注解,此处将业务视图函数逐个展示和注解:
全局变量设置,即没有链接访问此视图函数,html模板也可以使用这里面的变量:
def global_setting(request):
"""
将settings里面的变量 注册为全局变量
"""
active_categories = Category.objects.filter(active=True).order_by('index')
return {
'SITE_NAME': settings.SITE_NAME,
'SITE_DESC': settings.SITE_DESCRIPTION,
'SITE_KEY': settings.SECRET_KEY,
'SITE_MAIL': settings.SITE_MAIL,
'SITE_ICP': settings.SITE_ICP,
'SITE_ICP_URL': settings.SITE_ICP_URL,
'SITE_TITLE': settings.SITE_TITLE,
'SITE_TYPE_CHINESE': settings.SITE_TYPE_CHINESE,
'SITE_TYPE_ENGLISH': settings.SITE_TYPE_ENGLISH,
'active_categories': active_categories
}
友情链接业务视图:调用friends.html模板,提供链接信息相关数据
class Friends(View):
"""
友链链接展示
"""
def get(self, request):
links = Links.objects.all()
#随机整数
card_num = random.randint(1, 10)
return render(request, 'friends.html', {
'links': links,
'card_num': card_num,
})
文章详情业务视图:调用detail.html模板,返回文章内容及对Markdown样式的文章处理过后展示的页面:
class Detail(View):
"""
文章详情页
"""
def get(self, request, pk):
article = Article.objects.get(id=int(pk))
article.viewed()
mk = mistune.Markdown()
output = mk(article.content)
return render(request, 'detail.html', {
'article': article,
'detail_html': output,
})
文章归档业务视图:调用archive.html模板,返回所有文章、所有文章创建时间等数据:
class Archive(View):
"""
文章归档
"""
def get(self, request):
all_articles = Article.objects.all().order_by('-add_time')
all_date = all_articles.values('add_time')
latest_date = all_date[0]['add_time']
all_date_list = []
for i in all_date:
all_date_list.append(i['add_time'].strftime("%Y-%m-%d"))
# 遍历1年的日期
end = datetime.date(latest_date.year, latest_date.month, latest_date.day)
begin = datetime.date(latest_date.year-1, latest_date.month, latest_date.day)
d = begin
date_list = []
temp_list = []
delta = datetime.timedelta(days=1)
while d <= end:
day = d.strftime("%Y-%m-%d")
if day in all_date_list:
temp_list.append(day)
temp_list.append(all_date_list.count(day))
else:
temp_list.append(day)
temp_list.append(0)
d += delta
date_list.append(temp_list)
temp_list = []
# 文章归档分页
try:
page = request.GET.get('page', 1)
except PageNotAnInteger:
page = 1
p = Paginator(all_articles, 10, request=request)
articles = p.page(page)
return render(request, 'archive.html', {
'all_articles': articles,
'date_list': date_list,
'end': str(end),
'begin': str(begin),
})
分类清单业务视图:调用category.html模板,返回文章分类数据:
#分类清单
class CategoryList(View):
def get(self, request):
categories = Category.objects.all()
return render(request, 'category.html', {
'categories': categories,
})
分类页面文章视图:调用article_category.html模板,返回文章分类数据、文章数据:
class CategoryView(View):
def get(self, request, pk):
categories = Category.objects.all()
articles = Category.objects.get(id=int(pk)).article_set.all()
#文章分页
try:
page = request.GET.get('page', 1)
except PageNotAnInteger:
page = 1
p = Paginator(articles, 9, request=request)
articles = p.page(page)
return render(request, 'article_category.html', {
'categories': categories,
'pk': int(pk),
'articles': articles
})
标签列表视图:调用tag.html模板,返回文章标签数据:
class TagList(View):
def get(self, request):
tags = Tag.objects.all()
return render(request, 'tag.html', {
'tags': tags,
})
文章标签视图:调用article_tag.html模板,返回文章标签数据、文章数据:
class TagView(View):
def get(self, request, pk):
tags = Tag.objects.all()
articles = Tag.objects.get(id=int(pk)).article_set.all()
try:
page = request.GET.get('page', 1)
except PageNotAnInteger:
page = 1
p = Paginator(articles, 9, request=request)
articles = p.page(page)
return render(request, 'article_tag.html', {
'tags': tags,
'pk': int(pk),
'articles': articles,
})
关于我页面视图:调用about.html模板,返回文章标签数据、文章数据、文章分类数据:
class About(View):
def get(self, request):
#获取文章清单,按创建时间倒序
articles = Article.objects.all().order_by('-add_time')
categories = Category.objects.all()
tags = Tag.objects.all()
#文章数据中,创建时间数据
all_date = articles.values('add_time')
#由于是倒序排列的,所以第一个时间是最后的创建时间
latest_date = all_date[0]['add_time']
end_year = latest_date.strftime("%Y")
end_month = latest_date.strftime("%m")
date_list = []
for i in range(int(end_month), 13):
date = str(int(end_year)-1)+'-'+str(i)
date_list.append(date)
for j in range(1, int(end_month)+1):
date = end_year + '-' + str(j)
date_list.append(date)
value_list = []
all_date_list = []
for i in all_date:
all_date_list.append(i['add_time'].strftime("%Y-%m"))
for i in date_list:
value_list.append(all_date_list.count(i))
temp_list = [] # 临时集合
tags_list = [] # 存放每个标签对应的文章数
tags = Tag.objects.all()
for tag in tags:
temp_list.append(tag.name)
temp_list.append(len(tag.article_set.all()))
tags_list.append(temp_list)
temp_list = []
tags_list.sort(key=lambda x: x[1], reverse=True) # 根据文章数排序
top10_tags = []
top10_tags_values = []
for i in tags_list[:10]:
top10_tags.append(i[0])
top10_tags_values.append(i[1])
return render(request, 'about.html', {
'articles': articles,
'categories': categories,
'tags': tags,
'date_list': date_list,
'value_list': value_list,
'top10_tags': top10_tags,
'top10_tags_values': top10_tags_values
})
结束语
整个项目内容较多,自己看的时候基本看得懂,但是如果要自己从头写估计难以写出来。
由于篇幅有限,暂且只记录这些信息,后续有时间的话分篇学习和记录里面的知识点,像Markdon处理包的使用,分页功能包的使用,导入导出功能包的使用等。