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
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
from django.utils.translation import ugettext as _
from django.utils.encoding import smart_unicode
from utils import PickledObjectField
from threading import Thread
from forum.utils import html
from base import *
import re
class ActionQuerySet(CachedQuerySet):
def obj_from_datadict(self, datadict):
cls = ActionProxyMetaClass.types.get(datadict['action_type'], None)
if cls:
obj = cls()
obj.__dict__.update(datadict)
return obj
else:
return super(ActionQuerySet, self).obj_from_datadict(datadict)
def get(self, *args, **kwargs):
action = super(ActionQuerySet, self).get(*args, **kwargs).leaf
if not isinstance(action, self.model):
raise self.model.DoesNotExist()
return action
class ActionManager(CachedManager):
use_for_related_fields = True
def get_query_set(self):
qs = ActionQuerySet(self.model)
if self.model is not Action:
return qs.filter(action_type=self.model.get_type())
else:
return qs
def get_for_types(self, types, *args, **kwargs):
kwargs['action_type__in'] = [t.get_type() for t in types]
return self.get(*args, **kwargs)
class Action(BaseModel):
user = models.ForeignKey('User', related_name="actions")
real_user = models.ForeignKey('User', related_name="proxied_actions", null=True)
ip = models.CharField(max_length=39)
node = models.ForeignKey('Node', null=True, related_name="actions")
action_type = models.CharField(max_length=32)
action_date = models.DateTimeField(default=datetime.datetime.now)
extra = PickledObjectField()
canceled = models.BooleanField(default=False)
canceled_by = models.ForeignKey('User', null=True, related_name="canceled_actions")
canceled_at = models.DateTimeField(null=True)
canceled_ip = models.CharField(max_length=39)
hooks = {}
objects = ActionManager()
@property
def at(self):
return self.action_date
@property
def by(self):
return self.user
def repute_users(self):
pass
def process_data(self, **data):
pass
def process_action(self):
pass
def cancel_action(self):
pass
@property
def verb(self):
return ""
def describe(self, viewer=None):
return self.__class__.__name__
def get_absolute_url(self):
if self.node:
return self.node.get_absolute_url()
else:
return self.user.get_profile_url()
def repute(self, user, value):
repute = ActionRepute(action=self, user=user, value=value)
repute.save()
return repute
def cancel_reputes(self):
for repute in self.reputes.all():
cancel = ActionRepute(action=self, user=repute.user, value=(-repute.value), by_canceled=True)
cancel.save()
@property
def leaf(self):
leaf_cls = ActionProxyMetaClass.types.get(self.action_type, None)
if leaf_cls is None:
return self
leaf = leaf_cls()
d = self._as_dict()
leaf.__dict__.update(self._as_dict())
l = leaf._as_dict()
return leaf
@classmethod
def get_type(cls):
return re.sub(r'action$', '', cls.__name__.lower())
def save(self, data=None, threaded=True, *args, **kwargs):
isnew = False
if not self.id:
self.action_type = self.__class__.get_type()
isnew = True
if data:
self.process_data(**data)
super(Action, self).save(*args, **kwargs)
if isnew:
if (self.node is None) or (not self.node.nis.wiki):
self.repute_users()
self.process_action()
self.trigger_hooks(threaded, True)
return self
def delete(self, *args, **kwargs):
self.cancel_action()
super(Action, self).delete(*args, **kwargs)
def cancel(self, user=None, ip=None):
if not self.canceled:
self.canceled = True
self.canceled_at = datetime.datetime.now()
self.canceled_by = (user is None) and self.user or user
if ip:
self.canceled_ip = ip
self.save()
self.cancel_reputes()
self.cancel_action()
#self.trigger_hooks(False)
@classmethod
def get_current(cls, **kwargs):
kwargs['canceled'] = False
try:
return cls.objects.get(**kwargs)
except cls.MultipleObjectsReturned:
logging.error("Got multiple values for action %s with args %s", cls.__name__,
", ".join(["%s='%s'" % i for i in kwargs.items()]))
raise
except cls.DoesNotExist:
return None
@classmethod
def hook(cls, fn):
if not Action.hooks.get(cls, None):
Action.hooks[cls] = []
Action.hooks[cls].append(fn)
def trigger_hooks(self, threaded, new=True):
if threaded:
thread = Thread(target=trigger_hooks, args=[self, Action.hooks, new])
thread.setDaemon(True)
thread.start()
else:
trigger_hooks(self, Action.hooks, new)
class Meta:
app_label = 'forum'
def trigger_hooks(action, hooks, new):
for cls, hooklist in hooks.items():
if isinstance(action, cls):
for hook in hooklist:
try:
hook(action=action, new=new)
except Exception, e:
import traceback
logging.error("Error in %s hook: %s" % (cls.__name__, str(e)))
logging.error(traceback.format_exc())
class ActionProxyMetaClass(BaseMetaClass):
types = {}
def __new__(cls, *args, **kwargs):
new_cls = super(ActionProxyMetaClass, cls).__new__(cls, *args, **kwargs)
cls.types[new_cls.get_type()] = new_cls
class Meta:
proxy = True
new_cls.Meta = Meta
return new_cls
class ActionProxy(Action):
__metaclass__ = ActionProxyMetaClass
def friendly_username(self, viewer, user):
return (viewer == user) and _('You') or smart_unicode(user.username)
def friendly_ownername(self, owner, user):
return (owner == user) and _('your') or smart_unicode(user.username)
def viewer_or_user_verb(self, viewer, user, viewer_verb, user_verb):
return (viewer == user) and viewer_verb or user_verb
def hyperlink(self, url, title, **attrs):
return html.hyperlink(url, title, **attrs)
def describe_node(self, viewer, node):
node_link = self.hyperlink(node.get_absolute_url(), node.headline)
if node.parent:
node_desc = _("on %(link)s") % {'link': node_link}
else:
node_desc = node_link
return _("%(user)s %(node_name)s %(node_desc)s") % {
'user': self.hyperlink(node.author.get_profile_url(), self.friendly_ownername(viewer, node.author)),
'node_name': node.friendly_name,
'node_desc': node_desc,
}
def affected_links(self, viewer):
return ", ".join([self.hyperlink(u.get_profile_url(), self.friendly_username(viewer, u)) for u in set([r.user for r in self.reputes.all()])])
class Meta:
proxy = True
class DummyActionProxyMetaClass(type):
def __new__(cls, *args, **kwargs):
new_cls = super(DummyActionProxyMetaClass, cls).__new__(cls, *args, **kwargs)
ActionProxyMetaClass.types[new_cls.get_type()] = new_cls
return new_cls
class DummyActionProxy(object):
__metaclass__ = DummyActionProxyMetaClass
hooks = []
def __init__(self, ip=None):
self.ip = ip
def process_data(self, **data):
pass
def process_action(self):
pass
def save(self, data=None):
self.process_action()
if data:
self.process_data(**data)
for hook in self.__class__.hooks:
hook(self, True)
@classmethod
def get_type(cls):
return re.sub(r'action$', '', cls.__name__.lower())
@classmethod
def hook(cls, fn):
cls.hooks.append(fn)
class ActionRepute(models.Model):
action = models.ForeignKey(Action, related_name='reputes')
date = models.DateTimeField(default=datetime.datetime.now)
user = models.ForeignKey('User', related_name='reputes')
value = models.IntegerField(default=0)
by_canceled = models.BooleanField(default=False)
@property
def positive(self):
if self.value > 0: return self.value
return 0
@property
def negative(self):
if self.value < 0: return self.value
return 0
def _add_to_rep(self, value):
if int(self.user.reputation + value) < 1 and not settings.ALLOW_NEGATIVE_REPUTATION:
return 0
else:
return models.F('reputation') + value
def save(self, *args, **kwargs):
super(ActionRepute, self).save(*args, **kwargs)
self.user.reputation = self._add_to_rep(self.value)
self.user.save()
def delete(self):
self.user.reputation = self._add_to_rep(-self.value)
self.user.save()
super(ActionRepute, self).delete()
class Meta:
app_label = 'forum'