Create quarantined subreddits content gate

When a user visits a quarantined subreddit for the first time, they are
presented with a content gate so that they can choose to optin to view
the subreddit's content. This preference is remembered per subreddit.
If a user is logged out, it requires the user to log in before validating
that the user has chosen to opt in to view the subreddit.
This commit is contained in:
MelissaCole
2015-07-21 16:21:14 -07:00
committed by Florence Yeun
parent 48e6429f7e
commit ae1296cf87
7 changed files with 130 additions and 12 deletions

View File

@@ -70,6 +70,7 @@ def make_map():
mc('/submit', controller='front', action='submit')
mc('/over18', controller='post', action='over18')
mc('/quarantine', controller='post', action='quarantine')
mc('/rules', controller='front', action='rules')
mc('/sup', controller='front', action='sup')

View File

@@ -81,6 +81,16 @@ class PostController(ApiController):
return BoringPage(_("over 18?"), content=Over18(),
show_sidebar=False).render()
@validate(
dest=VDestination(default='/'),
)
def GET_quarantine(self, dest):
sr = UrlParser(dest).get_subreddit()
return BoringPage(_("opt in to potentially offensive content?"),
content=Quarantine(sr.name),
show_sidebar=False,
).render()
@validate(VModhash(fatal=False),
over18 = nop('over18'),
dest = VDestination(default = '/'))
@@ -100,6 +110,23 @@ class PostController(ApiController):
delete_over18_cookie()
return self.redirect('/')
@validate(
VModhash(),
sr=VSRByName('sr_name'),
accept=VBoolean('accept'),
dest=VDestination(default='/'),
)
def POST_quarantine(self, sr, accept, dest):
if not c.user_is_loggedin:
return self.redirect(dest)
if accept:
QuarantinedSubredditOptInsByAccount.opt_in(c.user, sr)
return self.redirect(dest)
else:
QuarantinedSubredditOptInsByAccount.opt_out(c.user, sr)
return self.redirect('/')
@csrf_exempt
@validate(msg_hash = nop('x'))
def POST_optout(self, msg_hash):

View File

@@ -1668,13 +1668,11 @@ class RedditController(OAuth2ResourceController):
# do not leak the existence of multis via 403.
self.abort404()
elif not c.site.is_exposed(c.user):
errpage = pages.RedditError(
strings.quarantine_subreddit_title,
strings.quarantine_subreddit_message,
image="subreddit-banned.png",
)
request.environ['usable_error_content'] = errpage.render()
self.abort403()
if not c.user_is_loggedin:
return self.intermediate_redirect('/login', sr_path=False)
else:
return self.intermediate_redirect("/quarantine", sr_path=False)
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(

View File

@@ -2457,6 +2457,13 @@ class Over18(Templated):
"""The creepy 'over 18' check page for nsfw content."""
pass
class Quarantine(Templated):
"""The opt in page for viewing quarantined content."""
def __init__(self, sr_name):
Templated.__init__(self, sr_name=sr_name)
class SubredditTopBar(CachedTemplate):
"""The horizontal strip at the top of most pages for navigating

View File

@@ -1076,3 +1076,33 @@ class SubredditParticipationByAccount(tdb_cassandra.DenormalizedRelation):
@classmethod
def mark_participated(cls, account, subreddit):
cls.create(account, [subreddit])
class QuarantinedSubredditOptInsByAccount(tdb_cassandra.DenormalizedRelation):
_use_db = True
_last_modified_name = 'QuarantineSubredditOptin'
_read_consistency_level = tdb_cassandra.CL.QUORUM
_write_consistency_level = tdb_cassandra.CL.QUORUM
_connection_pool = 'main'
_views = []
@classmethod
def value_for(cls, thing1, thing2):
return datetime.now(g.tz)
@classmethod
def opt_in(cls, account, subreddit):
if subreddit.quarantine:
cls.create(account, subreddit)
@classmethod
def opt_out(cls, account, subreddit):
cls.destroy(account, subreddit)
@classmethod
def is_opted_in(cls, user, subreddit):
try:
r = cls.fast_query(user, [subreddit])
except tdb_cassandra.NotFound:
return False
return (user, subreddit) in r

View File

@@ -37,7 +37,12 @@ from pylons import c, g, request
from pylons.i18n import _, N_
from r2.lib.db.thing import Thing, Relation, NotFound
from account import Account, AccountsActiveBySR, FakeAccount
from account import (
Account,
AccountsActiveBySR,
FakeAccount,
QuarantinedSubredditOptInsByAccount,
)
from printable import Printable
from r2.lib.db.userrel import UserRel, MigratingUserRel
from r2.lib.db.operators import lower, or_, and_, not_, desc
@@ -793,7 +798,7 @@ class Subreddit(Thing, Printable, BaseSite):
def can_view(self, user):
if c.user_is_admin:
return True
if self.spammy() or not self.is_exposed(user):
return False
elif self.type in ('public', 'restricted',
@@ -811,9 +816,15 @@ class Subreddit(Thing, Printable, BaseSite):
self.is_moderator_invite(user))
def is_exposed(self, user):
"""Checks if visible to user based on the quarantine attribute."""
return not self.quarantine or (c.user_is_loggedin and
self.is_subscriber(user))
if c.user_is_admin:
return True
elif not self.quarantine:
return True
elif (c.user_is_loggedin and
QuarantinedSubredditOptInsByAccount.is_opted_in(user, self)):
return True
return False
def can_demod(self, bully, victim):
bully_rel = self.get_moderator(bully)

View File

@@ -0,0 +1,44 @@
## The contents of this file are subject to the Common Public Attribution
## License Version 1.0. (the "License"); you may not use this file except in
## compliance with the License. You may obtain a copy of the License at
## http://code.reddit.com/LICENSE. The License is based on the Mozilla Public
## License Version 1.1, but Sections 14 and 15 have been added to cover use of
## software over a computer network and provide for limited attribution for the
## Original Developer. In addition, Exhibit A has been modified to be
## consistent with Exhibit B.
##
## Software distributed under the License is distributed on an "AS IS" basis,
## WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
## the specific language governing rights and limitations under the License.
##
## The Original Code is reddit.
##
## The Original Developer is the Initial Developer. The Initial Developer of
## the Original Code is reddit Inc.
##
## All portions of the code written by reddit are Copyright (c) 2006-2015
## reddit Inc. All Rights Reserved.
###############################################################################
<% from r2.lib.template_helpers import static %>
<div class="content over18" style="text-align: center">
<p class="error">
${_("the following content is questionable")}
</p>
<img src="${static('over18.png')}" alt="" height="254" width="180" />
<form method="post" action="" class="pretty-form"
${"target='_top'" if c.cname else ""}>
<input type="hidden" name="uh" value="${c.modhash}" />
<input type="hidden" name="sr_name" value="${thing.sr_name}" />
<p>
${_("are you willing to see this questionable content?")}
</p>
<p>
<button type="submit" name="accept" value="yes">yes</button>
<button type="submit" name="accept" value="no">no</button>
</p>
</form>
</div>