/dev/api: Fully document the "subreddits" section.

This commit is contained in:
Neil Williams
2013-12-06 15:18:09 -08:00
parent 89af42e64d
commit 6b2cc95ba2
4 changed files with 170 additions and 28 deletions

View File

@@ -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 <li> 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 []

View File

@@ -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,

View File

@@ -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)

View File

@@ -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...!