Web Suporte Início Rápido

Construindo Dados da Documentação

Para fazer uso, do pacote de suporte a web, em sua aplicação é necessário construir os dados que o pacote utiliza. Esses dados incluem arquivos tipo pickle representando os documentos, índices de busca e dados que serão usados para comentários e outras finalidades dentro do documento. Para isso é necessário criar uma instância da WebSupport e chamar o método build():

from sphinxcontrib.websupport import WebSupport

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

support.build()

Isto irá ler as fontes reStructuredText de srcdir e colocar os dados necessários em builddir. O builddir conterá dois subdiretórios: um denominado “data” que contém todos os dados necessários para exibir documentos, pesquisar documentos e adicionar comentários a documentos. O outro diretório será chamado de “estático” e contém arquivos estáticos que devem ser atendidos a partir de “/static”.

Nota

Se desejar servir arquivos estáticos a partir de outro caminho que não “/static”, deverá ser providenciado o argumento staticdir quando criada a WebSupport do objeto.

Integrando Documentos Sphinx em sua aplicação Web

Agora que os dados foram construídos, é hora de fazer algo útil com eles. Inicie criando a WebSupport do objeto para sua aplicação:

from sphinxcontrib.websupport import WebSupport

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

Só será necessário um desses conjuntos de documentação no qual está trabalhando. Pode ser chamado o método get_document() para acessar individualmente os documentos:

contents = support.get_document('contents')

Isso irá retornar um dicionário contendo os seguintes itens:

  • body: O corpo principal do documento como HTML

  • sidebar: A barra lateral do documento como HTML

  • relbal: Div contendo links para documentos relacionados

  • title: O título do documento

  • css: Links para arquivos CSS usados pelo Sphinx

  • script: JavaScript contendo opções de comentários

Esse dicionário dict, pode então ser usado como contexto para modelos. O objetivo é facilitar a integração com seu sistema de modelos. Um exemplo usando 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 %}

Autenticação

Para cirar certas funcionalidades como enquete, deve ser possível autenticar usuários. Os detalhes da autenticação são deixados para sua aplicação. Quando um usuário for autenticado, podemos passar a usar certos detalhes de classes WebSupport e métodos usando argumentos username e moderador . O suporte ao pacote web irá armazenar nome do usuário com os comentários e votos. O cuidado é para que quando o nome do usuário for atualizado, sua aplicação deverá atualizar os dados de websuporte.

support.update_username(old_username, new_username)

username deve ser string única que identifica o usuário, e moderator deve ser um booleano representando onde o usuário tem privilégio de moderação. O valor padrão para moderator é False.

Um exemplo é a função Flask que verifica se o usuário está logado e então recupera documentos:

from sphinxcontrib.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)

O primeiro detalhe a notar é que docname é o caminho da requisição. Isso torna fácil acessar o caminho correto do documento em uma simples visão. Se o usuário está autenticado, então o nome do usuário e situação de moderação são passadas junto com o método get_document(). O pacote suporte a web irá adicionar dados em COMMENT_OPTIONS os quais serão usados como modelo.

Nota

Isso só funciona se sua documentação é servida a partir do diretório raiz. Se for servida de outro diretório, deve ser informado o prefixo complementar da url até esse diretório no argumento docroot quando criado o objeto websupport:

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

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

Realizando Pesquisas

Para usar o formulário de pesquisa integrado à barra lateral do Sphinx, crie uma função para manipular solicitações para a URL ‘search’ em relação à raiz da documentação. A consulta de pesquisa do usuário estará nos parâmetros GET, com a chave q. Em seguida use o método get_search_results() para recuperar os resultados da pesquisa. Em Flask seria assim:

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

Note que usamos o mesmo modelo para renderizar os resultados da pesquisa, como fizemos com nossos documentos. Isto é porque o método get_search_results() retorna o mesmo formato que o método get_document() utiliza.

Comentários & Propostas

Agora que isto está feito, podemos definir as funções para manusear chamadas AJAX a partir do script. Precisaremos de três funções. A primeira função é usada para adicionar novo comentário e irá chamar o método de suporte da web 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)

Você notará que tanto um parent_id quanto um node_id são enviados com a solicitação. Se o comentário estiver sendo anexado diretamente a um nó, parent_id estará vazia. Se o comentário for filho de outro comentário, então node_id estará vazia. Em seguida, a próxima função manipula a recuperação de comentários para um nó específico e é apropriadamente denominada 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)

A função final irá chamar o método process_vote() e ira manusear os votos do usuário nos comentários:

@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"

Moderação de Comentário

Por padrão, todos os comentários adicionados através do add_comment() são exibidos automaticamente. Se você deseja ter alguma forma de moderação, você pode passar o argumento de palavra-chave displayed:

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

Pode ser criada uma nova visão para manusear moderação de comentários. Será ativada quando o moderador decidir que um comentário deva ser aceito e exibido:

@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'

Rejeitar comentários ocorre através de apagar comentário.

Para executar ação configurada (como email para moderador) quando um novo comentário for adicionado, mas ainda não estar exibido, pode ser chamada a classe WebSupport quando instanciado o suporte do objeto:

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

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

A chamada de retorno da moderação deve ter um argumento que deve ser o mesmo dict comentado que foi retornado com add_comment().