mirror of
https://github.com/reddit-archive/reddit.git
synced 2026-01-31 01:38:08 -05:00
ModAction model and logging through API.
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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
250
r2/r2/models/modaction.py
Normal 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)
|
||||
Reference in New Issue
Block a user