Modaction display.

This commit is contained in:
bsimpson63
2011-12-05 18:56:28 -08:00
parent 5a03a41c3c
commit 61227124d4
24 changed files with 312 additions and 18 deletions

View File

@@ -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')

View File

@@ -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()

View File

@@ -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"),

View File

@@ -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 = '')]

View File

@@ -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')

View File

@@ -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)

View File

@@ -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):

View File

@@ -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):

View File

@@ -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 */
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 589 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 758 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 781 B

View File

@@ -0,0 +1 @@
reddit_ban.png

View File

@@ -0,0 +1 @@
reddit_flair.png

View File

@@ -0,0 +1 @@
reddit_edit.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 670 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 603 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 715 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 768 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 741 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 649 B

39
r2/r2/templates/modaction.html Executable file
View 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)}&#32;ago</td>
<td class="whitespace:nowrap">${thing.mod}</td>
<td class="button">${thing.button}</td>
<td class="description">${thing.text}&#32;
%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>

View 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:")}&#32;
%if thing.prev:
${plain_link(_("first"), thing.first, _sr_path = (c.site != Sub), rel="nofollow first")}
&#32;|&#32;
${plain_link(_("prev"), thing.prev, _sr_path = (c.site != Sub), rel="nofollow prev")}
%endif
%if thing.prev and thing.next:
&#32;|&#32;
%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