mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
updated docs
This commit is contained in:
parent
978afa9c0d
commit
ac066fb54a
@ -10,10 +10,45 @@ The WebSupport Class
|
|||||||
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.
|
||||||
|
|
||||||
:param srcdir: the directory containing the reStructuredText files
|
The class takes the following keyword arguments:
|
||||||
:param outdir: the directory in which to place the built data
|
|
||||||
:param search: the search system to use
|
srcdir
|
||||||
:param comments: an instance of a CommentBackend
|
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.
|
||||||
|
|
||||||
|
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`.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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')
|
||||||
|
|
||||||
Methods
|
Methods
|
||||||
~~~~~~~
|
~~~~~~~
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
.. _websupportfrontend:
|
|
||||||
|
|
||||||
Web Support Frontend
|
|
||||||
====================
|
|
||||||
|
|
||||||
More coming soon.
|
|
@ -7,26 +7,26 @@ Building Documentation Data
|
|||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
To make use of the web support package in your application you'll
|
To make use of the web support package in your application you'll
|
||||||
need to build that 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.api.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/',
|
||||||
outdir='/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 `outdir`. This directory contains all the data needed
|
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
|
to display documents, search through documents, and add comments to
|
||||||
documents. It will also contain a subdirectory named "static", which
|
documents. The other directory will be called "static" and contains static
|
||||||
contains static files. These files will be linked to by Sphinx documents,
|
files that should be served from "/static".
|
||||||
and should be served from "/static".
|
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
@ -37,7 +37,7 @@ and should be served from "/static".
|
|||||||
Integrating Sphinx Documents Into Your Webapp
|
Integrating Sphinx Documents Into Your Webapp
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Now that you have the data, 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::
|
||||||
|
|
||||||
@ -59,8 +59,8 @@ This will return a dictionary containing the following items:
|
|||||||
* **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
|
||||||
* **DOCUMENTATION_OPTIONS**: Javascript containing documentation options
|
* **css**: Links to css files used by Sphinx
|
||||||
* **COMMENT_OPTIONS**: 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
|
||||||
@ -74,13 +74,15 @@ easy to integrate with your existing templating system. An example using
|
|||||||
{{ document.title }}
|
{{ document.title }}
|
||||||
{%- endblock %}
|
{%- endblock %}
|
||||||
|
|
||||||
{%- block js %}
|
{% block css %}
|
||||||
<script type="text/javascript">
|
|
||||||
{{ document.DOCUMENTATION_OPTIONS|safe }}
|
|
||||||
{{ document.COMMENT_OPTIONS|safe }}
|
|
||||||
</script>
|
|
||||||
{{ super() }}
|
{{ super() }}
|
||||||
<script type="text/javascript" src="/static/websupport.js"></script>
|
{{ document.css|safe }}
|
||||||
|
<link rel="stylesheet" href="/static/websupport-custom.css" type="text/css">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{%- block js %}
|
||||||
|
{{ super() }}
|
||||||
|
{{ document.js|safe }}
|
||||||
{%- endblock %}
|
{%- endblock %}
|
||||||
|
|
||||||
{%- block relbar %}
|
{%- block relbar %}
|
||||||
@ -99,12 +101,12 @@ Authentication
|
|||||||
--------------
|
--------------
|
||||||
|
|
||||||
To use certain features such as voting it must be possible to authenticate
|
To use certain features such as voting it must be possible to authenticate
|
||||||
users. The details of the authentication are left to the your application.
|
users. The details of the authentication are left to your application.
|
||||||
Once a user has been authenticated you can pass the user's details to certain
|
Once a user has been authenticated you can pass the user's details to certain
|
||||||
:class:`~sphinx.websupport.WebSupport` methods using the *username* and
|
:class:`~sphinx.websupport.WebSupport` methods using the *username* and
|
||||||
*moderator* keyword arguments. The web support package will store the
|
*moderator* keyword arguments. The web support package will store the
|
||||||
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)
|
||||||
|
|
||||||
@ -113,18 +115,22 @@ should be a boolean representing whether the user has moderation
|
|||||||
privilieges. The default value for *moderator* is *False*.
|
privilieges. The default value for *moderator* is *False*.
|
||||||
|
|
||||||
An example `Flask <http://flask.pocoo.org/>`_ function that checks whether
|
An example `Flask <http://flask.pocoo.org/>`_ function that checks whether
|
||||||
a user is logged in, and the retrieves a document is::
|
a user is logged in and then retrieves a document is::
|
||||||
|
|
||||||
|
from sphinx.websupport.errors import *
|
||||||
|
|
||||||
@app.route('/<path:docname>')
|
@app.route('/<path:docname>')
|
||||||
def doc(docname):
|
def doc(docname):
|
||||||
if g.user:
|
username = g.user.name if g.user else ''
|
||||||
document = support.get_document(docname, g.user.name,
|
moderator = g.user.moderator if g.user else False
|
||||||
g.user.moderator)
|
try:
|
||||||
else:
|
document = support.get_document(docname, username, moderator)
|
||||||
document = support.get_document(docname)
|
except DocumentNotFoundError:
|
||||||
|
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.
|
||||||
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
|
||||||
@ -134,7 +140,11 @@ will then add this data to the COMMENT_OPTIONS that are used in the template.
|
|||||||
|
|
||||||
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::
|
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>')
|
@app.route('/docs/<path:docname>')
|
||||||
|
|
||||||
@ -160,8 +170,8 @@ did to render our documents. That's because
|
|||||||
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.
|
||||||
|
|
||||||
Comments
|
Comments & Proposals
|
||||||
~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Now that this is done it's time to define the functions that handle
|
Now that this is done it's time to define the functions that handle
|
||||||
the AJAX calls from the script. You will need three functions. The first
|
the AJAX calls from the script. You will need three functions. The first
|
||||||
@ -171,20 +181,29 @@ function is used to add a new comment, and will call the web support method
|
|||||||
@app.route('/docs/add_comment', methods=['POST'])
|
@app.route('/docs/add_comment', methods=['POST'])
|
||||||
def add_comment():
|
def add_comment():
|
||||||
parent_id = request.form.get('parent', '')
|
parent_id = request.form.get('parent', '')
|
||||||
|
node_id = request.form.get('node', '')
|
||||||
text = request.form.get('text', '')
|
text = request.form.get('text', '')
|
||||||
|
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(parent_id, text, username=username)
|
comment = support.add_comment(text, node_id='node_id',
|
||||||
|
parent_id='parent_id',
|
||||||
|
username=username, proposal=proposal)
|
||||||
return jsonify(comment=comment)
|
return jsonify(comment=comment)
|
||||||
|
|
||||||
Then next function handles the retrieval of comments for a specific node,
|
You'll notice that both a `parent_id` and `node_id` are sent with the
|
||||||
and is aptly named :meth:`~sphinx.websupport.WebSupport.get_data`::
|
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
|
||||||
|
:meth:`~sphinx.websupport.WebSupport.get_data`::
|
||||||
|
|
||||||
@app.route('/docs/get_comments')
|
@app.route('/docs/get_comments')
|
||||||
def get_comments():
|
def get_comments():
|
||||||
user_id = g.user.id if g.user else None
|
username = g.user.name if g.user else None
|
||||||
parent_id = request.args.get('parent', '')
|
moderator = g.user.moderator if g.user else False
|
||||||
comments = support.get_data(parent_id, user_id)
|
node_id = request.args.get('node', '')
|
||||||
return jsonify(comments=comments)
|
data = support.get_data(parent_id, user_id)
|
||||||
|
return jsonify(**data)
|
||||||
|
|
||||||
The final function that is needed will call
|
The final function that is needed will call
|
||||||
:meth:`~sphinx.websupport.WebSupport.process_vote`, and will handle user
|
:meth:`~sphinx.websupport.WebSupport.process_vote`, and will handle user
|
||||||
@ -201,12 +220,49 @@ votes on comments::
|
|||||||
support.process_vote(comment_id, g.user.id, value)
|
support.process_vote(comment_id, g.user.id, value)
|
||||||
return "success"
|
return "success"
|
||||||
|
|
||||||
.. note::
|
Comment Moderation
|
||||||
|
~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Authentication is left up to your existing web application. If you do
|
By default all comments added through
|
||||||
not have an existing authentication system there are many readily
|
:meth:`~sphinx.websupport.WebSupport.add_comment` are automatically
|
||||||
available for different frameworks. The web support system stores only
|
displayed. If you wish to have some form of moderation, you can pass
|
||||||
the user's unique integer `user_id` and uses this both for storing votes
|
the `displayed` keyword argument::
|
||||||
and retrieving vote information. It is up to you to ensure that the
|
|
||||||
user_id passed in is unique, and that the user is authenticated. The
|
comment = support.add_comment(text, node_id='node_id',
|
||||||
default backend will only allow one vote per comment per `user_id`.
|
parent_id='parent_id',
|
||||||
|
username=username, proposal=proposal,
|
||||||
|
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
|
||||||
|
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'
|
||||||
|
|
||||||
|
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'
|
||||||
|
|
||||||
|
To perform a custom action (such as emailing a moderator) when a new comment
|
||||||
|
is added but not displayed, you can pass callable to the
|
||||||
|
:class:`~sphinx.websupport.WebSupport` class when instantiating your support
|
||||||
|
object::
|
||||||
|
|
||||||
|
def moderation_callback(comment):
|
||||||
|
Do something...
|
||||||
|
|
||||||
|
support = WebSupport(...
|
||||||
|
moderation_callback=moderation_callback)
|
||||||
|
|
||||||
|
The moderation callback must take one argument, which will be the same
|
||||||
|
comment dict that is returned by add_comment.
|
@ -11,7 +11,7 @@ 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,
|
||||||
outdir=outdir,
|
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
|
||||||
|
@ -5,6 +5,22 @@
|
|||||||
Storage Backends
|
Storage Backends
|
||||||
================
|
================
|
||||||
|
|
||||||
|
To create a custom storage backend you will need to subclass the
|
||||||
|
:class:`~StorageBackend` class. Then create an instance of the new class
|
||||||
|
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())
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
StorageBackend Methods
|
StorageBackend Methods
|
||||||
~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
@ -16,4 +32,14 @@ StorageBackend Methods
|
|||||||
|
|
||||||
.. automethod:: sphinx.websupport.storage.StorageBackend.add_comment
|
.. automethod:: sphinx.websupport.storage.StorageBackend.add_comment
|
||||||
|
|
||||||
|
.. automethod:: sphinx.websupport.storage.StorageBackend.delete_comment
|
||||||
|
|
||||||
.. automethod:: sphinx.websupport.storage.StorageBackend.get_data
|
.. automethod:: sphinx.websupport.storage.StorageBackend.get_data
|
||||||
|
|
||||||
|
.. automethod:: sphinx.websupport.storage.StorageBackend.process_vote
|
||||||
|
|
||||||
|
.. automethod:: sphinx.websupport.storage.StorageBackend.update_username
|
||||||
|
|
||||||
|
.. automethod:: sphinx.websupport.storage.StorageBackend.accept_comment
|
||||||
|
|
||||||
|
.. automethod:: sphinx.websupport.storage.StorageBackend.reject_comment
|
@ -11,6 +11,5 @@ into your web application. To learn more read the
|
|||||||
|
|
||||||
web/quickstart
|
web/quickstart
|
||||||
web/api
|
web/api
|
||||||
web/frontend
|
|
||||||
web/searchadapters
|
web/searchadapters
|
||||||
web/storagebackends
|
web/storagebackends
|
@ -52,6 +52,8 @@ class WebSupport(object):
|
|||||||
self._init_search(search)
|
self._init_search(search)
|
||||||
self._init_storage(storage)
|
self._init_storage(storage)
|
||||||
|
|
||||||
|
self._make_base_comment_options()
|
||||||
|
|
||||||
def _init_storage(self, storage):
|
def _init_storage(self, storage):
|
||||||
if isinstance(storage, StorageBackend):
|
if isinstance(storage, StorageBackend):
|
||||||
self.storage = storage
|
self.storage = storage
|
||||||
@ -90,11 +92,11 @@ class WebSupport(object):
|
|||||||
"""Build the documentation. Places the data into the `outdir`
|
"""Build the documentation. Places the data into the `outdir`
|
||||||
directory. Use it like this::
|
directory. Use it like this::
|
||||||
|
|
||||||
support = WebSupport(srcdir, outdir, search='xapian')
|
support = WebSupport(srcdir, builddir, search='xapian')
|
||||||
support.build()
|
support.build()
|
||||||
|
|
||||||
This will read reStructured text files from `srcdir`. Then it
|
This will read reStructured text files from `srcdir`. Then it will
|
||||||
build the pickles and search index, placing them into `outdir`.
|
build the pickles and search index, placing them into `builddir`.
|
||||||
It will also save node data to the database.
|
It will also save node data to the database.
|
||||||
"""
|
"""
|
||||||
if not self.srcdir:
|
if not self.srcdir:
|
||||||
@ -116,7 +118,7 @@ class WebSupport(object):
|
|||||||
be a dict object which can be used to render a template::
|
be a dict object which can be used to render a template::
|
||||||
|
|
||||||
support = WebSupport(datadir=datadir)
|
support = WebSupport(datadir=datadir)
|
||||||
support.get_document('index')
|
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
|
||||||
@ -124,13 +126,28 @@ class WebSupport(object):
|
|||||||
|
|
||||||
@app.route('/<path:docname>')
|
@app.route('/<path:docname>')
|
||||||
def index(docname):
|
def index(docname):
|
||||||
q = request.args.get('q')
|
username = g.user.name if g.user else ''
|
||||||
document = support.get_search_results(q)
|
moderator = g.user.moderator if g.user else False
|
||||||
|
try:
|
||||||
|
document = support.get_document(docname, username,
|
||||||
|
moderator)
|
||||||
|
except DocumentNotFoundError:
|
||||||
|
abort(404)
|
||||||
render_template('doc.html', document=document)
|
render_template('doc.html', document=document)
|
||||||
|
|
||||||
The document dict that is returned contains the following items
|
The document dict that is returned contains the following items
|
||||||
to be used during template rendering.
|
to be used during template rendering.
|
||||||
|
|
||||||
|
* **body**: The main body 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 raises :class:`~sphinx.websupport.errors.DocumentNotFoundError`
|
||||||
|
if a document matching `docname` is not found.
|
||||||
|
|
||||||
:param docname: the name of the document to load.
|
:param docname: the name of the document to load.
|
||||||
"""
|
"""
|
||||||
infilename = path.join(self.datadir, 'pickles', docname + '.fpickle')
|
infilename = path.join(self.datadir, 'pickles', docname + '.fpickle')
|
||||||
@ -146,7 +163,191 @@ class WebSupport(object):
|
|||||||
document['js'] = comment_opts + '\n' + document['js']
|
document['js'] = comment_opts + '\n' + document['js']
|
||||||
return document
|
return document
|
||||||
|
|
||||||
def _make_comment_options(self, username, moderator):
|
def get_search_results(self, q):
|
||||||
|
"""Perform a search for the query `q`, and create a set
|
||||||
|
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
|
||||||
|
"""
|
||||||
|
results = self.search.query(q)
|
||||||
|
ctx = {'search_performed': True,
|
||||||
|
'search_results': results,
|
||||||
|
'q': q}
|
||||||
|
document = self.get_document('search')
|
||||||
|
document['body'] = self.results_template.render(ctx)
|
||||||
|
document['title'] = 'Search Results'
|
||||||
|
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
|
||||||
|
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
|
||||||
|
having the following items:
|
||||||
|
|
||||||
|
============= ======================================================
|
||||||
|
Key Contents
|
||||||
|
============= ======================================================
|
||||||
|
text The comment text.
|
||||||
|
username The username that was stored with the comment.
|
||||||
|
id The comment's unique identifier.
|
||||||
|
rating The comment's current rating.
|
||||||
|
age The time in seconds since the comment was added.
|
||||||
|
time A dict containing time information. It contains the
|
||||||
|
following keys: year, month, day, hour, minute, second,
|
||||||
|
iso, and delta. `iso` is the time formatted in ISO
|
||||||
|
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
|
||||||
|
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
|
||||||
|
a node, this will be null.
|
||||||
|
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
|
||||||
|
the current source and the user's proposed source.
|
||||||
|
============= ======================================================
|
||||||
|
|
||||||
|
:param node_id: the id of the node to get comments for.
|
||||||
|
:param username: the username of the user viewing the comments.
|
||||||
|
:param moderator: whether the user is a moderator.
|
||||||
|
"""
|
||||||
|
return self.storage.get_data(node_id, username, moderator)
|
||||||
|
|
||||||
|
def delete_comment(self, comment_id, username='', moderator=False):
|
||||||
|
"""Delete a comment. Doesn't actually delete the comment, but
|
||||||
|
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
|
||||||
|
`moderator` is False, the comment will only be deleted if the
|
||||||
|
`username` matches the `username` on the comment.
|
||||||
|
|
||||||
|
This raises :class:`~sphinx.websupport.errors.UserNotAuthorizedError`
|
||||||
|
if moderator is False and `username` doesn't match username on the
|
||||||
|
comment.
|
||||||
|
|
||||||
|
:param comment_id: the id of the comment to delete.
|
||||||
|
:param username: the username requesting the deletion.
|
||||||
|
:param moderator: whether the requestor is a moderator.
|
||||||
|
"""
|
||||||
|
self.storage.delete_comment(comment_id, username, moderator)
|
||||||
|
|
||||||
|
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
|
||||||
|
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,
|
||||||
|
username=username)
|
||||||
|
|
||||||
|
:param parent_id: the prefixed id of the comment's parent.
|
||||||
|
:param text: the text of the comment.
|
||||||
|
:param displayed: for moderation purposes
|
||||||
|
:param username: the username of the user making the comment.
|
||||||
|
:param time: the time the comment was created, defaults to now.
|
||||||
|
"""
|
||||||
|
comment = self.storage.add_comment(text, displayed, username,
|
||||||
|
time, proposal, node_id,
|
||||||
|
parent_id, moderator)
|
||||||
|
if not displayed and self.moderation_callback:
|
||||||
|
self.moderation_callback(comment)
|
||||||
|
return comment
|
||||||
|
|
||||||
|
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
|
||||||
|
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
|
||||||
|
voting data. An example, once again in Flask::
|
||||||
|
|
||||||
|
@app.route('/docs/process_vote', methods=['POST'])
|
||||||
|
def process_vote():
|
||||||
|
if g.user is None:
|
||||||
|
abort(401)
|
||||||
|
comment_id = request.form.get('comment_id')
|
||||||
|
value = request.form.get('value')
|
||||||
|
if value is None or comment_id is None:
|
||||||
|
abort(400)
|
||||||
|
support.process_vote(comment_id, g.user.name, value)
|
||||||
|
return "success"
|
||||||
|
|
||||||
|
:param comment_id: the comment being voted on
|
||||||
|
:param username: the unique username of the user voting
|
||||||
|
:param value: 1 for an upvote, -1 for a downvote, 0 for an unvote.
|
||||||
|
"""
|
||||||
|
value = int(value)
|
||||||
|
if not -1 <= value <= 1:
|
||||||
|
raise ValueError('vote value %s out of range (-1, 1)' % value)
|
||||||
|
self.storage.process_vote(comment_id, username, value)
|
||||||
|
|
||||||
|
def update_username(self, old_username, new_username):
|
||||||
|
"""To remain decoupled from a webapp's authentication system, the
|
||||||
|
web support package stores a user's username with each of their
|
||||||
|
comments and votes. If the authentication system allows a user to
|
||||||
|
change their username, this can lead to stagnate data in the web
|
||||||
|
support system. To avoid this, each time a username is changed, this
|
||||||
|
method should be called.
|
||||||
|
|
||||||
|
:param old_username: The original username.
|
||||||
|
:param new_username: The new username.
|
||||||
|
"""
|
||||||
|
self.storage.update_username(old_username, new_username)
|
||||||
|
|
||||||
|
def accept_comment(self, comment_id, moderator=False):
|
||||||
|
"""Accept a comment that is pending moderation.
|
||||||
|
|
||||||
|
This raises :class:`~sphinx.websupport.errors.UserNotAuthorizedError`
|
||||||
|
if moderator is False.
|
||||||
|
|
||||||
|
:param comment_id: The id of the comment that was accepted.
|
||||||
|
:param moderator: Whether the user making the request is a moderator.
|
||||||
|
"""
|
||||||
|
if not moderator:
|
||||||
|
raise UserNotAuthorizedError()
|
||||||
|
self.storage.accept_comment(comment_id)
|
||||||
|
|
||||||
|
def reject_comment(self, comment_id, moderator=False):
|
||||||
|
"""Reject a comment that is pending moderation.
|
||||||
|
|
||||||
|
This raises :class:`~sphinx.websupport.errors.UserNotAuthorizedError`
|
||||||
|
if moderator is False.
|
||||||
|
|
||||||
|
:param comment_id: The id of the comment that was accepted.
|
||||||
|
:param moderator: Whether the user making the request is a moderator.
|
||||||
|
"""
|
||||||
|
if not moderator:
|
||||||
|
raise UserNotAuthorizedError()
|
||||||
|
self.storage.reject_comment(comment_id)
|
||||||
|
|
||||||
|
def _make_base_comment_options(self):
|
||||||
|
"""Helper method to create the part of the COMMENT_OPTIONS javascript
|
||||||
|
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 = {']
|
'var COMMENT_OPTIONS = {']
|
||||||
if self.docroot is not '':
|
if self.docroot is not '':
|
||||||
@ -171,6 +372,16 @@ 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)
|
||||||
|
|
||||||
|
def _make_comment_options(self, username, moderator):
|
||||||
|
"""Helper method to create the parts of the COMMENT_OPTIONS
|
||||||
|
javascript that are unique to each request.
|
||||||
|
|
||||||
|
:param username: The username of the user making the request.
|
||||||
|
:param moderator: Whether the user making the request is a moderator.
|
||||||
|
"""
|
||||||
|
parts = [self.base_comment_opts]
|
||||||
if username is not '':
|
if username is not '':
|
||||||
parts.append('voting: true,')
|
parts.append('voting: true,')
|
||||||
parts.append('username: "%s",' % username)
|
parts.append('username: "%s",' % username)
|
||||||
@ -179,167 +390,3 @@ class WebSupport(object):
|
|||||||
parts.append('</script>')
|
parts.append('</script>')
|
||||||
return '\n'.join(parts)
|
return '\n'.join(parts)
|
||||||
|
|
||||||
def get_search_results(self, q):
|
|
||||||
"""Perform a search for the query `q`, and create a set
|
|
||||||
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
|
|
||||||
"""
|
|
||||||
results = self.search.query(q)
|
|
||||||
ctx = {'search_performed': True,
|
|
||||||
'search_results': results,
|
|
||||||
'q': q}
|
|
||||||
document = self.get_document('search')
|
|
||||||
document['body'] = self.results_template.render(ctx)
|
|
||||||
document['title'] = 'Search Results'
|
|
||||||
return document
|
|
||||||
|
|
||||||
def get_data(self, node_id, username=None, moderator=False):
|
|
||||||
"""Get the comments and source associated with `node_id`. If
|
|
||||||
`user_id` is given vote information will be included with the
|
|
||||||
returned comments. The default CommentBackend returns dict with
|
|
||||||
two keys, *source*, and *comments*. *comments* is a list of
|
|
||||||
dicts that represent a comment, each having the following items:
|
|
||||||
|
|
||||||
============ ======================================================
|
|
||||||
Key Contents
|
|
||||||
============ ======================================================
|
|
||||||
text The comment text.
|
|
||||||
username The username that was stored with the comment.
|
|
||||||
id The comment's unique identifier.
|
|
||||||
rating The comment's current rating.
|
|
||||||
age The time in seconds since the comment was added.
|
|
||||||
time A dict containing time information. It contains the
|
|
||||||
following keys: year, month, day, hour, minute, second,
|
|
||||||
iso, and delta. `iso` is the time formatted in ISO
|
|
||||||
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
|
|
||||||
downvote, or 0 if unvoted.
|
|
||||||
node 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
|
|
||||||
to if it is not attached to a node.
|
|
||||||
children A list of all children, in this format.
|
|
||||||
============ ======================================================
|
|
||||||
|
|
||||||
:param node_id: the id of the node to get comments for.
|
|
||||||
:param user_id: the id of the user viewing the comments.
|
|
||||||
"""
|
|
||||||
return self.storage.get_data(node_id, username, moderator)
|
|
||||||
|
|
||||||
def delete_comment(self, comment_id, username='', moderator=False):
|
|
||||||
"""Delete a comment. Doesn't actually delete the comment, but
|
|
||||||
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
|
|
||||||
`moderator` is False, the comment will only be deleted if the
|
|
||||||
`username` matches the `username` on the comment.
|
|
||||||
|
|
||||||
:param comment_id: the id of the comment to delete.
|
|
||||||
:param username: the username requesting the deletion.
|
|
||||||
:param moderator: whether the requestor is a moderator.
|
|
||||||
"""
|
|
||||||
self.storage.delete_comment(comment_id, username, moderator)
|
|
||||||
|
|
||||||
def add_comment(self, text, node_id='', parent_id='', displayed=True,
|
|
||||||
username=None, rating=0, 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
|
|
||||||
node keyword argument::
|
|
||||||
|
|
||||||
comment = support.add_comment(text, node=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=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,
|
|
||||||
username=username)
|
|
||||||
|
|
||||||
:param parent_id: the prefixed id of the comment's parent.
|
|
||||||
:param text: the text of the comment.
|
|
||||||
:param displayed: for moderation purposes
|
|
||||||
:param username: the username of the user making the comment.
|
|
||||||
:param rating: the starting rating of the comment, defaults to 0.
|
|
||||||
:param time: the time the comment was created, defaults to now.
|
|
||||||
"""
|
|
||||||
comment = self.storage.add_comment(text, displayed, username, rating,
|
|
||||||
time, proposal, node_id,
|
|
||||||
parent_id, moderator)
|
|
||||||
if not displayed and self.moderation_callback:
|
|
||||||
self.moderation_callback(comment)
|
|
||||||
return comment
|
|
||||||
|
|
||||||
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
|
|
||||||
typically receive a comment_id and value from a form, and then
|
|
||||||
make sure the user is authenticated. A unique integer `user_id`
|
|
||||||
(usually the User primary key) must be passed in, which will
|
|
||||||
also be used to retrieve the user's past voting information.
|
|
||||||
An example, once again in Flask::
|
|
||||||
|
|
||||||
@app.route('/docs/process_vote', methods=['POST'])
|
|
||||||
def process_vote():
|
|
||||||
if g.user is None:
|
|
||||||
abort(401)
|
|
||||||
comment_id = request.form.get('comment_id')
|
|
||||||
value = request.form.get('value')
|
|
||||||
if value is None or comment_id is None:
|
|
||||||
abort(400)
|
|
||||||
support.process_vote(comment_id, g.user.id, value)
|
|
||||||
return "success"
|
|
||||||
|
|
||||||
:param comment_id: the comment being voted on
|
|
||||||
:param user_id: the unique integer id of the user voting
|
|
||||||
:param value: 1 for an upvote, -1 for a downvote, 0 for an unvote.
|
|
||||||
"""
|
|
||||||
value = int(value)
|
|
||||||
if not -1 <= value <= 1:
|
|
||||||
raise ValueError('vote value %s out of range (-1, 1)' % value)
|
|
||||||
self.storage.process_vote(comment_id, username, value)
|
|
||||||
|
|
||||||
def update_username(self, old_username, new_username):
|
|
||||||
"""To remain decoupled from a webapp's authentication system, the
|
|
||||||
web support package stores a user's username with each of their
|
|
||||||
comments and votes. If the authentication system allows a user to
|
|
||||||
change their username, this can lead to stagnate data in the web
|
|
||||||
support system. To avoid this, each time a username is changed, this
|
|
||||||
method should be called.
|
|
||||||
|
|
||||||
:param old_username: The original username.
|
|
||||||
:param new_username: The new username.
|
|
||||||
"""
|
|
||||||
self.storage.update_username(old_username, new_username)
|
|
||||||
|
|
||||||
def accept_comment(self, comment_id, moderator=False):
|
|
||||||
"""Accept a comment that is pending moderation.
|
|
||||||
|
|
||||||
:param comment_id: The id of the comment that was accepted.
|
|
||||||
"""
|
|
||||||
if not moderator:
|
|
||||||
raise UserNotAuthorizedError()
|
|
||||||
self.storage.accept_comment(comment_id)
|
|
||||||
|
|
||||||
def reject_comment(self, comment_id, moderator=False):
|
|
||||||
"""Reject a comment that is pending moderation.
|
|
||||||
|
|
||||||
:param comment_id: The id of the comment that was accepted.
|
|
||||||
"""
|
|
||||||
if not moderator:
|
|
||||||
raise UserNotAuthorizedError()
|
|
||||||
self.storage.reject_comment(comment_id)
|
|
||||||
|
@ -20,7 +20,7 @@ class BaseSearch(object):
|
|||||||
is a list of pagenames that will be reindexed. You may want to remove
|
is a list of pagenames that will be reindexed. You may want to remove
|
||||||
these from the search index before indexing begins.
|
these from the search index before indexing begins.
|
||||||
|
|
||||||
`param changed` is a list of pagenames that will be re-indexed
|
:param changed: a list of pagenames that will be re-indexed
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -37,11 +37,9 @@ class BaseSearch(object):
|
|||||||
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.
|
||||||
|
|
||||||
`pagename` is the name of the page to be indexed
|
:param pagename: the name of the page to be indexed
|
||||||
|
:param title: the title of the page to be indexed
|
||||||
`title` is the title of the page to be indexed
|
:param doctree: is the docutils doctree representation of the page
|
||||||
|
|
||||||
`doctree` is the docutils doctree representation of the page
|
|
||||||
"""
|
"""
|
||||||
self.add_document(pagename, title, doctree.astext())
|
self.add_document(pagename, title, doctree.astext())
|
||||||
|
|
||||||
@ -50,18 +48,16 @@ class BaseSearch(object):
|
|||||||
This method should should do everything necessary to add a single
|
This method should should do everything necessary to add a single
|
||||||
document to the search index.
|
document to the search index.
|
||||||
|
|
||||||
`pagename` is name of the page being indexed.
|
`pagename` is name of the page being indexed. It is the combination
|
||||||
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.
|
||||||
|
|
||||||
`title` is the page's title, and will need to be returned with
|
:param pagename: the name of the page being indexed
|
||||||
search results.
|
:param title: the page's title
|
||||||
|
:param text: the full text of the page
|
||||||
`text` is the full text of the page. You probably want to store this
|
|
||||||
somehow to use while creating the context for search results.
|
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
@ -73,7 +69,7 @@ class BaseSearch(object):
|
|||||||
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.
|
||||||
|
|
||||||
`q` is 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)
|
||||||
return self.handle_query(q)
|
return self.handle_query(q)
|
||||||
@ -91,6 +87,8 @@ class BaseSearch(object):
|
|||||||
|
|
||||||
The :meth:`extract_context` method is provided as a simple way
|
The :meth:`extract_context` method is provided as a simple way
|
||||||
to create the `context`.
|
to create the `context`.
|
||||||
|
|
||||||
|
:param q: the search query
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
@ -98,9 +96,8 @@ class BaseSearch(object):
|
|||||||
"""Extract the context for the search query from the documents
|
"""Extract the context for the search query from the documents
|
||||||
full `text`.
|
full `text`.
|
||||||
|
|
||||||
`text` is the full text of the document to create the context for.
|
:param text: the full text of the document to create the context for
|
||||||
|
:param length: the length of the context snippet to return.
|
||||||
`length` is the length of the context snippet to return.
|
|
||||||
"""
|
"""
|
||||||
res = self.context_re.search(text)
|
res = self.context_re.search(text)
|
||||||
if res is None:
|
if res is None:
|
||||||
|
@ -16,16 +16,14 @@ class StorageBackend(object):
|
|||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def add_node(self, document, line, source, treeloc):
|
def add_node(self, document, line, source):
|
||||||
"""Add a node to the StorageBackend.
|
"""Add a node to the StorageBackend.
|
||||||
|
|
||||||
`document` is the name of the document the node belongs to.
|
:param document: the name of the document the node belongs to.
|
||||||
|
|
||||||
`line` is the line in the source where the node begins.
|
:param line: the line in the source where the node begins.
|
||||||
|
|
||||||
`source` is the source files name.
|
:param source: the source files name.
|
||||||
|
|
||||||
`treeloc` is for future use.
|
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
@ -35,14 +33,77 @@ class StorageBackend(object):
|
|||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def add_comment(self, text, displayed, username, rating, time,
|
def add_comment(self, text, displayed, username, time,
|
||||||
proposal, node, parent):
|
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 displayed: whether the comment should be displayed
|
||||||
|
:param username: the name of the user adding the comment
|
||||||
|
:param time: a date object with the time the comment was added
|
||||||
|
:param proposal: the text of the proposal the user made
|
||||||
|
:param node_id: the id of the node that the comment is being added to
|
||||||
|
:param parent_id: the id of the comment's parent comment.
|
||||||
|
:param moderator: whether the user adding the comment is a moderator
|
||||||
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def get_data(self, parent_id, user_id, moderator):
|
def delete_comment(self, comment_id, username, moderator):
|
||||||
"""Called to retrieve all comments for a node."""
|
"""Delete a comment.
|
||||||
|
|
||||||
|
Raises :class:`~sphinx.websupport.errors.UserNotAuthorizedError`
|
||||||
|
if moderator is False and `username` doesn't match the username
|
||||||
|
on the comment.
|
||||||
|
|
||||||
|
:param comment_id: The id of the comment being deleted.
|
||||||
|
:param username: The username of the user requesting the deletion.
|
||||||
|
:param moderator: Whether the user is a moderator.
|
||||||
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def process_vote(self, comment_id, user_id, value):
|
def get_data(self, node_id, username, moderator):
|
||||||
|
"""Called to retrieve all data for a node. This should return a
|
||||||
|
dict with two keys, *source* and *comments* as described by
|
||||||
|
:class:`~sphinx.websupport.WebSupport`'s
|
||||||
|
:meth:`~sphinx.websupport.WebSupport.get_data` method.
|
||||||
|
|
||||||
|
:param node_id: The id of the node to get data for.
|
||||||
|
:param username: The name of the user requesting the data.
|
||||||
|
:param moderator: Whether the requestor is a moderator.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def process_vote(self, comment_id, username, value):
|
||||||
|
"""Process a vote that is being cast. `value` will be either -1, 0,
|
||||||
|
or 1.
|
||||||
|
|
||||||
|
:param comment_id: The id of the comment being voted on.
|
||||||
|
:param username: The username of the user casting the vote.
|
||||||
|
:param value: The value of the vote being cast.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def update_username(self, old_username, new_username):
|
||||||
|
"""If a user is allowed to change their username this method should
|
||||||
|
be called so that there is not stagnate data in the storage system.
|
||||||
|
|
||||||
|
:param old_username: The username being changed.
|
||||||
|
:param new_username: What the username is being changed to.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def accept_comment(self, comment_id):
|
||||||
|
"""Called when a moderator accepts a comment. After the method is
|
||||||
|
called the comment should be displayed to all users.
|
||||||
|
|
||||||
|
:param comment_id: The id of the comment being accepted.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def reject_comment(self, comment_id):
|
||||||
|
"""Called when a moderator rejects a comment. The comment should
|
||||||
|
then be deleted.
|
||||||
|
|
||||||
|
:param comment_id: The id of the comment being accepted.
|
||||||
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
@ -81,11 +81,10 @@ class Node(Base):
|
|||||||
|
|
||||||
return comments
|
return comments
|
||||||
|
|
||||||
def __init__(self, document, line, source, treeloc):
|
def __init__(self, document, line, source):
|
||||||
self.document = document
|
self.document = document
|
||||||
self.line = line
|
self.line = line
|
||||||
self.source = source
|
self.source = source
|
||||||
self.treeloc = treeloc
|
|
||||||
|
|
||||||
class Comment(Base):
|
class Comment(Base):
|
||||||
__tablename__ = db_prefix + 'comments'
|
__tablename__ = db_prefix + 'comments'
|
||||||
|
@ -27,8 +27,8 @@ class SQLAlchemyStorage(StorageBackend):
|
|||||||
def pre_build(self):
|
def pre_build(self):
|
||||||
self.build_session = Session()
|
self.build_session = Session()
|
||||||
|
|
||||||
def add_node(self, document, line, source, treeloc):
|
def add_node(self, document, line, source):
|
||||||
node = Node(document, line, source, treeloc)
|
node = Node(document, line, source)
|
||||||
self.build_session.add(node)
|
self.build_session.add(node)
|
||||||
self.build_session.flush()
|
self.build_session.flush()
|
||||||
return node
|
return node
|
||||||
@ -37,7 +37,7 @@ class SQLAlchemyStorage(StorageBackend):
|
|||||||
self.build_session.commit()
|
self.build_session.commit()
|
||||||
self.build_session.close()
|
self.build_session.close()
|
||||||
|
|
||||||
def add_comment(self, text, displayed, username, rating, time,
|
def add_comment(self, text, displayed, username, time,
|
||||||
proposal, node_id, parent_id, moderator):
|
proposal, node_id, parent_id, moderator):
|
||||||
session = Session()
|
session = Session()
|
||||||
|
|
||||||
@ -55,7 +55,7 @@ class SQLAlchemyStorage(StorageBackend):
|
|||||||
else:
|
else:
|
||||||
proposal_diff = None
|
proposal_diff = None
|
||||||
|
|
||||||
comment = Comment(text, displayed, username, rating,
|
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()
|
||||||
|
@ -57,6 +57,5 @@ class WebSupportTranslator(HTMLTranslator):
|
|||||||
storage = self.builder.app.storage
|
storage = self.builder.app.storage
|
||||||
db_node_id = storage.add_node(document=self.builder.cur_docname,
|
db_node_id = storage.add_node(document=self.builder.cur_docname,
|
||||||
line=node.line,
|
line=node.line,
|
||||||
source=node.rawsource or node.astext(),
|
source=node.rawsource or node.astext())
|
||||||
treeloc='???')
|
|
||||||
return db_node_id
|
return db_node_id
|
||||||
|
Loading…
Reference in New Issue
Block a user