Modaction display.
@@ -50,6 +50,7 @@ def make_map(global_conf={}, app_conf={}):
|
||||
mc('/account-activity', controller='front', action='account_activity')
|
||||
|
||||
mc('/about/message/:where', controller='message', action='listing')
|
||||
mc('/about/log', controller='front', action='moderationlog')
|
||||
mc('/about/:location', controller='front',
|
||||
action='editreddit', location = 'about')
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
################################################################################
|
||||
from validator import *
|
||||
from pylons.i18n import _, ungettext
|
||||
from reddit_base import RedditController, base_listing
|
||||
from reddit_base import RedditController, base_listing, base_cassandra_listing
|
||||
from r2 import config
|
||||
from r2.models import *
|
||||
from r2.lib.pages import *
|
||||
@@ -362,6 +362,59 @@ class FrontController(RedditController):
|
||||
else:
|
||||
return self.abort404()
|
||||
|
||||
def _make_moderationlog(self, num, after, reverse, count, mod=None, action=None):
|
||||
|
||||
if mod and action:
|
||||
query = c.site.get_modactions(mod=mod, action=None)
|
||||
|
||||
def keep_fn(ma):
|
||||
return ma.action == action
|
||||
else:
|
||||
query = c.site.get_modactions(mod=mod, action=action)
|
||||
|
||||
def keep_fn(ma):
|
||||
return True
|
||||
|
||||
builder = QueryBuilder(query, skip=True, num=num, after=after,
|
||||
keep_fn=keep_fn, count=count,
|
||||
reverse=reverse,
|
||||
wrap=default_thing_wrapper())
|
||||
listing = ModActionListing(builder)
|
||||
pane = listing.listing()
|
||||
return pane
|
||||
|
||||
@base_cassandra_listing
|
||||
@validate(mod=VAccountByName('mod'),
|
||||
action=VOneOf('type', ModAction.actions))
|
||||
def GET_moderationlog(self, num, after, reverse, count, mod, action):
|
||||
|
||||
is_moderator = c.user_is_loggedin and c.site.is_moderator(c.user) or c.user_is_admin
|
||||
|
||||
if not is_moderator:
|
||||
return self.abort404()
|
||||
|
||||
panes = PaneStack()
|
||||
pane = self._make_moderationlog(num, after, reverse, count,
|
||||
mod=mod, action=action)
|
||||
panes.append(pane)
|
||||
|
||||
action_buttons = [NavButton(_('all'), None, opt='type', css_class='primary')]
|
||||
for a in ModAction.actions:
|
||||
action_buttons.append(NavButton(ModAction._menu[a], a, opt='type'))
|
||||
|
||||
mod_ids = c.site.moderators
|
||||
mods = Account._byID(mod_ids)
|
||||
mod_buttons = [NavButton(_('all'), None, opt='mod', css_class='primary')]
|
||||
for mod_id in mod_ids:
|
||||
mod = mods[mod_id]
|
||||
mod_buttons.append(NavButton(mod.name, mod.name, opt='mod'))
|
||||
base_path = request.path
|
||||
menus = [NavMenu(action_buttons, base_path=base_path,
|
||||
title=_('filter by action'), type='lightdrop', css_class='modaction-drop'),
|
||||
NavMenu(mod_buttons, base_path=base_path,
|
||||
title=_('filter by moderator'), type='lightdrop')]
|
||||
return EditReddit(content=panes, nav_menus=menus, extension_handling=False).render()
|
||||
|
||||
def _make_spamlisting(self, location, num, after, reverse, count):
|
||||
if location == 'reports':
|
||||
query = c.site.get_reported()
|
||||
|
||||
@@ -131,6 +131,7 @@ menu = MenuHandler(hot = _('hot'),
|
||||
banned = _("ban users"),
|
||||
banusers = _("ban users"),
|
||||
flair = _("edit user flair"),
|
||||
log = _("moderation log"),
|
||||
|
||||
popular = _("popular"),
|
||||
create = _("create"),
|
||||
|
||||
@@ -26,6 +26,8 @@ from r2.models import Friends, All, Sub, NotFound, DomainSR, Random, Mod, Random
|
||||
from r2.models import Link, Printable, Trophy, bidding, PromotionWeights, Comment
|
||||
from r2.models import Flair, FlairTemplate, FlairTemplateBySubredditIndex
|
||||
from r2.models.oauth2 import OAuth2Client
|
||||
from r2.models import ModAction
|
||||
from r2.models import Thing
|
||||
from r2.config import cache
|
||||
from r2.lib.tracking import AdframeInfo
|
||||
from r2.lib.jsonresponse import json_respond
|
||||
@@ -203,6 +205,7 @@ class Reddit(Templated):
|
||||
NamedButton('spam', css_class = 'reddit-spam'),
|
||||
NamedButton('banned', css_class = 'reddit-ban'),
|
||||
NamedButton('flair', css_class = 'reddit-flair'),
|
||||
NamedButton('log', css_class = 'reddit-moderationlog'),
|
||||
])
|
||||
return [NavMenu(buttons, type = "flat_vert", base_path = "/about/",
|
||||
css_class = "icon-menu", separator = '')]
|
||||
@@ -517,6 +520,7 @@ class SubredditInfoBar(CachedTemplate):
|
||||
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 = '')]
|
||||
|
||||
@@ -45,7 +45,7 @@ EXTRA_FACTOR = 1.5
|
||||
MAX_RECURSION = 10
|
||||
|
||||
class Builder(object):
|
||||
def __init__(self, wrap = Wrapped, keep_fn = None, stale = True):
|
||||
def __init__(self, wrap=Wrapped, keep_fn=None, stale=True):
|
||||
self.stale = stale
|
||||
self.wrap = wrap
|
||||
self.keep_fn = keep_fn
|
||||
@@ -272,9 +272,8 @@ class Builder(object):
|
||||
return True
|
||||
|
||||
class QueryBuilder(Builder):
|
||||
def __init__(self, query, wrap = Wrapped, keep_fn = None,
|
||||
skip = False, **kw):
|
||||
Builder.__init__(self, wrap, keep_fn)
|
||||
def __init__(self, query, wrap=Wrapped, keep_fn=None, skip=False, **kw):
|
||||
Builder.__init__(self, wrap=wrap, keep_fn=keep_fn)
|
||||
self.query = query
|
||||
self.skip = skip
|
||||
self.num = kw.get('num')
|
||||
|
||||
@@ -89,6 +89,10 @@ class Listing(object):
|
||||
def __iter__(self):
|
||||
return iter(self.things)
|
||||
|
||||
class TableListing(Listing): pass
|
||||
|
||||
class ModActionListing(TableListing): pass
|
||||
|
||||
class LinkListing(Listing):
|
||||
def __init__(self, *a, **kw):
|
||||
Listing.__init__(self, *a, **kw)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from r2.lib.db import tdb_cassandra
|
||||
from r2.lib.utils import tup
|
||||
from r2.models import Account, Subreddit, Link, Printable
|
||||
from r2.models import Account, Subreddit, Link, Comment, Printable
|
||||
from pycassa.system_manager import TIME_UUID_TYPE
|
||||
from uuid import UUID
|
||||
from pylons.i18n import _
|
||||
@@ -86,7 +86,7 @@ class ModAction(tdb_cassandra.UuidThing, Printable):
|
||||
'flair_clear_template': _('clear flair templates')}
|
||||
|
||||
# This stuff won't change
|
||||
cache_ignore = set(['subreddit']).union(Printable.cache_ignore)
|
||||
cache_ignore = set(['subreddit', 'target']).union(Printable.cache_ignore)
|
||||
|
||||
# Thing properties for Printable
|
||||
@property
|
||||
@@ -187,15 +187,54 @@ class ModAction(tdb_cassandra.UuidThing, Printable):
|
||||
|
||||
from r2.lib.menus import NavButton
|
||||
from r2.lib.db.thing import Thing
|
||||
from r2.lib.pages import WrappedUser, SimpleLinkDisplay
|
||||
from r2.lib.pages import WrappedUser
|
||||
from r2.lib.filters import _force_unicode
|
||||
|
||||
TITLE_MAX_WIDTH = 50
|
||||
|
||||
request_path = request.path
|
||||
|
||||
target_fullnames = [item.target_fullname for item in wrapped if hasattr(item, 'target_fullname')]
|
||||
targets = Thing._by_fullname(target_fullnames)
|
||||
targets = Thing._by_fullname(target_fullnames, data=True)
|
||||
authors = Account._byID([t.author_id for t in targets.values() if hasattr(t, 'author_id')], data=True)
|
||||
links = Link._byID([t.link_id for t in targets.values() if hasattr(t, 'link_id')], data=True)
|
||||
subreddits = Subreddit._byID([t.sr_id for t in targets.values() if hasattr(t, 'sr_id')])
|
||||
|
||||
# Assemble target links
|
||||
target_links = {}
|
||||
target_accounts = {}
|
||||
for fullname, target in targets.iteritems():
|
||||
if isinstance(target, Link):
|
||||
author = authors[target.author_id]
|
||||
title = _force_unicode(target.title)
|
||||
if len(title) > TITLE_MAX_WIDTH:
|
||||
short_title = title[:TITLE_MAX_WIDTH] + '...'
|
||||
else:
|
||||
short_title = title
|
||||
text = '"%(title)s" %(by)s %(author)s' % {'title': short_title,
|
||||
'by': _('by'),
|
||||
'author': author.name}
|
||||
path = target.make_permalink(subreddits[target.sr_id])
|
||||
target_links[fullname] = (text, path, title)
|
||||
elif isinstance(target, Comment):
|
||||
author = authors[target.author_id]
|
||||
link = links[target.link_id]
|
||||
title = _force_unicode(link.title)
|
||||
if len(title) > TITLE_MAX_WIDTH:
|
||||
short_title = title[:TITLE_MAX_WIDTH] + '...'
|
||||
else:
|
||||
short_title = title
|
||||
text = '%(by)s %(author)s %(on)s "%(title)s"' % {'by': _('by'),
|
||||
'author': author.name,
|
||||
'on': _('on'),
|
||||
'title': short_title}
|
||||
path = target.make_permalink(link, subreddits[target.sr_id])
|
||||
target_links[fullname] = (text, path, title)
|
||||
elif isinstance(target, Account):
|
||||
target_accounts[fullname] = WrappedUser(target)
|
||||
|
||||
for item in wrapped:
|
||||
# Can I move these buttons somewhere else? Does it make sense to do so?
|
||||
# Can I move these buttons somewhere else? Not great to have request stuff in here
|
||||
css_class = 'modactions %s' % item.action
|
||||
item.button = NavButton('', item.action, opt='type', css_class=css_class)
|
||||
item.button.build(base_path=request_path)
|
||||
@@ -206,14 +245,13 @@ class ModAction(tdb_cassandra.UuidThing, Printable):
|
||||
item.text = ModAction._text.get(item.action, '')
|
||||
item.details = item.get_extra_text()
|
||||
|
||||
# Can extend default_thing_wrapper to also lookup the targets
|
||||
if hasattr(item, 'target_fullname') and not item.target_fullname == None:
|
||||
if hasattr(item, 'target_fullname') and item.target_fullname:
|
||||
target = targets[item.target_fullname]
|
||||
if isinstance(target, Account):
|
||||
item.target = WrappedUser(target)
|
||||
elif isinstance(target, Comment) or isinstance(target, Link):
|
||||
item.target = SimpleLinkDisplay(target)
|
||||
|
||||
item.target_wrapped_user = target_accounts[item.target_fullname]
|
||||
elif isinstance(target, Link) or isinstance(target, Comment):
|
||||
item.target_text, item.target_path, item.target_title = target_links[item.target_fullname]
|
||||
|
||||
Printable.add_props(user, wrapped)
|
||||
|
||||
class ModActionBySR(tdb_cassandra.View):
|
||||
|
||||
@@ -38,7 +38,6 @@ from r2.lib.filters import _force_unicode
|
||||
from r2.lib.db import tdb_cassandra
|
||||
from r2.lib.cache import CL_ONE
|
||||
|
||||
|
||||
import os.path
|
||||
import random
|
||||
|
||||
@@ -333,6 +332,10 @@ class Subreddit(Thing, Printable):
|
||||
from r2.lib.db import queries
|
||||
return queries.get_sr_comments(self)
|
||||
|
||||
def get_modactions(self, mod=None, action=None):
|
||||
# Get a query that will yield ModAction objects with mod and action
|
||||
from r2.models import ModAction
|
||||
return ModAction.get_actions(self, mod=mod, action=action)
|
||||
|
||||
@classmethod
|
||||
def add_props(cls, user, wrapped):
|
||||
|
||||
@@ -146,7 +146,7 @@ label.disabled { color: gray; }
|
||||
|
||||
.hover a:hover { text-decoration: underline }
|
||||
|
||||
.selected { font-weight: bold; }
|
||||
.selected, .choice.primary { font-weight: bold; }
|
||||
|
||||
.flat-list {list-style-type: none; display: inline;}
|
||||
.flat-list li, .flat-list form {display: inline; white-space: nowrap; }
|
||||
@@ -4250,6 +4250,7 @@ dd { margin-left: 20px; }
|
||||
.icon-menu .reddit-spam:before,
|
||||
.icon-menu .reddit-ban:before,
|
||||
.icon-menu .reddit-flair:before,
|
||||
.icon-menu .reddit-moderationlog:before,
|
||||
.icon-menu .reddit-moderators:before,
|
||||
.icon-menu .moderator-mail:before,
|
||||
.icon-menu .reddit-contributors:before,
|
||||
@@ -4304,6 +4305,10 @@ dd { margin-left: 20px; }
|
||||
/* Work around a centering difference between this icon and reddit_ban.png */
|
||||
margin-left: 1px;
|
||||
}
|
||||
.icon-menu .reddit-moderationlog:before {
|
||||
background-image: url(../reddit_moderationlog.png); /* SPRITE */
|
||||
margin-left: 1px;
|
||||
}
|
||||
.icon-menu .reddit-moderators:before {
|
||||
background-image: url(../shield.png); /* SPRITE */
|
||||
}
|
||||
@@ -4917,3 +4922,83 @@ tr.gold-accent + tr > td {
|
||||
background: #e4e4e4;
|
||||
box-shadow: inset 0px -1px 0px white;
|
||||
}
|
||||
|
||||
.modactionlisting table {
|
||||
margin: 0 15px;
|
||||
}
|
||||
|
||||
.modactionlisting td.timestamp {
|
||||
white-space: nowrap;
|
||||
padding-left: 0;
|
||||
padding-right: 1.5em;
|
||||
}
|
||||
|
||||
.modactionlisting td.button {
|
||||
padding-right: 0;
|
||||
padding-left: 1.5em;
|
||||
}
|
||||
|
||||
.modactionlisting td.description em {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.modactions td {
|
||||
font-size: small;
|
||||
text-align: left;
|
||||
padding: 2px;
|
||||
}
|
||||
.modactions.banuser,
|
||||
.modactions.unbanuser,
|
||||
.modactions.removelink,
|
||||
.modactions.approvelink,
|
||||
.modactions.removecomment,
|
||||
.modactions.approvecomment,
|
||||
.modactions.addmoderator,
|
||||
.modactions.removemoderator,
|
||||
.modactions.addcontributor,
|
||||
.modactions.removecontributor,
|
||||
.modactions.editsettings,
|
||||
.modactions.editflair {
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
display: block;
|
||||
content: " ";
|
||||
float: left;
|
||||
margin-right: 5px;
|
||||
}
|
||||
.modactions.banuser {
|
||||
background-image: url(../modactions_banuser.png); /* SPRITE */
|
||||
}
|
||||
.modactions.unbanuser {
|
||||
background-image: url(../modactions_unbanuser.png); /* SPRITE */
|
||||
}
|
||||
.modactions.removelink {
|
||||
background-image: url(../modactions_removelink.png); /* SPRITE */
|
||||
}
|
||||
.modactions.approvelink {
|
||||
background-image: url(../modactions_approvelink.png); /* SPRITE */
|
||||
}
|
||||
.modactions.removecomment {
|
||||
background-image: url(../modactions_removecomment.png); /* SPRITE */
|
||||
}
|
||||
.modactions.approvecomment {
|
||||
background-image: url(../modactions_approvecomment.png); /* SPRITE */
|
||||
}
|
||||
.modactions.addmoderator {
|
||||
background-image: url(../modactions_addmoderator.png); /* SPRITE */
|
||||
}
|
||||
.modactions.removemoderator {
|
||||
background-image: url(../modactions_removemoderator.png); /* SPRITE */
|
||||
}
|
||||
.modactions.addcontributor {
|
||||
background-image: url(../modactions_addcontributor.png); /* SPRITE */
|
||||
}
|
||||
.modactions.removecontributor {
|
||||
background-image: url(../modactions_removecontributor.png); /* SPRITE */
|
||||
}
|
||||
.modactions.editsettings {
|
||||
background-image: url(../modactions_editsettings.png); /* SPRITE */
|
||||
}
|
||||
.modactions.editflair {
|
||||
background-image: url(../modactions_editflair.png); /* SPRITE */
|
||||
}
|
||||
|
||||
BIN
r2/r2/public/static/modactions_addcontributor.png
Executable file
|
After Width: | Height: | Size: 589 B |
BIN
r2/r2/public/static/modactions_addmoderator.png
Executable file
|
After Width: | Height: | Size: 758 B |
BIN
r2/r2/public/static/modactions_approvecomment.png
Executable file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
r2/r2/public/static/modactions_approvelink.png
Executable file
|
After Width: | Height: | Size: 781 B |
1
r2/r2/public/static/modactions_banuser.png
Symbolic link
@@ -0,0 +1 @@
|
||||
reddit_ban.png
|
||||
1
r2/r2/public/static/modactions_editflair.png
Symbolic link
@@ -0,0 +1 @@
|
||||
reddit_flair.png
|
||||
1
r2/r2/public/static/modactions_editsettings.png
Symbolic link
@@ -0,0 +1 @@
|
||||
reddit_edit.png
|
||||
BIN
r2/r2/public/static/modactions_removecomment.png
Executable file
|
After Width: | Height: | Size: 670 B |
BIN
r2/r2/public/static/modactions_removecontributor.png
Executable file
|
After Width: | Height: | Size: 603 B |
BIN
r2/r2/public/static/modactions_removelink.png
Executable file
|
After Width: | Height: | Size: 715 B |
BIN
r2/r2/public/static/modactions_removemoderator.png
Executable file
|
After Width: | Height: | Size: 768 B |
BIN
r2/r2/public/static/modactions_unbanuser.png
Executable file
|
After Width: | Height: | Size: 741 B |
BIN
r2/r2/public/static/reddit_moderationlog.png
Executable file
|
After Width: | Height: | Size: 649 B |
39
r2/r2/templates/modaction.html
Executable file
@@ -0,0 +1,39 @@
|
||||
## 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 CondeNet, Inc.
|
||||
##
|
||||
## All portions of the code written by CondeNet are Copyright (c) 2006-2010
|
||||
## CondeNet, Inc. All Rights Reserved.
|
||||
################################################################################
|
||||
|
||||
<%namespace file="utils.html" import="timestamp, plain_link"/>
|
||||
|
||||
<tr class="modactions">
|
||||
<td class="timestamp whitespace:nowrap">${timestamp(thing.date)} ago</td>
|
||||
<td class="whitespace:nowrap">${thing.mod}</td>
|
||||
<td class="button">${thing.button}</td>
|
||||
<td class="description">${thing.text} 
|
||||
%if hasattr(thing, 'target_text'):
|
||||
${plain_link(thing.target_text, thing.target_path, title=thing.target_title, sr_path=False, cname=False, _class="subreddit hover")}
|
||||
%elif hasattr(thing, 'target_wrapped_user'):
|
||||
${thing.target_wrapped_user}
|
||||
%endif
|
||||
%if hasattr(thing, 'details') and thing.details:
|
||||
<em>(${thing.details})</em>
|
||||
%endif
|
||||
</td>
|
||||
</tr>
|
||||
64
r2/r2/templates/tablelisting.html
Executable file
@@ -0,0 +1,64 @@
|
||||
## 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 CondeNet, Inc.
|
||||
##
|
||||
## All portions of the code written by CondeNet are Copyright (c) 2006-2010
|
||||
## CondeNet, Inc. All Rights Reserved.
|
||||
################################################################################
|
||||
<%!
|
||||
from r2.models import Sub
|
||||
%>
|
||||
<%namespace file="utils.html" import="plain_link" />
|
||||
|
||||
<%
|
||||
_id = ("_%s" % thing.parent_name) if hasattr(thing, 'parent_name') else ''
|
||||
cls = thing.lookups[0].__class__.__name__.lower()
|
||||
%>
|
||||
|
||||
<style type="text/css">
|
||||
.generic-table {
|
||||
font-size: small;
|
||||
color: black;
|
||||
margin-left: 5px;}
|
||||
</style>
|
||||
|
||||
<div id="siteTable${_id}" class="sitetable ${cls}">
|
||||
<table class="generic-table">
|
||||
%for a in thing.things:
|
||||
${a}
|
||||
%endfor
|
||||
</table>
|
||||
</div>
|
||||
|
||||
%if thing.nextprev and (thing.prev or thing.next):
|
||||
<p class="nextprev"> ${_("view more:")} 
|
||||
%if thing.prev:
|
||||
${plain_link(_("first"), thing.first, _sr_path = (c.site != Sub), rel="nofollow first")}
|
||||
 | 
|
||||
${plain_link(_("prev"), thing.prev, _sr_path = (c.site != Sub), rel="nofollow prev")}
|
||||
%endif
|
||||
%if thing.prev and thing.next:
|
||||
 | 
|
||||
%endif
|
||||
%if thing.next:
|
||||
${plain_link(_("next"), thing.next, _sr_path = (c.site != Sub), rel="nofollow next")}
|
||||
%endif
|
||||
</p>
|
||||
%endif
|
||||
%if not thing.things:
|
||||
<p id="noresults" class="error">${_("there doesn't seem to be anything here")}</p>
|
||||
%endif
|
||||