Webサポートクイックスタート

ドキュメントデータのビルド

アプリケーションの中でウェブサポートパッケージを使用する場合は、まずはデータを作る必要があります。データには、pickle化されたドキュメントや検索インデックス、コメントなどがどのドキュメントに付加されたのかを追跡するためのノードデータが含まれます。これを行うためには、 WebSupport クラスのインスタンスを作り、 build() メソッドを呼ぶ必要があります:

from sphinx.websupport import WebSupport

support = WebSupport(srcdir='/path/to/rst/sources/',
                     builddir='/path/to/build/outdir',
                     search='xapian')

support.build()

このコードは、reStructuredTextのソースコードを srcdir から読み込み、必要なデータを builddir に書き出します。 builddir は二つのサブディレクトリを含みます。 data には、ドキュメントを表示したり、ドキュメントを検索したり、ドキュメントにコメントを付けるのに必要なデータがすべて含まれます。もう一つの static ディレクトリは、 '/static' からファイルを配信するための、静的ファイルを含みます。

注釈

もし “/static” 以外のパスから静的ファイルの配信をしたい場合には、 WebSupport オブジェクトを作る時に、 staticdir キーワード引数を指定してください。

Sphinxドキュメントをウェブアプリケーションに統合

データができましたので、次はこれを使う番です。アプリケーションのための WebSupport オブジェクトを作るところから始めます:

from sphinx.websupport import WebSupport

support = WebSupport(datadir='/path/to/the/data',
                     search='xapian')

次に、個々のドキュメントに対する処理を作っていきます。 get_document() メソッドを呼ぶと、個々のドキュメントにアクセスできます:

contents = support.get_document('contents')

このメソッドは、次のキーを持つ辞書を返します:

  • body: HTML形式のドキュメント本体です。

  • sidebar: HTML形式のサイドバーです。

  • relbar: このdivは、関連ドキュメントへのリンクを含んでいます。

  • title: ドキュメントのタイトルです。

  • css: Sphinxが使用するCSSファイルへのリンクです。

  • script: JavaScriptはコメントオプションを含みます。

この辞書はテンプレートのコンテキストとして利用できます。これを利用することであなたの既存のテンプレートシステムに簡単に統合出来ます。 Jinja2 を使った例は以下の通りです:

{%- extends "layout.html" %}

{%- block title %}
    {{ document.title }}
{%- endblock %}

{% block css %}
    {{ super() }}
    {{ document.css|safe }}
    <link rel="stylesheet" href="/static/websupport-custom.css" type="text/css">
{% endblock %}

{%- block script %}
    {{ super() }}
    {{ document.script|safe }}
{%- endblock %}

{%- block relbar %}
    {{ document.relbar|safe }}
{%- endblock %}

{%- block body %}
    {{ document.body|safe }}
{%- endblock %}

{%- block sidebar %}
    {{ document.sidebar|safe }}
{%- endblock %}

認証

投票のような機能を実装する場合、ユーザ認証ができる必要があります。認証をどのように実装するかはアプリケーションに任されています。ユーザが認証されたら、ユーザの情報を WebSupport のメソッドの usernamemoderator キーワード引数に渡すことができます。ウェブサポートパッケージはユーザ名を、コメントや投票と一緒に保存します。注意点を1つあげるとすれば、もしユーザに対して名前の変更を行えるようにするのであれば、ウェブサポートパッケージの内部のユーザ名のデータも更新する必要があります:

support.update_username(old_username, new_username)

username はユーザを識別できる一意な文字列であるべきで、 moderator はboolean型のユーザが適切な権限を持つかどうかを表すべきです。 moderator のデフォルト値は False です。

例えば、 Flask を使用して、ユーザがログインしているかどうかを確認し、ドキュメントを読めるようにするには、次のようなコードで行えます:

from sphinx.websupport.errors import *

@app.route('/<path:docname>')
def doc(docname):
    username = g.user.name if g.user else ''
    moderator = g.user.moderator if g.user else False
    try:
        document = support.get_document(docname, username, moderator)
    except DocumentNotFoundError:
        abort(404)
    return render_template('doc.html', document=document)

まず、 docname が要求されたパスを表すことに気づきます。これにより、正しいドキュメントへのアクセスが行えます。もしユーザの認証が行われていたら、ユーザ名とモデレート権限情報が get_document() に渡されます。ウェブサポートパッケージは、テンプレートの中で使用される COMMENT_OPTIONS をこのデータに付加します。

注釈

このプログラムはドキュメントがルートで提供される場合にのみ動作します。もし、他のディレクトリからドキュメントを提供したい場合には、URLのプリフィックスを指定する必要があります。これは、ウェブサポートオブジェクトを作成する時に、 docroot キーワード引数として与えます:

support = WebSupport(..., docroot='docs')

@app.route('/docs/<path:docname>')

検索の実行

Sphinxサイドバーの検索機能を使うと、ドキュメントルート以下の ‘search’ というパスに対するリクエストが発生します。ユーザの検索クエリーは、GETパラメータの q キーに格納されています。 get_search_results() メソッドに渡すと、検索結果が得られます。 Flask では次のようになります:

@app.route('/search')
def search():
    q = request.args.get('q')
    document = support.get_search_results(q)
    return render_template('doc.html', document=document)

ドキュメントと検索結果の表示には、同じテンプレートを使用しています。これは、 get_search_results() メソッドが、 get_document() と同じ形式のコンテキスト辞書を返すからです。

コメント&提案

それでは、コメントなどをAJAXで処理するための関数を定義します。3つの関数を定義する必要があります。1つ目は、新しいコメントが投稿されたときに、ウェブサポートオブジェクトの add_comment() メソッドを呼び出すものです:

@app.route('/docs/add_comment', methods=['POST'])
def add_comment():
    parent_id = request.form.get('parent', '')
    node_id = request.form.get('node', '')
    text = request.form.get('text', '')
    proposal = request.form.get('proposal', '')
    username = g.user.name if g.user is not None else 'Anonymous'
    comment = support.add_comment(text, node_id='node_id',
                                  parent_id='parent_id',
                                  username=username, proposal=proposal)
    return jsonify(comment=comment)

リクエストには、 parent_idnode_id を送っています。コメントが他のノードに直接追加された場合には、 parent_id は空になります。また、コメントが他のコメントの子供として付加された場合には、 node_id が空になります。次の関数は、 get_data() メソッドを利用して、特定のノードに対するコメントを取り扱います:

@app.route('/docs/get_comments')
def get_comments():
    username = g.user.name if g.user else None
    moderator = g.user.moderator if g.user else False
    node_id = request.args.get('node', '')
    data = support.get_data(node_id, username, moderator)
    return jsonify(**data)

最後の関数は、 process_vote() を呼び出して、コメントに対するユーザの投票を取り扱う関数です:

@app.route('/docs/process_vote', methods=['POST'])
def process_vote():
    if g.user is None:
        abort(401)
    comment_id = request.form.get('comment_id')
    value = request.form.get('value')
    if value is None or comment_id is None:
        abort(400)
    support.process_vote(comment_id, g.user.id, value)
    return "success"

コメントのモデレート

デフォルトでは、 add_comment() を使って追加したすべてのコメントは表示されます。もし、モデレーションを行って、承認されたコメントだけを表示したいのであれば、 displayed キーワード引数を渡します:

comment = support.add_comment(text, node_id='node_id',
                              parent_id='parent_id',
                              username=username, proposal=proposal,
                              displayed=False)

コメントのモデレートを取り扱うビューを追加する必要があります。モデレータがコメントを受け入れて、表示するかどうかを決定するときに、この関数が呼び出されます:

@app.route('/docs/accept_comment', methods=['POST'])
def accept_comment():
    moderator = g.user.moderator if g.user else False
    comment_id = request.form.get('id')
    support.accept_comment(comment_id, moderator=moderator)
    return 'OK'

リジェクトされると、コメントは削除されます。

非表示の新しいコメントが追加されたときに、Eメールによるモデレートなど、カスタムのアクションを行いたい場合には、 WebSupport のインスタンスを作る時に、呼び出し可能なオブジェクトを渡します:

def moderation_callback(comment):
    """Do something..."""

support = WebSupport(..., moderation_callback=moderation_callback)

このコールバック関数は、 add_comment() が返すのと同じ形式の辞書を引数として受け取ります。