mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Removed trailing whitespace
This commit is contained in:
parent
d77cbf3043
commit
023f342bed
@ -7,49 +7,49 @@ The WebSupport Class
|
|||||||
|
|
||||||
.. class:: WebSupport
|
.. class:: WebSupport
|
||||||
|
|
||||||
The main API class for the web support package. All interactions
|
The main API class for the web support package. All interactions
|
||||||
with the web support package should occur through this class.
|
with the web support package should occur through this class.
|
||||||
|
|
||||||
The class takes the following keyword arguments:
|
The class takes the following keyword arguments:
|
||||||
|
|
||||||
srcdir
|
srcdir
|
||||||
The directory containing reStructuredText source files.
|
The directory containing reStructuredText source files.
|
||||||
|
|
||||||
builddir
|
builddir
|
||||||
The directory that build data and static files should be placed in.
|
The directory that build data and static files should be placed in.
|
||||||
This should be used when creating a :class:`WebSupport` object that
|
This should be used when creating a :class:`WebSupport` object that
|
||||||
will be used to build data.
|
will be used to build data.
|
||||||
|
|
||||||
datadir:
|
datadir:
|
||||||
The directory that the web support data is in. This should be used
|
The directory that the web support data is in. This should be used
|
||||||
when creating a :class:`WebSupport` object that will be used to
|
when creating a :class:`WebSupport` object that will be used to
|
||||||
retrieve data.
|
retrieve data.
|
||||||
|
|
||||||
search:
|
search:
|
||||||
This may contain either a string (e.g. 'xapian') referencing a
|
This may contain either a string (e.g. 'xapian') referencing a
|
||||||
built-in search adapter to use, or an instance of a subclass of
|
built-in search adapter to use, or an instance of a subclass of
|
||||||
:class:`~sphinx.websupport.search.BaseSearch`.
|
:class:`~sphinx.websupport.search.BaseSearch`.
|
||||||
|
|
||||||
storage:
|
storage:
|
||||||
This may contain either a string representing a database uri, or an
|
This may contain either a string representing a database uri, or an
|
||||||
instance of a subclass of
|
instance of a subclass of
|
||||||
:class:`~sphinx.websupport.storage.StorageBackend`. If this is not
|
:class:`~sphinx.websupport.storage.StorageBackend`. If this is not
|
||||||
provided a new sqlite database will be created.
|
provided a new sqlite database will be created.
|
||||||
|
|
||||||
moderation_callback:
|
moderation_callback:
|
||||||
A callable to be called when a new comment is added that is not
|
A callable to be called when a new comment is added that is not
|
||||||
displayed. It must accept one argument: a dict representing the
|
displayed. It must accept one argument: a dict representing the
|
||||||
comment that was added.
|
comment that was added.
|
||||||
|
|
||||||
staticdir:
|
staticdir:
|
||||||
If static files are served from a location besides "/static", this
|
If static files are served from a location besides "/static", this
|
||||||
should be a string with the name of that location
|
should be a string with the name of that location
|
||||||
(e.g. '/static_files').
|
(e.g. '/static_files').
|
||||||
|
|
||||||
|
docroot:
|
||||||
|
If the documentation is not served from the base path of a URL, this
|
||||||
|
should be a string specifying that path (e.g. 'docs')
|
||||||
|
|
||||||
docroot:
|
|
||||||
If the documentation is not served from the base path of a URL, this
|
|
||||||
should be a string specifying that path (e.g. 'docs')
|
|
||||||
|
|
||||||
Methods
|
Methods
|
||||||
~~~~~~~
|
~~~~~~~
|
||||||
|
|
||||||
@ -64,4 +64,3 @@ Methods
|
|||||||
.. automethod:: sphinx.websupport.WebSupport.process_vote
|
.. automethod:: sphinx.websupport.WebSupport.process_vote
|
||||||
|
|
||||||
.. automethod:: sphinx.websupport.WebSupport.get_search_results
|
.. automethod:: sphinx.websupport.WebSupport.get_search_results
|
||||||
|
|
||||||
|
@ -10,22 +10,22 @@ To make use of the web support package in your application you'll
|
|||||||
need to build the data it uses. This data includes pickle files representing
|
need to build the data it uses. This data includes pickle files representing
|
||||||
documents, search indices, and node data that is used to track where
|
documents, search indices, and node data that is used to track where
|
||||||
comments and other things are in a document. To do this you will need
|
comments and other things are in a document. To do this you will need
|
||||||
to create an instance of the :class:`~sphinx.websupport.WebSupport`
|
to create an instance of the :class:`~sphinx.websupport.WebSupport`
|
||||||
class and call it's :meth:`~sphinx.websupport.WebSupport.build` method::
|
class and call it's :meth:`~sphinx.websupport.WebSupport.build` method::
|
||||||
|
|
||||||
from sphinx.websupport import WebSupport
|
from sphinx.websupport import WebSupport
|
||||||
|
|
||||||
support = WebSupport(srcdir='/path/to/rst/sources/',
|
support = WebSupport(srcdir='/path/to/rst/sources/',
|
||||||
builddir='/path/to/build/outdir',
|
builddir='/path/to/build/outdir',
|
||||||
search='xapian')
|
search='xapian')
|
||||||
|
|
||||||
support.build()
|
support.build()
|
||||||
|
|
||||||
This will read reStructuredText sources from `srcdir` and place the
|
This will read reStructuredText sources from `srcdir` and place the
|
||||||
necessary data in `builddir`. The `builddir` will contain two
|
necessary data in `builddir`. The `builddir` will contain two
|
||||||
sub-directories. One named "data" that contains all the data needed
|
sub-directories. One named "data" that contains all the data needed
|
||||||
to display documents, search through documents, and add comments to
|
to display documents, search through documents, and add comments to
|
||||||
documents. The other directory will be called "static" and contains static
|
documents. The other directory will be called "static" and contains static
|
||||||
files that should be served from "/static".
|
files that should be served from "/static".
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
@ -40,14 +40,14 @@ Integrating Sphinx Documents Into Your Webapp
|
|||||||
Now that the data is built, it's time to do something useful with it.
|
Now that the data is built, it's time to do something useful with it.
|
||||||
Start off by creating a :class:`~sphinx.websupport.WebSupport` object
|
Start off by creating a :class:`~sphinx.websupport.WebSupport` object
|
||||||
for your application::
|
for your application::
|
||||||
|
|
||||||
from sphinx.websupport import WebSupport
|
from sphinx.websupport import WebSupport
|
||||||
|
|
||||||
support = WebSupport(datadir='/path/to/the/data',
|
support = WebSupport(datadir='/path/to/the/data',
|
||||||
search='xapian')
|
search='xapian')
|
||||||
|
|
||||||
You'll only need one of these for each set of documentation you will be
|
You'll only need one of these for each set of documentation you will be
|
||||||
working with. You can then call it's
|
working with. You can then call it's
|
||||||
:meth:`~sphinx.websupport.WebSupport.get_document` method to access
|
:meth:`~sphinx.websupport.WebSupport.get_document` method to access
|
||||||
individual documents::
|
individual documents::
|
||||||
|
|
||||||
@ -56,14 +56,14 @@ individual documents::
|
|||||||
This will return a dictionary containing the following items:
|
This will return a dictionary containing the following items:
|
||||||
|
|
||||||
* **body**: The main body of the document as HTML
|
* **body**: The main body of the document as HTML
|
||||||
* **sidebar**: The sidebar of the document as HTML
|
* **sidebar**: The sidebar of the document as HTML
|
||||||
* **relbar**: A div containing links to related documents
|
* **relbar**: A div containing links to related documents
|
||||||
* **title**: The title of the document
|
* **title**: The title of the document
|
||||||
* **css**: Links to css files used by Sphinx
|
* **css**: Links to css files used by Sphinx
|
||||||
* **js**: Javascript containing comment options
|
* **js**: Javascript containing comment options
|
||||||
|
|
||||||
This dict can then be used as context for templates. The goal is to be
|
This dict can then be used as context for templates. The goal is to be
|
||||||
easy to integrate with your existing templating system. An example using
|
easy to integrate with your existing templating system. An example using
|
||||||
`Jinja2 <http://jinja.pocoo.org/2/>`_ is:
|
`Jinja2 <http://jinja.pocoo.org/2/>`_ is:
|
||||||
|
|
||||||
.. sourcecode:: html+jinja
|
.. sourcecode:: html+jinja
|
||||||
@ -71,30 +71,30 @@ easy to integrate with your existing templating system. An example using
|
|||||||
{%- extends "layout.html" %}
|
{%- extends "layout.html" %}
|
||||||
|
|
||||||
{%- block title %}
|
{%- block title %}
|
||||||
{{ document.title }}
|
{{ document.title }}
|
||||||
{%- endblock %}
|
{%- endblock %}
|
||||||
|
|
||||||
{% block css %}
|
{% block css %}
|
||||||
{{ super() }}
|
{{ super() }}
|
||||||
{{ document.css|safe }}
|
{{ document.css|safe }}
|
||||||
<link rel="stylesheet" href="/static/websupport-custom.css" type="text/css">
|
<link rel="stylesheet" href="/static/websupport-custom.css" type="text/css">
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{%- block js %}
|
{%- block js %}
|
||||||
{{ super() }}
|
{{ super() }}
|
||||||
{{ document.js|safe }}
|
{{ document.js|safe }}
|
||||||
{%- endblock %}
|
{%- endblock %}
|
||||||
|
|
||||||
{%- block relbar %}
|
{%- block relbar %}
|
||||||
{{ document.relbar|safe }}
|
{{ document.relbar|safe }}
|
||||||
{%- endblock %}
|
{%- endblock %}
|
||||||
|
|
||||||
{%- block body %}
|
{%- block body %}
|
||||||
{{ document.body|safe }}
|
{{ document.body|safe }}
|
||||||
{%- endblock %}
|
{%- endblock %}
|
||||||
|
|
||||||
{%- block sidebar %}
|
{%- block sidebar %}
|
||||||
{{ document.sidebar|safe }}
|
{{ document.sidebar|safe }}
|
||||||
{%- endblock %}
|
{%- endblock %}
|
||||||
|
|
||||||
Authentication
|
Authentication
|
||||||
@ -108,7 +108,7 @@ Once a user has been authenticated you can pass the user's details to certain
|
|||||||
username with comments and votes. The only caveat is that if you allow users
|
username with comments and votes. The only caveat is that if you allow users
|
||||||
to change their username you must update the websupport package's data::
|
to change their username you must update the websupport package's data::
|
||||||
|
|
||||||
support.update_username(old_username, new_username)
|
support.update_username(old_username, new_username)
|
||||||
|
|
||||||
*username* should be a unique string which identifies a user, and *moderator*
|
*username* should be a unique string which identifies a user, and *moderator*
|
||||||
should be a boolean representing whether the user has moderation
|
should be a boolean representing whether the user has moderation
|
||||||
@ -121,32 +121,32 @@ a user is logged in and then retrieves a document is::
|
|||||||
|
|
||||||
@app.route('/<path:docname>')
|
@app.route('/<path:docname>')
|
||||||
def doc(docname):
|
def doc(docname):
|
||||||
username = g.user.name if g.user else ''
|
username = g.user.name if g.user else ''
|
||||||
moderator = g.user.moderator if g.user else False
|
moderator = g.user.moderator if g.user else False
|
||||||
try:
|
try:
|
||||||
document = support.get_document(docname, username, moderator)
|
document = support.get_document(docname, username, moderator)
|
||||||
except DocumentNotFoundError:
|
except DocumentNotFoundError:
|
||||||
abort(404)
|
abort(404)
|
||||||
return render_template('doc.html', document=document)
|
return render_template('doc.html', document=document)
|
||||||
|
|
||||||
The first thing to notice is that the *docname* is just the request path.
|
The first thing to notice is that the *docname* is just the request path.
|
||||||
This makes accessing the correct document easy from a single view.
|
This makes accessing the correct document easy from a single view.
|
||||||
If the user is authenticated then the username and moderation status are
|
If the user is authenticated then the username and moderation status are
|
||||||
passed along with the docname to
|
passed along with the docname to
|
||||||
:meth:`~sphinx.websupport.WebSupport.get_document`. The web support package
|
: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.
|
will then add this data to the COMMENT_OPTIONS that are used in the template.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
This only works works if your documentation is served from your
|
This only works works if your documentation is served from your
|
||||||
document root. If it is served from another directory, you will
|
document root. If it is served from another directory, you will
|
||||||
need to prefix the url route with that directory, and give the `docroot`
|
need to prefix the url route with that directory, and give the `docroot`
|
||||||
keyword argument when creating the web support object::
|
keyword argument when creating the web support object::
|
||||||
|
|
||||||
support = WebSupport(...
|
support = WebSupport(...
|
||||||
docroot='docs')
|
docroot='docs')
|
||||||
|
|
||||||
@app.route('/docs/<path:docname>')
|
@app.route('/docs/<path:docname>')
|
||||||
|
|
||||||
Performing Searches
|
Performing Searches
|
||||||
~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~
|
||||||
@ -155,7 +155,7 @@ 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.
|
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`.
|
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
|
Then use the :meth:`~sphinx.websupport.WebSupport.get_search_results` method
|
||||||
to retrieve search results. In `Flask <http://flask.pocoo.org/>`_ that
|
to retrieve search results. In `Flask <http://flask.pocoo.org/>`_ that
|
||||||
would be like this::
|
would be like this::
|
||||||
|
|
||||||
@app.route('/search')
|
@app.route('/search')
|
||||||
@ -165,7 +165,7 @@ would be like this::
|
|||||||
return render_template('doc.html', document=document)
|
return render_template('doc.html', document=document)
|
||||||
|
|
||||||
Note that we used the same template to render our search results as we
|
Note that we used the same template to render our search results as we
|
||||||
did to render our documents. That's because
|
did to render our documents. That's because
|
||||||
:meth:`~sphinx.websupport.WebSupport.get_search_results` returns a context
|
:meth:`~sphinx.websupport.WebSupport.get_search_results` returns a context
|
||||||
dict in the same format that
|
dict in the same format that
|
||||||
:meth:`~sphinx.websupport.WebSupport.get_document` does.
|
:meth:`~sphinx.websupport.WebSupport.get_document` does.
|
||||||
@ -186,22 +186,22 @@ function is used to add a new comment, and will call the web support method
|
|||||||
proposal = request.form.get('proposal', '')
|
proposal = request.form.get('proposal', '')
|
||||||
username = g.user.name if g.user is not None else 'Anonymous'
|
username = g.user.name if g.user is not None else 'Anonymous'
|
||||||
comment = support.add_comment(text, node_id='node_id',
|
comment = support.add_comment(text, node_id='node_id',
|
||||||
parent_id='parent_id',
|
parent_id='parent_id',
|
||||||
username=username, proposal=proposal)
|
username=username, proposal=proposal)
|
||||||
return jsonify(comment=comment)
|
return jsonify(comment=comment)
|
||||||
|
|
||||||
You'll notice that both a `parent_id` and `node_id` are sent with the
|
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`
|
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`
|
will be empty. If the comment is a child of another comment, then `node_id`
|
||||||
will be empty. Then next function handles the retrieval of comments for a
|
will be empty. Then next function handles the retrieval of comments for a
|
||||||
specific node, and is aptly named
|
specific node, and is aptly named
|
||||||
:meth:`~sphinx.websupport.WebSupport.get_data`::
|
:meth:`~sphinx.websupport.WebSupport.get_data`::
|
||||||
|
|
||||||
@app.route('/docs/get_comments')
|
@app.route('/docs/get_comments')
|
||||||
def get_comments():
|
def get_comments():
|
||||||
username = g.user.name if g.user else None
|
username = g.user.name if g.user else None
|
||||||
moderator = g.user.moderator if g.user else False
|
moderator = g.user.moderator if g.user else False
|
||||||
node_id = request.args.get('node', '')
|
node_id = request.args.get('node', '')
|
||||||
data = support.get_data(parent_id, user_id)
|
data = support.get_data(parent_id, user_id)
|
||||||
return jsonify(**data)
|
return jsonify(**data)
|
||||||
|
|
||||||
@ -223,15 +223,15 @@ votes on comments::
|
|||||||
Comment Moderation
|
Comment Moderation
|
||||||
~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
By default all comments added through
|
By default all comments added through
|
||||||
:meth:`~sphinx.websupport.WebSupport.add_comment` are automatically
|
:meth:`~sphinx.websupport.WebSupport.add_comment` are automatically
|
||||||
displayed. If you wish to have some form of moderation, you can pass
|
displayed. If you wish to have some form of moderation, you can pass
|
||||||
the `displayed` keyword argument::
|
the `displayed` keyword argument::
|
||||||
|
|
||||||
comment = support.add_comment(text, node_id='node_id',
|
comment = support.add_comment(text, node_id='node_id',
|
||||||
parent_id='parent_id',
|
parent_id='parent_id',
|
||||||
username=username, proposal=proposal,
|
username=username, proposal=proposal,
|
||||||
displayed=False)
|
displayed=False)
|
||||||
|
|
||||||
You can then create two new views to handle the moderation of comments. The
|
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
|
first will be called when a moderator decides a comment should be accepted
|
||||||
@ -240,18 +240,18 @@ and displayed::
|
|||||||
@app.route('/docs/accept_comment', methods=['POST'])
|
@app.route('/docs/accept_comment', methods=['POST'])
|
||||||
def accept_comment():
|
def accept_comment():
|
||||||
moderator = g.user.moderator if g.user else False
|
moderator = g.user.moderator if g.user else False
|
||||||
comment_id = request.form.get('id')
|
comment_id = request.form.get('id')
|
||||||
support.accept_comment(comment_id, moderator=moderator)
|
support.accept_comment(comment_id, moderator=moderator)
|
||||||
return 'OK'
|
return 'OK'
|
||||||
|
|
||||||
The next is very similar, but used when rejecting a comment::
|
The next is very similar, but used when rejecting a comment::
|
||||||
|
|
||||||
@app.route('/docs/reject_comment', methods=['POST'])
|
@app.route('/docs/reject_comment', methods=['POST'])
|
||||||
def reject_comment():
|
def reject_comment():
|
||||||
moderator = g.user.moderator if g.user else False
|
moderator = g.user.moderator if g.user else False
|
||||||
comment_id = request.form.get('id')
|
comment_id = request.form.get('id')
|
||||||
support.reject_comment(comment_id, moderator=moderator)
|
support.reject_comment(comment_id, moderator=moderator)
|
||||||
return 'OK'
|
return 'OK'
|
||||||
|
|
||||||
To perform a custom action (such as emailing a moderator) when a new comment
|
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
|
is added but not displayed, you can pass callable to the
|
||||||
@ -265,4 +265,4 @@ object::
|
|||||||
moderation_callback=moderation_callback)
|
moderation_callback=moderation_callback)
|
||||||
|
|
||||||
The moderation callback must take one argument, which will be the same
|
The moderation callback must take one argument, which will be the same
|
||||||
comment dict that is returned by add_comment.
|
comment dict that is returned by add_comment.
|
||||||
|
@ -10,26 +10,26 @@ To create a custom search adapter you will need to subclass the
|
|||||||
and pass that as the `search` keyword argument when you create the
|
and pass that as the `search` keyword argument when you create the
|
||||||
:class:`~sphinx.websupport.WebSupport` object::
|
:class:`~sphinx.websupport.WebSupport` object::
|
||||||
|
|
||||||
support = Websupport(srcdir=srcdir,
|
support = Websupport(srcdir=srcdir,
|
||||||
builddir=builddir,
|
builddir=builddir,
|
||||||
search=MySearch())
|
search=MySearch())
|
||||||
|
|
||||||
For more information about creating a custom search adapter, please see
|
For more information about creating a custom search adapter, please see
|
||||||
the documentation of the :class:`BaseSearch` class below.
|
the documentation of the :class:`BaseSearch` class below.
|
||||||
|
|
||||||
.. class:: BaseSearch
|
.. class:: BaseSearch
|
||||||
|
|
||||||
Defines an interface for search adapters.
|
Defines an interface for search adapters.
|
||||||
|
|
||||||
BaseSearch Methods
|
BaseSearch Methods
|
||||||
~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
The following methods are defined in the BaseSearch class. Some methods
|
The following methods are defined in the BaseSearch class. Some methods
|
||||||
do not need to be overridden, but some (
|
do not need to be overridden, but some (
|
||||||
:meth:`~sphinx.websupport.search.BaseSearch.add_document` and
|
:meth:`~sphinx.websupport.search.BaseSearch.add_document` and
|
||||||
:meth:`~sphinx.websupport.search.BaseSearch.handle_query`) must be
|
:meth:`~sphinx.websupport.search.BaseSearch.handle_query`) must be
|
||||||
overridden in your subclass. For a working example, look at the
|
overridden in your subclass. For a working example, look at the
|
||||||
built-in adapter for whoosh.
|
built-in adapter for whoosh.
|
||||||
|
|
||||||
.. automethod:: sphinx.websupport.search.BaseSearch.init_indexing
|
.. automethod:: sphinx.websupport.search.BaseSearch.init_indexing
|
||||||
|
|
||||||
@ -44,4 +44,3 @@ BaseSearch Methods
|
|||||||
.. automethod:: sphinx.websupport.search.BaseSearch.handle_query
|
.. automethod:: sphinx.websupport.search.BaseSearch.handle_query
|
||||||
|
|
||||||
.. automethod:: sphinx.websupport.search.BaseSearch.extract_context
|
.. automethod:: sphinx.websupport.search.BaseSearch.extract_context
|
||||||
|
|
||||||
|
@ -10,16 +10,16 @@ To create a custom storage backend you will need to subclass the
|
|||||||
and pass that as the `storage` keyword argument when you create the
|
and pass that as the `storage` keyword argument when you create the
|
||||||
:class:`~sphinx.websupport.WebSupport` object::
|
:class:`~sphinx.websupport.WebSupport` object::
|
||||||
|
|
||||||
support = Websupport(srcdir=srcdir,
|
support = Websupport(srcdir=srcdir,
|
||||||
builddir=builddir,
|
builddir=builddir,
|
||||||
storage=MyStorage())
|
storage=MyStorage())
|
||||||
|
|
||||||
For more information about creating a custom storage backend, please see
|
For more information about creating a custom storage backend, please see
|
||||||
the documentation of the :class:`StorageBackend` class below.
|
the documentation of the :class:`StorageBackend` class below.
|
||||||
|
|
||||||
.. class:: StorageBackend
|
.. class:: StorageBackend
|
||||||
|
|
||||||
Defines an interface for storage backends.
|
Defines an interface for storage backends.
|
||||||
|
|
||||||
StorageBackend Methods
|
StorageBackend Methods
|
||||||
~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~
|
||||||
@ -42,4 +42,4 @@ StorageBackend Methods
|
|||||||
|
|
||||||
.. automethod:: sphinx.websupport.storage.StorageBackend.accept_comment
|
.. automethod:: sphinx.websupport.storage.StorageBackend.accept_comment
|
||||||
|
|
||||||
.. automethod:: sphinx.websupport.storage.StorageBackend.reject_comment
|
.. automethod:: sphinx.websupport.storage.StorageBackend.reject_comment
|
||||||
|
@ -4,12 +4,12 @@ Sphinx Web Support
|
|||||||
==================
|
==================
|
||||||
|
|
||||||
Sphinx provides a way to easily integrate Sphinx documentation
|
Sphinx provides a way to easily integrate Sphinx documentation
|
||||||
into your web application. To learn more read the
|
into your web application. To learn more read the
|
||||||
:ref:`websupportquickstart`.
|
:ref:`websupportquickstart`.
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
|
|
||||||
web/quickstart
|
web/quickstart
|
||||||
web/api
|
web/api
|
||||||
web/searchadapters
|
web/searchadapters
|
||||||
web/storagebackends
|
web/storagebackends
|
||||||
|
@ -58,7 +58,7 @@ class WebSupportBuilder(StandaloneHTMLBuilder):
|
|||||||
def handle_page(self, pagename, addctx, templatename='page.html',
|
def handle_page(self, pagename, addctx, templatename='page.html',
|
||||||
outfilename=None, event_arg=None):
|
outfilename=None, event_arg=None):
|
||||||
# This is mostly copied from StandaloneHTMLBuilder. However, instead
|
# This is mostly copied from StandaloneHTMLBuilder. However, instead
|
||||||
# of rendering the template and saving the html, create a context
|
# of rendering the template and saving the html, create a context
|
||||||
# dict and pickle it.
|
# dict and pickle it.
|
||||||
ctx = self.globalcontext.copy()
|
ctx = self.globalcontext.copy()
|
||||||
ctx['pagename'] = pagename
|
ctx['pagename'] = pagename
|
||||||
@ -140,7 +140,7 @@ class WebSupportBuilder(StandaloneHTMLBuilder):
|
|||||||
FILE_SUFFIX: '',
|
FILE_SUFFIX: '',
|
||||||
HAS_SOURCE: '%s'
|
HAS_SOURCE: '%s'
|
||||||
};
|
};
|
||||||
</script>"""
|
</script>"""
|
||||||
opts = opts % (ctx.get('url_root', ''), escape(ctx['release']),
|
opts = opts % (ctx.get('url_root', ''), escape(ctx['release']),
|
||||||
str(ctx['has_source']).lower())
|
str(ctx['has_source']).lower())
|
||||||
scripts = []
|
scripts = []
|
||||||
@ -148,4 +148,3 @@ class WebSupportBuilder(StandaloneHTMLBuilder):
|
|||||||
scripts.append(make_script(file))
|
scripts.append(make_script(file))
|
||||||
scripts.append(make_script('_static/websupport.js'))
|
scripts.append(make_script('_static/websupport.js'))
|
||||||
return opts + '\n' + '\n'.join(scripts)
|
return opts + '\n' + '\n'.join(scripts)
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@ class WebSupport(object):
|
|||||||
self.moderation_callback = moderation_callback
|
self.moderation_callback = moderation_callback
|
||||||
|
|
||||||
self._init_templating()
|
self._init_templating()
|
||||||
self._init_search(search)
|
self._init_search(search)
|
||||||
self._init_storage(storage)
|
self._init_storage(storage)
|
||||||
|
|
||||||
self._make_base_comment_options()
|
self._make_base_comment_options()
|
||||||
@ -105,7 +105,7 @@ class WebSupport(object):
|
|||||||
doctreedir = path.join(self.outdir, 'doctrees')
|
doctreedir = path.join(self.outdir, 'doctrees')
|
||||||
app = WebSupportApp(self.srcdir, self.srcdir,
|
app = WebSupportApp(self.srcdir, self.srcdir,
|
||||||
self.outdir, doctreedir, 'websupport',
|
self.outdir, doctreedir, 'websupport',
|
||||||
search=self.search, status=self.status,
|
search=self.search, status=self.status,
|
||||||
warning=self.warning, storage=self.storage,
|
warning=self.warning, storage=self.storage,
|
||||||
staticdir=self.staticdir, builddir=self.builddir)
|
staticdir=self.staticdir, builddir=self.builddir)
|
||||||
|
|
||||||
@ -119,8 +119,8 @@ class WebSupport(object):
|
|||||||
|
|
||||||
support = WebSupport(datadir=datadir)
|
support = WebSupport(datadir=datadir)
|
||||||
support.get_document('index', username, moderator)
|
support.get_document('index', username, moderator)
|
||||||
|
|
||||||
In most cases `docname` will be taken from the request path and
|
In most cases `docname` will be taken from the request path and
|
||||||
passed directly to this function. In Flask, that would be something
|
passed directly to this function. In Flask, that would be something
|
||||||
like this::
|
like this::
|
||||||
|
|
||||||
@ -139,7 +139,7 @@ class WebSupport(object):
|
|||||||
to be used during template rendering.
|
to be used during template rendering.
|
||||||
|
|
||||||
* **body**: The main body of the document as HTML
|
* **body**: The main body of the document as HTML
|
||||||
* **sidebar**: The sidebar of the document as HTML
|
* **sidebar**: The sidebar of the document as HTML
|
||||||
* **relbar**: A div containing links to related documents
|
* **relbar**: A div containing links to related documents
|
||||||
* **title**: The title of the document
|
* **title**: The title of the document
|
||||||
* **css**: Links to css files used by Sphinx
|
* **css**: Links to css files used by Sphinx
|
||||||
@ -161,7 +161,7 @@ class WebSupport(object):
|
|||||||
document = pickle.load(f)
|
document = pickle.load(f)
|
||||||
comment_opts = self._make_comment_options(username, moderator)
|
comment_opts = self._make_comment_options(username, moderator)
|
||||||
comment_metadata = self.storage.get_metadata(docname, moderator)
|
comment_metadata = self.storage.get_metadata(docname, moderator)
|
||||||
|
|
||||||
document['js'] = '\n'.join([comment_opts,
|
document['js'] = '\n'.join([comment_opts,
|
||||||
self._make_metadata(comment_metadata),
|
self._make_metadata(comment_metadata),
|
||||||
document['js']])
|
document['js']])
|
||||||
@ -172,7 +172,7 @@ class WebSupport(object):
|
|||||||
of search results. Then render the search results as html and
|
of search results. Then render the search results as html and
|
||||||
return a context dict like the one created by
|
return a context dict like the one created by
|
||||||
:meth:`get_document`::
|
:meth:`get_document`::
|
||||||
|
|
||||||
document = support.get_search_results(q)
|
document = support.get_search_results(q)
|
||||||
|
|
||||||
:param q: the search query
|
:param q: the search query
|
||||||
@ -187,12 +187,12 @@ class WebSupport(object):
|
|||||||
return document
|
return document
|
||||||
|
|
||||||
def get_data(self, node_id, username=None, moderator=False):
|
def get_data(self, node_id, username=None, moderator=False):
|
||||||
"""Get the comments and source associated with `node_id`. If
|
"""Get the comments and source associated with `node_id`. If
|
||||||
`username` is given vote information will be included with the
|
`username` is given vote information will be included with the
|
||||||
returned comments. The default CommentBackend returns a dict with
|
returned comments. The default CommentBackend returns a dict with
|
||||||
two keys, *source*, and *comments*. *source* is raw source of the
|
two keys, *source*, and *comments*. *source* is raw source of the
|
||||||
node and is used as the starting point for proposals a user can
|
node and is used as the starting point for proposals a user can
|
||||||
add. *comments* is a list of dicts that represent a comment, each
|
add. *comments* is a list of dicts that represent a comment, each
|
||||||
having the following items:
|
having the following items:
|
||||||
|
|
||||||
============= ======================================================
|
============= ======================================================
|
||||||
@ -209,12 +209,12 @@ class WebSupport(object):
|
|||||||
8601 format. `delta` is a printable form of how old
|
8601 format. `delta` is a printable form of how old
|
||||||
the comment is (e.g. "3 hours ago").
|
the comment is (e.g. "3 hours ago").
|
||||||
vote If `user_id` was given, this will be an integer
|
vote If `user_id` was given, this will be an integer
|
||||||
representing the vote. 1 for an upvote, -1 for a
|
representing the vote. 1 for an upvote, -1 for a
|
||||||
downvote, or 0 if unvoted.
|
downvote, or 0 if unvoted.
|
||||||
node The id of the node that the comment is attached to.
|
node The id of the node that the comment is attached to.
|
||||||
If the comment's parent is another comment rather than
|
If the comment's parent is another comment rather than
|
||||||
a node, this will be null.
|
a node, this will be null.
|
||||||
parent The id of the comment that this comment is attached
|
parent The id of the comment that this comment is attached
|
||||||
to if it is not attached to a node.
|
to if it is not attached to a node.
|
||||||
children A list of all children, in this format.
|
children A list of all children, in this format.
|
||||||
proposal_diff An HTML representation of the differences between the
|
proposal_diff An HTML representation of the differences between the
|
||||||
@ -232,7 +232,7 @@ class WebSupport(object):
|
|||||||
instead replaces the username and text files with "[deleted]" so
|
instead replaces the username and text files with "[deleted]" so
|
||||||
as not to leave any comments orphaned.
|
as not to leave any comments orphaned.
|
||||||
|
|
||||||
If `moderator` is True, the comment will always be deleted. If
|
If `moderator` is True, the comment will always be deleted. If
|
||||||
`moderator` is False, the comment will only be deleted if the
|
`moderator` is False, the comment will only be deleted if the
|
||||||
`username` matches the `username` on the comment.
|
`username` matches the `username` on the comment.
|
||||||
|
|
||||||
@ -246,25 +246,25 @@ class WebSupport(object):
|
|||||||
"""
|
"""
|
||||||
self.storage.delete_comment(comment_id, username, moderator)
|
self.storage.delete_comment(comment_id, username, moderator)
|
||||||
|
|
||||||
def add_comment(self, text, node_id='', parent_id='', displayed=True,
|
def add_comment(self, text, node_id='', parent_id='', displayed=True,
|
||||||
username=None, time=None, proposal=None,
|
username=None, time=None, proposal=None,
|
||||||
moderator=False):
|
moderator=False):
|
||||||
"""Add a comment to a node or another comment. Returns the comment
|
"""Add a comment to a node or another comment. Returns the comment
|
||||||
in the same format as :meth:`get_comments`. If the comment is being
|
in the same format as :meth:`get_comments`. If the comment is being
|
||||||
attached to a node, pass in the node's id (as a string) with the
|
attached to a node, pass in the node's id (as a string) with the
|
||||||
node keyword argument::
|
node keyword argument::
|
||||||
|
|
||||||
comment = support.add_comment(text, node_id=node_id)
|
comment = support.add_comment(text, node_id=node_id)
|
||||||
|
|
||||||
If the comment is the child of another comment, provide the parent's
|
If the comment is the child of another comment, provide the parent's
|
||||||
id (as a string) with the parent keyword argument::
|
id (as a string) with the parent keyword argument::
|
||||||
|
|
||||||
comment = support.add_comment(text, parent_id=parent_id)
|
comment = support.add_comment(text, parent_id=parent_id)
|
||||||
|
|
||||||
If you would like to store a username with the comment, pass
|
If you would like to store a username with the comment, pass
|
||||||
in the optional `username` keyword argument::
|
in the optional `username` keyword argument::
|
||||||
|
|
||||||
comment = support.add_comment(text, node=node_id,
|
comment = support.add_comment(text, node=node_id,
|
||||||
username=username)
|
username=username)
|
||||||
|
|
||||||
:param parent_id: the prefixed id of the comment's parent.
|
:param parent_id: the prefixed id of the comment's parent.
|
||||||
@ -274,7 +274,7 @@ class WebSupport(object):
|
|||||||
:param time: the time the comment was created, defaults to now.
|
:param time: the time the comment was created, defaults to now.
|
||||||
"""
|
"""
|
||||||
comment = self.storage.add_comment(text, displayed, username,
|
comment = self.storage.add_comment(text, displayed, username,
|
||||||
time, proposal, node_id,
|
time, proposal, node_id,
|
||||||
parent_id, moderator)
|
parent_id, moderator)
|
||||||
if not displayed and self.moderation_callback:
|
if not displayed and self.moderation_callback:
|
||||||
self.moderation_callback(comment)
|
self.moderation_callback(comment)
|
||||||
@ -282,10 +282,10 @@ class WebSupport(object):
|
|||||||
|
|
||||||
def process_vote(self, comment_id, username, value):
|
def process_vote(self, comment_id, username, value):
|
||||||
"""Process a user's vote. The web support package relies
|
"""Process a user's vote. The web support package relies
|
||||||
on the API user to perform authentication. The API user will
|
on the API user to perform authentication. The API user will
|
||||||
typically receive a comment_id and value from a form, and then
|
typically receive a comment_id and value from a form, and then
|
||||||
make sure the user is authenticated. A unique username must be
|
make sure the user is authenticated. A unique username must be
|
||||||
passed in, which will also be used to retrieve the user's past
|
passed in, which will also be used to retrieve the user's past
|
||||||
voting data. An example, once again in Flask::
|
voting data. An example, once again in Flask::
|
||||||
|
|
||||||
@app.route('/docs/process_vote', methods=['POST'])
|
@app.route('/docs/process_vote', methods=['POST'])
|
||||||
@ -352,20 +352,20 @@ class WebSupport(object):
|
|||||||
that remains the same throughout the lifetime of the
|
that remains the same throughout the lifetime of the
|
||||||
:class:`~sphinx.websupport.WebSupport` object.
|
:class:`~sphinx.websupport.WebSupport` object.
|
||||||
"""
|
"""
|
||||||
parts = ['<script type="text/javascript">',
|
parts = ['<script type="text/javascript">',
|
||||||
'var COMMENT_OPTIONS = {']
|
'var COMMENT_OPTIONS = {']
|
||||||
if self.docroot is not '':
|
if self.docroot is not '':
|
||||||
parts.append('addCommentURL: "/%s/%s",' % (self.docroot,
|
parts.append('addCommentURL: "/%s/%s",' % (self.docroot,
|
||||||
'add_comment'))
|
'add_comment'))
|
||||||
parts.append('getCommentsURL: "/%s/%s",' % (self.docroot,
|
parts.append('getCommentsURL: "/%s/%s",' % (self.docroot,
|
||||||
'get_comments'))
|
'get_comments'))
|
||||||
parts.append('processVoteURL: "/%s/%s",' % (self.docroot,
|
parts.append('processVoteURL: "/%s/%s",' % (self.docroot,
|
||||||
'process_vote'))
|
'process_vote'))
|
||||||
parts.append('acceptCommentURL: "/%s/%s",' % (self.docroot,
|
parts.append('acceptCommentURL: "/%s/%s",' % (self.docroot,
|
||||||
'accept_comment'))
|
'accept_comment'))
|
||||||
parts.append('rejectCommentURL: "/%s/%s",' % (self.docroot,
|
parts.append('rejectCommentURL: "/%s/%s",' % (self.docroot,
|
||||||
'reject_comment'))
|
'reject_comment'))
|
||||||
parts.append('deleteCommentURL: "/%s/%s",' % (self.docroot,
|
parts.append('deleteCommentURL: "/%s/%s",' % (self.docroot,
|
||||||
'delete_comment'))
|
'delete_comment'))
|
||||||
|
|
||||||
if self.staticdir != 'static':
|
if self.staticdir != 'static':
|
||||||
@ -378,8 +378,8 @@ class WebSupport(object):
|
|||||||
parts.append('upArrowPressed: "/%s",' % p('up-pressed.png'))
|
parts.append('upArrowPressed: "/%s",' % p('up-pressed.png'))
|
||||||
parts.append('downArrowPressed: "/%s",' % p('down-pressed.png'))
|
parts.append('downArrowPressed: "/%s",' % p('down-pressed.png'))
|
||||||
|
|
||||||
self.base_comment_opts = '\n'.join(parts)
|
self.base_comment_opts = '\n'.join(parts)
|
||||||
|
|
||||||
def _make_comment_options(self, username, moderator):
|
def _make_comment_options(self, username, moderator):
|
||||||
"""Helper method to create the parts of the COMMENT_OPTIONS
|
"""Helper method to create the parts of the COMMENT_OPTIONS
|
||||||
javascript that are unique to each request.
|
javascript that are unique to each request.
|
||||||
@ -394,14 +394,13 @@ class WebSupport(object):
|
|||||||
parts.append('moderator: %s' % str(moderator).lower())
|
parts.append('moderator: %s' % str(moderator).lower())
|
||||||
parts.append('};')
|
parts.append('};')
|
||||||
parts.append('</script>')
|
parts.append('</script>')
|
||||||
return '\n'.join(parts)
|
return '\n'.join(parts)
|
||||||
|
|
||||||
def _make_metadata(self, data):
|
def _make_metadata(self, data):
|
||||||
node_js = ', '.join(['%s: %s' % (node_id, comment_count)
|
node_js = ', '.join(['%s: %s' % (node_id, comment_count)
|
||||||
for node_id, comment_count in data.iteritems()])
|
for node_id, comment_count in data.iteritems()])
|
||||||
js = """
|
js = """
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
var COMMENT_METADATA = {%s};
|
var COMMENT_METADATA = {%s};
|
||||||
</script>""" % node_js
|
</script>""" % node_js
|
||||||
return js
|
return js
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ class BaseSearch(object):
|
|||||||
def feed(self, pagename, title, doctree):
|
def feed(self, pagename, title, doctree):
|
||||||
"""Called by the builder to add a doctree to the index. Converts the
|
"""Called by the builder to add a doctree to the index. Converts the
|
||||||
`doctree` to text and passes it to :meth:`add_document`. You probably
|
`doctree` to text and passes it to :meth:`add_document`. You probably
|
||||||
won't want to override this unless you need access to the `doctree`.
|
won't want to override this unless you need access to the `doctree`.
|
||||||
Override :meth:`add_document` instead.
|
Override :meth:`add_document` instead.
|
||||||
|
|
||||||
:param pagename: the name of the page to be indexed
|
:param pagename: the name of the page to be indexed
|
||||||
@ -50,11 +50,11 @@ class BaseSearch(object):
|
|||||||
|
|
||||||
`pagename` is name of the page being indexed. It is the combination
|
`pagename` is name of the page being indexed. It is the combination
|
||||||
of the source files relative path and filename,
|
of the source files relative path and filename,
|
||||||
minus the extension. For example, if the source file is
|
minus the extension. For example, if the source file is
|
||||||
"ext/builders.rst", the `pagename` would be "ext/builders". This
|
"ext/builders.rst", the `pagename` would be "ext/builders". This
|
||||||
will need to be returned with search results when processing a
|
will need to be returned with search results when processing a
|
||||||
query.
|
query.
|
||||||
|
|
||||||
:param pagename: the name of the page being indexed
|
:param pagename: the name of the page being indexed
|
||||||
:param title: the page's title
|
:param title: the page's title
|
||||||
:param text: the full text of the page
|
:param text: the full text of the page
|
||||||
@ -62,13 +62,13 @@ class BaseSearch(object):
|
|||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def query(self, q):
|
def query(self, q):
|
||||||
"""Called by the web support api to get search results. This method
|
"""Called by the web support api to get search results. This method
|
||||||
compiles the regular expression to be used when
|
compiles the regular expression to be used when
|
||||||
:meth:`extracting context <extract_context>`, then calls
|
:meth:`extracting context <extract_context>`, then calls
|
||||||
:meth:`handle_query`. You won't want to override this unless you
|
:meth:`handle_query`. You won't want to override this unless you
|
||||||
don't want to use the included :meth:`extract_context` method.
|
don't want to use the included :meth:`extract_context` method.
|
||||||
Override :meth:`handle_query` instead.
|
Override :meth:`handle_query` instead.
|
||||||
|
|
||||||
:param q: the search query string.
|
:param q: the search query string.
|
||||||
"""
|
"""
|
||||||
self.context_re = re.compile('|'.join(q.split()), re.I)
|
self.context_re = re.compile('|'.join(q.split()), re.I)
|
||||||
@ -119,4 +119,3 @@ search_adapters = {
|
|||||||
'whoosh': ('whooshsearch', 'WhooshSearch'),
|
'whoosh': ('whooshsearch', 'WhooshSearch'),
|
||||||
'null': ('nullsearch', 'NullSearch')
|
'null': ('nullsearch', 'NullSearch')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ class XapianSearch(BaseSearch):
|
|||||||
|
|
||||||
def init_indexing(self, changed=[]):
|
def init_indexing(self, changed=[]):
|
||||||
ensuredir(self.db_path)
|
ensuredir(self.db_path)
|
||||||
self.database = xapian.WritableDatabase(self.db_path,
|
self.database = xapian.WritableDatabase(self.db_path,
|
||||||
xapian.DB_CREATE_OR_OPEN)
|
xapian.DB_CREATE_OR_OPEN)
|
||||||
self.indexer = xapian.TermGenerator()
|
self.indexer = xapian.TermGenerator()
|
||||||
stemmer = xapian.Stem("english")
|
stemmer = xapian.Stem("english")
|
||||||
|
@ -20,13 +20,13 @@ class StorageBackend(object):
|
|||||||
"""Add a node to the StorageBackend.
|
"""Add a node to the StorageBackend.
|
||||||
|
|
||||||
:param document: the name of the document the node belongs to.
|
:param document: the name of the document the node belongs to.
|
||||||
|
|
||||||
:param line: the line in the source where the node begins.
|
:param line: the line in the source where the node begins.
|
||||||
|
|
||||||
:param source: the source files name.
|
:param source: the source files name.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def post_build(self):
|
def post_build(self):
|
||||||
"""Called after a build has completed. Use this to finalize the
|
"""Called after a build has completed. Use this to finalize the
|
||||||
addition of nodes if needed.
|
addition of nodes if needed.
|
||||||
@ -36,7 +36,7 @@ class StorageBackend(object):
|
|||||||
def add_comment(self, text, displayed, username, time,
|
def add_comment(self, text, displayed, username, time,
|
||||||
proposal, node_id, parent_id, moderator):
|
proposal, node_id, parent_id, moderator):
|
||||||
"""Called when a comment is being added.
|
"""Called when a comment is being added.
|
||||||
|
|
||||||
:param text: the text of the comment
|
:param text: the text of the comment
|
||||||
:param displayed: whether the comment should be displayed
|
:param displayed: whether the comment should be displayed
|
||||||
:param username: the name of the user adding the comment
|
:param username: the name of the user adding the comment
|
||||||
|
@ -81,7 +81,7 @@ class Node(Base):
|
|||||||
comment, vote = r if username else (r, 0)
|
comment, vote = r if username else (r, 0)
|
||||||
|
|
||||||
inheritance_chain = comment.path.split('.')[1:]
|
inheritance_chain = comment.path.split('.')[1:]
|
||||||
|
|
||||||
if len(inheritance_chain) == len(list_stack) + 1:
|
if len(inheritance_chain) == len(list_stack) + 1:
|
||||||
parent = list_stack[-1][-1]
|
parent = list_stack[-1][-1]
|
||||||
list_stack.append(parent['children'])
|
list_stack.append(parent['children'])
|
||||||
@ -90,7 +90,7 @@ class Node(Base):
|
|||||||
list_stack.pop()
|
list_stack.pop()
|
||||||
|
|
||||||
list_stack[-1].append(comment.serializable(vote=vote))
|
list_stack[-1].append(comment.serializable(vote=vote))
|
||||||
|
|
||||||
return comments
|
return comments
|
||||||
|
|
||||||
def __init__(self, document, line, source):
|
def __init__(self, document, line, source):
|
||||||
@ -115,7 +115,7 @@ class Comment(Base):
|
|||||||
node_id = Column(Integer, ForeignKey(db_prefix + 'nodes.id'))
|
node_id = Column(Integer, ForeignKey(db_prefix + 'nodes.id'))
|
||||||
node = relation(Node, backref="comments")
|
node = relation(Node, backref="comments")
|
||||||
|
|
||||||
def __init__(self, text, displayed, username, rating, time,
|
def __init__(self, text, displayed, username, rating, time,
|
||||||
proposal, proposal_diff):
|
proposal, proposal_diff):
|
||||||
self.text = text
|
self.text = text
|
||||||
self.displayed = displayed
|
self.displayed = displayed
|
||||||
|
@ -53,12 +53,12 @@ class CombinedHtmlDiff(object):
|
|||||||
return text
|
return text
|
||||||
elif prefix == '?':
|
elif prefix == '?':
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
if next is not None and next[0] == '?':
|
if next is not None and next[0] == '?':
|
||||||
tag = 'ins' if prefix == '+' else 'del'
|
tag = 'ins' if prefix == '+' else 'del'
|
||||||
text = self._highlight_text(text, next, tag)
|
text = self._highlight_text(text, next, tag)
|
||||||
css_class = 'prop_added' if prefix == '+' else 'prop_removed'
|
css_class = 'prop_added' if prefix == '+' else 'prop_removed'
|
||||||
|
|
||||||
return '<span class="%s">%s</span>\n' % (css_class, text.rstrip())
|
return '<span class="%s">%s</span>\n' % (css_class, text.rstrip())
|
||||||
|
|
||||||
def _highlight_text(self, text, next, tag):
|
def _highlight_text(self, text, next, tag):
|
||||||
@ -76,4 +76,3 @@ class CombinedHtmlDiff(object):
|
|||||||
start = match.end()
|
start = match.end()
|
||||||
new_text.append(text[start:])
|
new_text.append(text[start:])
|
||||||
return ''.join(new_text)
|
return ''.join(new_text)
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ from sphinx.websupport.storage.db import Base, Node, Comment, CommentVote,\
|
|||||||
from sphinx.websupport.storage.differ import CombinedHtmlDiff
|
from sphinx.websupport.storage.differ import CombinedHtmlDiff
|
||||||
|
|
||||||
class SQLAlchemyStorage(StorageBackend):
|
class SQLAlchemyStorage(StorageBackend):
|
||||||
"""A :class:`~sphinx.websupport.storage.StorageBackend` using
|
"""A :class:`~sphinx.websupport.storage.StorageBackend` using
|
||||||
SQLAlchemy.
|
SQLAlchemy.
|
||||||
"""
|
"""
|
||||||
def __init__(self, engine):
|
def __init__(self, engine):
|
||||||
@ -59,7 +59,7 @@ class SQLAlchemyStorage(StorageBackend):
|
|||||||
raise CommentNotAllowedError(
|
raise CommentNotAllowedError(
|
||||||
"Can't add child to a parent that is not displayed")
|
"Can't add child to a parent that is not displayed")
|
||||||
|
|
||||||
comment = Comment(text, displayed, username, 0,
|
comment = Comment(text, displayed, username, 0,
|
||||||
time or datetime.now(), proposal, proposal_diff)
|
time or datetime.now(), proposal, proposal_diff)
|
||||||
session.add(comment)
|
session.add(comment)
|
||||||
session.flush()
|
session.flush()
|
||||||
@ -88,7 +88,7 @@ class SQLAlchemyStorage(StorageBackend):
|
|||||||
def get_metadata(self, docname, moderator):
|
def get_metadata(self, docname, moderator):
|
||||||
session = Session()
|
session = Session()
|
||||||
subquery = session.query(
|
subquery = session.query(
|
||||||
Comment.id, Comment.node_id,
|
Comment.id, Comment.node_id,
|
||||||
func.count('*').label('comment_count')).group_by(
|
func.count('*').label('comment_count')).group_by(
|
||||||
Comment.node_id).subquery()
|
Comment.node_id).subquery()
|
||||||
nodes = session.query(Node.id, subquery.c.comment_count).outerjoin(
|
nodes = session.query(Node.id, subquery.c.comment_count).outerjoin(
|
||||||
|
@ -27,7 +27,7 @@ def teardown_module():
|
|||||||
|
|
||||||
def search_adapter_helper(adapter):
|
def search_adapter_helper(adapter):
|
||||||
clear_builddir()
|
clear_builddir()
|
||||||
|
|
||||||
settings = {'builddir': os.path.join(test_root, 'websupport'),
|
settings = {'builddir': os.path.join(test_root, 'websupport'),
|
||||||
'status': StringIO(),
|
'status': StringIO(),
|
||||||
'warning': StringIO()}
|
'warning': StringIO()}
|
||||||
@ -81,4 +81,3 @@ def test_whoosh():
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
sys.stderr.write('info: not running whoosh tests, ' \
|
sys.stderr.write('info: not running whoosh tests, ' \
|
||||||
'whoosh doesn\'t seem to be installed')
|
'whoosh doesn\'t seem to be installed')
|
||||||
|
|
||||||
|
@ -64,7 +64,7 @@ def test_build(support):
|
|||||||
@with_support()
|
@with_support()
|
||||||
def test_get_document(support):
|
def test_get_document(support):
|
||||||
raises(DocumentNotFoundError, support.get_document, 'nonexisting')
|
raises(DocumentNotFoundError, support.get_document, 'nonexisting')
|
||||||
|
|
||||||
contents = support.get_document('contents')
|
contents = support.get_document('contents')
|
||||||
assert contents['title'] and contents['body'] \
|
assert contents['title'] and contents['body'] \
|
||||||
and contents['sidebar'] and contents['relbar']
|
and contents['sidebar'] and contents['relbar']
|
||||||
@ -78,27 +78,27 @@ def test_comments(support):
|
|||||||
second_node = nodes[1]
|
second_node = nodes[1]
|
||||||
|
|
||||||
# Create a displayed comment and a non displayed comment.
|
# Create a displayed comment and a non displayed comment.
|
||||||
comment = support.add_comment('First test comment',
|
comment = support.add_comment('First test comment',
|
||||||
node_id=str(first_node.id),
|
node_id=str(first_node.id),
|
||||||
username='user_one')
|
username='user_one')
|
||||||
hidden_comment = support.add_comment('Hidden comment',
|
hidden_comment = support.add_comment('Hidden comment',
|
||||||
node_id=str(first_node.id),
|
node_id=str(first_node.id),
|
||||||
displayed=False)
|
displayed=False)
|
||||||
# Make sure that comments can't be added to a comment where
|
# Make sure that comments can't be added to a comment where
|
||||||
# displayed == False, since it could break the algorithm that
|
# displayed == False, since it could break the algorithm that
|
||||||
# converts a nodes comments to a tree.
|
# converts a nodes comments to a tree.
|
||||||
raises(CommentNotAllowedError, support.add_comment, 'Not allowed',
|
raises(CommentNotAllowedError, support.add_comment, 'Not allowed',
|
||||||
parent_id=str(hidden_comment['id']))
|
parent_id=str(hidden_comment['id']))
|
||||||
# Add a displayed and not displayed child to the displayed comment.
|
# Add a displayed and not displayed child to the displayed comment.
|
||||||
support.add_comment('Child test comment', parent_id=str(comment['id']),
|
support.add_comment('Child test comment', parent_id=str(comment['id']),
|
||||||
username='user_one')
|
username='user_one')
|
||||||
support.add_comment('Hidden child test comment',
|
support.add_comment('Hidden child test comment',
|
||||||
parent_id=str(comment['id']), displayed=False)
|
parent_id=str(comment['id']), displayed=False)
|
||||||
# Add a comment to another node to make sure it isn't returned later.
|
# Add a comment to another node to make sure it isn't returned later.
|
||||||
support.add_comment('Second test comment',
|
support.add_comment('Second test comment',
|
||||||
node_id=str(second_node.id),
|
node_id=str(second_node.id),
|
||||||
username='user_two')
|
username='user_two')
|
||||||
|
|
||||||
# Access the comments as a moderator.
|
# Access the comments as a moderator.
|
||||||
data = support.get_data(str(first_node.id), moderator=True)
|
data = support.get_data(str(first_node.id), moderator=True)
|
||||||
comments = data['comments']
|
comments = data['comments']
|
||||||
@ -130,7 +130,7 @@ def test_voting(support):
|
|||||||
data = support.get_data(str(node.id))
|
data = support.get_data(str(node.id))
|
||||||
comment = data['comments'][0]
|
comment = data['comments'][0]
|
||||||
assert comment['rating'] == val, '%s != %s' % (comment['rating'], val)
|
assert comment['rating'] == val, '%s != %s' % (comment['rating'], val)
|
||||||
|
|
||||||
support.process_vote(comment['id'], 'user_one', '1')
|
support.process_vote(comment['id'], 'user_one', '1')
|
||||||
support.process_vote(comment['id'], 'user_two', '1')
|
support.process_vote(comment['id'], 'user_two', '1')
|
||||||
support.process_vote(comment['id'], 'user_three', '1')
|
support.process_vote(comment['id'], 'user_three', '1')
|
||||||
@ -161,7 +161,7 @@ def test_proposals(support):
|
|||||||
source = data['source']
|
source = data['source']
|
||||||
proposal = source[:5] + source[10:15] + 'asdf' + source[15:]
|
proposal = source[:5] + source[10:15] + 'asdf' + source[15:]
|
||||||
|
|
||||||
comment = support.add_comment('Proposal comment',
|
comment = support.add_comment('Proposal comment',
|
||||||
node_id=str(node.id),
|
node_id=str(node.id),
|
||||||
proposal=proposal)
|
proposal=proposal)
|
||||||
|
|
||||||
@ -195,7 +195,7 @@ def test_moderator_delete_comments(support):
|
|||||||
return support.get_data(str(node.id), moderator=True)['comments'][1]
|
return support.get_data(str(node.id), moderator=True)['comments'][1]
|
||||||
|
|
||||||
comment = get_comment()
|
comment = get_comment()
|
||||||
support.delete_comment(comment['id'], username='user_two',
|
support.delete_comment(comment['id'], username='user_two',
|
||||||
moderator=True)
|
moderator=True)
|
||||||
comment = get_comment()
|
comment = get_comment()
|
||||||
assert comment['username'] == '[deleted]'
|
assert comment['username'] == '[deleted]'
|
||||||
@ -228,9 +228,9 @@ def moderation_callback(comment):
|
|||||||
|
|
||||||
@with_support(moderation_callback=moderation_callback)
|
@with_support(moderation_callback=moderation_callback)
|
||||||
def test_moderation(support):
|
def test_moderation(support):
|
||||||
accepted = support.add_comment('Accepted Comment', node_id=3,
|
accepted = support.add_comment('Accepted Comment', node_id=3,
|
||||||
displayed=False)
|
displayed=False)
|
||||||
rejected = support.add_comment('Rejected comment', node_id=3,
|
rejected = support.add_comment('Rejected comment', node_id=3,
|
||||||
displayed=False)
|
displayed=False)
|
||||||
# Make sure the moderation_callback is called.
|
# Make sure the moderation_callback is called.
|
||||||
assert called == True
|
assert called == True
|
||||||
@ -248,7 +248,7 @@ def test_moderation(support):
|
|||||||
def test_differ():
|
def test_differ():
|
||||||
differ = CombinedHtmlDiff()
|
differ = CombinedHtmlDiff()
|
||||||
source = 'Lorem ipsum dolor sit amet,\nconsectetur adipisicing elit,\n' \
|
source = 'Lorem ipsum dolor sit amet,\nconsectetur adipisicing elit,\n' \
|
||||||
'sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.'
|
'sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.'
|
||||||
prop = 'Lorem dolor sit amet,\nconsectetur nihil adipisicing elit,\n' \
|
prop = 'Lorem dolor sit amet,\nconsectetur nihil adipisicing elit,\n' \
|
||||||
'sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.'
|
'sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.'
|
||||||
differ.make_html(source, prop)
|
differ.make_html(source, prop)
|
||||||
|
Loading…
Reference in New Issue
Block a user