Add documentation examples to flesh out api_docs.

This commit is contained in:
Max Goodman
2012-03-21 10:29:59 -07:00
parent e62dcd38af
commit 1462cd3ae2
4 changed files with 150 additions and 3 deletions

View File

@@ -54,6 +54,7 @@ from r2.lib.subreddit_search import search_reddits
from r2.lib.log import log_text
from r2.lib.filters import safemarkdown
from r2.lib.scraper import str_to_image
from r2.controllers.api_docs import api_doc, api_section
import csv
from collections import defaultdict
@@ -76,12 +77,14 @@ def reject_vote(thing):
(voteword, c.user.name, request.ip, thing.__class__.__name__,
thing._id36, request.referer), "info")
class ApiminimalController(MinimalController):
"""
Put API calls in here which don't rely on the user being logged in
"""
@validatedForm()
@api_doc(api_section.misc)
def POST_new_captcha(self, form, jquery, *a, **kw):
iden = get_iden()
jquery("body").captcha(iden)
@@ -100,6 +103,7 @@ class ApiController(RedditController):
@validate(link1 = VUrl(['url']),
link2 = VByName('id'),
count = VLimit('limit'))
@api_doc(api_section.links_and_comments)
def GET_info(self, link1, link2, count):
"""
Gets a listing of links which have the provided url.
@@ -115,7 +119,13 @@ class ApiController(RedditController):
@json_validate()
@api_doc(api_section.account, extensions=["json"])
def GET_me(self, responder):
"""
Get info about the currently authenticated user.
Response includes a modhash, karma, and new mail status.
"""
if c.user_is_loggedin:
return Wrapped(c.user).render()
else:
@@ -155,6 +165,7 @@ class ApiController(RedditController):
to = VMessageRecipient('to'),
subject = VRequired('subject', errors.NO_SUBJECT),
body = VMarkdown(['text', 'message']))
@api_doc(api_section.messages)
def POST_compose(self, form, jquery, to, subject, body, ip):
"""
handles message composition under /message/compose.
@@ -186,6 +197,7 @@ class ApiController(RedditController):
then = VOneOf('then', ('tb', 'comments'),
default='comments'),
extension = VLength("extension", 20))
@api_doc(api_section.links_and_comments)
def POST_submit(self, form, jquery, url, selftext, kind, title,
save, sr, ip, then, extension):
from r2.models.admintools import is_banned_domain
@@ -373,10 +385,12 @@ class ApiController(RedditController):
responder._send_data(cookie = user.make_cookie())
@cross_domain(allow_credentials=True)
@api_doc(api_section.account)
def POST_login(self, *args, **kwargs):
return self._handle_login(*args, **kwargs)
@cross_domain(allow_credentials=True)
@api_doc(api_section.account)
def POST_register(self, *args, **kwargs):
return self._handle_register(*args, **kwargs)
@@ -430,6 +444,7 @@ class ApiController(RedditController):
@noresponse(VUser(),
VModhash(),
container = VByName('id'))
@api_doc(api_section.moderation)
def POST_leavemoderator(self, container):
"""
Handles self-removal as moderator from a subreddit as rendered
@@ -444,6 +459,7 @@ class ApiController(RedditController):
@noresponse(VUser(),
VModhash(),
container = VByName('id'))
@api_doc(api_section.moderation)
def POST_leavecontributor(self, container):
"""
same comment as for POST_leave_moderator.
@@ -459,6 +475,7 @@ class ApiController(RedditController):
container = VByName('container'),
type = VOneOf('type', ('friend', 'enemy', 'moderator',
'contributor', 'banned')))
@api_doc(api_section.users)
def POST_unfriend(self, nuser, iuser, container, type):
"""
Handles removal of a friend (a user-user relation) or removal
@@ -507,6 +524,7 @@ class ApiController(RedditController):
type = VOneOf('type', ('friend', 'moderator',
'contributor', 'banned')),
note = VLength('note', 300))
@api_doc(api_section.users)
def POST_friend(self, form, jquery, ip, friend,
container, type, note):
"""
@@ -597,8 +615,13 @@ class ApiController(RedditController):
VModhash(),
password = VPassword(['curpass', 'curpass']),
dest = VDestination())
@api_doc(api_section.account)
def POST_clear_sessions(self, form, jquery, password, dest):
"""Clear all session cookies and update the current one."""
"""
Clear all session cookies and update the current one.
A valid password (`curpass`) must be supplied.
"""
# password is required to proceed
if form.has_errors("curpass", errors.WRONG_PASSWORD):
return
@@ -617,9 +640,14 @@ class ApiController(RedditController):
email = ValidEmails("email", num = 1),
password = VPassword(['newpass', 'verpass']),
verify = VBoolean("verify"))
@api_doc(api_section.account)
def POST_update(self, form, jquery, email, password, verify):
"""
handles /prefs/update for updating email address and password.
Update account email address and password.
Called by /prefs/update on the site. For frontend form verification
purposes, `newpass` and `verpass` must be equal for a password change
to succeed.
"""
# password is required to proceed
if form.has_errors("curpass", errors.WRONG_PASSWORD):
@@ -678,9 +706,16 @@ class ApiController(RedditController):
username = VRequired("user", errors.NOT_USER),
user = VThrottledLogin(["user", "passwd"]),
confirm = VBoolean("confirm"))
@api_doc(api_section.account)
def POST_delete_user(self, form, jquery, delete_message, username, user, confirm):
"""
/prefs/delete. Check the username/password and confirmation.
Delete an account.
A valid username/password and confirmation must be supplied. An
optional `delete_message` may be supplied to explain the reason the
account is to be deleted.
Called by /prefs/delete on the site.
"""
if username and username.lower() != c.user.name.lower():
c.errors.add(errors.NOT_USER, field="user")
@@ -746,6 +781,7 @@ class ApiController(RedditController):
VModhash(),
VSrCanAlter('id'),
thing = VByName('id'))
@api_doc(api_section.links_and_comments)
def POST_marknsfw(self, thing):
thing.over_18 = True
thing._commit()
@@ -761,6 +797,7 @@ class ApiController(RedditController):
VModhash(),
VSrCanAlter('id'),
thing = VByName('id'))
@api_doc(api_section.links_and_comments)
def POST_unmarknsfw(self, thing):
thing.over_18 = False
thing._commit()
@@ -774,6 +811,7 @@ class ApiController(RedditController):
@noresponse(VUser(), VModhash(),
thing = VByName('id'))
@api_doc(api_section.links_and_comments)
def POST_report(self, thing):
'''for reporting...'''
if not thing or thing._deleted:
@@ -800,6 +838,7 @@ class ApiController(RedditController):
@noresponse(VUser(), VModhash(),
thing=VByName('id'))
@api_doc(api_section.messages)
def POST_block(self, thing):
'''for blocking via inbox'''
if not thing:
@@ -841,6 +880,7 @@ class ApiController(RedditController):
VModhash(),
item = VByNameIfAuthor('thing_id'),
text = VSelfText('text'))
@api_doc(api_section.links_and_comments)
def POST_editusertext(self, form, jquery, item, text):
if (not form.has_errors("text",
errors.NO_TEXT, errors.TOO_LONG) and
@@ -880,6 +920,7 @@ class ApiController(RedditController):
ip = ValidIP(),
parent = VSubmitParent(['thing_id', 'parent']),
comment = VMarkdown(['text', 'comment']))
@api_doc(api_section.links_and_comments)
def POST_comment(self, commentform, jquery, parent, comment, ip):
should_ratelimit = True
#check the parent type here cause we need that for the
@@ -1065,6 +1106,7 @@ class ApiController(RedditController):
ip = ValidIP(),
dir = VInt('dir', min=-1, max=1),
thing = VByName('id'))
@api_doc(api_section.links_and_comments)
def POST_vote(self, dir, thing, ip, vote_type):
ip = request.ip
user = c.user
@@ -1098,6 +1140,7 @@ class ApiController(RedditController):
# nop is safe: handled after auth checks below
stylesheet_contents = nop('stylesheet_contents'),
op = VOneOf('op',['save','preview']))
@api_doc(api_section.subreddits)
def POST_subreddit_stylesheet(self, form, jquery,
stylesheet_contents = '', op='save'):
if not c.site.can_change_stylesheet(c.user):
@@ -1171,6 +1214,7 @@ class ApiController(RedditController):
@validatedForm(VSrModerator(),
VModhash(),
name = VCssName('img_name'))
@api_doc(api_section.subreddits)
def POST_delete_sr_img(self, form, jquery, name):
"""
Called called upon requested delete on /about/stylesheet.
@@ -1188,6 +1232,7 @@ class ApiController(RedditController):
@validatedForm(VSrModerator(),
VModhash(),
sponsor = VInt("sponsor", min = 0, max = 1))
@api_doc(api_section.subreddits)
def POST_delete_sr_header(self, form, jquery, sponsor):
"""
Called when the user request that the header on a sr be reset.
@@ -1235,6 +1280,7 @@ class ApiController(RedditController):
form_id = VLength('formid', max_length = 100),
header = VInt('header', max=1, min=0),
sponsor = VSubredditSponsorship('sponsor'))
@api_doc(api_section.subreddits)
def POST_upload_sr_img(self, file, header, sponsor, name, form_id, img_type):
"""
Called on /about/stylesheet when an image needs to be replaced
@@ -1323,6 +1369,7 @@ class ApiController(RedditController):
sponsor_url = VLength('sponsorship-url', max_length = 500),
css_on_cname = VBoolean("css_on_cname"),
)
@api_doc(api_section.subreddits)
def POST_site_admin(self, form, jquery, name, ip, sr,
sponsor_text, sponsor_url, sponsor_name, **kw):
# the status button is outside the form -- have to reset by hand
@@ -1441,6 +1488,7 @@ class ApiController(RedditController):
why = VSrCanBan('id'),
thing = VByName('id'),
spam = VBoolean('spam', default=True))
@api_doc(api_section.moderation)
def POST_remove(self, why, thing, spam):
# Don't remove a promoted link
@@ -1479,6 +1527,7 @@ class ApiController(RedditController):
@noresponse(VUser(), VModhash(),
why = VSrCanBan('id'),
thing = VByName('id'))
@api_doc(api_section.moderation)
def POST_approve(self, why, thing):
if not thing: return
if thing._deleted: return
@@ -1502,6 +1551,7 @@ class ApiController(RedditController):
VCanDistinguish(('id', 'how')),
thing = VByName('id'),
how = VOneOf('how', ('yes','no','admin','special')))
@api_doc(api_section.moderation)
def POST_distinguish(self, form, jquery, thing, how):
if not thing:return
@@ -1532,6 +1582,7 @@ class ApiController(RedditController):
@noresponse(VUser(),
VModhash(),
thing = VByName('id'))
@api_doc(api_section.links_and_comments)
def POST_save(self, thing):
if not thing: return
r = thing._save(c.user)
@@ -1541,6 +1592,7 @@ class ApiController(RedditController):
@noresponse(VUser(),
VModhash(),
thing = VByName('id'))
@api_doc(api_section.links_and_comments)
def POST_unsave(self, thing):
if not thing: return
r = thing._unsave(c.user)
@@ -1619,18 +1671,21 @@ class ApiController(RedditController):
@noresponse(VUser(),
VModhash(),
things = VByName('id', multiple=True, limit=25))
@api_doc(api_section.messages)
def POST_unread_message(self, things):
self.unread_handler(things, True)
@noresponse(VUser(),
VModhash(),
things = VByName('id', multiple=True, limit=25))
@api_doc(api_section.messages)
def POST_read_message(self, things):
self.unread_handler(things, False)
@noresponse(VUser(),
VModhash(),
thing = VByName('id'))
@api_doc(api_section.links_and_comments)
def POST_hide(self, thing):
if not thing: return
r = thing._hide(c.user)
@@ -1640,6 +1695,7 @@ class ApiController(RedditController):
@noresponse(VUser(),
VModhash(),
thing = VByName('id'))
@api_doc(api_section.links_and_comments)
def POST_unhide(self, thing):
if not thing: return
r = thing._unhide(c.user)
@@ -1674,6 +1730,7 @@ class ApiController(RedditController):
children = VCommentIDs('children'),
pv_hex = VPrintable('pv_hex', 40),
mc_id = nop('id'))
@api_doc(api_section.links_and_comments)
def POST_morechildren(self, form, jquery, link, sort, children,
pv_hex, mc_id):
user = c.user if c.user_is_loggedin else None
@@ -1879,6 +1936,7 @@ class ApiController(RedditController):
VModhash(),
action = VOneOf('action', ('sub', 'unsub')),
sr = VSubscribeSR('sr', 'sr_name'))
@api_doc(api_section.subreddits)
def POST_subscribe(self, action, sr):
# only users who can make edits are allowed to subscribe.
# Anyone can leave.
@@ -2055,6 +2113,7 @@ class ApiController(RedditController):
prefer_existing=True),
text = VFlairText("text"),
css_class = VFlairCss("css_class"))
@api_doc(api_section.flair)
def POST_flair(self, form, jquery, user, text, css_class):
# Check validation.
if form.has_errors('name', errors.USER_DOESNT_EXIST, errors.NO_USER):
@@ -2103,6 +2162,7 @@ class ApiController(RedditController):
VModhash(),
user = VExistingUname("name", allow_deleted=True,
prefer_existing=True))
@api_doc(api_section.flair)
def POST_deleteflair(self, form, jquery, user):
# Check validation.
if form.has_errors('name', errors.USER_DOESNT_EXIST, errors.NO_USER):
@@ -2123,6 +2183,7 @@ class ApiController(RedditController):
@validate(VFlairManager(),
VModhash(),
flair_csv = nop('flair_csv'))
@api_doc(api_section.flair)
def POST_flaircsv(self, flair_csv):
limit = 100 # max of 100 flair settings per call
results = FlairCsv()
@@ -2190,6 +2251,7 @@ class ApiController(RedditController):
@validatedForm(VUser(),
VModhash(),
flair_enabled = VBoolean("flair_enabled"))
@api_doc(api_section.flair)
def POST_setflairenabled(self, form, jquery, flair_enabled):
setattr(c.user, 'flair_%s_enabled' % c.site._id, flair_enabled)
c.user._commit()
@@ -2201,6 +2263,7 @@ class ApiController(RedditController):
flair_enabled = VBoolean("flair_enabled"),
flair_position = VOneOf("flair_position", ("left", "right")),
flair_self_assign_enabled = VBoolean("flair_self_assign_enabled"))
@api_doc(api_section.flair)
def POST_flairconfig(self, form, jquery, flair_enabled, flair_position,
flair_self_assign_enabled):
if c.site.flair_enabled != flair_enabled:
@@ -2222,6 +2285,7 @@ class ApiController(RedditController):
@validate(VFlairManager(),
user = VOptionalExistingUname('name', allow_deleted=True,
prefer_existing=True))
@api_doc(api_section.flair)
def GET_flairlist(self, num, after, reverse, count, user):
flair = FlairList(num, after, reverse, '', user)
return BoringPage(_("API"), content = flair).render()
@@ -2232,6 +2296,7 @@ class ApiController(RedditController):
text = VFlairText('text'),
css_class = VFlairCss('css_class'),
text_editable = VBoolean('text_editable'))
@api_doc(api_section.flair)
def POST_flairtemplate(self, form, jquery, flair_template, text,
css_class, text_editable):
if text is None:
@@ -2286,6 +2351,7 @@ class ApiController(RedditController):
@validatedForm(VFlairManager(),
VModhash(),
flair_template = VFlairTemplateByID('flair_template_id'))
@api_doc(api_section.flair)
def POST_deleteflairtemplate(self, form, jquery, flair_template):
idx = FlairTemplateBySubredditIndex.by_sr(c.site._id)
if idx.delete_by_id(flair_template._id):
@@ -2294,6 +2360,7 @@ class ApiController(RedditController):
details='flair_delete_template')
@validatedForm(VFlairManager(), VModhash())
@api_doc(api_section.flair)
def POST_clearflairtemplates(self, form, jquery):
FlairTemplateBySubredditIndex.clear(c.site._id)
jquery.refresh()
@@ -2313,6 +2380,7 @@ class ApiController(RedditController):
user = VOptionalExistingUname('name'),
flair_template = VFlairTemplateByID('flair_template_id'),
text = VFlairText("text"))
@api_doc(api_section.flair)
def POST_selectflair(self, form, jquery, user, flair_template, text):
if not flair_template:
# TODO: serve error to client

View File

@@ -46,6 +46,7 @@ from r2.lib import sup
import r2.lib.db.thing as thing
from errors import errors
from listingcontroller import ListingController
from api_docs import api_doc, api_section
from pylons import c, request, request, Response
import string
@@ -86,6 +87,7 @@ class FrontController(RedditController):
# redirect should be smarter and handle extensions, etc.
return self.redirect(new_url, code=301)
@api_doc(api_section.listings)
def GET_random(self):
"""The Serendipity button"""
sort = rand.choice(('new','hot'))
@@ -179,6 +181,9 @@ class FrontController(RedditController):
sort = VMenu('controller', CommentSortMenu),
limit = VInt('limit'),
depth = VInt('depth'))
@api_doc(api_section.listings,
uri='/comments/{article}',
extensions=['json', 'xml'])
def GET_comments(self, article, comment, context, sort, limit, depth):
"""Comment page for a given 'article'."""
if comment and comment.link_id != article._id:
@@ -388,6 +393,7 @@ class FrontController(RedditController):
@paginated_listing(max_page_size=500, backend='cassandra')
@validate(mod=VAccountByName('mod'),
action=VOneOf('type', ModAction.actions))
@api_doc(api_section.moderation)
def GET_moderationlog(self, num, after, reverse, count, mod, action):
if not c.user_is_loggedin:
return self.abort404()
@@ -606,6 +612,7 @@ class FrontController(RedditController):
return self._edit_normal_reddit(location, num, after, reverse,
count, created, name, user)
@api_doc(api_section.subreddits, uri='/r/{subreddit}/about', extensions=['json'])
def GET_about(self):
"""Return information about the subreddit.
@@ -670,6 +677,7 @@ class FrontController(RedditController):
@base_listing
@validate(query = nop('q'))
@api_doc(api_section.subreddits, uri='/reddits/search', extensions=['json', 'xml'])
def GET_search_reddits(self, query, reverse, after, count, num):
"""Search reddits by title and description."""
q = SubredditSearchQuery(query)
@@ -692,6 +700,7 @@ class FrontController(RedditController):
@validate(query = VLength('q', max_length=512),
sort = VMenu('sort', SearchSortMenu, remember=False),
restrict_sr = VBoolean('restrict_sr', default=False))
@api_doc(api_section.search, extensions=['json', 'xml'])
def GET_search(self, query, num, reverse, after, count, sort, restrict_sr):
"""Search links page."""
if query and '.' in query:

View File

@@ -44,12 +44,14 @@ from r2.lib import sup
from r2.lib.promote import randomized_promotion_list, get_promote_srid
import socket
from api_docs import api_doc, api_section
from admin import admin_profile_query
from pylons.i18n import _
from pylons import Response
import random
from functools import partial
class ListingController(RedditController):
"""Generalized controller for pages with lists of links."""
@@ -180,10 +182,13 @@ class ListingController(RedditController):
builder_wrapper = staticmethod(default_thing_wrapper())
@base_listing
@api_doc(api_section.listings, extensions=['json', 'xml'])
def GET_listing(self, **env):
check_cheating('site')
return self.build_listing(**env)
listing_api_doc = partial(api_doc, section=api_section.listings, extends=ListingController.GET_listing)
class FixListing(object):
"""When sorting by hotness, computing a listing when the before/after
link has a hottness of 0 is very slow. This class avoids drawing
@@ -337,6 +342,7 @@ class HotController(FixListing, ListingController):
def title(self):
return c.site.title
@listing_api_doc(uri='/hot')
def GET_listing(self, **env):
self.infotext = request.get.get('deleted') and strings.user_deleted
return ListingController.GET_listing(self, **env)
@@ -350,6 +356,7 @@ class SavedController(ListingController):
return queries.get_saved(c.user)
@validate(VUser())
@listing_api_doc(uri='/saved')
def GET_listing(self, **env):
return ListingController.GET_listing(self, **env)
@@ -396,6 +403,7 @@ class NewController(ListingController):
return self.redirect(request.fullpath + query_string(dict(sort=sort)))
@validate(sort = VMenu('controller', NewMenu))
@listing_api_doc(uri='/new')
def GET_listing(self, sort, **env):
self.sort = sort
return ListingController.GET_listing(self, **env)
@@ -433,6 +441,7 @@ class BrowseController(ListingController):
# TODO: this is a hack with sort.
@validate(sort = VOneOf('sort', ('top', 'controversial')),
t = VMenu('sort', ControversyTimeMenu))
@listing_api_doc(uri='/{sort}', uri_variants=['/top', '/controversial'])
def GET_listing(self, sort, t, **env):
self.sort = sort
if sort == 'top':
@@ -578,6 +587,10 @@ class UserController(ListingController):
@validate(vuser = VExistingUname('username'),
sort = VMenu('sort', ProfileSortMenu, remember = False),
time = VMenu('t', TimeMenu, remember = False))
@listing_api_doc(section=api_section.users, uri='/{username}/{where}',
uri_variants=['/{username}/' + where for where in [
'overview', 'submitted', 'commented',
'liked', 'disliked', 'hidden']])
def GET_listing(self, where, vuser, sort, time, **env):
self.where = where
self.sort = sort
@@ -613,7 +626,9 @@ class UserController(ListingController):
return ListingController.GET_listing(self, **env)
@validate(vuser = VExistingUname('username'))
@api_doc(section=api_section.users, uri='/{username}/about', extensions=['json'])
def GET_about(self, vuser):
"""Return information about the user, including karma and gold status."""
if not is_api() or not vuser:
return self.abort404()
return Reddit(content = Wrapped(vuser)).render()
@@ -766,6 +781,9 @@ class MessageController(ListingController):
@validate(VUser(),
message = VMessageID('mid'),
mark = VOneOf('mark',('true','false')))
@listing_api_doc(section=api_section.messages,
uri='/message/{where}',
uri_variants=['/message/inbox', '/message/unread', '/message/sent'])
def GET_listing(self, where, mark, message, subwhere = None, **env):
if not (c.default_sr or c.site.is_moderator(c.user) or c.user_is_admin):
abort(403, "forbidden")
@@ -835,6 +853,10 @@ class RedditsController(ListingController):
reddits._filter(Subreddit.c.over_18 == False)
return reddits
@listing_api_doc(section=api_section.subreddits,
uri='/reddits/{where}',
uri_variants=['/reddits/popular', '/reddits/new', '/reddits/banned'])
def GET_listing(self, where, **env):
self.where = where
return ListingController.GET_listing(self, **env)
@@ -883,6 +905,9 @@ class MyredditsController(ListingController):
return stack
@validate(VUser())
@listing_api_doc(section=api_section.subreddits,
uri='/reddits/mine/{where}',
uri_variants=['/reddits/mine/subscriber', '/reddits/mine/contributor', '/reddits/mine/moderator'])
def GET_listing(self, where = 'inbox', **env):
self.where = where
return ListingController.GET_listing(self, **env)

View File

@@ -601,6 +601,11 @@ class VByName(Validator):
return self.set_error(self._error)
def param_docs(self):
return {
self.param: _('an existing thing id')
}
class VByNameIfAuthor(VByName):
def run(self, fullname):
thing = VByName.run(self, fullname)
@@ -637,6 +642,11 @@ class VModhash(Validator):
def run(self, uh):
pass
def param_docs(self):
return {
self.param: _('a modhash')
}
class VVotehash(Validator):
def run(self, vh, thing_name):
return True
@@ -817,6 +827,11 @@ class VSubmitParent(VByName):
#else
abort(403, "forbidden")
def param_docs(self):
return {
self.param[0]: _('id of parent thing')
}
class VSubmitSR(Validator):
def __init__(self, srname_param, linktype_param=None, promotion=False):
self.require_linktype = False
@@ -998,6 +1013,16 @@ class VUrl(VRequired):
return url
return self.error(errors.BAD_URL)
def param_docs(self):
params = {}
try:
params[self.param[0]] = _('a valid URL')
params[self.param[1]] = _('a subreddit')
params[self.param[2]] = _('boolean value')
except IndexError:
pass
return params
class VOptionalExistingUname(VRequired):
def __init__(self, item, allow_deleted=False, prefer_existing=False,
*a, **kw):
@@ -1038,6 +1063,11 @@ class VExistingUname(VOptionalExistingUname):
self.error()
return user
def param_docs(self):
return {
self.param: _('the name of an existing user')
}
class VMessageRecipient(VExistingUname):
def run(self, name):
if not name:
@@ -1081,6 +1111,11 @@ class VBoolean(Validator):
return False
return bool(val)
def param_docs(self):
return {
self.param: _('boolean value')
}
class VNumber(Validator):
def __init__(self, param, min=None, max=None, coerce = True,
error = errors.BAD_NUMBER, *a, **kw):
@@ -1361,6 +1396,11 @@ class VOneOf(Validator):
else:
return val
def param_docs(self):
return {
self.param: _('one of (%s)') % ', '.join(self.options)
}
class VImageType(Validator):
def run(self, img_type):
if not img_type in ('png', 'jpg'):
@@ -1546,6 +1586,11 @@ class VDestination(Validator):
return "/"
def param_docs(self):
return {
self.param: _('destination url (must be same-domain)')
}
class ValidAddress(Validator):
def __init__(self, param, allowed_countries = ["United States"]):
self.allowed_countries = allowed_countries