從零開發部落格-讓Flask-admin支援markdown編輯器(一)
前言
flask-admin 算是一個很不錯的 flask 後臺管理了,用它來做部落格系統的管理後端再合適不過了,節約時間成本,避免重複造輪子,但是作為一個程式設計師,寫文章怎麼可以沒有 markdown 呢? 現在讓我們嘗試一下讓 flask-admin 支援 markdown 吧.
實踐
Flask-PageDown
這個庫是對 Pagedown 的封裝,將其整合到了Flask-WTF表單中了。具體內容可以去它的 github 倉庫看一下: Flask-PageDown
優缺點
優點:
- 配置簡單
- 對 flask-admin 的改動較小
缺點:
- 無法實現對程式碼的高亮
- 生成的編輯器是上下佈局的,不太美觀
使用方法
安裝和配置
通過 pip 進行安裝:
pip install flask-pagedown
然後在專案中初始化:
from flask_pagedown import PageDown
app = Flask(__name__)
pagedown = PageDown(app)
當然也可以通過 init_app(app) 的方式進行初始化
from flask import Flask from flask_pagedown import PageDown pagedown = PageDown() def create_app(config_name): app = Flask(__name__, static_folder='static', static_url_path='') app.config.from_object(config[config_name]) # 省略部分程式碼 pagedown.init_app(app) return app
這個編輯器還依賴兩個 js 檔案,所以還需要在我們的對應的模板中新增進去相應的方法:
<html>
<head>
{{ pagedown.html_head() }}
</head>
<body>
...
</body>
</html>
預設情況下這兩個 js 是通過 CDN 載入的,也就意味著你的伺服器需要可以訪問網路,如果只想通過本地就可以訪問的話,很簡單把這兩個 js 檔案下載下來,然後將 pagedown.html_head() 替換成兩個檔案的本地路徑就可以了. 兩個檔案的下載地址:
Markdown.Sanitizer
新增自定義的 ModelView
flask-admin 支援自定義的 ModelView, 這是我們可以實現這個功能的基礎.
class PeachPostView(ModelView):
form_overrides = {
'content': PageDownField
}
def __init__(self, model, session, **kwargs):
super(PeachPostView, self).__init__(model, session, **kwargs)
# 省略部分程式碼
最後在我們的 flask-admin 給我們需要 markdown 編輯器的 model 新增上自定義的 ModelView 就可以了.
# 省略部分程式碼
admin.add_view(PeachPostView(Post, db.session, endpoint='AdminPost'))
完整程式碼可以檢視 use:使用 flask-pagedown 作為 markdown 編輯器
最終的效果圖:
使用開源的 markdown 編輯器
沒錯,這個方法就比較自由了,找自己喜歡的 markdown 編輯器然後整合進去. 這裡我最終選擇了 [editor.md]
(https://github.com/pandao/editor.md.git) 這個開源編輯器,這個好像很久沒維護了,但是顏值挺高的了,就決定是它了.
優缺點
優點:
- 優點很明星,顏值高,滿足我對 markdown 的所有需求了,支援程式碼高亮.
缺點:
- 對 flask-admin 的改動較多,需要自己寫的部分比較多.
使用方法
下載 editor.md 外掛
外掛下載很簡單,從官網上或者 github 上下載, 下載完成後,我這裡將 editor.md 下載的檔案儲存到 static 的 plugin 的目錄下了.
覆蓋 flask-admin 原有檔案
這裡由於我們需要使用外掛,所以我們需要對 flask-admin 原有的 create.html 和 edit.html 檔案進行重寫.
- 在 templates 目錄下新建 admin 目錄
- 在 admin 目錄下建立 model 目錄, 並新增 peach-post-create.html, peach-post-edit.html 檔案,將 flask-admin 對應的 create.html 和 edit.html 內容複製進去.
- 在 admin 目錄下新增 peach-base.html 和 peach-lib.html 檔案,將 flask-admin 對應的 base.html 和 lib.html 檔案複製進去
tip: 上述說所的 flask-admin 的內容,均在你的開發環境中找到 flask-admin 的庫所在目錄,然後需要複製的檔案在: flask_admin\templates\bootstrap3\admin 下
然後配置,使其訪問我們指定的檔案
# 自定義的 modelview
class PeachPostView(ModelView):
# 指定訪問檔案
create_template = 'admin/model/peach-post-create.html'
edit_template = 'admin/model/peach-post-edit.html'
def __init__(self, model, session, **kwargs):
super(PeachPostView, self).__init__(model, session, **kwargs)
# 指定 base_template 檔案
admin = Admin(name=name, template_mode=template_mode,index_view=PeachAdminIndexView(), base_template='admin/peach-base.html')
# 省略部分程式碼
admin.add_view(PeachPostView(Post, db.session, endpoint='AdminPost'))
修改檔案, 支援 markdown
動手修改檔案的之前,我們需要先了解一下 flask-amdmin 原有的檔案是什麼樣子的,然後才能正確的修改,這裡以 create.html 為例子,擷取關鍵程式碼.
# create.html
{% import 'admin/lib.html' as lib with context %}
# ...
{% block edit_form %}
{{ lib.render_form(form, return_url, extra(), form_opts) }} # 使用了 lib 中的函式,看一下這個函式
{% endblock %}
# lib.html
{% macro render_form(form, cancel_url, extra=None, form_opts=None, action=None, is_modal=False) -%}
{% call form_tag(action=action) %}
{{ render_form_fields(form, form_opts=form_opts) }} # 重點是表單渲染,再看一下 render_form_fields 函式
{{ render_form_buttons(cancel_url, extra, is_modal) }}
{% endcall %}
{% endmacro %}
{% macro render_form_fields(form, form_opts=None) %}
# 省略部分程式碼
{{ render_field(form, f, kwargs) }} # 看一下 render_field 這個函式
{% endmacro %}
{% macro render_field(form, field, kwargs={}, caller=None) %}
{% set direct_error = h.is_field_error(field.errors) %}
<div class="form-group{{ ' has-error' if direct_error else '' }}">
<label for="{{ field.id }}" class="col-md-2 control-label">{{ field.label.text }}
{% if h.is_required_form_field(field) %}
<strong style="color: red">*</strong>
{%- else -%}
{%- endif %}
</label>
<div class="{{ kwargs.get('column_class', 'col-md-10') }}">
{% set _dummy = kwargs.setdefault('class', 'form-control') %}
{{ field(**kwargs)|safe }}
{% if field.description %}
<p class="help-block">{{ field.description|safe }}</p>
{% endif %}
{% if direct_error %}
<ul class="help-block input-errors">
{% for e in field.errors if e is string %}
<li>{{ e }}</li>
{% endfor %}
</ul>
{% endif %}
</div>
{% if caller %}
{{ caller(form, field, direct_error, kwargs) }}
{% endif %}
</div>
{% endmacro %}
通過上述 flask-admin 原始碼的瀏覽,我們不難發現,最終的渲染最核心的函式就是 lib.html 中的 render_field 函式, 要達到我們的目的,我們就需要對這個函式進行一點的修改了.由於我們只需要對資料庫模型的某個欄位支援 markdown 就可以了,所以需要我們可以通過 field.id == '欄位名' 的方法去判斷,從而讓他支援 markdown.
為了更好的理解接下來的程式碼,我們對 editor.md 的使用做一個簡單的說明:
<link rel="stylesheet" href="editormd.min.css" />
<div id="editormd">
<textarea style="display:none;">### Hello Editor.md !</textarea>
</div>
<script src="jquery.min.js"></script>
<script src="editormd.min.js"></script>
<script type="text/javascript">
$(function() {
var editor = editormd("editormd", {
path : "../lib/" // Autoload modules mode, codemirror, marked... dependents libs path
});
/*
// or
var editor = editormd({
id : "editormd",
path : "../lib/"
});
*/
});
</script>
說明完了怎麼使用 editor.md,我們來說一下我們最終的思路, 通過欄位名去判斷,然後符合新增的情況下,新建一個 div, id 為 editor,然後在 peach-post-create.html 按照 editor.md 的使用方法渲染.說完了思路,接下來就看程式碼.
修改 peach-lib.html 和 peach-post-create.html 檔案
# peach-lib.html
{% macro render_field(form, field, kwargs={}, caller=None) %}
{% set direct_error = h.is_field_error(field.errors) %}
<div class="form-group{{ ' has-error' if direct_error else '' }}">
<label for="{{ field.id }}" class="col-md-2 control-label">{{ field.label.text }}
{% if h.is_required_form_field(field) %}
<strong style="color: red">*</strong>
{%- else -%}
{%- endif %}
</label>
<div class="{{ kwargs.get('column_class', 'col-md-10') }}">
{% set _dummy = kwargs.setdefault('class', 'form-control') %}
{% if field.id == 'content' %} # 符合條件新建一個 div
<div id='editormd'>
{{ field(**kwargs)|safe }}
</div>
{% else %}
{{ field(**kwargs)|safe }}
{% endif %}
{% if field.description %}
<p class="help-block">{{ field.description|safe }}</p>
{% endif %}
{% if direct_error %}
<ul class="help-block input-errors">
{% for e in field.errors if e is string %}
<li>{{ e }}</li>
{% endfor %}
</ul>
{% endif %}
</div>
{% if caller %}
{{ caller(form, field, direct_error, kwargs) }}
{% endif %}
</div>
{% endmacro %}
# peach-post-create.html
{% extends 'admin/master.html' %}
{% import 'admin/peach-lib.html' as lib with context %}
{% from 'admin/peach-lib.html' import extra with context %} {# backward compatible #}
# 省略部分程式碼
{{ super() }}
{{ lib.form_css() }}
<link href="{{ url_for('static', filename='plugins/editor.md/css/editormd.min.css')}}" rel='stylesheet'>
{% endblock %}
# 省略部分程式碼
{% block tail %}
{{ super() }}
{{ lib.form_js() }}
<script src="{{ url_for('static', filename='plugins/editor.md/editormd.js')}}"></script>
<script type="text/javascript">
$(function() {
var editor = editormd("editormd", {
path : "/plugins/editor.md/lib/",
height : 640
});
});
</script>
{% endblock %}
修改 modelview
class PeachPostView(ModelView):
# 省略部分程式碼
create_template = 'admin/model/peach-post-create.html'
admin = Admin(name=name, template_mode=template_mode,index_view=PeachAdminIndexView(), base_template='admin/peach-base.html')
# 省略部分程式碼
admin.add_view(PeachPostView(Post, db.session, endpoint='AdminPost'))
完整程式碼可以檢視 add:使用 editor.md 編輯器
最後看一下效果圖:
最後
到此大功告成,歡迎大家關注我的公共號-Leetao, 偶爾分享 flask 之外的知識.