对国光大佬Django重写Hexo博客项目的学习


前言

前两天在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项目的结构

1589715406857

安装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

1589727714129

登陆进去:

1589727781370

查看博客首页:

image-20200518151554278

二、内容分析

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界面如下:

image-20200518153040929

​ 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界面如下:

image-20200518153158746

​ 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界面如下:

image-20200518154520619

​ 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界面如下:

image-20200518155117123

​ 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界面如下:

image-20200518162634016

​ 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 :

1589982335021

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>&nbsp;&nbsp;宁静致远
                    </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>&nbsp;&nbsp;推荐文章</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&nbsp;&copy;
            <span id="year">年份</span>
            <a href="https://www.sqlsec.com" target="_blank">国光</a>
            |&nbsp;Powered by&nbsp;<a href="https://www.djangoproject.com/" target="_blank" rel="nofollow">Django</a>
            |&nbsp;Theme&nbsp;<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>&nbsp;&nbsp;搜索</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处理包的使用,分页功能包的使用,导入导出功能包的使用等。


文章作者: 无咎
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 无咎 !
评论
 上一篇
Django Web开发入门——MVC框架开发新应用 Django Web开发入门——MVC框架开发新应用
接着上篇启动项目后,开始按Django的MVC框架开发新应用。 创建APP并注册进入刚刚创建的项目目录,使用以下命令创建APP,命名为base: python manage.py startapp base 进入workdesk\setti
2020-05-17
下一篇 
Django Web开发入门——启动项目 Django Web开发入门——启动项目
学习Django有一段时间了,现在整理一下笔记,发布到Bolg。 环境安装使用PIP安装Django: pip install django Django原生支持多种数据库,此处我们使用MySQL,安装方式百度很多,此处不赘述。 创建项目进
2020-05-16
  目录