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
|
||||
|
||||
The main API class for the web support package. All interactions
|
||||
with the web support package should occur through this class.
|
||||
The main API class for the web support package. All interactions
|
||||
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
|
||||
The directory containing reStructuredText source files.
|
||||
srcdir
|
||||
The directory containing reStructuredText source files.
|
||||
|
||||
builddir
|
||||
The directory that build data and static files should be placed in.
|
||||
This should be used when creating a :class:`WebSupport` object that
|
||||
will be used to build data.
|
||||
builddir
|
||||
The directory that build data and static files should be placed in.
|
||||
This should be used when creating a :class:`WebSupport` object that
|
||||
will be used to build data.
|
||||
|
||||
datadir:
|
||||
The directory that the web support data is in. This should be used
|
||||
when creating a :class:`WebSupport` object that will be used to
|
||||
retrieve data.
|
||||
datadir:
|
||||
The directory that the web support data is in. This should be used
|
||||
when creating a :class:`WebSupport` object that will be used to
|
||||
retrieve data.
|
||||
|
||||
search:
|
||||
This may contain either a string (e.g. 'xapian') referencing a
|
||||
built-in search adapter to use, or an instance of a subclass of
|
||||
:class:`~sphinx.websupport.search.BaseSearch`.
|
||||
search:
|
||||
This may contain either a string (e.g. 'xapian') referencing a
|
||||
built-in search adapter to use, or an instance of a subclass of
|
||||
:class:`~sphinx.websupport.search.BaseSearch`.
|
||||
|
||||
storage:
|
||||
This may contain either a string representing a database uri, or an
|
||||
instance of a subclass of
|
||||
:class:`~sphinx.websupport.storage.StorageBackend`. If this is not
|
||||
provided a new sqlite database will be created.
|
||||
storage:
|
||||
This may contain either a string representing a database uri, or an
|
||||
instance of a subclass of
|
||||
:class:`~sphinx.websupport.storage.StorageBackend`. If this is not
|
||||
provided a new sqlite database will be created.
|
||||
|
||||
moderation_callback:
|
||||
A callable to be called when a new comment is added that is not
|
||||
displayed. It must accept one argument: a dict representing the
|
||||
comment that was added.
|
||||
moderation_callback:
|
||||
A callable to be called when a new comment is added that is not
|
||||
displayed. It must accept one argument: a dict representing the
|
||||
comment that was added.
|
||||
|
||||
staticdir:
|
||||
If static files are served from a location besides "/static", this
|
||||
should be a string with the name of that location
|
||||
(e.g. '/static_files').
|
||||
staticdir:
|
||||
If static files are served from a location besides "/static", this
|
||||
should be a string with the name of that location
|
||||
(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
|
||||
~~~~~~~
|
||||
|
||||
@ -64,4 +64,3 @@ Methods
|
||||
.. automethod:: sphinx.websupport.WebSupport.process_vote
|
||||
|
||||
.. 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
|
||||
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
|
||||
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::
|
||||
|
||||
from sphinx.websupport import WebSupport
|
||||
|
||||
support = WebSupport(srcdir='/path/to/rst/sources/',
|
||||
builddir='/path/to/build/outdir',
|
||||
search='xapian')
|
||||
search='xapian')
|
||||
|
||||
support.build()
|
||||
|
||||
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
|
||||
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".
|
||||
|
||||
.. 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.
|
||||
Start off by creating a :class:`~sphinx.websupport.WebSupport` object
|
||||
for your application::
|
||||
|
||||
|
||||
from sphinx.websupport import WebSupport
|
||||
|
||||
support = WebSupport(datadir='/path/to/the/data',
|
||||
search='xapian')
|
||||
|
||||
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
|
||||
individual documents::
|
||||
|
||||
@ -56,14 +56,14 @@ individual documents::
|
||||
This will return a dictionary containing the following items:
|
||||
|
||||
* **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
|
||||
* **title**: The title of the document
|
||||
* **css**: Links to css files used by Sphinx
|
||||
* **js**: Javascript containing comment options
|
||||
|
||||
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:
|
||||
|
||||
.. sourcecode:: html+jinja
|
||||
@ -71,30 +71,30 @@ easy to integrate with your existing templating system. An example using
|
||||
{%- extends "layout.html" %}
|
||||
|
||||
{%- block title %}
|
||||
{{ document.title }}
|
||||
{{ document.title }}
|
||||
{%- endblock %}
|
||||
|
||||
{% block css %}
|
||||
{{ super() }}
|
||||
{{ document.css|safe }}
|
||||
<link rel="stylesheet" href="/static/websupport-custom.css" type="text/css">
|
||||
{{ super() }}
|
||||
{{ document.css|safe }}
|
||||
<link rel="stylesheet" href="/static/websupport-custom.css" type="text/css">
|
||||
{% endblock %}
|
||||
|
||||
{%- block js %}
|
||||
{{ super() }}
|
||||
{{ document.js|safe }}
|
||||
{{ super() }}
|
||||
{{ document.js|safe }}
|
||||
{%- endblock %}
|
||||
|
||||
{%- block relbar %}
|
||||
{{ document.relbar|safe }}
|
||||
{{ document.relbar|safe }}
|
||||
{%- endblock %}
|
||||
|
||||
{%- block body %}
|
||||
{{ document.body|safe }}
|
||||
{{ document.body|safe }}
|
||||
{%- endblock %}
|
||||
|
||||
{%- block sidebar %}
|
||||
{{ document.sidebar|safe }}
|
||||
{{ document.sidebar|safe }}
|
||||
{%- endblock %}
|
||||
|
||||
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
|
||||
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*
|
||||
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>')
|
||||
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)
|
||||
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)
|
||||
|
||||
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.
|
||||
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
|
||||
will then add this data to the COMMENT_OPTIONS that are used in the template.
|
||||
|
||||
.. note::
|
||||
|
||||
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::
|
||||
|
||||
support = WebSupport(...
|
||||
docroot='docs')
|
||||
|
||||
@app.route('/docs/<path:docname>')
|
||||
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::
|
||||
|
||||
support = WebSupport(...
|
||||
docroot='docs')
|
||||
|
||||
@app.route('/docs/<path:docname>')
|
||||
|
||||
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.
|
||||
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
|
||||
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::
|
||||
|
||||
@app.route('/search')
|
||||
@ -165,7 +165,7 @@ would be like this::
|
||||
return render_template('doc.html', document=document)
|
||||
|
||||
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
|
||||
dict in the same format that
|
||||
: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', '')
|
||||
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)
|
||||
parent_id='parent_id',
|
||||
username=username, proposal=proposal)
|
||||
return jsonify(comment=comment)
|
||||
|
||||
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`
|
||||
will be empty. Then next function handles the retrieval of comments for a
|
||||
specific node, and is aptly named
|
||||
will be empty. Then next function handles the retrieval of comments for a
|
||||
specific node, and is aptly named
|
||||
:meth:`~sphinx.websupport.WebSupport.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', '')
|
||||
moderator = g.user.moderator if g.user else False
|
||||
node_id = request.args.get('node', '')
|
||||
data = support.get_data(parent_id, user_id)
|
||||
return jsonify(**data)
|
||||
|
||||
@ -223,15 +223,15 @@ votes on comments::
|
||||
Comment Moderation
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
By default all comments added through
|
||||
By default all comments added through
|
||||
: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',
|
||||
parent_id='parent_id',
|
||||
parent_id='parent_id',
|
||||
username=username, proposal=proposal,
|
||||
displayed=False)
|
||||
displayed=False)
|
||||
|
||||
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
|
||||
@ -240,18 +240,18 @@ and displayed::
|
||||
@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'
|
||||
comment_id = request.form.get('id')
|
||||
support.accept_comment(comment_id, moderator=moderator)
|
||||
return 'OK'
|
||||
|
||||
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
|
||||
comment_id = request.form.get('id')
|
||||
support.reject_comment(comment_id, moderator=moderator)
|
||||
return 'OK'
|
||||
comment_id = request.form.get('id')
|
||||
support.reject_comment(comment_id, moderator=moderator)
|
||||
return 'OK'
|
||||
|
||||
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
|
||||
@ -265,4 +265,4 @@ object::
|
||||
moderation_callback=moderation_callback)
|
||||
|
||||
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
|
||||
:class:`~sphinx.websupport.WebSupport` object::
|
||||
|
||||
support = Websupport(srcdir=srcdir,
|
||||
builddir=builddir,
|
||||
search=MySearch())
|
||||
support = Websupport(srcdir=srcdir,
|
||||
builddir=builddir,
|
||||
search=MySearch())
|
||||
|
||||
For more information about creating a custom search adapter, please see
|
||||
the documentation of the :class:`BaseSearch` class below.
|
||||
|
||||
.. class:: BaseSearch
|
||||
|
||||
Defines an interface for search adapters.
|
||||
Defines an interface for search adapters.
|
||||
|
||||
BaseSearch Methods
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The following methods are defined in the BaseSearch class. Some methods
|
||||
do not need to be overridden, but some (
|
||||
:meth:`~sphinx.websupport.search.BaseSearch.add_document` and
|
||||
:meth:`~sphinx.websupport.search.BaseSearch.handle_query`) must be
|
||||
overridden in your subclass. For a working example, look at the
|
||||
built-in adapter for whoosh.
|
||||
The following methods are defined in the BaseSearch class. Some methods
|
||||
do not need to be overridden, but some (
|
||||
:meth:`~sphinx.websupport.search.BaseSearch.add_document` and
|
||||
:meth:`~sphinx.websupport.search.BaseSearch.handle_query`) must be
|
||||
overridden in your subclass. For a working example, look at the
|
||||
built-in adapter for whoosh.
|
||||
|
||||
.. 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.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
|
||||
:class:`~sphinx.websupport.WebSupport` object::
|
||||
|
||||
support = Websupport(srcdir=srcdir,
|
||||
builddir=builddir,
|
||||
storage=MyStorage())
|
||||
support = Websupport(srcdir=srcdir,
|
||||
builddir=builddir,
|
||||
storage=MyStorage())
|
||||
|
||||
For more information about creating a custom storage backend, please see
|
||||
the documentation of the :class:`StorageBackend` class below.
|
||||
|
||||
.. class:: StorageBackend
|
||||
|
||||
Defines an interface for storage backends.
|
||||
Defines an interface for storage backends.
|
||||
|
||||
StorageBackend Methods
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
@ -42,4 +42,4 @@ StorageBackend Methods
|
||||
|
||||
.. 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
|
||||
into your web application. To learn more read the
|
||||
into your web application. To learn more read the
|
||||
:ref:`websupportquickstart`.
|
||||
|
||||
.. toctree::
|
||||
|
||||
web/quickstart
|
||||
web/api
|
||||
web/searchadapters
|
||||
web/storagebackends
|
||||
web/quickstart
|
||||
web/api
|
||||
web/searchadapters
|
||||
web/storagebackends
|
||||
|
@ -58,7 +58,7 @@ class WebSupportBuilder(StandaloneHTMLBuilder):
|
||||
def handle_page(self, pagename, addctx, templatename='page.html',
|
||||
outfilename=None, event_arg=None):
|
||||
# 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.
|
||||
ctx = self.globalcontext.copy()
|
||||
ctx['pagename'] = pagename
|
||||
@ -140,7 +140,7 @@ class WebSupportBuilder(StandaloneHTMLBuilder):
|
||||
FILE_SUFFIX: '',
|
||||
HAS_SOURCE: '%s'
|
||||
};
|
||||
</script>"""
|
||||
</script>"""
|
||||
opts = opts % (ctx.get('url_root', ''), escape(ctx['release']),
|
||||
str(ctx['has_source']).lower())
|
||||
scripts = []
|
||||
@ -148,4 +148,3 @@ class WebSupportBuilder(StandaloneHTMLBuilder):
|
||||
scripts.append(make_script(file))
|
||||
scripts.append(make_script('_static/websupport.js'))
|
||||
return opts + '\n' + '\n'.join(scripts)
|
||||
|
||||
|
@ -49,7 +49,7 @@ class WebSupport(object):
|
||||
self.moderation_callback = moderation_callback
|
||||
|
||||
self._init_templating()
|
||||
self._init_search(search)
|
||||
self._init_search(search)
|
||||
self._init_storage(storage)
|
||||
|
||||
self._make_base_comment_options()
|
||||
@ -105,7 +105,7 @@ class WebSupport(object):
|
||||
doctreedir = path.join(self.outdir, 'doctrees')
|
||||
app = WebSupportApp(self.srcdir, self.srcdir,
|
||||
self.outdir, doctreedir, 'websupport',
|
||||
search=self.search, status=self.status,
|
||||
search=self.search, status=self.status,
|
||||
warning=self.warning, storage=self.storage,
|
||||
staticdir=self.staticdir, builddir=self.builddir)
|
||||
|
||||
@ -119,8 +119,8 @@ class WebSupport(object):
|
||||
|
||||
support = WebSupport(datadir=datadir)
|
||||
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
|
||||
like this::
|
||||
|
||||
@ -139,7 +139,7 @@ class WebSupport(object):
|
||||
to be used during template rendering.
|
||||
|
||||
* **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
|
||||
* **title**: The title of the document
|
||||
* **css**: Links to css files used by Sphinx
|
||||
@ -161,7 +161,7 @@ class WebSupport(object):
|
||||
document = pickle.load(f)
|
||||
comment_opts = self._make_comment_options(username, moderator)
|
||||
comment_metadata = self.storage.get_metadata(docname, moderator)
|
||||
|
||||
|
||||
document['js'] = '\n'.join([comment_opts,
|
||||
self._make_metadata(comment_metadata),
|
||||
document['js']])
|
||||
@ -172,7 +172,7 @@ class WebSupport(object):
|
||||
of search results. Then render the search results as html and
|
||||
return a context dict like the one created by
|
||||
:meth:`get_document`::
|
||||
|
||||
|
||||
document = support.get_search_results(q)
|
||||
|
||||
:param q: the search query
|
||||
@ -187,12 +187,12 @@ class WebSupport(object):
|
||||
return document
|
||||
|
||||
def get_data(self, node_id, username=None, moderator=False):
|
||||
"""Get the comments and source associated with `node_id`. If
|
||||
`username` is given vote information will be included with the
|
||||
"""Get the comments and source associated with `node_id`. If
|
||||
`username` is given vote information will be included with the
|
||||
returned comments. The default CommentBackend returns a dict with
|
||||
two keys, *source*, and *comments*. *source* is raw source of the
|
||||
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:
|
||||
|
||||
============= ======================================================
|
||||
@ -209,12 +209,12 @@ class WebSupport(object):
|
||||
8601 format. `delta` is a printable form of how old
|
||||
the comment is (e.g. "3 hours ago").
|
||||
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.
|
||||
node The id of the node that the comment is attached to.
|
||||
If the comment's parent is another comment rather than
|
||||
node The id of the node that the comment is attached to.
|
||||
If the comment's parent is another comment rather than
|
||||
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.
|
||||
children A list of all children, in this format.
|
||||
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
|
||||
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
|
||||
`username` matches the `username` on the comment.
|
||||
|
||||
@ -246,25 +246,25 @@ class WebSupport(object):
|
||||
"""
|
||||
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,
|
||||
moderator=False):
|
||||
"""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
|
||||
attached to a node, pass in the node's id (as a string) with the
|
||||
"""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
|
||||
attached to a node, pass in the node's id (as a string) with the
|
||||
node keyword argument::
|
||||
|
||||
comment = support.add_comment(text, node_id=node_id)
|
||||
|
||||
If the comment is the child of another comment, provide the parent's
|
||||
id (as a string) with the parent keyword argument::
|
||||
|
||||
|
||||
comment = support.add_comment(text, parent_id=parent_id)
|
||||
|
||||
If you would like to store a username with the comment, pass
|
||||
in the optional `username` keyword argument::
|
||||
|
||||
comment = support.add_comment(text, node=node_id,
|
||||
comment = support.add_comment(text, node=node_id,
|
||||
username=username)
|
||||
|
||||
: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.
|
||||
"""
|
||||
comment = self.storage.add_comment(text, displayed, username,
|
||||
time, proposal, node_id,
|
||||
time, proposal, node_id,
|
||||
parent_id, moderator)
|
||||
if not displayed and self.moderation_callback:
|
||||
self.moderation_callback(comment)
|
||||
@ -282,10 +282,10 @@ class WebSupport(object):
|
||||
|
||||
def process_vote(self, comment_id, username, value):
|
||||
"""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
|
||||
make sure the user is authenticated. A unique username must be
|
||||
passed in, which will also be used to retrieve the user's past
|
||||
make sure the user is authenticated. A unique username must be
|
||||
passed in, which will also be used to retrieve the user's past
|
||||
voting data. An example, once again in Flask::
|
||||
|
||||
@app.route('/docs/process_vote', methods=['POST'])
|
||||
@ -352,20 +352,20 @@ class WebSupport(object):
|
||||
that remains the same throughout the lifetime of the
|
||||
:class:`~sphinx.websupport.WebSupport` object.
|
||||
"""
|
||||
parts = ['<script type="text/javascript">',
|
||||
parts = ['<script type="text/javascript">',
|
||||
'var COMMENT_OPTIONS = {']
|
||||
if self.docroot is not '':
|
||||
parts.append('addCommentURL: "/%s/%s",' % (self.docroot,
|
||||
parts.append('addCommentURL: "/%s/%s",' % (self.docroot,
|
||||
'add_comment'))
|
||||
parts.append('getCommentsURL: "/%s/%s",' % (self.docroot,
|
||||
parts.append('getCommentsURL: "/%s/%s",' % (self.docroot,
|
||||
'get_comments'))
|
||||
parts.append('processVoteURL: "/%s/%s",' % (self.docroot,
|
||||
parts.append('processVoteURL: "/%s/%s",' % (self.docroot,
|
||||
'process_vote'))
|
||||
parts.append('acceptCommentURL: "/%s/%s",' % (self.docroot,
|
||||
parts.append('acceptCommentURL: "/%s/%s",' % (self.docroot,
|
||||
'accept_comment'))
|
||||
parts.append('rejectCommentURL: "/%s/%s",' % (self.docroot,
|
||||
parts.append('rejectCommentURL: "/%s/%s",' % (self.docroot,
|
||||
'reject_comment'))
|
||||
parts.append('deleteCommentURL: "/%s/%s",' % (self.docroot,
|
||||
parts.append('deleteCommentURL: "/%s/%s",' % (self.docroot,
|
||||
'delete_comment'))
|
||||
|
||||
if self.staticdir != 'static':
|
||||
@ -378,8 +378,8 @@ class WebSupport(object):
|
||||
parts.append('upArrowPressed: "/%s",' % p('up-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):
|
||||
"""Helper method to create the parts of the COMMENT_OPTIONS
|
||||
javascript that are unique to each request.
|
||||
@ -394,14 +394,13 @@ class WebSupport(object):
|
||||
parts.append('moderator: %s' % str(moderator).lower())
|
||||
parts.append('};')
|
||||
parts.append('</script>')
|
||||
return '\n'.join(parts)
|
||||
return '\n'.join(parts)
|
||||
|
||||
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()])
|
||||
js = """
|
||||
<script type="text/javascript">
|
||||
var COMMENT_METADATA = {%s};
|
||||
</script>""" % node_js
|
||||
return js
|
||||
|
||||
|
@ -34,7 +34,7 @@ class BaseSearch(object):
|
||||
def feed(self, pagename, title, doctree):
|
||||
"""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
|
||||
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.
|
||||
|
||||
: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
|
||||
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
|
||||
will need to be returned with search results when processing a
|
||||
will need to be returned with search results when processing a
|
||||
query.
|
||||
|
||||
|
||||
:param pagename: the name of the page being indexed
|
||||
:param title: the page's title
|
||||
:param text: the full text of the page
|
||||
@ -62,13 +62,13 @@ class BaseSearch(object):
|
||||
raise NotImplementedError()
|
||||
|
||||
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
|
||||
:meth:`extracting context <extract_context>`, then calls
|
||||
:meth:`handle_query`. You won't want to override this unless you
|
||||
don't want to use the included :meth:`extract_context` method.
|
||||
Override :meth:`handle_query` instead.
|
||||
|
||||
|
||||
:param q: the search query string.
|
||||
"""
|
||||
self.context_re = re.compile('|'.join(q.split()), re.I)
|
||||
@ -119,4 +119,3 @@ search_adapters = {
|
||||
'whoosh': ('whooshsearch', 'WhooshSearch'),
|
||||
'null': ('nullsearch', 'NullSearch')
|
||||
}
|
||||
|
||||
|
@ -28,7 +28,7 @@ class XapianSearch(BaseSearch):
|
||||
|
||||
def init_indexing(self, changed=[]):
|
||||
ensuredir(self.db_path)
|
||||
self.database = xapian.WritableDatabase(self.db_path,
|
||||
self.database = xapian.WritableDatabase(self.db_path,
|
||||
xapian.DB_CREATE_OR_OPEN)
|
||||
self.indexer = xapian.TermGenerator()
|
||||
stemmer = xapian.Stem("english")
|
||||
|
@ -20,13 +20,13 @@ class StorageBackend(object):
|
||||
"""Add a node to the StorageBackend.
|
||||
|
||||
:param document: the name of the document the node belongs to.
|
||||
|
||||
|
||||
:param line: the line in the source where the node begins.
|
||||
|
||||
:param source: the source files name.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
def post_build(self):
|
||||
"""Called after a build has completed. Use this to finalize the
|
||||
addition of nodes if needed.
|
||||
@ -36,7 +36,7 @@ class StorageBackend(object):
|
||||
def add_comment(self, text, displayed, username, time,
|
||||
proposal, node_id, parent_id, moderator):
|
||||
"""Called when a comment is being added.
|
||||
|
||||
|
||||
:param text: the text of the comment
|
||||
:param displayed: whether the comment should be displayed
|
||||
: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)
|
||||
|
||||
inheritance_chain = comment.path.split('.')[1:]
|
||||
|
||||
|
||||
if len(inheritance_chain) == len(list_stack) + 1:
|
||||
parent = list_stack[-1][-1]
|
||||
list_stack.append(parent['children'])
|
||||
@ -90,7 +90,7 @@ class Node(Base):
|
||||
list_stack.pop()
|
||||
|
||||
list_stack[-1].append(comment.serializable(vote=vote))
|
||||
|
||||
|
||||
return comments
|
||||
|
||||
def __init__(self, document, line, source):
|
||||
@ -115,7 +115,7 @@ class Comment(Base):
|
||||
node_id = Column(Integer, ForeignKey(db_prefix + 'nodes.id'))
|
||||
node = relation(Node, backref="comments")
|
||||
|
||||
def __init__(self, text, displayed, username, rating, time,
|
||||
def __init__(self, text, displayed, username, rating, time,
|
||||
proposal, proposal_diff):
|
||||
self.text = text
|
||||
self.displayed = displayed
|
||||
|
@ -53,12 +53,12 @@ class CombinedHtmlDiff(object):
|
||||
return text
|
||||
elif prefix == '?':
|
||||
return ''
|
||||
|
||||
|
||||
if next is not None and next[0] == '?':
|
||||
tag = 'ins' if prefix == '+' else 'del'
|
||||
text = self._highlight_text(text, next, tag)
|
||||
css_class = 'prop_added' if prefix == '+' else 'prop_removed'
|
||||
|
||||
|
||||
return '<span class="%s">%s</span>\n' % (css_class, text.rstrip())
|
||||
|
||||
def _highlight_text(self, text, next, tag):
|
||||
@ -76,4 +76,3 @@ class CombinedHtmlDiff(object):
|
||||
start = match.end()
|
||||
new_text.append(text[start:])
|
||||
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
|
||||
|
||||
class SQLAlchemyStorage(StorageBackend):
|
||||
"""A :class:`~sphinx.websupport.storage.StorageBackend` using
|
||||
"""A :class:`~sphinx.websupport.storage.StorageBackend` using
|
||||
SQLAlchemy.
|
||||
"""
|
||||
def __init__(self, engine):
|
||||
@ -59,7 +59,7 @@ class SQLAlchemyStorage(StorageBackend):
|
||||
raise CommentNotAllowedError(
|
||||
"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)
|
||||
session.add(comment)
|
||||
session.flush()
|
||||
@ -88,7 +88,7 @@ class SQLAlchemyStorage(StorageBackend):
|
||||
def get_metadata(self, docname, moderator):
|
||||
session = Session()
|
||||
subquery = session.query(
|
||||
Comment.id, Comment.node_id,
|
||||
Comment.id, Comment.node_id,
|
||||
func.count('*').label('comment_count')).group_by(
|
||||
Comment.node_id).subquery()
|
||||
nodes = session.query(Node.id, subquery.c.comment_count).outerjoin(
|
||||
|
@ -27,7 +27,7 @@ def teardown_module():
|
||||
|
||||
def search_adapter_helper(adapter):
|
||||
clear_builddir()
|
||||
|
||||
|
||||
settings = {'builddir': os.path.join(test_root, 'websupport'),
|
||||
'status': StringIO(),
|
||||
'warning': StringIO()}
|
||||
@ -81,4 +81,3 @@ def test_whoosh():
|
||||
except ImportError:
|
||||
sys.stderr.write('info: not running whoosh tests, ' \
|
||||
'whoosh doesn\'t seem to be installed')
|
||||
|
||||
|
@ -64,7 +64,7 @@ def test_build(support):
|
||||
@with_support()
|
||||
def test_get_document(support):
|
||||
raises(DocumentNotFoundError, support.get_document, 'nonexisting')
|
||||
|
||||
|
||||
contents = support.get_document('contents')
|
||||
assert contents['title'] and contents['body'] \
|
||||
and contents['sidebar'] and contents['relbar']
|
||||
@ -78,27 +78,27 @@ def test_comments(support):
|
||||
second_node = nodes[1]
|
||||
|
||||
# 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),
|
||||
username='user_one')
|
||||
hidden_comment = support.add_comment('Hidden comment',
|
||||
node_id=str(first_node.id),
|
||||
hidden_comment = support.add_comment('Hidden comment',
|
||||
node_id=str(first_node.id),
|
||||
displayed=False)
|
||||
# Make sure that comments can't be added to a comment where
|
||||
# displayed == False, since it could break the algorithm that
|
||||
# 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']))
|
||||
# Add a displayed and not displayed child to the displayed comment.
|
||||
support.add_comment('Child test comment', parent_id=str(comment['id']),
|
||||
username='user_one')
|
||||
support.add_comment('Hidden child test comment',
|
||||
support.add_comment('Hidden child test comment',
|
||||
parent_id=str(comment['id']), displayed=False)
|
||||
# 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),
|
||||
username='user_two')
|
||||
|
||||
|
||||
# Access the comments as a moderator.
|
||||
data = support.get_data(str(first_node.id), moderator=True)
|
||||
comments = data['comments']
|
||||
@ -130,7 +130,7 @@ def test_voting(support):
|
||||
data = support.get_data(str(node.id))
|
||||
comment = data['comments'][0]
|
||||
assert comment['rating'] == val, '%s != %s' % (comment['rating'], val)
|
||||
|
||||
|
||||
support.process_vote(comment['id'], 'user_one', '1')
|
||||
support.process_vote(comment['id'], 'user_two', '1')
|
||||
support.process_vote(comment['id'], 'user_three', '1')
|
||||
@ -161,7 +161,7 @@ def test_proposals(support):
|
||||
source = data['source']
|
||||
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),
|
||||
proposal=proposal)
|
||||
|
||||
@ -195,7 +195,7 @@ def test_moderator_delete_comments(support):
|
||||
return support.get_data(str(node.id), moderator=True)['comments'][1]
|
||||
|
||||
comment = get_comment()
|
||||
support.delete_comment(comment['id'], username='user_two',
|
||||
support.delete_comment(comment['id'], username='user_two',
|
||||
moderator=True)
|
||||
comment = get_comment()
|
||||
assert comment['username'] == '[deleted]'
|
||||
@ -228,9 +228,9 @@ def moderation_callback(comment):
|
||||
|
||||
@with_support(moderation_callback=moderation_callback)
|
||||
def test_moderation(support):
|
||||
accepted = support.add_comment('Accepted Comment', node_id=3,
|
||||
accepted = support.add_comment('Accepted Comment', node_id=3,
|
||||
displayed=False)
|
||||
rejected = support.add_comment('Rejected comment', node_id=3,
|
||||
rejected = support.add_comment('Rejected comment', node_id=3,
|
||||
displayed=False)
|
||||
# Make sure the moderation_callback is called.
|
||||
assert called == True
|
||||
@ -248,7 +248,7 @@ def test_moderation(support):
|
||||
def test_differ():
|
||||
differ = CombinedHtmlDiff()
|
||||
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' \
|
||||
'sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.'
|
||||
differ.make_html(source, prop)
|
||||
|
Loading…
Reference in New Issue
Block a user