ModAction model and logging through API.

This commit is contained in:
bsimpson63
2011-12-05 18:50:23 -08:00
parent eb74ad4714
commit 5a03a41c3c
3 changed files with 338 additions and 5 deletions

View File

@@ -432,6 +432,8 @@ class ApiController(RedditController):
if container and container.is_moderator(c.user):
container.remove_moderator(c.user)
Subreddit.special_reddits(c.user, "moderator", _update=True)
ModAction.create(container, c.user, 'removemoderator', target=c.user,
details='remove_self')
@noresponse(VUser(),
VModhash(),
@@ -477,7 +479,13 @@ class ApiController(RedditController):
if type in ("friend", "enemy") and container != c.user:
abort(403, 'forbidden')
fn = getattr(container, 'remove_' + type)
fn(victim)
new = fn(victim)
# Log this action
if new and type in ('moderator','contributor','banned'):
action = dict(banned='unbanuser', moderator='removemoderator',
contributor='removecontributor').get(type, None)
ModAction.create(c.site, c.user, action, target=victim)
if type == "friend" and c.user.gold:
c.user.friend_rels_cache(_update=True)
@@ -518,6 +526,12 @@ class ApiController(RedditController):
new = fn(friend)
# Log this action
if new and type in ('moderator','contributor','banned'):
action = dict(banned='banuser', moderator='addmoderator',
contributor='addcontributor').get(type, None)
ModAction.create(c.site, c.user, action, target=friend)
if type == "friend" and c.user.gold:
# Yes, the order of the next two lines is correct.
# First you recalculate the rel_ids, then you find
@@ -1069,6 +1083,9 @@ class ApiController(RedditController):
c.site._commit()
ModAction.create(c.site, c.user, action='editsettings',
details='stylesheet')
form.set_html(".status", _('saved'))
form.set_html(".errors ul", "")
@@ -1114,7 +1131,8 @@ class ApiController(RedditController):
return self.abort(403,'forbidden')
c.site.del_image(name)
c.site._commit()
ModAction.create(c.site, c.user, action='editsettings',
details='del_image', description=name)
@validatedForm(VSrModerator(),
VModhash(),
@@ -1136,6 +1154,9 @@ class ApiController(RedditController):
c.site.header = None
c.site.header_size = None
c.site._commit()
ModAction.create(c.site, c.user, action='editsettings',
details='del_header')
# hide the button which started this
form.find('.delete-img').hide()
# hide the preview box
@@ -1215,6 +1236,14 @@ class ApiController(RedditController):
if add_image_to_sr:
c.site.add_image(name, url = new_url)
c.site._commit()
if header:
ModAction.create(c.site, c.user, action='editsettings',
details='upload_image_header')
else:
ModAction.create(c.site, c.user, action='editsettings',
details='upload_image', description=name)
return UploadedImage(_('saved'), new_url, name,
errors=errors, form_id=form_id).render()
@@ -1313,6 +1342,9 @@ class ApiController(RedditController):
if not sr.domain:
del kw['css_on_cname']
for k, v in kw.iteritems():
if getattr(sr, k, None) != v:
ModAction.create(sr, c.user, action='editsettings',
details=k)
setattr(sr, k, v)
sr._commit()
@@ -1358,6 +1390,16 @@ class ApiController(RedditController):
end_trial(thing, why + "-removed")
admintools.spam(thing, False, not c.user_is_admin, c.user.name)
if isinstance(c.site, FakeSubreddit):
sr = Subreddit._byID(thing.sr_id)
else:
sr = c.site
if isinstance(thing, Link):
ModAction.create(sr, c.user, 'removelink', target=thing)
elif isinstance(thing, Comment):
ModAction.create(sr, c.user, 'removecomment', target=thing)
@noresponse(VUser(), VModhash(),
why = VSrCanBan('id'),
thing = VByName('id'))
@@ -1368,6 +1410,16 @@ class ApiController(RedditController):
end_trial(thing, why + "-approved")
admintools.unspam(thing, c.user.name)
if isinstance(c.site, FakeSubreddit):
sr = Subreddit._byID(thing.sr_id)
else:
sr = c.site
if isinstance(thing, Link):
ModAction.create(sr, c.user, 'approvelink', target=thing)
elif isinstance(thing, Comment):
ModAction.create(sr, c.user, 'approvecomment', target=thing)
@validatedForm(VUser(), VModhash(),
VCanDistinguish(('id', 'how')),
thing = VByName('id'),
@@ -1904,6 +1956,10 @@ class ApiController(RedditController):
setattr(user, 'flair_%s_css_class' % c.site._id, css_class)
user._commit()
if c.user != user:
ModAction.create(c.site, c.user, action='editflair', target=user,
details='flair_edit')
if new:
jquery.redirect('?name=%s' % user.name)
else:
@@ -1929,6 +1985,9 @@ class ApiController(RedditController):
setattr(user, 'flair_%s_css_class' % c.site._id, None)
user._commit()
ModAction.create(c.site, c.user, action='editflair', target=user,
details='flair_delete')
jquery('#flairrow_%s' % user._id36).remove()
unflair = WrappedUser(
user, include_flair_selector=True).render(style='html')
@@ -1992,6 +2051,10 @@ class ApiController(RedditController):
setattr(user, 'flair_%s_text' % c.site._id, text)
setattr(user, 'flair_%s_css_class' % c.site._id, css_class)
user._commit()
ModAction.create(c.site, c.user, action='editflair', target=user,
details='flair_csv')
line_result.status = '%s flair for user %s' % (mode, user.name)
line_result.ok = True
@@ -2013,9 +2076,18 @@ class ApiController(RedditController):
flair_self_assign_enabled = VBoolean("flair_self_assign_enabled"))
def POST_flairconfig(self, form, jquery, flair_enabled, flair_position,
flair_self_assign_enabled):
c.site.flair_enabled = flair_enabled
c.site.flair_position = flair_position
c.site.flair_self_assign_enabled = flair_self_assign_enabled
if c.site.flair_enabled != flair_enabled:
c.site.flair_enabled = flair_enabled
ModAction.create(c.site, c.user, action='editflair',
details='flair_enabled')
if c.site.flair_position != flair_position:
c.site.flair_position = flair_position
ModAction.create(c.site, c.user, action='editflair',
details='flair_position')
if c.site.flair_self_assign_enabled != flair_self_assign_enabled:
c.site.flair_self_assign_enabled = flair_self_assign_enabled
ModAction.create(c.site, c.user, action='editflair',
details='flair_self_enabled')
c.site._commit()
jquery.refresh()
@@ -2081,6 +2153,8 @@ class ApiController(RedditController):
form.set_html('.status', _('saved'))
jquery('input[name="text"]').data('saved', text)
jquery('input[name="css_class"]').data('saved', css_class)
ModAction.create(c.site, c.user, action='editflair',
details='flair_template')
@validatedForm(VFlairManager(),
VModhash(),
@@ -2089,11 +2163,15 @@ class ApiController(RedditController):
idx = FlairTemplateBySubredditIndex.by_sr(c.site._id)
if idx.delete_by_id(flair_template._id):
jquery('#%s' % flair_template._id).parent().remove()
ModAction.create(c.site, c.user, action='editflair',
details='flair_delete_template')
@validatedForm(VFlairManager(), VModhash())
def POST_clearflairtemplates(self, form, jquery):
FlairTemplateBySubredditIndex.clear(c.site._id)
jquery.refresh()
ModAction.create(c.site, c.user, action='editflair',
details='flair_clear_template')
@validate(VUser(),
user = VOptionalExistingUname('name'))
@@ -2137,6 +2215,10 @@ class ApiController(RedditController):
setattr(user, 'flair_%s_css_class' % c.site._id, css_class)
user._commit()
if (c.site.is_moderator(c.user) or c.user_is_admin) and c.user != user:
ModAction.create(c.site, c.user, action='editflair', target=user,
details='flair_edit')
# Push some client-side updates back to the browser.
u = WrappedUser(user, force_show_flair=True,
flair_text_editable=flair_template.text_editable,

View File

@@ -36,3 +36,4 @@ from mail_queue import Email, has_opted_out, opt_count
from gold import *
from admintools import *
from oauth2 import *
from modaction import *

250
r2/r2/models/modaction.py Normal file
View File

@@ -0,0 +1,250 @@
from r2.lib.db import tdb_cassandra
from r2.lib.utils import tup
from r2.models import Account, Subreddit, Link, Printable
from pycassa.system_manager import TIME_UUID_TYPE
from uuid import UUID
from pylons.i18n import _
from pylons import request
class ModAction(tdb_cassandra.UuidThing, Printable):
"""
Columns:
sr_id - Subreddit id36
mod_id - Account id36 of moderator
action - specific name of action, must be in ModAction.actions
target_fullname - optional fullname of the target of the action
details - subcategory available for some actions, must show up in
description - optional user
"""
_use_db = True
_connection_pool = 'main'
_str_props = ('sr_id36', 'mod_id36', 'target_fullname', 'action', 'details',
'description')
_defaults = {}
actions = ('banuser', 'unbanuser', 'removelink', 'approvelink',
'removecomment', 'approvecomment', 'addmoderator',
'removemoderator', 'addcontributor', 'removecontributor',
'editsettings', 'editflair')
_menu = {'banuser': _('ban user'),
'unbanuser': _('unban user'),
'removelink': _('remove post'),
'approvelink': _('approve post'),
'removecomment': _('remove comment'),
'approvecomment': _('approve comment'),
'addmoderator': _('add moderator'),
'removemoderator': _('remove moderator'),
'addcontributor': _('add contributor'),
'removecontributor': _('remove contributor'),
'editsettings': _('edit settings'),
'editflair': _('edit user flair')}
_text = {'banuser': _('banned'),
'unbanuser': _('unbanned'),
'removelink': _('removed post'),
'approvelink': _('approved post'),
'removecomment': _('removed comment'),
'approvecomment': _('approved comment'),
'addmoderator': _('added moderator'),
'removemoderator': _('removed moderator'),
'addcontributor': _('added approved contributor'),
'removecontributor': _('removed approved contributor'),
'editsettings': _('edited settings'),
'editflair': _('edited user flair')}
_details_text = {# removemoderator
'remove_self': _('removed self'),
# editsettings
'title': _('title'),
'description': _('description'),
'lang': _('language'),
'type': _('type'),
'link_type': _('link type'),
'over_18': _('toggle viewers must be over 18'),
'allow_top': _('toggle allow in default set'),
'show_media': _('toggle show thumbnail images of content'),
'domain': _('domain'),
'show_cname_sidebar': _('toggle show sidebar from cname'),
'css_on_cname': _('toggle custom CSS from cname'),
'header_title': _('header title'),
'stylesheet': _('stylesheet'),
'del_header': _('delete header image'),
'del_image': _('delete image'),
'upload_image_header': _('upload header image'),
'upload_image': _('upload image'),
# editflair
'flair_edit': _('add/edit flair'),
'flair_delete': _('delete flair'),
'flair_csv': _('edit by csv'),
'flair_enabled': _('toggle flair enabled'),
'flair_position': _('toggle flair position'),
'flair_self_enabled': _('toggle user assigned flair enabled'),
'flair_template': _('add/edit flair templates'),
'flair_delete_template': _('delete flair template'),
'flair_clear_template': _('clear flair templates')}
# This stuff won't change
cache_ignore = set(['subreddit']).union(Printable.cache_ignore)
# Thing properties for Printable
@property
def author_id(self):
return int(self.mod_id36, 36)
@property
def sr_id(self):
return int(self.sr_id36, 36)
@property
def _ups(self):
return 0
@property
def _downs(self):
return 0
@property
def _deleted(self):
return False
@property
def _spam(self):
return False
@property
def reported(self):
return False
@classmethod
def create(cls, sr, mod, action, details=None, target=None, description=None):
# Split this off into separate function to check for valid actions?
if not action in cls.actions:
raise ValueError("Invalid ModAction: %s" % action)
kw = dict(sr_id36=sr._id36, mod_id36=mod._id36, action=action)
if target:
kw['target_fullname'] = target._fullname
if details:
kw['details'] = details
if description:
kw['description'] = description
ma = cls(**kw)
ma._commit()
return ma
def _on_create(self):
"""
Update all Views.
"""
views = (ModActionBySR, ModActionBySRMod, ModActionBySRAction)
for v in views:
v.add_object(self)
@classmethod
def get_actions(cls, sr, mod=None, action=None, after=None, reverse=False, count=1000):
"""
Get a ColumnQuery that yields ModAction objects according to
specified criteria.
"""
if after and isinstance(after, basestring):
after = cls._byID(UUID(after))
elif after and isinstance(after, UUID):
after = cls._byID(after)
if not isinstance(after, cls):
after = None
if not mod and not action:
rowkey = sr._id36
q = ModActionBySR.query(rowkey, after=after, reverse=reverse, count=count)
elif mod and not action:
rowkey = '%s_%s' % (sr._id36, mod._id36)
q = ModActionBySRMod.query(rowkey, after=after, reverse=reverse, count=count)
elif not mod and action:
rowkey = '%s_%s' % (sr._id36, action)
q = ModActionBySRAction.query(rowkey, after=after, reverse=reverse, count=count)
else:
raise NotImplementedError("Can't query by both mod and action")
return q
def get_extra_text(self):
text = ''
if hasattr(self, 'details') and not self.details == None:
text += self._details_text.get(self.details, self.details)
if hasattr(self, 'description') and not self.description == None:
text += ' %s' % self.description
return text
@classmethod
def add_props(cls, user, wrapped):
from r2.lib.menus import NavButton
from r2.lib.db.thing import Thing
from r2.lib.pages import WrappedUser, SimpleLinkDisplay
request_path = request.path
target_fullnames = [item.target_fullname for item in wrapped if hasattr(item, 'target_fullname')]
targets = Thing._by_fullname(target_fullnames)
for item in wrapped:
# Can I move these buttons somewhere else? Does it make sense to do so?
css_class = 'modactions %s' % item.action
item.button = NavButton('', item.action, opt='type', css_class=css_class)
item.button.build(base_path=request_path)
mod_name = item.author.name
item.mod = NavButton(mod_name, mod_name, opt='mod')
item.mod.build(base_path=request_path)
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:
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)
Printable.add_props(user, wrapped)
class ModActionBySR(tdb_cassandra.View):
_use_db = True
_connection_pool = 'main'
_compare_with = TIME_UUID_TYPE
_view_of = ModAction
_ttl = 60*60*24*30*3 # 3 month ttl
@classmethod
def _rowkey(cls, ma):
return ma.sr_id36
class ModActionBySRMod(tdb_cassandra.View):
_use_db = True
_connection_pool = 'main'
_compare_with = TIME_UUID_TYPE
_view_of = ModAction
_ttl = 60*60*24*30*3 # 3 month ttl
@classmethod
def _rowkey(cls, ma):
return '%s_%s' % (ma.sr_id36, ma.mod_id36)
class ModActionBySRAction(tdb_cassandra.View):
_use_db = True
_connection_pool = 'main'
_compare_with = TIME_UUID_TYPE
_view_of = ModAction
_ttl = 60*60*24*30*3 # 3 month ttl
@classmethod
def _rowkey(cls, ma):
return '%s_%s' % (ma.sr_id36, ma.action)