2010-06-04 16:13:53 -05:00
|
|
|
.. _websupportquickstart:
|
|
|
|
|
|
|
|
Web Support Quick Start
|
|
|
|
=======================
|
|
|
|
|
2010-07-12 01:41:39 -05:00
|
|
|
Building Documentation Data
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
2010-06-26 14:49:30 -05:00
|
|
|
|
2010-07-12 12:15:01 -05:00
|
|
|
To make use of the web support package in your application you'll
|
2010-08-09 14:22:31 -05:00
|
|
|
need to build the data it uses. This data includes pickle files representing
|
2010-07-12 01:41:39 -05:00
|
|
|
documents, search indices, and node data that is used to track where
|
2010-07-12 12:15:01 -05:00
|
|
|
comments and other things are in a document. To do this you will need
|
2010-08-10 10:11:11 -05:00
|
|
|
to create an instance of the :class:`~sphinx.websupport.WebSupport`
|
2010-07-12 01:41:39 -05:00
|
|
|
class and call it's :meth:`~sphinx.websupport.WebSupport.build` method::
|
2010-06-04 16:13:53 -05:00
|
|
|
|
2010-07-12 01:41:39 -05:00
|
|
|
from sphinx.websupport import WebSupport
|
2010-06-04 16:13:53 -05:00
|
|
|
|
2010-07-12 01:41:39 -05:00
|
|
|
support = WebSupport(srcdir='/path/to/rst/sources/',
|
2010-08-09 14:22:31 -05:00
|
|
|
builddir='/path/to/build/outdir',
|
2010-08-10 10:11:11 -05:00
|
|
|
search='xapian')
|
2010-06-04 16:13:53 -05:00
|
|
|
|
2010-07-12 01:41:39 -05:00
|
|
|
support.build()
|
2010-06-26 14:49:30 -05:00
|
|
|
|
2010-07-12 01:41:39 -05:00
|
|
|
This will read reStructuredText sources from `srcdir` and place the
|
2010-08-10 10:11:11 -05:00
|
|
|
necessary data in `builddir`. The `builddir` will contain two
|
2010-08-09 14:22:31 -05:00
|
|
|
sub-directories. One named "data" that contains all the data needed
|
2010-07-12 01:41:39 -05:00
|
|
|
to display documents, search through documents, and add comments to
|
2010-08-10 10:11:11 -05:00
|
|
|
documents. The other directory will be called "static" and contains static
|
2010-08-09 14:22:31 -05:00
|
|
|
files that should be served from "/static".
|
2010-08-07 17:04:45 -05:00
|
|
|
|
|
|
|
.. note::
|
|
|
|
|
|
|
|
If you wish to serve static files from a path other than "/static", you
|
|
|
|
can do so by providing the *staticdir* keyword argument when creating
|
|
|
|
the :class:`~sphinx.websupport.api.WebSupport` object.
|
2010-06-04 16:13:53 -05:00
|
|
|
|
2010-07-12 01:41:39 -05:00
|
|
|
Integrating Sphinx Documents Into Your Webapp
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
2010-06-04 16:13:53 -05:00
|
|
|
|
2010-08-09 14:22:31 -05:00
|
|
|
Now that the data is built, it's time to do something useful with it.
|
2010-07-12 01:41:39 -05:00
|
|
|
Start off by creating a :class:`~sphinx.websupport.WebSupport` object
|
|
|
|
for your application::
|
2010-08-10 10:11:11 -05:00
|
|
|
|
2010-07-12 01:41:39 -05:00
|
|
|
from sphinx.websupport import WebSupport
|
2010-06-04 16:13:53 -05:00
|
|
|
|
2010-07-12 01:41:39 -05:00
|
|
|
support = WebSupport(datadir='/path/to/the/data',
|
|
|
|
search='xapian')
|
2010-06-26 14:49:30 -05:00
|
|
|
|
2010-07-12 01:41:39 -05:00
|
|
|
You'll only need one of these for each set of documentation you will be
|
2010-08-10 10:11:11 -05:00
|
|
|
working with. You can then call it's
|
2010-07-12 01:41:39 -05:00
|
|
|
:meth:`~sphinx.websupport.WebSupport.get_document` method to access
|
2010-07-12 12:15:01 -05:00
|
|
|
individual documents::
|
2010-06-26 14:49:30 -05:00
|
|
|
|
2010-07-12 01:41:39 -05:00
|
|
|
contents = support.get_document('contents')
|
2010-06-04 16:13:53 -05:00
|
|
|
|
2010-07-12 01:41:39 -05:00
|
|
|
This will return a dictionary containing the following items:
|
2010-06-04 16:13:53 -05:00
|
|
|
|
2010-07-12 01:41:39 -05:00
|
|
|
* **body**: The main body of the document as HTML
|
2010-08-10 10:11:11 -05:00
|
|
|
* **sidebar**: The sidebar of the document as HTML
|
2010-07-12 01:41:39 -05:00
|
|
|
* **relbar**: A div containing links to related documents
|
|
|
|
* **title**: The title of the document
|
2010-08-09 14:22:31 -05:00
|
|
|
* **css**: Links to css files used by Sphinx
|
|
|
|
* **js**: Javascript containing comment options
|
2010-06-16 08:33:33 -05:00
|
|
|
|
2010-07-12 01:41:39 -05:00
|
|
|
This dict can then be used as context for templates. The goal is to be
|
2010-08-10 10:11:11 -05:00
|
|
|
easy to integrate with your existing templating system. An example using
|
2010-07-12 01:41:39 -05:00
|
|
|
`Jinja2 <http://jinja.pocoo.org/2/>`_ is:
|
2010-06-26 14:49:30 -05:00
|
|
|
|
2010-07-12 01:41:39 -05:00
|
|
|
.. sourcecode:: html+jinja
|
2010-06-26 14:49:30 -05:00
|
|
|
|
2010-07-12 01:41:39 -05:00
|
|
|
{%- extends "layout.html" %}
|
2010-06-26 14:49:30 -05:00
|
|
|
|
2010-07-12 01:41:39 -05:00
|
|
|
{%- block title %}
|
2010-08-10 10:11:11 -05:00
|
|
|
{{ document.title }}
|
2010-07-12 01:41:39 -05:00
|
|
|
{%- endblock %}
|
2010-06-26 14:49:30 -05:00
|
|
|
|
2010-08-09 14:22:31 -05:00
|
|
|
{% block css %}
|
2010-08-10 10:11:11 -05:00
|
|
|
{{ super() }}
|
|
|
|
{{ document.css|safe }}
|
|
|
|
<link rel="stylesheet" href="/static/websupport-custom.css" type="text/css">
|
2010-08-09 14:22:31 -05:00
|
|
|
{% endblock %}
|
|
|
|
|
2010-08-07 17:04:45 -05:00
|
|
|
{%- block js %}
|
2010-08-10 10:11:11 -05:00
|
|
|
{{ super() }}
|
|
|
|
{{ document.js|safe }}
|
2010-08-07 17:04:45 -05:00
|
|
|
{%- endblock %}
|
|
|
|
|
2010-07-12 01:41:39 -05:00
|
|
|
{%- block relbar %}
|
2010-08-10 10:11:11 -05:00
|
|
|
{{ document.relbar|safe }}
|
2010-07-12 01:41:39 -05:00
|
|
|
{%- endblock %}
|
2010-06-26 14:49:30 -05:00
|
|
|
|
2010-07-12 01:41:39 -05:00
|
|
|
{%- block body %}
|
2010-08-10 10:11:11 -05:00
|
|
|
{{ document.body|safe }}
|
2010-07-12 01:41:39 -05:00
|
|
|
{%- endblock %}
|
2010-06-04 16:13:53 -05:00
|
|
|
|
2010-07-12 01:41:39 -05:00
|
|
|
{%- block sidebar %}
|
2010-08-10 10:11:11 -05:00
|
|
|
{{ document.sidebar|safe }}
|
2010-07-12 01:41:39 -05:00
|
|
|
{%- endblock %}
|
2010-06-04 16:13:53 -05:00
|
|
|
|
2010-08-07 17:04:45 -05:00
|
|
|
Authentication
|
|
|
|
--------------
|
|
|
|
|
|
|
|
To use certain features such as voting it must be possible to authenticate
|
2010-08-09 14:22:31 -05:00
|
|
|
users. The details of the authentication are left to your application.
|
2010-08-07 17:04:45 -05:00
|
|
|
Once a user has been authenticated you can pass the user's details to certain
|
|
|
|
:class:`~sphinx.websupport.WebSupport` methods using the *username* and
|
|
|
|
*moderator* keyword arguments. The web support package will store the
|
|
|
|
username with comments and votes. The only caveat is that if you allow users
|
2010-08-09 14:22:31 -05:00
|
|
|
to change their username you must update the websupport package's data::
|
2010-08-07 17:04:45 -05:00
|
|
|
|
2010-08-10 10:11:11 -05:00
|
|
|
support.update_username(old_username, new_username)
|
2010-08-07 17:04:45 -05:00
|
|
|
|
|
|
|
*username* should be a unique string which identifies a user, and *moderator*
|
|
|
|
should be a boolean representing whether the user has moderation
|
|
|
|
privilieges. The default value for *moderator* is *False*.
|
|
|
|
|
|
|
|
An example `Flask <http://flask.pocoo.org/>`_ function that checks whether
|
2010-08-09 14:22:31 -05:00
|
|
|
a user is logged in and then retrieves a document is::
|
|
|
|
|
|
|
|
from sphinx.websupport.errors import *
|
2010-06-04 16:13:53 -05:00
|
|
|
|
2010-07-12 01:41:39 -05:00
|
|
|
@app.route('/<path:docname>')
|
2010-06-04 16:13:53 -05:00
|
|
|
def doc(docname):
|
2010-08-10 10:11:11 -05:00
|
|
|
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)
|
2010-06-04 16:13:53 -05:00
|
|
|
return render_template('doc.html', document=document)
|
|
|
|
|
2010-08-07 17:04:45 -05:00
|
|
|
The first thing to notice is that the *docname* is just the request path.
|
2010-08-09 14:22:31 -05:00
|
|
|
This makes accessing the correct document easy from a single view.
|
2010-08-07 17:04:45 -05:00
|
|
|
If the user is authenticated then the username and moderation status are
|
2010-08-10 10:11:11 -05:00
|
|
|
passed along with the docname to
|
2010-08-07 17:04:45 -05:00
|
|
|
:meth:`~sphinx.websupport.WebSupport.get_document`. The web support package
|
|
|
|
will then add this data to the COMMENT_OPTIONS that are used in the template.
|
2010-06-26 14:49:30 -05:00
|
|
|
|
2010-07-12 01:41:39 -05:00
|
|
|
.. note::
|
2010-06-04 16:13:53 -05:00
|
|
|
|
2010-08-10 10:11:11 -05:00
|
|
|
This only works works if your documentation is served from your
|
|
|
|
document root. If it is served from another directory, you will
|
|
|
|
need to prefix the url route with that directory, and give the `docroot`
|
|
|
|
keyword argument when creating the web support object::
|
|
|
|
|
2010-08-14 14:48:27 -05:00
|
|
|
support = WebSupport(..., docroot='docs')
|
2010-08-10 10:11:11 -05:00
|
|
|
|
|
|
|
@app.route('/docs/<path:docname>')
|
2010-06-26 14:49:30 -05:00
|
|
|
|
2010-07-12 01:41:39 -05:00
|
|
|
Performing Searches
|
|
|
|
~~~~~~~~~~~~~~~~~~~
|
2010-06-16 08:33:33 -05:00
|
|
|
|
2010-07-12 01:41:39 -05:00
|
|
|
To use the search form built-in to the Sphinx sidebar, create a function
|
|
|
|
to handle requests to the url 'search' relative to the documentation root.
|
|
|
|
The user's search query will be in the GET parameters, with the key `q`.
|
|
|
|
Then use the :meth:`~sphinx.websupport.WebSupport.get_search_results` method
|
2010-08-10 10:11:11 -05:00
|
|
|
to retrieve search results. In `Flask <http://flask.pocoo.org/>`_ that
|
2010-07-12 01:41:39 -05:00
|
|
|
would be like this::
|
|
|
|
|
|
|
|
@app.route('/search')
|
|
|
|
def search():
|
|
|
|
q = request.args.get('q')
|
|
|
|
document = support.get_search_results(q)
|
|
|
|
return render_template('doc.html', document=document)
|
|
|
|
|
|
|
|
Note that we used the same template to render our search results as we
|
2010-08-10 10:11:11 -05:00
|
|
|
did to render our documents. That's because
|
2010-07-12 01:41:39 -05:00
|
|
|
:meth:`~sphinx.websupport.WebSupport.get_search_results` returns a context
|
2010-07-12 12:15:01 -05:00
|
|
|
dict in the same format that
|
|
|
|
:meth:`~sphinx.websupport.WebSupport.get_document` does.
|
2010-07-12 01:41:39 -05:00
|
|
|
|
2010-08-09 14:22:31 -05:00
|
|
|
Comments & Proposals
|
|
|
|
~~~~~~~~~~~~~~~~~~~~
|
2010-07-12 01:41:39 -05:00
|
|
|
|
2010-07-12 12:15:01 -05:00
|
|
|
Now that this is done it's time to define the functions that handle
|
|
|
|
the AJAX calls from the script. You will need three functions. The first
|
|
|
|
function is used to add a new comment, and will call the web support method
|
|
|
|
:meth:`~sphinx.websupport.WebSupport.add_comment`::
|
|
|
|
|
|
|
|
@app.route('/docs/add_comment', methods=['POST'])
|
|
|
|
def add_comment():
|
|
|
|
parent_id = request.form.get('parent', '')
|
2010-08-09 14:22:31 -05:00
|
|
|
node_id = request.form.get('node', '')
|
2010-07-12 12:15:01 -05:00
|
|
|
text = request.form.get('text', '')
|
2010-08-09 14:22:31 -05:00
|
|
|
proposal = request.form.get('proposal', '')
|
2010-07-12 12:15:01 -05:00
|
|
|
username = g.user.name if g.user is not None else 'Anonymous'
|
2010-08-09 14:22:31 -05:00
|
|
|
comment = support.add_comment(text, node_id='node_id',
|
2010-08-10 10:11:11 -05:00
|
|
|
parent_id='parent_id',
|
|
|
|
username=username, proposal=proposal)
|
2010-07-12 12:15:01 -05:00
|
|
|
return jsonify(comment=comment)
|
|
|
|
|
2010-08-09 14:22:31 -05:00
|
|
|
You'll notice that both a `parent_id` and `node_id` are sent with the
|
|
|
|
request. If the comment is being attached directly to a node, `parent_id`
|
|
|
|
will be empty. If the comment is a child of another comment, then `node_id`
|
2010-08-10 10:11:11 -05:00
|
|
|
will be empty. Then next function handles the retrieval of comments for a
|
|
|
|
specific node, and is aptly named
|
2010-08-09 14:22:31 -05:00
|
|
|
:meth:`~sphinx.websupport.WebSupport.get_data`::
|
2010-07-12 12:15:01 -05:00
|
|
|
|
|
|
|
@app.route('/docs/get_comments')
|
|
|
|
def get_comments():
|
2010-08-09 14:22:31 -05:00
|
|
|
username = g.user.name if g.user else None
|
2010-08-10 10:11:11 -05:00
|
|
|
moderator = g.user.moderator if g.user else False
|
|
|
|
node_id = request.args.get('node', '')
|
2010-08-09 14:22:31 -05:00
|
|
|
data = support.get_data(parent_id, user_id)
|
|
|
|
return jsonify(**data)
|
2010-07-12 12:15:01 -05:00
|
|
|
|
|
|
|
The final function that is needed will call
|
|
|
|
:meth:`~sphinx.websupport.WebSupport.process_vote`, and will handle user
|
|
|
|
votes on comments::
|
|
|
|
|
|
|
|
@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"
|
|
|
|
|
2010-08-09 14:22:31 -05:00
|
|
|
Comment Moderation
|
|
|
|
~~~~~~~~~~~~~~~~~~
|
|
|
|
|
2010-08-10 10:11:11 -05:00
|
|
|
By default all comments added through
|
2010-08-09 14:22:31 -05:00
|
|
|
:meth:`~sphinx.websupport.WebSupport.add_comment` are automatically
|
|
|
|
displayed. If you wish to have some form of moderation, you can pass
|
|
|
|
the `displayed` keyword argument::
|
|
|
|
|
|
|
|
comment = support.add_comment(text, node_id='node_id',
|
2010-08-10 10:11:11 -05:00
|
|
|
parent_id='parent_id',
|
2010-08-09 14:22:31 -05:00
|
|
|
username=username, proposal=proposal,
|
2010-08-10 10:11:11 -05:00
|
|
|
displayed=False)
|
2010-08-09 14:22:31 -05:00
|
|
|
|
|
|
|
You can then create two new views to handle the moderation of comments. The
|
|
|
|
first will be called when a moderator decides a comment should be accepted
|
|
|
|
and displayed::
|
|
|
|
|
|
|
|
@app.route('/docs/accept_comment', methods=['POST'])
|
|
|
|
def accept_comment():
|
|
|
|
moderator = g.user.moderator if g.user else False
|
2010-08-10 10:11:11 -05:00
|
|
|
comment_id = request.form.get('id')
|
|
|
|
support.accept_comment(comment_id, moderator=moderator)
|
|
|
|
return 'OK'
|
2010-08-09 14:22:31 -05:00
|
|
|
|
|
|
|
The next is very similar, but used when rejecting a comment::
|
|
|
|
|
|
|
|
@app.route('/docs/reject_comment', methods=['POST'])
|
|
|
|
def reject_comment():
|
|
|
|
moderator = g.user.moderator if g.user else False
|
2010-08-10 10:11:11 -05:00
|
|
|
comment_id = request.form.get('id')
|
|
|
|
support.reject_comment(comment_id, moderator=moderator)
|
|
|
|
return 'OK'
|
2010-08-09 14:22:31 -05:00
|
|
|
|
|
|
|
To perform a custom action (such as emailing a moderator) when a new comment
|
|
|
|
is added but not displayed, you can pass callable to the
|
|
|
|
:class:`~sphinx.websupport.WebSupport` class when instantiating your support
|
|
|
|
object::
|
|
|
|
|
|
|
|
def moderation_callback(comment):
|
2010-08-14 14:48:27 -05:00
|
|
|
"""Do something..."""
|
2010-08-09 14:22:31 -05:00
|
|
|
|
2010-08-14 14:48:27 -05:00
|
|
|
support = WebSupport(..., moderation_callback=moderation_callback)
|
2010-07-12 12:15:01 -05:00
|
|
|
|
2010-08-09 14:22:31 -05:00
|
|
|
The moderation callback must take one argument, which will be the same
|
2010-08-10 10:11:11 -05:00
|
|
|
comment dict that is returned by add_comment.
|