diff --git a/r2/r2/controllers/api.py b/r2/r2/controllers/api.py
index fbe7a7bc7..117ca0f37 100755
--- a/r2/r2/controllers/api.py
+++ b/r2/r2/controllers/api.py
@@ -252,8 +252,16 @@ class ApiController(RedditController, OAuth2ResourceController):
queries.new_message(m, inbox_rel)
@json_validate()
- @api_doc(api_section.subreddits)
+ @api_doc(api_section.subreddits, uses_site=True, extensions=["json"])
def GET_submit_text(self, responder):
+ """Get the submission text for the subreddit.
+
+ This text is set by the subreddit moderators and intended to be
+ displayed on the submission form.
+
+ See also: [/api/site_admin](#POST_api_site_admin).
+
+ """
if c.site.over_18 and not c.over18:
submit_text = None
submit_text_html = None
@@ -903,7 +911,8 @@ class ApiController(RedditController, OAuth2ResourceController):
The authenticated user must have been invited to moderate the subreddit
by one of its current moderators.
- See also: [/api/friend](#POST_api_friend).
+ See also: [/api/friend](#POST_api_friend) and
+ [/subreddits/mine](#GET_subreddits_mine_{where}).
"""
@@ -1578,12 +1587,26 @@ class ApiController(RedditController, OAuth2ResourceController):
@validatedForm(VUser(),
VModhash(),
# nop is safe: handled after auth checks below
- stylesheet_contents = nop('stylesheet_contents'),
- prevstyle = VLength('prevstyle', max_length=36),
+ stylesheet_contents=nop('stylesheet_contents',
+ docs={"stylesheet_contents":
+ "the new stylesheet content"}),
+ prevstyle=VLength('prevstyle', max_length=36,
+ docs={"prevstyle":
+ "(optional) a revision ID"}),
op = VOneOf('op',['save','preview']))
@api_doc(api_section.subreddits, uses_site=True)
def POST_subreddit_stylesheet(self, form, jquery,
stylesheet_contents = '', prevstyle='', op='save'):
+ """Update a subreddit's stylesheet.
+
+ `op` should be `save` to update the contents of the stylesheet.
+
+ If `prevstyle` is specified, it should be the revision ID the submitted
+ version is a modification of. Wiki-style edit conflict resolution will
+ be done when this is specified, otherwise the content will be blindly
+ overwritten.
+
+ """
if form.has_errors("prevstyle", errors.TOO_LONG):
return
@@ -1681,10 +1704,16 @@ class ApiController(RedditController, OAuth2ResourceController):
name = VCssName('img_name'))
@api_doc(api_section.subreddits, uses_site=True)
def POST_delete_sr_img(self, form, jquery, name):
- """
- Called upon requested delete on /about/stylesheet.
- Updates the site's image list, and causes the
which wraps
- the image to be hidden.
+ """Remove an image from the subreddit's custom image set.
+
+ The image will no longer count against the subreddit's image limit.
+ However, the actual image data may still be accessible for an
+ unspecified amount of time. If the image is currently referenced by the
+ subreddit's stylesheet, that stylesheet will no longer validate and
+ won't be editable until the image reference is removed.
+
+ See also: [/api/upload_sr_img](#POST_api_upload_sr_img).
+
"""
# just in case we need to kill this feature from XSS
if g.css_killswitch:
@@ -1702,8 +1731,12 @@ class ApiController(RedditController, OAuth2ResourceController):
VModhash())
@api_doc(api_section.subreddits, uses_site=True)
def POST_delete_sr_header(self, form, jquery):
- """
- Called when the user request that the header on a sr be reset.
+ """Remove the subreddit's custom header image.
+
+ The sitewide-default header image will be shown again after this call.
+
+ See also: [/api/upload_sr_img](#POST_api_upload_sr_img).
+
"""
# just in case we need to kill this feature from XSS
if g.css_killswitch:
@@ -1740,24 +1773,32 @@ class ApiController(RedditController, OAuth2ResourceController):
file = VUploadLength('file', max_length=1024*500),
name = VCssName("name"),
img_type = VImageType('img_type'),
- form_id = VLength('formid', max_length = 100),
+ form_id = VLength('formid', max_length = 100,
+ docs={"formid": "(optional) can be ignored"}),
header = VInt('header', max=1, min=0))
@api_doc(api_section.subreddits, uses_site=True)
def POST_upload_sr_img(self, file, header, name, form_id, img_type):
- """
- Called on /about/stylesheet when an image needs to be replaced
- or uploaded, as well as on /about/edit for updating the
- header. Unlike every other POST in this controller, this
- method does not get called with Ajax but rather is from the
- original form POSTing to a hidden iFrame. Unfortunately, this
- means the response needs to generate an page with a script tag
- to fire the requisite updates to the parent document, and,
- more importantly, that we can't use our normal toolkit for
- passing those responses back.
+ """Add or replace a subreddit image or custom header logo.
+
+ If the `header` value is `0`, an image for use in the subreddit
+ stylesheet is uploaded with the name specified in `name`. If the value
+ of `header` is `1` then the image uploaded will be the subreddit's new
+ logo and `name` will be ignored.
+
+ The `img_type` field specifies whether to store the uploaded image as a
+ PNG or JPEG.
+
+ Subreddits have a limited number of images that can be in use at any
+ given time. If no image with the specified name already exists, one of
+ the slots will be consumed.
+
+ If an image with the specified name already exists, it will be
+ replaced. This does not affect the stylesheet immediately, but will
+ take effect the next time the stylesheet is saved.
+
+ See also: [/api/delete_sr_img](#POST_api_delete_sr_img) and
+ [/api/delete_sr_header](#POST_api_delete_sr_header).
- The result of this function is a rendered UploadedImage()
- object in charge of firing the completedUploadImage() call in
- JS.
"""
# default error list (default values will reset the errors in
@@ -1846,6 +1887,31 @@ class ApiController(RedditController, OAuth2ResourceController):
)
@api_doc(api_section.subreddits)
def POST_site_admin(self, form, jquery, name, ip, sr, **kw):
+ """Create or configure a subreddit.
+
+ If `sr` is specified, the request will attempt to modify the specified
+ subreddit. If not, a subreddit with name `name` will be created.
+
+ This endpoint expects *all* values to be supplied on every request. If
+ modifying a subset of options, it may be useful to get the current
+ settings from [/about/edit.json](#GET_r_{subreddit}_about_edit.json)
+ first.
+
+ The fields `prev_public_description_id`, `prev_description_id` and
+ `prev_submit_text_id` are optional. If specified, they should be the
+ wiki revision IDs of the last-seen versions of these pieces of text to
+ allow for wiki-style edit conflict resolution.
+
+ For backwards compatibility, `description` is the sidebar text and
+ `public_description` is the publicly visible subreddit description.
+
+ Most of the parameters for this endpoint are identical to options
+ visible in the user interface and their meanings are best explained
+ there.
+
+ See also: [/about/edit.json](#GET_r_{subreddit}_about_edit.json).
+
+ """
def apply_wikid_field(sr, form, pagename, value, prev, field, error):
id_field_name = 'prev_%s_id' % field
try:
@@ -2677,6 +2743,15 @@ class ApiController(RedditController, OAuth2ResourceController):
sr = VSubscribeSR('sr', 'sr_name'))
@api_doc(api_section.subreddits)
def POST_subscribe(self, action, sr):
+ """Subscribe to or unsubscribe from a subreddit.
+
+ To subscribe, `action` should be `sub`. To unsubscribe, `action` should
+ be `unsub`. The user must have access to the subreddit to be able to
+ subscribe to it.
+
+ See also: [/subreddits/mine/](#GET_subreddits_mine_{where}).
+
+ """
# only users who can make edits are allowed to subscribe.
# Anyone can leave.
if sr and (action != 'sub' or sr.can_comment(c.user)):
@@ -3382,6 +3457,7 @@ class ApiController(RedditController, OAuth2ResourceController):
@json_validate(query=VLength("query", max_length=50))
@api_doc(api_section.subreddits, extensions=["json"])
def GET_subreddits_by_topic(self, responder, query):
+ """Return a list of subreddits that are relevant to a search query."""
if not g.CLOUDSEARCH_SEARCH_API:
return []
diff --git a/r2/r2/controllers/front.py b/r2/r2/controllers/front.py
index 5e5f3ee77..f932f0e74 100755
--- a/r2/r2/controllers/front.py
+++ b/r2/r2/controllers/front.py
@@ -752,8 +752,16 @@ class FrontController(RedditController, OAuth2ResourceController):
@validate(location=nop('location'),
created=VOneOf('created', ('true','false'),
default='false'))
+ @api_doc(api_section.subreddits, uri="/r/{subreddit}/about/edit",
+ extensions=["json"])
def GET_editreddit(self, location, created):
- """Edit reddit form."""
+ """Get the current settings of a subreddit.
+
+ In the API, this returns the current settings of the subreddit as used
+ by [/api/site_admin](#POST_api_site_admin). On the HTML site, it will
+ display a form for editing the subreddit.
+
+ """
c.profilepage = True
if isinstance(c.site, FakeSubreddit):
return self.abort404()
@@ -845,10 +853,10 @@ class FrontController(RedditController, OAuth2ResourceController):
@base_listing
- @validate(query=nop('q'))
+ @validate(query=nop('q', docs={"q": "a search query"}))
@api_doc(api_section.subreddits, uri='/subreddits/search', extensions=['json', 'xml'])
def GET_search_reddits(self, query, reverse, after, count, num):
- """Search reddits by title and description."""
+ """Search subreddits by title and description."""
q = SubredditSearchQuery(query)
results, etime, spane = self._search(q, num=num, reverse=reverse,
diff --git a/r2/r2/controllers/listingcontroller.py b/r2/r2/controllers/listingcontroller.py
index 6e0f95989..178088e70 100755
--- a/r2/r2/controllers/listingcontroller.py
+++ b/r2/r2/controllers/listingcontroller.py
@@ -1023,8 +1023,16 @@ class RedditsController(ListingController):
@listing_api_doc(section=api_section.subreddits,
uri='/subreddits/{where}',
- uri_variants=['/subreddits/popular', '/subreddits/new', '/subreddits/banned'])
+ uri_variants=['/subreddits/popular', '/subreddits/new'])
def GET_listing(self, where, **env):
+ """Get all subreddits.
+
+ The `where` parameter chooses the order in which the subreddits are
+ displayed. `popular` sorts on the activity of the subreddit and the
+ position of the subreddits can shift around. `new` sorts the subreddits
+ based on their creation date, newest first.
+
+ """
self.where = where
return ListingController.GET_listing(self, **env)
@@ -1098,6 +1106,19 @@ class MyredditsController(ListingController, OAuth2ResourceController):
uri='/subreddits/mine/{where}',
uri_variants=['/subreddits/mine/subscriber', '/subreddits/mine/contributor', '/subreddits/mine/moderator'])
def GET_listing(self, where='subscriber', **env):
+ """Get subreddits the user has a relationship with.
+
+ The `where` parameter chooses which subreddits are returned as follows:
+
+ * `subscriber` - subreddits the user is subscribed to
+ * `contributor` - subreddits the user is an approved submitter in
+ * `moderator` - subreddits the user is a moderator of
+
+ See also: [/api/subscribe](#POST_api_subscribe),
+ [/api/friend](#POST_api_friend), and
+ [/api/accept_moderator_invite](#POST_api_accept_moderator_invite).
+
+ """
self.where = where
return ListingController.GET_listing(self, **env)
diff --git a/r2/r2/lib/validator/validator.py b/r2/r2/lib/validator/validator.py
index 2c34d1654..6dd841be0 100644
--- a/r2/r2/lib/validator/validator.py
+++ b/r2/r2/lib/validator/validator.py
@@ -365,6 +365,11 @@ class VLang(Validator):
def run(self, lang):
return VLang.validate_lang(lang)
+ def param_docs(self):
+ return {
+ self.param: "a valid IETF language tag (underscore separated)",
+ }
+
class VRequired(Validator):
def __init__(self, param, error, *a, **kw):
Validator.__init__(self, param, *a, **kw)
@@ -559,6 +564,12 @@ class VLength(Validator):
else:
return text
+ def param_docs(self):
+ return {
+ self.param:
+ "a string no longer than %d characters" % self.max_length,
+ }
+
class VUploadLength(VLength):
def run(self, upload, text2=''):
# upload is expected to be a FieldStorage object
@@ -567,6 +578,13 @@ class VUploadLength(VLength):
else:
self.set_error(self.empty_error, code=400)
+ def param_docs(self):
+ kibibytes = self.max_length / 1024
+ return {
+ self.param:
+ "file upload with maximum size of %d KiB" % kibibytes,
+ }
+
class VPrintable(VLength):
def run(self, text, text2 = ''):
text = VLength.run(self, text, text2)
@@ -1158,6 +1176,11 @@ class VSubscribeSR(VByName):
return sr
+ def param_docs(self):
+ return {
+ self.param[0]: "name of a subreddit",
+ }
+
MIN_PASSWORD_LENGTH = 3
class VPassword(Validator):
@@ -1495,6 +1518,11 @@ class VCssName(Validator):
self.set_error(errors.BAD_CSS_NAME)
return ''
+ def param_docs(self):
+ return {
+ self.param: "a valid subreddit image name",
+ }
+
class VMenu(Validator):
@@ -1715,6 +1743,11 @@ class VImageType(Validator):
return 'png'
return img_type
+ def param_docs(self):
+ return {
+ self.param: "one of `png` or `jpg` (default: `png`)",
+ }
+
class ValidEmails(Validator):
"""Validates a list of email addresses passed in as a string and
@@ -1837,6 +1870,10 @@ class VCnameDomain(Validator):
except UnicodeEncodeError:
self.set_error(errors.BAD_CNAME)
+ def param_docs(self):
+ # cnames are dead; don't advertise this.
+ return {}
+
# NOTE: make sure *never* to have res check these are present
# otherwise, the response could contain reference to these errors...!