mirror of
https://github.com/reddit-archive/reddit.git
synced 2026-01-25 23:08:22 -05:00
Delete the jury system.
This commit is contained in:
@@ -115,8 +115,6 @@ def make_map():
|
||||
mc('/prefs/:location', controller='forms', action='prefs',
|
||||
location='options')
|
||||
|
||||
mc('/depmod', controller='forms', action='depmod')
|
||||
|
||||
mc('/info/0:article/*rest', controller='front',
|
||||
action='oldinfo', dest='comments', type='ancient')
|
||||
mc('/info/:article/:dest/:comment', controller='front',
|
||||
|
||||
@@ -42,7 +42,6 @@ from r2.lib.pages import (EnemyList, FriendList, ContributorList, ModList,
|
||||
from r2.lib.pages import FlairList, FlairCsv, FlairTemplateEditor, \
|
||||
FlairSelector
|
||||
from r2.lib.pages import PrefApps
|
||||
from r2.lib.utils.trial_utils import indict, trial_info
|
||||
from r2.lib.pages.things import wrap_links, default_thing_wrapper
|
||||
from r2.models.last_modified import LastModified
|
||||
|
||||
@@ -952,15 +951,6 @@ class ApiController(RedditController, OAuth2ResourceController):
|
||||
return
|
||||
c.user.add_enemy(block_acct)
|
||||
|
||||
@noresponse(VAdmin(), VModhash(),
|
||||
thing = VByName('id'))
|
||||
def POST_indict(self, thing):
|
||||
'''put something on trial'''
|
||||
if not thing:
|
||||
log_text("indict: no thing", level="warning")
|
||||
|
||||
indict(thing)
|
||||
|
||||
@require_oauth2_scope("edit")
|
||||
@validatedForm(VUser(),
|
||||
VModhash(),
|
||||
@@ -1157,41 +1147,6 @@ class ApiController(RedditController, OAuth2ResourceController):
|
||||
VRatelimit.ratelimit(rate_user=True, rate_ip = True,
|
||||
prefix = "rate_share_")
|
||||
|
||||
@noresponse(VUser(),
|
||||
VModhash(),
|
||||
ip = ValidIP(),
|
||||
dir = VInt('dir', min=-1, max=1),
|
||||
thing = VByName('id'))
|
||||
def POST_juryvote(self, dir, thing, ip):
|
||||
if not thing:
|
||||
log_text("juryvote: no thing", level="warning")
|
||||
return
|
||||
|
||||
if not ip:
|
||||
log_text("juryvote: no ip", level="warning")
|
||||
return
|
||||
|
||||
if dir is None:
|
||||
log_text("juryvote: no dir", level="warning")
|
||||
return
|
||||
|
||||
j = Jury.by_account_and_defendant(c.user, thing)
|
||||
|
||||
if not trial_info([thing]).get(thing._fullname,False):
|
||||
log_text("juryvote: not on trial", level="warning")
|
||||
return
|
||||
|
||||
if not j:
|
||||
log_text("juryvote: not on the jury", level="warning")
|
||||
return
|
||||
|
||||
log_text("juryvote",
|
||||
"%s cast a %d juryvote on %s" % (c.user.name, dir, thing._id36),
|
||||
level="info")
|
||||
|
||||
j._name = str(dir)
|
||||
j._date = c.start_time
|
||||
j._commit()
|
||||
|
||||
@require_oauth2_scope("vote")
|
||||
@noresponse(VUser(),
|
||||
|
||||
@@ -492,9 +492,6 @@ class FrontController(RedditController, OAuth2ResourceController):
|
||||
query = c.site.get_reported()
|
||||
elif location == 'spam':
|
||||
query = c.site.get_spam()
|
||||
elif location == 'trials':
|
||||
query = c.site.get_trials()
|
||||
num = 1000
|
||||
elif location == 'modqueue':
|
||||
query = c.site.get_modqueue()
|
||||
elif location == 'unmoderated':
|
||||
@@ -518,14 +515,12 @@ class FrontController(RedditController, OAuth2ResourceController):
|
||||
return x.reported > 0 and not x._spam
|
||||
elif location == "spam":
|
||||
return x._spam
|
||||
elif location == "trials":
|
||||
return not getattr(x, "verdict", None)
|
||||
elif location == "modqueue":
|
||||
if x.reported > 0 and not x._spam:
|
||||
return True # reported but not banned
|
||||
verdict = getattr(x, "verdict", None)
|
||||
if verdict is None:
|
||||
return True # anything without a verdict (i.e., trials)
|
||||
return True # anything without a verdict
|
||||
if x._spam and verdict != 'mod-removed':
|
||||
return True # spam, unless banned by a moderator
|
||||
return False
|
||||
@@ -564,11 +559,8 @@ class FrontController(RedditController, OAuth2ResourceController):
|
||||
else:
|
||||
raise ValueError
|
||||
|
||||
if ((level == 'mod' and
|
||||
location in ('reports', 'spam', 'trials', 'modqueue', 'unmoderated'))
|
||||
or
|
||||
(level == 'all' and
|
||||
location == 'trials')):
|
||||
if (level == 'mod' and
|
||||
location in ('reports', 'spam', 'modqueue', 'unmoderated')):
|
||||
pane = self._make_spamlisting(location, num, after, reverse, count)
|
||||
if c.user.pref_private_feeds:
|
||||
extension_handling = "private"
|
||||
@@ -624,7 +616,7 @@ class FrontController(RedditController, OAuth2ResourceController):
|
||||
stylesheet = (c.site.stylesheet_contents_user or
|
||||
c.site.stylesheet_contents)
|
||||
pane = SubredditStylesheetSource(stylesheet_contents=stylesheet)
|
||||
elif (location in ('reports', 'spam', 'trials', 'modqueue', 'unmoderated')
|
||||
elif (location in ('reports', 'spam', 'modqueue', 'unmoderated')
|
||||
and is_moderator):
|
||||
c.allow_styles = True
|
||||
pane = self._make_spamlisting(location, num, after, reverse, count)
|
||||
@@ -1134,55 +1126,6 @@ class FormsController(RedditController):
|
||||
return BoringPage(_("reset password"),
|
||||
content=ResetPassword(key=key, done=done)).render()
|
||||
|
||||
@validate(VUser())
|
||||
def GET_depmod(self):
|
||||
displayPane = PaneStack()
|
||||
|
||||
active_trials = {}
|
||||
finished_trials = {}
|
||||
|
||||
juries = Jury.by_account(c.user)
|
||||
|
||||
trials = trial_info([j._thing2 for j in juries])
|
||||
|
||||
for j in juries:
|
||||
defendant = j._thing2
|
||||
|
||||
if trials.get(defendant._fullname, False):
|
||||
active_trials[defendant._fullname] = j._name
|
||||
else:
|
||||
finished_trials[defendant._fullname] = j._name
|
||||
|
||||
if active_trials:
|
||||
fullnames = sorted(active_trials.keys(), reverse=True)
|
||||
|
||||
def my_wrap(thing):
|
||||
w = Wrapped(thing)
|
||||
w.hide_score = True
|
||||
w.likes = None
|
||||
w.trial_mode = True
|
||||
w.render_class = LinkOnTrial
|
||||
w.juryvote = active_trials[thing._fullname]
|
||||
return w
|
||||
|
||||
listing = wrap_links(fullnames, wrapper=my_wrap)
|
||||
displayPane.append(InfoBar(strings.active_trials,
|
||||
extra_class="mellow"))
|
||||
displayPane.append(listing)
|
||||
|
||||
if finished_trials:
|
||||
fullnames = sorted(finished_trials.keys(), reverse=True)
|
||||
listing = wrap_links(fullnames)
|
||||
displayPane.append(InfoBar(strings.finished_trials,
|
||||
extra_class="mellow"))
|
||||
displayPane.append(listing)
|
||||
|
||||
displayPane.append(InfoBar(strings.more_info_link %
|
||||
dict(link="/help/deputies"),
|
||||
extra_class="mellow"))
|
||||
|
||||
return Reddit(content = displayPane).render()
|
||||
|
||||
@validate(VUser(),
|
||||
location = nop("location"))
|
||||
def GET_prefs(self, location=''):
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
# Inc. All Rights Reserved.
|
||||
###############################################################################
|
||||
|
||||
from r2.models import Account, Link, Comment, Trial, Vote, SaveHide, Report
|
||||
from r2.models import Account, Link, Comment, Vote, SaveHide, Report
|
||||
from r2.models import Message, Inbox, Subreddit, ModContribSR, ModeratorInbox, MultiReddit
|
||||
from r2.lib.db.thing import Thing, Merge
|
||||
from r2.lib.db.operators import asc, desc, timeago
|
||||
@@ -459,19 +459,6 @@ class QueryishList(list):
|
||||
else:
|
||||
raise StopIteration
|
||||
|
||||
def get_trials_links(sr):
|
||||
l = Trial.defendants_by_sr(sr)
|
||||
s = QueryishList(l)
|
||||
s._sort = [db_sort('new')]
|
||||
return s
|
||||
|
||||
def get_trials(sr):
|
||||
if isinstance(sr, (ModContribSR, MultiReddit)):
|
||||
srs = Subreddit._byID(sr.sr_ids, return_dict=False)
|
||||
return get_trials_links(srs)
|
||||
else:
|
||||
return get_trials_links(sr)
|
||||
|
||||
@merged_cached_query
|
||||
def get_modqueue(sr):
|
||||
q = []
|
||||
|
||||
@@ -35,10 +35,9 @@ class PrintableButtons(Styled):
|
||||
def __init__(self, style, thing,
|
||||
show_delete = False, show_report = True,
|
||||
show_distinguish = False, show_marknsfw = False,
|
||||
show_unmarknsfw = False, show_indict = False, is_link=False,
|
||||
show_unmarknsfw = False, is_link=False,
|
||||
show_flair = False, **kw):
|
||||
show_ignore = (thing.show_reports or
|
||||
(thing.reveal_trial_info and not thing.show_spam))
|
||||
show_ignore = thing.show_reports
|
||||
approval_checkmark = getattr(thing, "approval_checkmark", None)
|
||||
show_approve = (thing.show_spam or show_ignore or
|
||||
(is_link and approval_checkmark is None)) and not thing._deleted
|
||||
@@ -54,7 +53,6 @@ class PrintableButtons(Styled):
|
||||
show_delete = show_delete,
|
||||
show_approve = show_approve,
|
||||
show_report = show_report,
|
||||
show_indict = show_indict,
|
||||
show_distinguish = show_distinguish,
|
||||
show_marknsfw = show_marknsfw,
|
||||
show_unmarknsfw = show_unmarknsfw,
|
||||
@@ -78,9 +76,6 @@ class LinkButtons(PrintableButtons):
|
||||
|
||||
if c.user_is_admin and thing.promoted is None:
|
||||
show_report = False
|
||||
show_indict = True
|
||||
else:
|
||||
show_indict = False
|
||||
|
||||
if (thing.can_ban or is_author) and not thing.nsfw:
|
||||
show_marknsfw = True
|
||||
@@ -129,7 +124,6 @@ class LinkButtons(PrintableButtons):
|
||||
hidden = thing.hidden,
|
||||
show_delete = show_delete,
|
||||
show_report = show_report and c.user_is_loggedin,
|
||||
show_indict = show_indict,
|
||||
show_distinguish = show_distinguish,
|
||||
show_marknsfw = show_marknsfw,
|
||||
show_unmarknsfw = show_unmarknsfw,
|
||||
|
||||
@@ -87,8 +87,6 @@ string_dict = dict(
|
||||
|
||||
sr_created = _('your reddit has been created'),
|
||||
|
||||
active_trials = _("we haven't yet decided whether these things are spam, so you have a chance to change your vote:"),
|
||||
finished_trials = _("it's too late to change your vote on these things (the verdict has been issued):"),
|
||||
more_info_link = _("visit [%(link)s](%(link)s) for more information"),
|
||||
|
||||
sr_messages = dict(
|
||||
|
||||
@@ -28,4 +28,4 @@ from cmd_utils import *
|
||||
try:
|
||||
from r2admin.lib.admin_utils import *
|
||||
except ImportError:
|
||||
from admin_utils import *
|
||||
pass
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
# 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-2012 reddit
|
||||
# Inc. All Rights Reserved.
|
||||
###############################################################################
|
||||
|
||||
def jury_cache_dict(account, ip, slash16):
|
||||
return {"recent-juror-key": 1}
|
||||
|
||||
def voir_dire_priv (account, ip, slash16, defendant, sr):
|
||||
return True
|
||||
@@ -1,237 +0,0 @@
|
||||
# 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-2012 reddit
|
||||
# Inc. All Rights Reserved.
|
||||
###############################################################################
|
||||
|
||||
from pylons import c, g, request
|
||||
from r2.lib.utils import jury_cache_dict, voir_dire_priv, tup
|
||||
from r2.lib.memoize import memoize
|
||||
from r2.lib.log import log_text
|
||||
import random as rand
|
||||
|
||||
# Hardcache lifetime for a trial.
|
||||
# The regular hardcache reaper should never run on one of these,
|
||||
# since a mistrial should be declared if the trial is still open
|
||||
# after 24 hours. So the "3 days" expiration isn't really real.
|
||||
TRIAL_TIME = 3 * 86400
|
||||
|
||||
def trial_key(thing):
|
||||
return "trial-" + thing._fullname
|
||||
|
||||
def trial_info(things):
|
||||
things = tup(things)
|
||||
keys = dict((trial_key(thing), thing._fullname)
|
||||
for thing in things)
|
||||
# TODO: disabling trial lookup for now, since there aren't any
|
||||
# vals = g.hardcache.get_multi(keys)
|
||||
vals = {}
|
||||
return dict((keys[key], val)
|
||||
for (key, val)
|
||||
in vals.iteritems())
|
||||
|
||||
def end_trial(thing, verdict=None):
|
||||
from r2.models import Trial
|
||||
if trial_info(thing):
|
||||
g.hardcache.delete(trial_key(thing))
|
||||
Trial.all_defendants(_update=True)
|
||||
|
||||
if verdict is not None:
|
||||
thing.verdict = verdict
|
||||
thing._commit()
|
||||
|
||||
def indict(defendant):
|
||||
from r2.models import Trial
|
||||
tk = trial_key(defendant)
|
||||
|
||||
rv = False
|
||||
if defendant._deleted:
|
||||
result = "already deleted"
|
||||
elif getattr(defendant, "promoted", None) is not None:
|
||||
result = "it's promoted"
|
||||
elif hasattr(defendant, "verdict") and defendant.verdict is not None:
|
||||
result = "it already has a verdict"
|
||||
elif g.hardcache.get(tk):
|
||||
result = "it's already on trial"
|
||||
else:
|
||||
# The spams/koshers dict is just a infrequently-updated cache; the
|
||||
# official source of the data is the Jury relation.
|
||||
g.hardcache.set(tk, dict(spams=0, koshers=0), TRIAL_TIME)
|
||||
Trial.all_defendants(_update=True)
|
||||
result = "it's now indicted: %s" % tk
|
||||
rv = True
|
||||
|
||||
log_text("indict_result", "%s: %s" % (defendant._id36, result), level="info")
|
||||
|
||||
return rv
|
||||
|
||||
# These are spam/kosher votes, not up/down votes
|
||||
def update_voting(defendant, koshers, spams):
|
||||
tk = trial_key(defendant)
|
||||
d = g.hardcache.get(tk)
|
||||
if d is None:
|
||||
log_text("update_voting() fail",
|
||||
"%s not on trial" % defendant._id36,
|
||||
level="error")
|
||||
else:
|
||||
d["koshers"] = koshers
|
||||
d["spams"] = spams
|
||||
g.hardcache.set(tk, d, TRIAL_TIME)
|
||||
|
||||
# Check to see if a juror is eligible to serve on a jury for a given link.
|
||||
def voir_dire(account, ip, slash16, defendants_assigned_to, defendant, sr):
|
||||
from r2.models import Link
|
||||
|
||||
if defendant._deleted:
|
||||
g.log.debug("%s is deleted" % defendant)
|
||||
return False
|
||||
|
||||
if defendant._id in defendants_assigned_to:
|
||||
g.log.debug("%s is already assigned to %s" % (account.name, defendant))
|
||||
return False
|
||||
|
||||
if not isinstance(defendant, Link):
|
||||
g.log.debug("%s can't serve on a jury for %s: it's not a link" %
|
||||
account.name, defendant)
|
||||
return False
|
||||
|
||||
if g.debug:
|
||||
return True
|
||||
|
||||
if not voir_dire_priv(account, ip, slash16, defendant, sr):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def assign_trial(account, juries_already_on, ip, slash16):
|
||||
from r2.models import Jury, Subreddit, Trial
|
||||
from r2.lib.db import queries
|
||||
|
||||
defendants_assigned_to = []
|
||||
for jury in juries_already_on:
|
||||
defendants_assigned_to.append(jury._thing2_id)
|
||||
|
||||
subscribed_sr_ids = Subreddit.user_subreddits(account, ids=True, limit=None)
|
||||
|
||||
# Pull defendants, except ones which already have lots of juryvotes
|
||||
defs = Trial.all_defendants(quench=True)
|
||||
|
||||
# Filter out defendants outside this user's subscribed SRs
|
||||
defs = filter (lambda d: d.sr_id in subscribed_sr_ids, defs)
|
||||
|
||||
# Dictionary of sr_id => SR for all defendants' SRs
|
||||
srs = Subreddit._byID(set([ d.sr_id for d in defs ]))
|
||||
|
||||
# Dictionary of sr_id => eligibility bool
|
||||
submit_srs = {}
|
||||
for sr_id, sr in srs.iteritems():
|
||||
submit_srs[sr_id] = sr.can_submit(account) and not sr._spam
|
||||
|
||||
# Filter out defendants with ineligible SRs
|
||||
defs = filter (lambda d: submit_srs.get(d.sr_id), defs)
|
||||
|
||||
likes = queries.get_likes(account, defs)
|
||||
|
||||
if not g.debug:
|
||||
# Filter out things that the user has upvoted or downvoted
|
||||
defs = filter (lambda d: likes.get((account, d)) is None, defs)
|
||||
|
||||
# Prefer oldest trials
|
||||
defs.sort(key=lambda x: x._date)
|
||||
|
||||
for defendant in defs:
|
||||
sr = srs[defendant.sr_id]
|
||||
|
||||
if voir_dire(account, ip, slash16, defendants_assigned_to, defendant, sr):
|
||||
j = Jury._new(account, defendant)
|
||||
return defendant
|
||||
|
||||
return None
|
||||
|
||||
def populate_spotlight():
|
||||
raise Exception("this function is broken (re: ip_and_slash16) and pending demolition")
|
||||
|
||||
from r2.models import Jury
|
||||
from r2.lib.db.thing import NotFound
|
||||
|
||||
if not (c.user_is_loggedin and c.user.jury_betatester()):
|
||||
g.log.debug("not eligible")
|
||||
return None
|
||||
|
||||
try:
|
||||
juries_already_on = Jury.by_account(c.user)
|
||||
except NotFound:
|
||||
# This can happen if Jury.delete_old() just so happens to be cleaning
|
||||
# up this user's old Jury rels while they're visiting the front page.
|
||||
# In this unlucky case, just skip the 20% nagging below.
|
||||
juries_already_on = []
|
||||
|
||||
# If they're already on a jury, and haven't yet voted, re-show
|
||||
# it every five or so times.
|
||||
if rand.random() < 0.2:
|
||||
unvoted = filter(lambda j: j._name == '0', juries_already_on)
|
||||
defs = [u._thing2 for u in unvoted]
|
||||
active_trials = trial_info(defs)
|
||||
for d in defs:
|
||||
if active_trials.get(d._fullname, False):
|
||||
return d
|
||||
|
||||
if not g.cache.add("global-jury-key", True, 5):
|
||||
g.log.debug("not yet time to add another juror")
|
||||
return None
|
||||
|
||||
ip, slash16 = ip_and_slash16(request)
|
||||
|
||||
jcd = jury_cache_dict(c.user, ip, slash16)
|
||||
|
||||
if jcd is None:
|
||||
g.cache.delete("global-jury-key")
|
||||
return None
|
||||
|
||||
if g.cache.get_multi(jcd.keys()) and not g.debug:
|
||||
g.log.debug("recent juror")
|
||||
g.cache.delete("global-jury-key")
|
||||
return None
|
||||
|
||||
trial = assign_trial(c.user, juries_already_on, ip, slash16)
|
||||
|
||||
if trial is None:
|
||||
g.log.debug("nothing available")
|
||||
g.cache.delete("global-jury-key")
|
||||
return None
|
||||
|
||||
for k, v in jcd.iteritems():
|
||||
g.cache.set(k, True, v)
|
||||
|
||||
log_text("juryassignment",
|
||||
"%s was just assigned to the jury for %s" % (c.user.name, trial._id36),
|
||||
level="info")
|
||||
|
||||
return trial
|
||||
|
||||
def look_for_verdicts():
|
||||
from r2.models import Trial, Jury
|
||||
|
||||
print "checking all trials for verdicts..."
|
||||
for defendant in Trial.all_defendants():
|
||||
print "Looking at reddit.com/comments/%s/x" % defendant._id36
|
||||
v = Trial(defendant).check_verdict()
|
||||
print "Verdict: %r" % v
|
||||
|
||||
Jury.delete_old(verbose=True, limit=1000)
|
||||
@@ -29,8 +29,6 @@ from report import *
|
||||
from subreddit import *
|
||||
from flair import *
|
||||
from award import *
|
||||
from jury import *
|
||||
from trial import *
|
||||
from ad import *
|
||||
from bidding import *
|
||||
from mail_queue import Email, has_opted_out, opt_count
|
||||
|
||||
@@ -177,12 +177,6 @@ class Account(Thing):
|
||||
# Legacy, None means user may wiki
|
||||
return True
|
||||
return self.wiki_override
|
||||
|
||||
def jury_betatester(self):
|
||||
if g.cache.get("jury-killswitch"):
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def all_karmas(self):
|
||||
"""returns a list of tuples in the form (name, hover-text, link_karma,
|
||||
|
||||
@@ -369,10 +369,6 @@ def ip_span(ip):
|
||||
return '<!-- %s -->' % ip
|
||||
|
||||
def filter_quotas(unfiltered):
|
||||
from r2.lib.utils.trial_utils import trial_info
|
||||
|
||||
trials = trial_info(unfiltered)
|
||||
|
||||
now = datetime.now(g.tz)
|
||||
|
||||
baskets = {
|
||||
@@ -408,9 +404,7 @@ def filter_quotas(unfiltered):
|
||||
'admin-approved', 'mod-approved')
|
||||
|
||||
# Then, make sure it's worthy of quota-clogging
|
||||
if trials.get(item._fullname):
|
||||
pass
|
||||
elif item._spam:
|
||||
if item._spam:
|
||||
pass
|
||||
elif item._deleted:
|
||||
pass
|
||||
|
||||
@@ -228,7 +228,6 @@ class Builder(object):
|
||||
w.show_reports = False
|
||||
w.show_spam = False
|
||||
w.can_ban = False
|
||||
w.reveal_trial_info = False
|
||||
w.use_big_modbuttons = False
|
||||
|
||||
if (c.user_is_admin
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
# 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-2012 reddit
|
||||
# Inc. All Rights Reserved.
|
||||
###############################################################################
|
||||
|
||||
from r2.lib.db.thing import DataThing, Thing, MultiRelation, Relation
|
||||
from r2.lib.db.thing import NotFound, load_things
|
||||
from r2.lib.db.userrel import UserRel
|
||||
from r2.lib.db.operators import asc, desc, lower
|
||||
from r2.lib.memoize import memoize
|
||||
from r2.lib.utils import timeago
|
||||
from r2.models import Account, Link
|
||||
from pylons import c, g, request
|
||||
|
||||
class Jury(MultiRelation('jury',
|
||||
Relation(Account, Link))):
|
||||
@classmethod
|
||||
def _new(cls, account, defendant):
|
||||
j = Jury(account, defendant, "0")
|
||||
|
||||
j._commit()
|
||||
|
||||
Jury.by_account(account, _update=True)
|
||||
Jury.by_defendant(defendant, _update=True)
|
||||
|
||||
return j
|
||||
|
||||
@classmethod
|
||||
@memoize('jury.by_account')
|
||||
def by_account_cache(cls, account_id):
|
||||
q = cls._query(cls.c._thing1_id == account_id)
|
||||
q._limit = 100
|
||||
return [ j._fullname for j in q ]
|
||||
|
||||
@classmethod
|
||||
def by_account(cls, account, _update=False):
|
||||
rel_ids = cls.by_account_cache(account._id, _update=_update)
|
||||
juries = DataThing._by_fullname(rel_ids, data=True,
|
||||
return_dict = False)
|
||||
if juries:
|
||||
load_things(juries, load_data=True)
|
||||
return juries
|
||||
|
||||
@classmethod
|
||||
@memoize('jury.by_defendant')
|
||||
def by_defendant_cache(cls, defendant_id):
|
||||
q = cls._query(cls.c._thing2_id == defendant_id)
|
||||
q._limit = 1000
|
||||
return [ j._fullname for j in q ]
|
||||
|
||||
@classmethod
|
||||
def by_defendant(cls, defendant, _update=False):
|
||||
rel_ids = cls.by_defendant_cache(defendant._id, _update=_update)
|
||||
juries = DataThing._by_fullname(rel_ids, data=True,
|
||||
return_dict = False)
|
||||
if juries:
|
||||
load_things(juries, load_data=True)
|
||||
return juries
|
||||
|
||||
@classmethod
|
||||
def by_account_and_defendant(cls, account, defendant):
|
||||
q = cls._fast_query(account, defendant, ("-1", "0", "1"))
|
||||
v = filter(None, q.values())
|
||||
if v:
|
||||
return v[0]
|
||||
|
||||
@classmethod
|
||||
def delete_old(cls, age="3 days", limit=500, verbose=False):
|
||||
cutoff = timeago(age)
|
||||
q = cls._query(cls.c._date < cutoff)
|
||||
q._limit = limit
|
||||
|
||||
accounts = set()
|
||||
defendants = set()
|
||||
for j in q:
|
||||
accounts.add(j._thing1)
|
||||
defendants.add(j._thing2)
|
||||
j._delete()
|
||||
|
||||
for a in accounts:
|
||||
Jury.by_account(a, _update=True)
|
||||
|
||||
for d in defendants:
|
||||
if verbose:
|
||||
print "Deleting juries for defendant %s" % d._fullname
|
||||
Jury.by_defendant(d, _update=True)
|
||||
@@ -24,7 +24,6 @@ from r2.lib.db.thing import Thing, Relation, NotFound, MultiRelation, \
|
||||
CreationError
|
||||
from r2.lib.db.operators import desc
|
||||
from r2.lib.utils import base_url, tup, domain, title_to_url, UrlParser
|
||||
from r2.lib.utils.trial_utils import trial_info
|
||||
from account import Account, DeletedUser
|
||||
from subreddit import Subreddit, DomainSR
|
||||
from printable import Printable
|
||||
@@ -351,8 +350,6 @@ class Link(Thing, Printable):
|
||||
else:
|
||||
saved = hidden = clicked = {}
|
||||
|
||||
trials = trial_info(wrapped)
|
||||
|
||||
for item in wrapped:
|
||||
show_media = False
|
||||
if not hasattr(item, "score_fmt"):
|
||||
@@ -507,8 +504,6 @@ class Link(Thing, Printable):
|
||||
item.author = DeletedUser()
|
||||
item.as_deleted = True
|
||||
|
||||
item.trial_info = trials.get(item._fullname, None)
|
||||
|
||||
item.approval_checkmark = None
|
||||
|
||||
item_age = datetime.now(g.tz) - item._date
|
||||
@@ -529,10 +524,6 @@ class Link(Thing, Printable):
|
||||
else:
|
||||
item.approval_checkmark = _("approved by a moderator")
|
||||
|
||||
if item.trial_info is not None:
|
||||
item.reveal_trial_info = True
|
||||
item.use_big_modbuttons = True
|
||||
|
||||
item.expunged = False
|
||||
if item.is_self:
|
||||
item.expunged = Link._should_expunge_selftext(item)
|
||||
@@ -1364,14 +1355,6 @@ class Inbox(MultiRelation('inbox',
|
||||
res.append(i)
|
||||
return res
|
||||
|
||||
class LinkOnTrial(Printable):
|
||||
@classmethod
|
||||
def add_props(cls, user, wrapped):
|
||||
Link.add_props(user, wrapped)
|
||||
for item in wrapped:
|
||||
item.rowstyle = "link ontrial"
|
||||
# Run this last
|
||||
Printable.add_props(user, wrapped)
|
||||
|
||||
class ModeratorInbox(Relation(Subreddit, Message)):
|
||||
#TODO: shouldn't dupe this
|
||||
|
||||
@@ -480,10 +480,6 @@ class Subreddit(Thing, Printable):
|
||||
from r2.lib.db import queries
|
||||
return queries.get_reported(self)
|
||||
|
||||
def get_trials(self):
|
||||
from r2.lib.db import queries
|
||||
return queries.get_trials(self)
|
||||
|
||||
def get_modqueue(self):
|
||||
from r2.lib.db import queries
|
||||
return queries.get_modqueue(self)
|
||||
|
||||
@@ -1,211 +0,0 @@
|
||||
# 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-2012 reddit
|
||||
# Inc. All Rights Reserved.
|
||||
###############################################################################
|
||||
|
||||
from r2.models import Thing, Link, Subreddit, AllSR, admintools
|
||||
from r2.lib.utils import Storage, tup
|
||||
from r2.lib.memoize import memoize
|
||||
from datetime import datetime
|
||||
from pylons import g
|
||||
|
||||
class Trial(Storage):
|
||||
def __init__(self, defendant):
|
||||
from r2.lib.utils.trial_utils import trial_info
|
||||
|
||||
if not defendant._loaded:
|
||||
defendant._load()
|
||||
if not trial_info(defendant):
|
||||
raise ValueError ("Defendant %s is not on trial" % defendant._id)
|
||||
self.defendant = defendant
|
||||
|
||||
def convict(self, details = ''):
|
||||
# if self.defendant._spam:
|
||||
# TODO: PM submitter, maybe?
|
||||
# else:
|
||||
# TODO: PM submitter, maybe?
|
||||
admintools.spam(self.defendant, auto=False, moderator_banned=True,
|
||||
banner="deputy moderation" + details)
|
||||
|
||||
def acquit(self, details = ''):
|
||||
admintools.unspam(self.defendant, unbanner="deputy moderation" + details)
|
||||
|
||||
# if self.defendant._spam:
|
||||
# TODO: PM submitter
|
||||
# TODO: reset submission time:
|
||||
# self.defendant._date = datetime.now(g.tz)
|
||||
|
||||
def mistrial(self):
|
||||
#TODO: PM mods
|
||||
if self.defendant._spam:
|
||||
pass #TODO: PM submitter
|
||||
|
||||
def verdict(self):
|
||||
from r2.models import Jury
|
||||
from r2.lib.utils.trial_utils import update_voting
|
||||
|
||||
koshers = 0
|
||||
spams = 0
|
||||
nones = 0
|
||||
|
||||
now = datetime.now(g.tz)
|
||||
defendant_age = now - self.defendant._date
|
||||
if defendant_age.days > 0:
|
||||
return ("jury timeout", None, None)
|
||||
|
||||
latest_juryvote = None
|
||||
for j in Jury.by_defendant(self.defendant):
|
||||
if j._name == "0":
|
||||
nones += 1
|
||||
continue
|
||||
|
||||
# For non-zero votes, update latest_juryvote
|
||||
if latest_juryvote is None:
|
||||
latest_juryvote = j._date
|
||||
else:
|
||||
latest_juryvote = max(latest_juryvote, j._date)
|
||||
|
||||
if j._name == "1":
|
||||
koshers += 1
|
||||
elif j._name == "-1":
|
||||
spams += 1
|
||||
else:
|
||||
raise ValueError("weird jury vote: [%s]" % j._name)
|
||||
|
||||
# The following trace is temporary; it'll be removed once this
|
||||
# is done via cron job as opposed to manually
|
||||
print "%d koshers, %d spams, %d haven't voted yet" % (koshers, spams, nones)
|
||||
|
||||
update_voting(self.defendant, koshers, spams)
|
||||
|
||||
total_votes = koshers + spams
|
||||
|
||||
if total_votes < 7:
|
||||
g.log.debug("not enough votes yet")
|
||||
return (None, koshers, spams)
|
||||
|
||||
# Stop showing this in the spotlight box once it has 20 votes
|
||||
if total_votes >= 20:
|
||||
g.cache.set("quench_jurors-" + self.defendant._fullname, True)
|
||||
quenching = True
|
||||
else:
|
||||
quenching = False
|
||||
|
||||
# If a trial is less than an hour old, and votes are still trickling
|
||||
# in (i.e., there was one in the past five minutes), we're going to
|
||||
# require a nearly unanimous opinion to end the trial without
|
||||
# waiting for more votes.
|
||||
if defendant_age.seconds < 3600 and (now - latest_juryvote).seconds < 300:
|
||||
trickling = True
|
||||
else:
|
||||
trickling = False
|
||||
|
||||
kosher_pct = float(koshers) / float(total_votes)
|
||||
|
||||
if kosher_pct < 0.13:
|
||||
return ("guilty", koshers, spams)
|
||||
elif kosher_pct > 0.86:
|
||||
return ("innocent", koshers, spams)
|
||||
elif trickling:
|
||||
g.log.debug("votes still trickling in")
|
||||
return (None, koshers, spams)
|
||||
elif kosher_pct < 0.34:
|
||||
return ("guilty", koshers, spams)
|
||||
elif kosher_pct > 0.66:
|
||||
return ("innocent", koshers, spams)
|
||||
elif not quenching:
|
||||
g.log.debug("not yet quenching")
|
||||
return (None, koshers, spams)
|
||||
# At this point, we're not showing the link to any new jurors, and
|
||||
# the existing jurors haven't changed or submitted votes for several
|
||||
# minutes, so we're not really expecting to get many more votes.
|
||||
# Thus, lower our standards for consensus.
|
||||
elif kosher_pct < 0.3999:
|
||||
return ("guilty", koshers, spams)
|
||||
elif kosher_pct > 0.6001:
|
||||
return ("innocent", koshers, spams)
|
||||
elif total_votes >= 100:
|
||||
# This should never really happen; quenching should kick in
|
||||
# after 20 votes, so new jurors won't be assigned to the
|
||||
# trial. Just in case something goes wrong, close any trials
|
||||
# with more than 100 votes.
|
||||
return ("hung jury", koshers, spams)
|
||||
else:
|
||||
g.log.debug("hung jury, so far")
|
||||
return (None, koshers, spams) # no decision yet; wait for more voters
|
||||
|
||||
def check_verdict(self):
|
||||
from r2.lib.utils.trial_utils import end_trial
|
||||
|
||||
verdict, koshers, spams = self.verdict()
|
||||
if verdict is None:
|
||||
return # no verdict yet
|
||||
|
||||
if verdict in ("jury timeout", "hung jury"):
|
||||
self.mistrial()
|
||||
else:
|
||||
details=", %d-%d" % (spams, koshers)
|
||||
|
||||
if verdict == "guilty":
|
||||
self.convict(details)
|
||||
elif verdict == "innocent":
|
||||
self.acquit(details)
|
||||
else:
|
||||
raise ValueError("Invalid verdict [%s]" % verdict)
|
||||
|
||||
end_trial(self.defendant, verdict)
|
||||
|
||||
return verdict
|
||||
|
||||
@classmethod
|
||||
@memoize('trial.all_defendants')
|
||||
def all_defendants_cache(cls):
|
||||
fnames = g.hardcache.backend.ids_by_category("trial")
|
||||
return fnames
|
||||
|
||||
@classmethod
|
||||
def all_defendants(cls, quench=False, _update=False):
|
||||
all = cls.all_defendants_cache(_update=_update)
|
||||
|
||||
defs = Thing._by_fullname(all, data=True).values()
|
||||
|
||||
if quench:
|
||||
# Used for the spotlight, to filter out trials with over 20 votes;
|
||||
# otherwise, hung juries would hog the spotlight for an hour as
|
||||
# their vote counts continued to skyrocket
|
||||
|
||||
return filter (lambda d:
|
||||
not g.cache.get("quench_jurors-" + d._fullname),
|
||||
defs)
|
||||
else:
|
||||
return defs
|
||||
|
||||
# sr can be plural
|
||||
@classmethod
|
||||
def defendants_by_sr(cls, sr):
|
||||
all = cls.all_defendants()
|
||||
|
||||
if isinstance(sr, AllSR):
|
||||
return all
|
||||
|
||||
sr = tup(sr)
|
||||
sr_ids = [ s._id for s in sr ]
|
||||
|
||||
return filter (lambda x: x.sr_id in sr_ids, all)
|
||||
@@ -3223,20 +3223,12 @@ form input[type=radio] {margin: 2px .5em 0 0; }
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.entry .buttons li.trial-stamp, .entry .buttons li.reported-stamp {
|
||||
.entry .buttons li.reported-stamp {
|
||||
border: 1px solid black !important;
|
||||
padding: 0 4px;
|
||||
background-color: #f6e69f;
|
||||
}
|
||||
|
||||
.entry .buttons li.trial-stamp .spam {
|
||||
color: #e00;
|
||||
}
|
||||
|
||||
.entry .buttons li.trial-stamp .kosher {
|
||||
color: #090;
|
||||
}
|
||||
|
||||
.suspicious { background-color: #f6e69f }
|
||||
.thing.spam { background-color: #FA8072 }
|
||||
|
||||
@@ -5202,62 +5194,6 @@ a.pretty-button.positive.pressed {
|
||||
background-image: url(../bg-button-positive-pressed.png); /* SPRITE stretch-x */
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
.jury-box .pretty-button a.pressed {
|
||||
margin: -1px 4px -1px 1px;
|
||||
border-width: 2px;
|
||||
border-color: black;
|
||||
color: white;
|
||||
}
|
||||
*/
|
||||
.organic-listing .ontrial, .help-cover.juryduty {
|
||||
background-color: #ffecf8;
|
||||
}
|
||||
|
||||
.organic-listing .ontrial a.title {
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.organic-listing .ontrial .midcol {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.we-need-help {
|
||||
font-size: larger;
|
||||
font-weight: bold;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.organic-listing .how-to-classify {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.jury-box .arrow-msg {
|
||||
display: none;
|
||||
color: red;
|
||||
margin-top:5px;
|
||||
}
|
||||
|
||||
.jury-box .thanks-for-voting {
|
||||
margin-left: 1em;
|
||||
display: none;
|
||||
color: red;
|
||||
}
|
||||
|
||||
.jury-box .thanks-for-voting a {
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
.sitetable .linkontrial {
|
||||
/* border-top: solid 1px #ccc; */
|
||||
padding-top: 5px;
|
||||
}
|
||||
|
||||
.sitetable .we-need-help {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.oatmeal img {
|
||||
display: block;
|
||||
margin: 5px auto;
|
||||
|
||||
@@ -1193,24 +1193,6 @@ function big_mod_action(elem, dir) {
|
||||
return false;
|
||||
}
|
||||
|
||||
function juryvote(elem, dir) {
|
||||
var thing_id = elem.thing_id();
|
||||
|
||||
if (elem.hasClass("pressed")) {
|
||||
dir = 0;
|
||||
}
|
||||
|
||||
elem.toggleClass("pressed");
|
||||
elem.siblings(".pretty-button").removeClass("pressed");
|
||||
|
||||
d = {
|
||||
id: thing_id,
|
||||
dir: dir
|
||||
};
|
||||
$.request("juryvote", d, null, true);
|
||||
elem.siblings(".thanks-for-voting").show();
|
||||
return false;
|
||||
}
|
||||
|
||||
/* The ready method */
|
||||
$(function() {
|
||||
|
||||
@@ -42,9 +42,7 @@
|
||||
|
||||
<%def name="make_link(name, css_class)">
|
||||
<a class="${css_class} ${ c.user_is_loggedin and 'loggedin' or ''} ${thing.clicked and 'click' or ''}"
|
||||
%if not (getattr(thing, "trial_mode", None) and thing.is_self):
|
||||
href="${thing.href_url}"
|
||||
%endif
|
||||
href="${thing.href_url}"
|
||||
%if thing.nofollow:
|
||||
rel="nofollow"
|
||||
%endif
|
||||
@@ -133,8 +131,7 @@
|
||||
%>
|
||||
|
||||
## if we're not on a permalink page we'll render the buttons on top
|
||||
## (unless it's also a jury duty listing)
|
||||
%if not (expand or getattr(thing, "trial_mode", None)):
|
||||
%if not expand:
|
||||
${bottom_buttons()}
|
||||
%endif
|
||||
|
||||
@@ -195,8 +192,6 @@ ${parent.thing_data_attributes(what)} data-ups="${what.upvotes}" data-downs="${w
|
||||
${self.arrow(thing, 1, thing.likes)}
|
||||
%if thing.pref_compress:
|
||||
<div class="score-placeholder"></div>
|
||||
%elif getattr(thing, "trial_mode", None):
|
||||
<div class="score unvoted">•</div>
|
||||
%elif thing.hide_score:
|
||||
<div class="score likes">•</div>
|
||||
<div class="score unvoted">•</div>
|
||||
@@ -242,7 +237,7 @@ ${parent.thing_data_attributes(what)} data-ups="${what.upvotes}" data-downs="${w
|
||||
</%def>
|
||||
|
||||
<%def name="thumbnail()">
|
||||
%if thing.thumbnail and not getattr(thing, "trial_mode", None):
|
||||
%if thing.thumbnail:
|
||||
<%call expr="make_link('thumbnail', 'thumbnail ' + (thing.thumbnail if thing.thumbnail_sprited else ''))">
|
||||
% if not thing.thumbnail_sprited:
|
||||
<%
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
## 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-2012
|
||||
## reddit Inc. All Rights Reserved.
|
||||
###############################################################################
|
||||
|
||||
<%!
|
||||
from r2.lib.pages.things import LinkButtons
|
||||
from r2.lib.pages import WrappedUser
|
||||
%>
|
||||
|
||||
<%inherit file="link.html"/>
|
||||
<%namespace file="printablebuttons.html" import="ynbutton" />
|
||||
<%namespace file="utils.html" import="plain_link" />
|
||||
<%namespace file="utils.html" import="pretty_button" />
|
||||
|
||||
<%def name="buttons(comments=True, delete=True, report=True, additional='')">
|
||||
</%def>
|
||||
|
||||
<%def name="arrow(this, dir, mod)">
|
||||
<div class="arrow ${'up' if dir > 0 else 'down'}"
|
||||
onclick="$(this).thing().find('.arrow-msg').show()">
|
||||
</div>
|
||||
</%def>
|
||||
|
||||
<%def name="entry()">
|
||||
<span class="we-need-help">
|
||||
${_("reddit's spam filter needs your help!")}
|
||||
</span>
|
||||
|
||||
${parent.entry()}
|
||||
|
||||
<div class="jury-box">
|
||||
<span class="how-to-classify">
|
||||
${_("how would you classify this link?")}
|
||||
</span>
|
||||
|
||||
<%
|
||||
pos_class = "positive"
|
||||
neg_class = "negative"
|
||||
|
||||
if not hasattr(thing, "juryvote"):
|
||||
pass
|
||||
elif int(thing.juryvote) == 1:
|
||||
pos_class += " pressed"
|
||||
elif int(thing.juryvote) == -1:
|
||||
neg_class += " pressed"
|
||||
%>
|
||||
|
||||
${pretty_button(_("off-topic / spam"), "juryvote", -1, neg_class)}
|
||||
${pretty_button(_("kosher"), "juryvote", 1, pos_class)}
|
||||
|
||||
<span class="thanks-for-voting">
|
||||
${_("thanks for voting!")}
|
||||
|
||||
<a href="/help/deputies">
|
||||
${_("Click here for more info.").lower()}
|
||||
</a>
|
||||
</span>
|
||||
<div class="arrow-msg">
|
||||
${_("those arrows are only for show; they don't actually do anything.")}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</%def>
|
||||
@@ -132,7 +132,7 @@
|
||||
<tr>
|
||||
<th>${_("link options")}</th>
|
||||
<td class="prefright">
|
||||
%if c.user_is_loggedin and c.user.jury_betatester():
|
||||
%if c.user_is_loggedin:
|
||||
<p>
|
||||
${checkbox(_("show the spotlight box on the front page"), "organic")}
|
||||
 
|
||||
|
||||
@@ -33,11 +33,6 @@
|
||||
${ynbutton(_("delete"), _("deleted"), "del", "hide_thing")}
|
||||
</li>
|
||||
%endif
|
||||
%if thing.show_indict:
|
||||
<li>
|
||||
${ynbutton(_("indict"), _("indicted"), "indict")}
|
||||
</li>
|
||||
%endif
|
||||
%if thing.can_ban:
|
||||
%if not getattr(thing.thing, "use_big_modbuttons", False):
|
||||
%if not thing.show_spam:
|
||||
@@ -240,24 +235,6 @@
|
||||
%endif
|
||||
%endif
|
||||
|
||||
%if getattr(thing.thing, "reveal_trial_info", False):
|
||||
<li class="rounded trial-stamp stamp"
|
||||
title='${_('questionable submission; you should remove it or approve it')}'
|
||||
>
|
||||
${_("deputy opinion:")}
|
||||
 
|
||||
<span class="spam">
|
||||
${_("%d spam") % thing.thing.trial_info.get("spams", "?")}
|
||||
</span>
|
||||
 
|
||||
/
|
||||
 
|
||||
<span class="kosher">
|
||||
${_("%d kosher") % thing.thing.trial_info.get("koshers", "?")}
|
||||
</span>
|
||||
</li>
|
||||
%endif
|
||||
|
||||
%if thing.show_reports and not thing.show_spam:
|
||||
<li class="rounded reported-stamp stamp">
|
||||
${strings.reports % thing.thing.reported}
|
||||
|
||||
Reference in New Issue
Block a user