forked from reddit-archive/reddit
/
modaction.py
305 lines (260 loc) · 11.7 KB
/
modaction.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
from r2.lib.db import tdb_cassandra
from r2.lib.utils import tup
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 _
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 = {# approve comment/link
'unspam': _('unspam'),
# remove comment/link
'confirm_spam': _('confirmed spam'),
# 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', 'target']).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, srs, 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
srs = tup(srs)
if not mod and not action:
rowkeys = [sr._id36 for sr in srs]
q = ModActionBySR.query(rowkeys, after=after, reverse=reverse, count=count)
elif mod and not action:
rowkeys = ['%s_%s' % (sr._id36, mod._id36) for sr in srs]
q = ModActionBySRMod.query(rowkeys, after=after, reverse=reverse, count=count)
elif not mod and action:
rowkeys = ['%s_%s' % (sr._id36, action) for sr in srs]
q = ModActionBySRAction.query(rowkeys, 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
@staticmethod
def get_rgb(i, fade=0.8):
r = int(256 - (hash(str(i)) % 256)*(1-fade))
g = int(256 - (hash(str(i) + ' ') % 256)*(1-fade))
b = int(256 - (hash(str(i) + ' ') % 256)*(1-fade))
return (r, g, b)
@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
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, 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([item.sr_id for item in wrapped], data=True)
# 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[link.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? 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)
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()
if hasattr(item, 'target_fullname') and item.target_fullname:
target = targets[item.target_fullname]
if isinstance(target, Account):
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]
item.bgcolor = ModAction.get_rgb(item.sr_id)
item.sr_name = subreddits[item.sr_id].name
item.sr_path = subreddits[item.sr_id].path
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)