码上有坑


本文最后更新于:2020年7月3日 早上

暑假这几天,还真拿Django + Hexo 搭建出了一个博客(准确说是DjangoBlog + Hexo-Matery主题,然后不知道重构了多少代码,30%+?),这下应该不会换了,毕竟也是自己鼓搞半天的成果(谁知道我已经换了多少博客了呢),总之一句,处处有坑,希望这些坑对阅读本文的你有些帮助。

第一坑:Hexo中的ejs模板如何变成Django中的Jinja2模板?

闪烁之狐做的这个响应式的Matery主题挺棒的,有很多人也在使用,可是爱鼓搞的我并不想用Hexo(当然我已经在几年前就用过了), 又看到了且听风吟DjangoBlog,于是我就想把它们结合起来,所以……就有了这个问题。

最开始我并不是使用的Jinja2,而是Django自带的模板,但当我改动了一些后,发现ejs模板文件里那些功能用Django模板并不好实现时,果断换上了Jinja2。(用过了Jade, ejs, Django template, 还是Jinja2好用,抱歉,没用过Mako)

Hexo ejs中的partial是局部模板,其实差不多相当于Jinja2中的include

<%- partial('_partial/post-cover') %>
{% include '_partial/post-cover.html' %}

Hexo ejs中的url_for,其实可以用Django的static解决(什么,你问我怎么让Jinja2兼容,且看下一坑)

<%- url_for(theme.libs.css.fontAwesome) %>
{{ static(theme.libs.css.fontAwesome) }}

Hexo ejs默认都会继承layout这个布局,所以我们也要让Jinja2继承,没错,就是extends,当然了,还有巧用block

{% extends 'layout.html' %}
{% block header %}
{% endblock %}

Hexo为了国际化,使用了gettext这个i18n工具,Jinja2怎么办?Jinja2的扩展也不多,却刚好有i18n插件,不过网上说的过程都有些简单,我尝试了许久才成功(且看下一坑)。

<%= __('friends') %>
{{ gettext('friends') }}

Hexo还有个_config.yml怎么办,当然是用pyyaml load后,注册为模板的全局变量了(先保证最小改动)。

第二坑:Django与Jinja2的兼容,Jinja2的扩展

Jinja2本是Flask的模板,当时语法仿的Django模板,但最后却成了一个比Django模板更强大、更快的模板,所以,现在Django也可以用Jinja2模板,但是配置有点稍微麻烦。

首先在你的Django的[app]目录下建立一个jinja2_env.py文件。

from django.contrib.staticfiles.storage import staticfiles_storage
from django.urls import reverse
from django.utils.translation import gettext, ngettext
from jinja2 import Environment
from babel.support import Translations
from django.conf import settings
from compressor.contrib.jinja2ext import CompressorExtension

def jinja2_environment(**options):
    translations = Translations.load(settings.LOCALE_PATH, settings.LIST_OF_DESIRED_LOCALES)
    # LOCALE_PATH 存放翻译对应文件的文件夹路径, LIST_OF_DESIRED_LOCALES 支持的语言列表
    env = Environment(extensions=['jinja2.ext.i18n', 'jinja2.ext.autoescape', 'jinja2.ext.with_', , CompressorExtension], **options) 
    # 加载i18n插件(gettext支持),with_插件(with标签),autoescape插件(autoescape标签), CompressorExtension(compress 标签,自动压缩css,js)
    env.install_gettext_callables(gettext = gettext, ngettext = ngettext, newstyle = True)
    # 指定gettext,ngettext的方法
    env.install_gettext_translations(translations)
    # 从LOCALE_PATH的mo语言文件获取翻译
    env.globals.update({
        'static': staticfiles_storage.url,
        'url': reverse,
    }) #确保可以使用Django模板引擎中的{% url('') %} {% static('') %}这类的语句 

    env.filters['datetimeformat'] = datetimeformat
    # ... 还可以自定义很多filter
    
    return env

当然也可以使JInja2实现cache标签,这个可以看JInja2官方文档。

接着是app中settings.py中加上Jinja2

CONTEXT_PROCESSORS = [
    'django.template.context_processors.debug',
    'django.template.context_processors.request',
    'django.contrib.auth.context_processors.auth',
    'django.contrib.messages.context_processors.messages',
    'django.template.context_processors.i18n',
]

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.jinja2.Jinja2', 
        'DIRS': [os.path.join(BASE_DIR, 'templates')],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': CONTEXT_PROCESSORS,
            'environment': 'DjangoBlog.jinja2_env.jinja2_environment',

        },
    },
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': CONTEXT_PROCESSORS,
        },
    }
]

# Use pybabel to finish i18n
LIST_OF_DESIRED_LOCALES = ["zh_Hans"]
LOCALE_PATH = os.path.join(BASE_DIR, 'locale')

也别把默认的删了,不然Django Admin就会不能访问。

当你写好了{{ gettext('friends') }}这类的语句后,怎么从模板里抽取出来呢?答案:Babel (Django自带的只把Py文件里的抽了出来)

先写好配置文件babel.cfg

[jinja2: **/templates/**/**/*.html ]
extensions=jinja2.ext.i18n,jinja2.ext.autoescape,jinja2.ext.with_

(文件夹名前不要有_,文件夹最多有多少层就写多少层星号匹配)!

1. pybabel -v extract -F babel.cfg -o ./locale/messages.pot ./
2. pybabel init -i ./locale/messages.pot -d ./locale/ -l zh_Hans
3. pybabel compile -d ./locale/
4. pybabel update -i ./locale/messages.pot -d ./locale/

首次建立,$1 \rightarrow 2 \rightarrow 3 $,先1抽取模板文件得到messages.pot文件,2生成对应语言po文件,手动输入翻译,3编译为mo文件。

模板文件内有更新,$1 \rightarrow 4 \rightarrow 3 $,先1抽取模板文件得到messages.pot文件,4更新对应语言po文件,手动输入翻译,3编译为mo文件。

第三坑:Docker环境的建立

我还是比较喜欢用docker的,不过每次这环境配置起来都坑人。

DjangoBlog给了docker支持,不过好像用的是CPython,不行,我得用PyPy3,一搜,果然有jamiehewland/alpine-pypy:3.6-alpine3.11这个镜像,就在这基础上build吧

不过一定要记得,要用国内镜像,不然会慢的……Timeout……

RUN echo "http://mirrors.ustc.edu.cn/alpine/v3.11/main/" > /etc/apk/repositories
RUN pip3 install --upgrade pip -i https://mirrors.cloud.tencent.com/pypi/simple

由于gevent安装一直报错,最后我决定用uWSGI来优化Django(由于我用的PyPy3,于是又有了下一坑),不用gevent了……

还有,容器间怎么通信,主容器可以加--link命令链接其它容器,就可以用容器名ping通了(这个过了半天才想起

比如建立django_blog容器时link了mysql容器,就可以直接在blog配置里写mysql:3306

第四坑:uWSGI与PyPy3的“爱恨情仇”,外加Nginx一手

总所周知,Django的runserver性能实在不高,稳定性也一般,所以一般用uWSGI+Nginx部署Django项目,但遇上了PyPy3,这……好像又是一个坑……

uWSGI的安装,由于要用PyPy3,还是用pip安装,不过在alpine系统环境下,记得先装两个依赖外加gcc

apk --no-cache add gcc libc-dev linux-headers

然后Pip就行了……然后是最重要的配置

建立uwsgi.ini

[uwsgi]
socket=:8000 
#与Nginx通信端口,用socket
chdir=/app
#工作目录 docker时已经映射到本机
processes=4
threads=2
master=True
pidfile=/app/uwsgi/uwsgi.pid
buffer-size=65536
#daemonize=/app/uwsgi/uwsgi.log
module=[YourAppName].wsgi:application
pypy-home=/usr/local/bin/pypy3
pypy-lib=/usr/local/bin/libpypy3-c.so
pypy-wsgi=[YourAppName].wsgi:application
pypy-wsgi-file=/app/[YourAppName]/wsgi.py
pypy-pp=/app
#pythonPath目录,当然要写工作目录
pypy-setup=/app/uwsgi/pypy_setup.py
#uWSGI 有pypy插件,但是不支持pypy3, 幸好github上有dalao写了这个

Python 3 PyPy support for uwsgi

修改Nginx配置文件

upstream django {
	server 127.0.0.1:8000; #和上面端口一样
}
server {
	root /app/;
	listen 80;
    server_name localhost;
    charset utf-8;

	location /static/ {
		expires max;
		alias /app/collectedstatic/;
	}

	location / {
        uwsgi_pass django;
	    include /etc/nginx/uwsgi_params;
	}
}

最后用下面命令应该就能跑起来了(首先保证你的项目在runserver时能跑起来

uwsgi --ini ./uwsgi/uwsgi.ini

第五坑:Markdown与Mathjax(Latex)又打架,打酱油的代码高亮

Markdown真是一个好用的文本标记语言,Mathjax也是个好数学公式渲染引擎,但自从Markdown出来后,各种解析器就各立潮头,多少有些标准不同,但有两点是差不多都有的,\的转义和_转换成了<em></em>标签,于是,当你用上Mathjax时,有些公式就出大问题了……

我用的是mistune解析器,本来作者也写了math支持扩展,但是并没有给exmaple,而且照着网上的一些教程,发现作者的math支持扩展解析自定义块级元素,运行时会报错,最后翻了半天网站,终于又在国外网站找到了解决方法。

附上我稍微改动的代码:

#!/usr/bin/python3
# Modify from https://blog.depado.eu/post/mistune-parser-syntax-mathjax-centered-images
import re, mistune

class MathBlockGrammar(mistune.BlockGrammar):
    block_math = re.compile(r"^\$\$(.*?)\$\$", re.DOTALL)
    latex_environment = re.compile(r"^\\begin\{([a-z]*\*?)\}(.*?)\\end\{\1\}", re.DOTALL)

class MathBlockLexer(mistune.BlockLexer):
    default_rules = ['block_math', 'latex_environment'] + mistune.BlockLexer.default_rules

    def __init__(self, rules=None, **kwargs):
        if rules is None:
            rules = MathBlockGrammar()
        super(MathBlockLexer, self).__init__(rules, **kwargs)

    def parse_block_math(self, m):
        """Parse a $$math$$ block"""
        self.tokens.append({
            'type': 'block_math',
            'text': m.group(1)
        })

    def parse_latex_environment(self, m):
        self.tokens.append({
            'type': 'latex_environment',
            'name': m.group(1),
            'text': m.group(2)
        })

class MathInlineGrammar(mistune.InlineGrammar):
    math = re.compile(r"^\$(.+?)\$", re.DOTALL)
    block_math = re.compile(r"^\$\$(.+?)\$\$", re.DOTALL)
    text = re.compile(r'^[\s\S]+?(?=[\\<!\[_*`~\$]|https?://| {2,}\n|$)')

class MathInlineLexer(mistune.InlineLexer):
    default_rules = ['block_math', 'math'] + mistune.InlineLexer.default_rules

    def __init__(self, renderer, rules=None, **kwargs):
        if rules is None:
            rules = MathInlineGrammar()
        super(MathInlineLexer, self).__init__(renderer, rules, **kwargs)

    def output_math(self, m):
        return self.renderer.inline_math(m.group(1))

    def output_block_math(self, m):
        return self.renderer.block_math(m.group(1))

class MathRendererMixin(mistune.Renderer):
    def block_code(self, code, lang=None):
        code = code.rstrip('\n')
        if not lang:
            lang = 'text'
        code = mistune.escape(code, quote=True, smart_amp=False)
        return '<pre class="language-%s"><code class="language-%s">%s\n</code></pre>\n' % (lang, lang, code)

    def block_math(self, text):
        return '$$%s$$' % text

    def latex_environment(self, name, text):
        return r'\begin{%s}%s\end{%s}' % (name, text, name)

    def inline_math(self, text):
        return '$%s$' % text

class MarkdownWithMath(mistune.Markdown):
    def __init__(self, renderer, **kwargs):
        if 'inline' not in kwargs:
            kwargs['inline'] = MathInlineLexer
        if 'block' not in kwargs:
            kwargs['block'] = MathBlockLexer
        super(MarkdownWithMath, self).__init__(renderer, **kwargs)

    def output_block_math(self):
        return self.renderer.block_math(self.token['text'])

    def output_latex_environment(self):
        return self.renderer.latex_environment(self.token['name'], self.token['text'])

像下面这么用:

mk = MarkdownWithMath(renderer=MathRendererMixin())
content = mk(r"{}".format(content))

记住,一定要防止传入content时就被python转义了(就这又坑了好一会儿

一些可能会出错的例子:


The entries of $C$ are given by the exact formula:

but there are many ways to _implement_ this computation. $\approx 2mnp$ flops

$m$

$x^m$

$r=\overline{1,n}$

i.e. the $i^{th}$


至于block_code, 由于我用的CodeBlock.jsPrism.js 进行代码高亮,识别的代码块应该是<pre class="language-%s"><code class="language-%s">%s\n</code></pre>\n这种形式,所以重新实现了Rendererblock_code函数。

第六坑:materialize与 editormd 的暗中冲突,也别忘了matery

materialize是个好前端响应式框架,editormd 是个好前端markdown编辑器,但是它们的css冲突……

/* 在materialize.css里注释下面语句 */
select {
  /* display: none; */
}
select {
  background-color: rgba(255, 255, 255, 0.9);
  /*width: 100%;*/
  padding: 5px;
  border: 1px solid #f2f2f2;
  border-radius: 2px;
  height: 3rem;
}
.divider {
  height: 1px;
  /*overflow: hidden;
  background-color: #e0e0e0;*/
}

matery 也是个好主题,但是还是冲突……

/* 在matery.css里注释并修改下面语句 */

/* Resolve conflict with editormd
pre {
    padding: 2.5rem 1.5rem 1.5rem 1.5rem !important;
    margin: 1rem 0 !important;
    background: #272822;
    overflow: auto;
    border-radius: 0.35rem;
    tab-size: 4;
}*/

pre[class*="language-"] {
    /*padding: 1.2em;
    margin: .5em 0;*/
	
	padding: 2.5rem 1.5rem 1.5rem 1.5rem !important;
    margin: 1rem 0 !important;
    background: #272822;
    overflow: auto;
    border-radius: 0.35rem;
    tab-size: 4;
}

第n坑:总结

总之,这个用Nginx反代,uWSGI开多线程,PyPy3加速,Docker集成,Django + Jinja2驱动的博客总算能跑动了……

解决问题,一定要多看日志log,比上网查效率更高,解决更准确。

同时,也要多去查查些国外的网站,说不定有解决方法。

也要结合多个教程,去做出自己真正想要的结果。

就是要多角度思考,才能很好解决问题。

最重要的是不清楚就别乱删文件,否则……

等把代码重构了再开源吧


文章作者: SpaceSkyNet
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 SpaceSkyNet !
  目录
评论