Gold only subreddits

Users that have gold (or gold charter status) will be able to create and access
gold_only subreddits. Mods will be able to access gold_only status regardless of
their gold status. Approved submitters will not be able to access these subreddits.

To have a gold only subreddit, it must be created as gold only by a gold user or
have admin mode to convert it to gold only (requested via modmail to /r/reddit.com.
There is a default style (gold-only) applied to each gold_only subreddit.

Mods can choose to hide the sidebar adbox for gold-only subreddits. A "reddit gold"
('/subreddits/gold') tab is in '/subreddits' so that gold users can see all of the
gold only subreddits.

Being added as an approved submitter in gold_only and employee_only subreddits
don't allow you to see the subreddit. This will remove the link and ability to
edit the approved submitters so as to not confuse the mods.
This commit is contained in:
MelissaCole
2015-02-02 16:59:18 -08:00
parent b1761f1cb4
commit c24a01497a
13 changed files with 145 additions and 33 deletions

View File

@@ -81,7 +81,7 @@ def make_map():
mc('/subreddits/search', controller='front', action='search_reddits')
mc('/subreddits/login', controller='forms', action='login')
mc('/subreddits/:where', controller='reddits', action='listing',
where='popular', requirements=dict(where="popular|new|banned|employee"))
where='popular', requirements=dict(where="popular|new|banned|employee|gold"))
# If no subreddit is specified, might as well show a list of 'em.
mc('/r', controller='redirect', action='redirect', dest='/subreddits')

View File

@@ -2230,6 +2230,7 @@ class ApiController(RedditController):
wiki_edit_karma = VInt("wiki_edit_karma", coerce=False, num_default=0, min=0),
wiki_edit_age = VInt("wiki_edit_age", coerce=False, num_default=0, min=0),
css_on_cname = VBoolean("css_on_cname"),
hide_ads = VBoolean("hide_ads"),
)
@api_doc(api_section.subreddits)
def POST_site_admin(self, form, jquery, name, sr, **kw):
@@ -2278,7 +2279,7 @@ class ApiController(RedditController):
'header_title', 'over_18', 'wikimode', 'wiki_edit_karma',
'wiki_edit_age', 'allow_top', 'public_description',
'spam_links', 'spam_selfposts', 'spam_comments',
'submit_text'))
'submit_text', 'hide_ads'))
public_description = kw.pop('public_description')
description = kw.pop('description')
@@ -2327,6 +2328,17 @@ class ApiController(RedditController):
if kw['type'] == 'gold_restricted' and not can_set_gold_restricted:
c.errors.add(errors.INVALID_OPTION, field='type')
# can't create a gold only subreddit without having gold
can_set_gold_only = (c.user.gold or c.user.gold_charter or
(sr and sr.type == 'gold_only'))
if kw['type'] == 'gold_only' and not can_set_gold_only:
c.errors.add(errors.GOLD_REQUIRED, field='type')
can_set_hide_ads = can_set_gold_only and kw['type'] == 'gold_only'
if kw['hide_ads'] and not can_set_hide_ads:
form.set_error(errors.GOLD_ONLY_SR_REQUIRED, 'hide_ads')
c.errors.add(errors.GOLD_ONLY_SR_REQUIRED, field='hide_ads')
can_set_employees_only = c.user.employee
if kw['type'] == 'employees_only' and not can_set_employees_only:
c.errors.add(errors.INVALID_OPTION, field='type')
@@ -2339,6 +2351,12 @@ class ApiController(RedditController):
not c.user_is_admin):
form.set_error(errors.ADMIN_REQUIRED, 'type')
c.errors.add(errors.ADMIN_REQUIRED, field='type')
# if the user wants to convert an existing subreddit to gold_only,
# let them know that they'll need to contact an admin to convert it.
elif (sr and sr.type != 'gold_only' and kw['type'] == 'gold_only' and
not c.user_is_admin):
form.set_error(errors.CANT_CONVERT_TO_GOLD_ONLY, 'type')
c.errors.add(errors.CANT_CONVERT_TO_GOLD_ONLY, field='type')
elif not sr and form.has_errors("name", errors.SUBREDDIT_EXISTS,
errors.BAD_SR_NAME):
form.find('#example_name').hide()
@@ -2357,6 +2375,8 @@ class ApiController(RedditController):
pass
elif form.has_errors('comment_score_hide_mins', errors.BAD_NUMBER):
pass
elif form.has_errors('hide_ads', errors.GOLD_ONLY_SR_REQUIRED):
pass
#creating a new reddit
elif not sr:
#sending kw is ok because it was sanitized above
@@ -2371,7 +2391,7 @@ class ApiController(RedditController):
if sr.add_subscriber(c.user):
sr._incr('_ups', 1)
sr.add_moderator(c.user)
if sr.type != 'employees_only':
if not sr.hide_contributors:
sr.add_contributor(c.user)
redir = sr.path + "about/edit/?created=true"
if not c.user_is_admin:

View File

@@ -1236,6 +1236,14 @@ class RedditsController(ListingController):
cache_time=5 * 60,
)
reddits._sort = desc('_downs')
elif self.where == 'gold':
reddits = Subreddit._query(
Subreddit.c.type=='gold_only',
write_cache=True,
read_cache=True,
cache_time=5 * 60,
)
reddits._sort = desc('_downs')
else:
reddits = Subreddit._query( write_cache = True,
read_cache = True,
@@ -1254,7 +1262,12 @@ class RedditsController(ListingController):
@require_oauth2_scope("read")
@listing_api_doc(section=api_section.subreddits,
uri='/subreddits/{where}',
uri_variants=['/subreddits/popular', '/subreddits/new', '/subreddits/employee'])
uri_variants=[
'/subreddits/popular',
'/subreddits/new',
'/subreddits/employee',
'/subreddits/gold',
])
def GET_listing(self, where, **env):
"""Get all subreddits.
@@ -1591,6 +1604,9 @@ class UserListListingController(ListingController):
# Used for subreddits like /r/lounge
if c.site.hide_subscribers:
abort(403)
# used for subreddits that don't allow access to approved submitters
if c.site.hide_contributors:
abort(403)
self.listing_cls = ContributorListing
self.editable = has_mod_access

View File

@@ -1600,6 +1600,16 @@ class RedditController(OAuth2ResourceController):
if isinstance(c.site, LabeledMulti):
# do not leak the existence of multis via 403.
self.abort404()
elif c.site.type == 'gold_only' and not (c.user.gold or c.user.gold_charter):
public_description = c.site.public_description
errpage = pages.RedditError(
strings.gold_only_subreddit_title,
strings.gold_only_subreddit_message,
image="subreddit-gold-only.png",
sr_description=public_description,
)
request.environ['usable_error_content'] = errpage.render()
self.abort403()
else:
public_description = c.site.public_description
errpage = pages.RedditError(

View File

@@ -254,6 +254,7 @@ class Globals(object):
'timed_templates',
'autoexpand_media_types',
'multi_icons',
'hide_subscribers_srs',
],
ConfigValue.tuple_of(ConfigValue.int): [

View File

@@ -22,7 +22,7 @@
from webob.exc import HTTPBadRequest, HTTPForbidden, status_map
from r2.lib.utils import Storage, tup
from pylons import request
from pylons import g, request
from pylons.i18n import _
from copy import copy
@@ -74,6 +74,7 @@ error_list = dict((
('SUBREDDIT_NOEXIST', _('that subreddit doesn\'t exist')),
('SUBREDDIT_NOTALLOWED', _("you aren't allowed to post there.")),
('SUBREDDIT_REQUIRED', _('you must specify a subreddit')),
('SUBREDDIT_DISABLED_ADS', _('this subreddit has chosen to disable their ads at this time')),
('BAD_SR_NAME', _('that name isn\'t going to work')),
('COLLECTION_NOEXIST', _('that collection doesn\'t exist')),
('INVALID_TARGET', _('that target type is not valid')),
@@ -155,6 +156,9 @@ error_list = dict((
('USER_BLOCKED_MESSAGE', _("can't send message to that user")),
('USER_BAN_NO_MESSAGE', _("that user will not be sent a ban notification, remove note to be able to ban")),
('ADMIN_REQUIRED', _("you must be in admin mode for this")),
('CANT_CONVERT_TO_GOLD_ONLY', _("to convert an existing subreddit to gold only, send a message to %(admin_modmail)s")
% dict(admin_modmail=g.admin_message_acct)),
('GOLD_ONLY_SR_REQUIRED', _("this subreddit must be 'gold only' to select this")),
))
errors = Storage([(e, e) for e in error_list.keys()])

View File

@@ -214,6 +214,7 @@ class SubredditJsonTemplate(ThingJsonTemplate):
public_description="public_description",
public_description_html="public_description_html",
public_traffic="public_traffic",
hide_ads="hide_ads",
submission_type="link_type",
submit_link_label="submit_link_label",
submit_text_label="submit_text_label",
@@ -1058,6 +1059,7 @@ class SubredditSettingsTemplate(ThingJsonTemplate):
over_18='site.over_18',
public_description='site.public_description',
public_traffic='site.public_traffic',
hide_ads="site.hide_ads",
show_media='site.show_media',
submit_link_label='site.submit_link_label',
submit_text_label='site.submit_text_label',

View File

@@ -427,13 +427,11 @@ class Reddit(Templated):
buttons.append(NamedButton("moderators",
css_class="reddit-moderators"))
if c.site.type != "public":
buttons.append(NamedButton("contributors",
css_class="reddit-contributors"))
else:
buttons.append(NavButton(menu.contributors,
"contributors",
css_class="reddit-contributors"))
if not c.site.hide_contributors:
buttons.append(NavButton(
menu.contributors,
"contributors",
css_class="reddit-contributors"))
buttons.append(NamedButton("traffic", css_class="reddit-traffic"))
@@ -604,7 +602,11 @@ class Reddit(Templated):
show_cover=True))
no_ads_yet = True
show_adbox = (c.user.pref_show_adbox or not c.user.gold) and not g.disable_ads
user_disabled_ads = c.user.gold and not c.user.pref_show_adbox
sr_disabled_ads = (not isinstance(c.site, FakeSubreddit) and
c.site.type == "gold_only" and
c.site.hide_ads)
show_adbox = not (user_disabled_ads or sr_disabled_ads or g.disable_ads)
# don't show the subreddit info bar on cnames unless the option is set
if not isinstance(c.site, FakeSubreddit) and (not c.cname or c.site.show_cname_sidebar):
@@ -823,6 +825,9 @@ class Reddit(Templated):
if c.user_is_loggedin and c.user.pref_compress:
classes.add('compressed-display')
if getattr(c.site, 'type', None) == 'gold_only':
classes.add('gold-only')
if self.extra_page_classes:
classes.update(self.extra_page_classes)
if self.supplied_page_classes:
@@ -984,23 +989,6 @@ class SubredditInfoBar(CachedTemplate):
return self.sr.author.name
return None
def nav(self):
buttons = [NavButton(plurals.moderators, 'moderators')]
if self.type != 'public':
buttons.append(NavButton(getattr(plurals, "approved submitters"), 'contributors'))
if self.is_moderator or self.is_admin:
buttons.extend([
NamedButton('spam'),
NamedButton('reports'),
NavButton(menu.banusers, 'banned'),
NamedButton('traffic'),
NavButton(menu.community_settings, 'edit'),
NavButton(menu.flair, 'flair'),
NavButton(menu.modactions, 'modactions'),
])
return [NavMenu(buttons, type = "flat_vert", base_path = "/about/",
separator = '')]
class SponsorshipBox(Templated):
pass
@@ -1855,6 +1843,8 @@ class SubredditsPage(Reddit):
buttons.append(NamedButton("banned"))
if c.user.employee:
buttons.append(NamedButton("employee"))
if c.user.gold or c.user.gold_charter:
buttons.append(NamedButton("gold"))
if c.user_is_loggedin:
#add the aliases to "my reddits" stays highlighted
buttons.append(NamedButton("mine",

View File

@@ -102,6 +102,8 @@ string_dict = dict(
banned_subreddit_title = _("this subreddit has been banned"),
banned_subreddit_message = _("most likely this was done automatically by our spam filtering program. the program is still learning, and may even have some bugs, so if you feel the ban was a mistake, please submit a link to our [request a subreddit listing](%(link)s) and be sure to include the **exact name of the subreddit**."),
gold_only_subreddit_title = _("this subreddit is for gold members"),
gold_only_subreddit_message = _("you must have [reddit gold](/gold/about) to view this super secret subreddit ^[beta](/gold/about#gold-only-subreddits)"),
private_subreddit_title = _("this subreddit is private"),
private_subreddit_message = _("the moderators of this subreddit have set it to private. you must be a moderator or approved submitter to view its contents."),
comments_panel_text = _("""The following is a sample of what Reddit users had to say about this page. The full discussion is available [here](%(fd_link)s); you can also get there by clicking the link's title (in the middle of the toolbar, to the right of the comments button)."""),

View File

@@ -1232,6 +1232,10 @@ class VSubmitSR(Validator):
self.set_error(errors.SUBREDDIT_NOTALLOWED)
return
if sr.hide_ads:
self.set_error(errors.SUBREDDIT_DISABLED_ADS)
return
if self.require_linktype:
if link_type not in ('link', 'self'):
self.set_error(errors.INVALID_OPTION)

View File

@@ -241,7 +241,6 @@ class Subreddit(Thing, Printable, BaseSite):
public_description="",
submit_text="",
allow_gilding=True,
hide_subscribers=False,
public_traffic=False,
spam_links='high',
spam_selfposts='high',
@@ -250,6 +249,7 @@ class Subreddit(Thing, Printable, BaseSite):
gilding_server_seconds=0,
contest_mode_upvotes_only=False,
collapse_deleted_comments=False,
hide_ads=False,
)
_essentials = ('type', 'name', 'lang')
_data_int_props = Thing._data_int_props + ('mod_actions', 'reported',
@@ -267,6 +267,7 @@ class Subreddit(Thing, Printable, BaseSite):
valid_types = {
'archived',
'employees_only',
'gold_only',
'gold_restricted',
'private',
'public',
@@ -277,6 +278,7 @@ class Subreddit(Thing, Printable, BaseSite):
# unless you are a contributor or mod
private_types = {
'employees_only',
'gold_only',
'private',
}
@@ -504,6 +506,14 @@ class Subreddit(Thing, Printable, BaseSite):
def wiki_use_subreddit_karma(self):
return True
@property
def hide_subscribers(self):
return self.name.lower() in g.hide_subscribers_srs
@property
def hide_contributors(self):
return self.type in {'employees_only', 'gold_only'}
def get_accounts_active(self):
fuzzed = False
count = AccountsActiveBySR.get_count(self)
@@ -549,6 +559,8 @@ class Subreddit(Thing, Printable, BaseSite):
elif self.is_moderator(user) or self.is_contributor(user):
#private requires contributorship
return True
elif self.type == 'gold_only':
return user.gold or user.gold_charter
else:
return False
@@ -567,6 +579,8 @@ class Subreddit(Thing, Printable, BaseSite):
elif self.is_moderator(user) or self.is_contributor(user):
#restricted/private require contributorship
return True
elif self.type == 'gold_only':
return user.gold or user.gold_charter
elif self.type == 'gold_restricted' and user.gold:
return True
elif self.type == 'restricted' and promotion:
@@ -682,6 +696,12 @@ class Subreddit(Thing, Printable, BaseSite):
'gold_restricted', 'archived'):
return True
elif c.user_is_loggedin:
if self.type == 'gold_only':
return (user.gold or
user.gold_charter or
self.is_moderator(user) or
self.is_moderator_invite(user))
return (self.is_contributor(user) or
self.is_moderator(user) or
self.is_moderator_invite(user))

View File

@@ -25,7 +25,7 @@
from r2.lib.pages import UserText
from r2.lib.strings import strings
%>
<%namespace file="utils.html" import="error_field, language_tool, plain_link"/>
<%namespace file="utils.html" import="_md, error_field, language_tool, plain_link"/>
<%namespace file="utils.html" import="image_upload"/>
<%namespace file="printablebuttons.html" import="toggle_button, simple_button" />
@@ -136,9 +136,27 @@
_("only reddit employees can view; the employee list is pulled from live config"),
is_employees_only)}
%endif
<%
is_gold_only = thing.site and thing.site.type == 'gold_only'
can_set_gold_only = (is_gold_only or c.user_is_admin or
(not thing.site and (c.user.gold or c.user.gold_charter)))
if is_gold_only:
hover_title = "[!]"
hover_text = _('If you switch this subreddit to another type, you will not be able to switch back to "gold only" without the assistance of an admin')
else:
hover_title = "[?]"
hover_text = unsafe(capture(_md, 'BETA: Subreddits can be created as "gold only" during creation by a user that has gold. You can find more info about this feature [here](/gold/about#gold-only-subreddits)'))
%>
${utils.radio_type('type', "gold_only", _("gold only"),
_("only reddit gold members can view and submit"),
checked=is_gold_only, disabled=not can_set_gold_only, hover_title=hover_title,
hover_text=hover_text)}
</table>
${error_field("INVALID_OPTION", "type")}
${error_field("ADMIN_REQUIRED", "type")}
${error_field("GOLD_REQUIRED", "type")}
${error_field("CANT_CONVERT_TO_GOLD_ONLY", "type")}
</div>
</%utils:line_field>
@@ -322,6 +340,20 @@
${_("collapse deleted and removed comments")}
</label>
</li>
%if thing.site and thing.site.type == 'gold_only':
<li>
<input class="nomargin" type="checkbox"
name="hide_ads" id="hide_ads"
%if thing.site.hide_ads:
checked="checked"
%endif
>
<label class="buygold" for="hide_ads">
${_("hide ads (only available for gold only subreddits)")}
</label>
</li>
%endif
${error_field("GOLD_ONLY_SR_REQUIRED", "hide_ads")}
</ul>
</div>
<div class="usertext-edit">

View File

@@ -495,7 +495,7 @@ ${unsafe(txt)}
</%call>
</%def>
<%def name="radio_type(field_name, val_name, title, text=None, checked=False)">
<%def name="radio_type(field_name, val_name, title, text=None, checked=False, disabled=False, hover_title='[?]', hover_text=None)">
<% full_name = field_name + "_" + val_name %>
<tr>
<td class="nowrap nopadding">
@@ -504,8 +504,19 @@ ${unsafe(txt)}
%if checked:
checked="checked"
%endif
%if disabled:
disabled="disabled"
%endif
/>
<label for="${full_name}">${title}</label>
%if hover_text:
<span class="help help-hoverable">
<sup>${hover_title}</sup>
<div class="hover-bubble help-bubble anchor-top-left">
<p>${hover_text}</p>
</div>
</span>
%endif
</td>
%if text:
<td class="leftpad"><span class="gray">${text}</span></td>